Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Investigate issue with data section addressing limits. #3161

Open
otrho opened this issue Oct 27, 2022 · 12 comments
Open

Investigate issue with data section addressing limits. #3161

otrho opened this issue Oct 27, 2022 · 12 comments
Assignees
Labels
bug Something isn't working compiler: codegen Everything to do with IR->ASM, register allocation, etc. P: medium

Comments

@otrho
Copy link
Contributor

otrho commented Oct 27, 2022

@vaivaswatha mentioned that while implementing mem2reg he was testing against https://github.com/sway-libs/amm (which has become the de facto gargantuan library for testing Sway performance) and it failed:

'Unable to offset into the data section more than 2^12 bits. Unsupported data section length.', sway-core/src/asm_lang/allocated_ops.rs:506:19.

The data section uses a single register as a base and indexes elements using lw, which has a 12-bit index immediate. If the data section gets too large then this won't work any more.

We should look at both allowing a large DS and also optimising the DS for size by removing any redundancy.

  • Having multiple data sections isn't out of the question. Loading their offsets at runtime would add a minor cost.
  • As the data section is read-only there should never be any repeated values as they can be safely re-used. (This should already be the case with the current compiler, but we should double check.)
  • Word values like zero or one should never be in the data section as they can be taken directly from $zero and $one.
  • Values smaller than 2^18 should never be in the data section as they can be created with MOVI.
@otrho otrho added bug Something isn't working P: critical Should be looked at before anything else compiler: codegen Everything to do with IR->ASM, register allocation, etc. labels Oct 27, 2022
@otrho otrho self-assigned this Oct 29, 2022
@otrho
Copy link
Contributor Author

otrho commented Nov 3, 2022

I'd really like to repro this with amm to see exactly what is using up all the space in the data section. I could fake it with my own garbage (and probably will for an E2E test) but if anyone fancies a challenge where they can come up with a version Forc and the stdlibs which will compile amm and generate the above error that'd be great, thanks.

@vaivaswatha
Copy link
Contributor

On commit 64dd5fc6cfdadba7ac7e15840eb7c53abde7788d of the amm repo, apply this patch:

diff --git a/amm/Forc.lock b/amm/Forc.lock
index c39e1b2..24cb284 100644
--- a/amm/Forc.lock
+++ b/amm/Forc.lock
@@ -6,7 +6,6 @@ dependencies = ['std']
 [[package]]
 name = 'core'
 source = 'path+from-root-78008370C3B04E1D'
-dependencies = []
 
 [[package]]
 name = 'std'
diff --git a/amm/src/pool.sw b/amm/src/pool.sw
index 267c8f5..c5ca417 100644
--- a/amm/src/pool.sw
+++ b/amm/src/pool.sw
@@ -92,7 +92,7 @@ struct Tick {
     liquidity: U128,
     fee_growth_outside0: u64,
     fee_growth_outside1: u64,
-    seconds_growth_outside: U128
+    seconds_growth_outside: U256,
 }
 
 abi ConcentratedLiquidityPool {
@@ -173,27 +173,17 @@ storage {
 impl ConcentratedLiquidityPool for Contract {
     #[storage(read, write)]
     fn init(token0: ContractId, token1: ContractId, swap_fee: u64, sqrt_price: Q64x64, tick_spacing: u32) {
-        assert(price == 0);
         //TODO: assert lexographical order
         storage.token0 = token0;
         storage.token1 = token1;
         //TODO: _ensure_tick_spacing
-        storage.tick = get_tick_at_price(sqrt_price);
+        storage.nearest_tick = get_tick_at_price(sqrt_price);
         storage.price = sqrt_price;
-        storage.protocol_fee = 0;
+        storage.token0_protocol_fee = 0;
+        storage.token1_protocol_fee = 0;
         storage.swap_fee = swap_fee;
         storage.tick_spacing = tick_spacing;
-        unlocked = true;
-
-        log(InitEvent {
-            pool: std::context::call_frames::contract_id(),
-            fee: storage.swap_fee
-            tick: storage.tick,
-            sqrt_price: storage.price,
-            token0: storage.liquidity,
-            tick: storage.nearest_tick,
-            sqrt_price: storage.price
-        });
+        storage.unlocked = true;
     }
     #[storage(read, write)]
     fn swap(token_zero_to_one: bool, amount: u64, sprtPriceLimit: Q64x64, recipient: Identity) -> u64 {
@@ -338,15 +328,6 @@ impl ConcentratedLiquidityPool for Contract {
            // emit Swap(recipient, token1, token0, inAmount, amountOut)
         }
 
-        log(SwapEvent {
-            pool: std::context::call_frames::contract_id(),
-            token0_amount: amount_in,
-            token1_amount: amount_out,
-            liquidity: storage.liquidity,
-            tick: storage.nearest_tick,
-            sqrt_price: storage.price
-        });
-
         amount_out
     }
 
@@ -460,6 +441,7 @@ impl ConcentratedLiquidityPool for Contract {
         // _updateSecondsPerLiquidity(uint256(liquidity));
 
         let (amount0_fees, amount1_fees) = _update_position(recipient, lower, upper, liquidity_minted);
+        let sender: Identity= msg_sender().unwrap();
 
         if amount0_fees > 0 {
             transfer(amount0_fees, storage.token0, sender);
@@ -484,24 +466,11 @@ impl ConcentratedLiquidityPool for Contract {
         //IPositionManager(msg.sender).mintCallback(token0, token1, amount0Actual, amount1Actual, mintParams.native);
         _position_update_reserves(true, amount0_actual, amount1_actual);
 
-        let sender: Identity= msg_sender().unwrap();
-
-        log(MintEvent {
-            pool: std::context::call_frames::contract_id(),
-            sender,
-            recipient,
-            token0_amount: amount0_desired,
-            token1_amount: amount1_desired,
-            liquidity_minted,
-            tick_lower,
-            tick_upper,
-        });
-
         liquidity_minted
     }
 
     #[storage(read, write)]
-    fn burn(recipient: Address, lower: I24, upper: I24, liquidity_amount: U128) -> (u64, u64, u64, u64) {
+    fn burn(lower: I24, upper: I24, liquidity_amount: U128) -> (u64, u64, u64, u64) {
         let price_lower = get_price_sqrt_at_tick(lower);
         let price_upper = get_price_sqrt_at_tick(upper);
         let current_price = storage.price;
@@ -526,16 +495,6 @@ impl ConcentratedLiquidityPool for Contract {
         transfer(amount0, storage.token0, sender);
         transfer(amount1, storage.token1, sender);
 
-        log(BurnEvent {
-            pool: std::context::call_frames::contract_id(),
-            sender,
-            token0_amount: amount0_desired,
-            token1_amount: amount1_desired,
-            liquidity_burned: liquidity_amount,
-            tick_lower,
-            tick_upper
-        });
-
         // nearestTick = Ticks.remove(ticks, lower, upper, amount, nearestTick);
         //TODO: get fee growth in range and calculate fees based on liquidity
         (token0_amount, token1_amount, 0, 0)
@@ -698,7 +657,7 @@ fn empty_tick() -> Tick {
         liquidity: ~U128::from(0,0),
         fee_growth_outside0: 0,
         fee_growth_outside1: 0,
-        seconds_growth_outside: ~U128::from(0,0)
+        seconds_growth_outside: ~U256::from(0,0,0,0)
     }
 }
 
@@ -746,12 +705,11 @@ pub fn tick_cross(
     let outside_growth = storage.ticks.get(next).seconds_growth_outside;
 
     //cast outside_growth into U256
-    let seconds_growth_outside = ~U256::from(0,0,outside_growth.upper,outside_growth.lower);
+    let seconds_growth_outside = outside_growth;
 
     //do the math, downcast to U128, store in storage.ticks
     let outside_math: U256 = seconds_growth_global - seconds_growth_outside;
-    let outside_downcast = ~U128::from(outside_math.c, outside_math.d);
-    stored_tick.seconds_growth_outside = outside_downcast;
+    stored_tick.seconds_growth_outside = outside_math;
     storage.ticks.insert(next, stored_tick);
 
     let modulo_re_to24 = ~I24::from_uint(2);
@@ -863,7 +821,7 @@ fn tick_insert(
                 liquidity: amount,
                 fee_growth_outside0: 0,
                 fee_growth_outside1: 0,
-                seconds_growth_outside: ~U128::from(0,0)
+                seconds_growth_outside: ~U256::from(0,0,0,0)
             });
         }
         prev_tick.next_tick = below;
@@ -900,7 +858,7 @@ fn tick_insert(
                 liquidity: amount,
                 fee_growth_outside0: 0,
                 fee_growth_outside1: 0,
-                seconds_growth_outside: ~U128::from(0,0)
+                seconds_growth_outside: ~U256::from(0,0,0,0)
             });
         }
         prev_tick.next_tick = above;

This should give you a repro.

@ControlCplusControlV
Copy link
Contributor

ControlCplusControlV commented Nov 4, 2022

You should be able to repro with new-amm now

@otrho
Copy link
Contributor Author

otrho commented Nov 5, 2022

I've been looking at this today and I'll share some insights. If I force amm/amm to compile we end up with a 3.7MB binary. All of the conditional control flow beyond the 1MB mark requires the dynamic jumps using addresses loaded from the data section, introduced with #3109.

I was a bit confused initially when looking at this because the data section for actual const values used by the program is only about 2KB in size, which obviously shouldn't be a problem. But in the final binary we add over 19,500 new entries, purely for the dynamic jumps as mentioned above.

This is crazy.

I was also looking at it and realised that I'm pretty sure there's a bug in that code from #3109 because it never tries to adjust the offsets to be relative to $is. I'll make another issue for that. It'll make all that dynamic jumping a little more expensive as it'll be a lw then add then jne for every jump.

Fixing this problem will be non-trivial now too because loading from the data section will be 1, 2 or 4 (or 5?) instructions depending on where in the data section the value is and what type it is. This has to be known before the address realisation stage and complicates things there.

@ControlCplusControlV
Copy link
Contributor

I will point out that 3.7MB is actually an improvement from the once 5MB binary that was the size on the previous compiler. How could I force compile for now to gather initial data like gas usage for swaps?

@otrho
Copy link
Contributor Author

otrho commented Nov 5, 2022

I forced it to compile by generating bogus const data, so the binary I have is structurally valid but corrupt and wouldn't run, sorry.

@mohammadfawaz
Copy link
Contributor

mohammadfawaz commented Dec 1, 2022

@otrho so are we basically lowering the priority of this for the time being? If so, we should change the label P: critical.

@otrho
Copy link
Contributor Author

otrho commented Dec 2, 2022

Yeah, the code relocation has made the likelihood of data section overflow much rarer but it's still possible so we can't close this one yet. Until we actual see it happening in the real world P: medium is probably fine.

@otrho otrho added P: medium and removed P: critical Should be looked at before anything else labels Dec 2, 2022
@eightfilms
Copy link
Contributor

eightfilms commented Dec 3, 2022

edit: tested on forc 0.31.3 and master
Was trying to do advent of code day 2 using Sway and running into thread 'main' panicked at 'Unable to offset into the data section more than 2^12 bits. Unsupported data section length.', sway-core/src/asm_lang/allocated_ops.rs:632:19 whenever I ran forc test.

Was trying to do nested match statements in a while loop. Note that the input is of length 2500 because there are 2500 lines in the actual AoC input:

pub fn part_1(input: [(u64, u64); 2500]) -> u64 {
    let mut result: u64 = 0;

    // TODO: Write your solution here
    let mut i = 0;
    while i < 2500 {
        let opp_move = input[i].0;
        let my_move = input[i].1;

        let score = match opp_move {
            65 => match my_move {
                88 => 4,
                89 => 8,
                90 => 3,
                _ => 0,
            },
            66 => match my_move {
                88 => 4,
                89 => 8,
                90 => 3,
                _ => 0,
            },
            67 => match my_move {
                88 => 4,
                89 => 8,
                90 => 3,
                _ => 0,
            },
            _ => 0,
        };

        result += score;
        i += 1;
    }

    return result;
}

I tried shrinking this down, and this also fails:

pub fn part_1(input: [(u64, u64); 2500]) -> u64 {
    let mut result: u64 = 0;

    // TODO: Write your solution here
    let mut i = 0;
    while i < 2500 {
        let opp_move = input[i].0;
        let my_move = input[i].1;


        let score = match (opp_move, my_move) {
            (65, 88) => 4,
            _ => 0,
        };

        result += score;
        i += 1;
    }

    return result;
}

I removed the while loop and this finally built/tested:

pub fn part_1() -> u64 {
    let input = [(65, 88)];
    let mut result: u64 = 0;

    // TODO: Write your solution here
    let mut i = 0;
    let opp_move = input[i].0;
    let my_move = input[i].1;

    let score = match (opp_move, my_move) {
        (65, 88) => 4,
        _ => 0,
    };

    result += score;
    i += 1;

    return result;
}

Not sure if this is helpful since perhaps we aren't meant to do so many iterations using Sway?

EDIT: I dug more into this and seems like the problem was with assignment within the while loop. This fails for example:

    let mut result = 0;
    let mut i = 0;

    while i < 2 {
	let score = opp_move[i] + my_move[i];
        result += score;
        i += 1;
    }

@otrho
Copy link
Contributor Author

otrho commented Dec 5, 2022

How are you getting the input into the script? Are you actually compiling it as a const array of values? Because this will definitely blow out the data section as it can only have around 16KB of data. Adding this much constant data isn't typical for blockchain contracts.

@eightfilms
Copy link
Contributor

I am indeed compiling the input... What's an alternative to get a long input into a contract/script?

@otrho
Copy link
Contributor Author

otrho commented Dec 5, 2022

Good question! Sending each line in one at a time via contract args and using storage to maintain state between calls?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working compiler: codegen Everything to do with IR->ASM, register allocation, etc. P: medium
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants