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

Compiling 16 files with 0.7.6 CompilerError: Stack too deep when compiling inline assembly: Variable headStart is 2 slot(s) too deep inside the stack. Error HH600: Compilation failed #11638

Closed
SvenMeyer opened this issue Jul 8, 2021 · 46 comments
Labels
annoys users 😢 closed due inactivity The issue/PR was automatically closed due to inactivity. stale The issue/PR was marked as stale because it has been open for too long.

Comments

@SvenMeyer
Copy link

SvenMeyer commented Jul 8, 2021

This works fine in Remix with/wo optimization using 0.7.6

// SPDX-License-Identifier: MIT

pragma solidity >=0.7.6 <0.9.0;
pragma abicoder v2;

contract PurchaseStruct {

    struct Purchase {
        address purchaser;  // 20 Byte
        uint48 timestamp;   //  6 Byte
        // uint32  spare  ;    //  4 Byte
        bool wasFinalized;  //  1 Byte
        bool reverted;      //  1 Byte
        uint256 amount;
        uint256 costAmount;
        uint256 amountRedeemed;
    }

    Purchase[] public purchases; // Array with all purchase structs

    function addPurchase() public {
        purchases.push(Purchase({
            amount:          1234567890, // _amount,
            purchaser:       msg.sender,
            costAmount:      1234, // value,
            timestamp:       uint48(block.timestamp),
            amountRedeemed:  0,
            wasFinalized:    false, // isTokenSwapAtomic, /* If Atomic Swap */
            reverted:        false
        }));
    }


    function getPurchaseStruct(uint256 purchase_id) external view returns (Purchase memory purchase) {
        return purchases[purchase_id];
    }
}

However within my larger hardhat based project (just one contract with a few OpenZeppelin imports), as soon as I add pragma abicoder v2; I get this error message :

Compiling 16 files with 0.7.6
CompilerError: Stack too deep when compiling inline assembly: Variable headStart is 2 slot(s) too deep inside the stack.

Error HH600: Compilation failed

hardhat.config.ts

      {
        version: "0.7.6",
        settings: {
          optimizer: {
            enabled: true,
            runs: 200,
          },
        },
      },
@hrkrshnn hrkrshnn added this to New issues in Solidity via automation Jul 12, 2021
@hrkrshnn
Copy link
Member

hrkrshnn commented Jul 12, 2021

Unfortunately, this is a known issue, and does not have a good fix. (We are working on a solution for upcoming solidity versions, but until then, sorry)

Here are two suggestions that are not perfect, but might work

  1. Consider reducing the number of struct members.
  2. Consider disabling the Yul optimizer:
      {
        version: "0.7.6",
        settings: {
          optimizer: {
            enabled: true,
            runs: 200,
            details: {
              yul: false
            }
          },
        },
      },

If you do the second option and it works, could you please follow up?

@SvenMeyer
Copy link
Author

@hrkrshnn
Unfortunately yul: false does not make any difference.

It's kind of disappointing as I don't even have a large or complex contract. It's actually the code as above with some extra variables and functions "beside" that, so not even nested ... so I find it strange that the stack does not have 0 entries and can easily cope with it ...

@hrkrshnn
Copy link
Member

@SvenMeyer Could you post a minimal example with the stack too deep error? Perhaps we could help with a specific suggestion to fix it.

BTW, the function getPurchaseStruct is redundant here, since the compiler will automatically generate a getter for public state variables.

@cameel
Copy link
Member

cameel commented Jul 12, 2021

BTW, the function getPurchaseStruct is redundant here, since the compiler will automatically generate a getter for public state variables.

Though there's a gotcha to watch out for if the actual struct is not the same as in the code above: a getter would skip any array fields within the struct.

@SvenMeyer
Copy link
Author

SvenMeyer commented Jul 12, 2021

@hrkrshnn even without the getPurchaseStruct() I get the compiler error as soon as I add pragma abicoder v2;

I tried to come up with minimal version, that's the code above which I initally posted ... and that works.

Hard to say what needs to be removed to just break it. The struct does not look too complicated (with 7 entries , 4 x 256 Bit words) to me as well and can't be reduced further.

I invited you to my repo, just in case you want to have a look and may spot something at first sight ...

I assume solc 0.8.x would not make a difference either ?

@hrkrshnn
Copy link
Member

hrkrshnn commented Jul 12, 2021

I assume solc 0.8.x would not make a difference either ?

@SvenMeyer You can try running with the latest release / develop. There were some minor changes in stack management that might help.

@SvenMeyer
Copy link
Author

@hrkrshnn

Compiling 21 files with 0.8.4
CompilerError: Stack too deep when compiling inline assembly: Variable headStart is 2 slot(s) too deep inside the stack.
Error HH600: Compilation failed
Compiling 21 files with 0.8.6
CompilerError: Stack too deep when compiling inline assembly: Variable headStart is 2 slot(s) too deep inside the stack.
Error HH600: Compilation failed

:-(

@SvenMeyer
Copy link
Author

SvenMeyer commented Jul 12, 2021

@hrkrshnn

looks like with solc 0.8.4 and 0.8.6 I even get the error with pragma abicoder v2; commented out ... what ?? !!

https://github.com/polkastarter/fixed-swap-offchain-whitelist/tree/solc_0.8

@hrkrshnn
Copy link
Member

@SvenMeyer In 0.8.0, abi encoder v2 is enabled by default, please check the release notes.

@hrkrshnn

This comment has been minimized.

@cliffhall
Copy link

cliffhall commented Jul 20, 2021

Here are two suggestions that are not perfect, but might work

  1. Consider reducing the number of struct members.
  2. Consider disabling the Yul optimizer:
    ...
    If you do the second option and it works, could you please follow up?

Howdy all. Just wanted to let you know I did Option 2 and it fixed my problem.

I was adding the diamond proxy contracts by @mudgen to my project (which is being compiled with 0.8.5). Even though his project was not getting the error, I was consistently getting the following in mine after adding his contracts:

CompilerError: Stack too deep when compiling inline assembly: Variable headStart is 1 slot(s) too deep inside the stack.

The only difference was that @mudgen's hardhat.config.js had super simple config:

module.exports = {
  solidity: '0.8.6'
}

I personally had more going on:

  solidity: {
    version: "0.8.5",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  },

I had previously discovered that setting optimizer to false in my config would get rid of the error, but I don't want to go live like that so it wasn't a solution. Then I found @hrkrshnn's post about turning off YUL optimizer

  solidity: {
    version: "0.8.5",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
        details: {
          yul: false
        }
      }
    }
  }

My only question now is what the effect of disabling the YUL optimizer will be.

@hrkrshnn
Copy link
Member

My only question now is what the effect of disabling the YUL optimizer will be.

@cliffhall The ABI encoder v2 code for your contract would not be optimized.

mveytsman added a commit to thousandetherhomepage/ketherhomepage that referenced this issue Aug 30, 2021
There's a bug that causes a stack overflow with the new YUL optimizer
(ethereum/solidity#11638 (comment))
so we have to disable it
@n00b21337
Copy link

Had the same problemm with hardhat and larger number of structs. Putting
yul: false
helped.

@ylv-io
Copy link

ylv-io commented Mar 2, 2022

I am getting the same issue with 0.8.9 version of Solidity while trying to migrate from 0.7.x version. It doesn't even tell which file produce the issue. Is there way to check? None of suggested compiler settings helped with the problem.

@cameel
Copy link
Member

cameel commented Mar 3, 2022

Unfortunately not, because at the stage where the problem happens the connection between your original source and the part that is being processed is pretty tenuous. The code is already after conversion to Yul, optimization (including inlining) and is being translated from Yul to EVM bytecode. The Yul source being compiled is just a big dump of code from your contract and any other contracts/functions it calls internally. The information about which parts of your original source this Yul code maps to is either unavailable or unreliable. We'd like to improve error reporting for that case (#12449) but there isn't much that can be done without guessing, and in the end focusing more on features that will make these errors happen less seems more productive.

@chee-chyuan
Copy link

getting similar error on 0.8.13

@hrkrshnn
Copy link
Member

hrkrshnn commented Apr 6, 2022

@chee-chyuan Can you try setting viaIR to true?

@chee-chyuan
Copy link

@chee-chyuan Can you try setting viaIR to true?

thanks. compiling takes abit more time but that works

@chee-chyuan
Copy link

@chee-chyuan Can you try setting viaIR to true?

thanks. compiling takes abit more time but that works

also got in touch with etherscan & remix. hopefully they can support that soon

@0xpApaSmURf
Copy link

I'm having this problem on Solidity v0.8.10 without using abiencoder v2.

Can you try setting viaIR to true?

How and where does one do this?

@chee-chyuan
Copy link

I'm having this problem on Solidity v0.8.10 without using abiencoder v2.

Can you try setting viaIR to true?

How and where does one do this?

im not sure if its available in v0.8.10. got to ask to team

@0xpApaSmURf
Copy link

Ok... Regardless of version, how does one do that?

@cameel
Copy link
Member

cameel commented Apr 14, 2022

Simply add --via-ir option to compiler's command line. Or, when using Standard JSON it's a boolean settings.viaIR setting, see Compiler Input and Output JSON Description. If you're not using the compiler directly, your framework probably lets you specify the settings part from Standard JSON in its config file.

im not sure if its available in v0.8.10. got to ask to team

It's been available at least since 0.7.x, maybe even 0.6.x (you can check if your version supports it in solc --help). Just note that the CLI option was called --experimental-via-ir until 0.8.12. We only consider it stable and ready for general use starting with 0.8.13. Using it in production with earlier binaries is not recommended. In standard JSON the name did not change.

@0xpApaSmURf
Copy link

Thanks for the info and resources! Very helpful. Unfortunately nothing helped. I've tried disabling the Yul optimizer, disabling the optimizer entirely, enabling viaIR, disabling peephole, enabling Yul stack allocation.

In my case I'm getting only 1 slot too deep error.

@cameel
Copy link
Member

cameel commented Apr 14, 2022

On 0.8.13? Can you post a new snippet that reproduces this?

@0xpApaSmURf
Copy link

0xpApaSmURf commented Apr 14, 2022

Turns out it was because my contract constructor had too many input arguments:

contract Breaks {
    constructor(string memory name_, string memory symbol_, 
        address _0,
        address _1,
        address _2,
        address _3,
        address _4,
        address _5,
        address _6,
        address _7,
        address _8,
        address _9
    ) {}
}

Removing address _9 compiles fine.

Although oddly, if I add any more arguments to the constructor, the error still reports Stack too deep when compiling inline assembly: Variable headStart is 1 slot(s) too deep inside the stack., whereas I would have expected the error to deepen in slots.

@cameel
Copy link
Member

cameel commented Apr 14, 2022

Thanks. So I just checked that with --via-ir and it compiles just fine, even with more arguments. So switching to 0.8.13 and using the IR codegen should solve the problem for you.

@MartinL-MG
Copy link

I have the same issue when my struct has too many variables. How can I turn this --via-ir on? I am using truffle. When I add this to truffle-config.js I have still the same issue. Maybe I add it in the wrong way?

Here is my current config with this key

`require('babel-register');
require('babel-polyfill');

module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 7545,
network_id: "*" // Match any network id
},
},
contracts_directory: './src/contracts/',
contracts_build_directory: './src/abis/',
compilers: {
solc: {
version : "^0.8.13",
optimizer: {
enabled: true,
runs: 200
},
evmVersion: "petersburg"
}
},
viaIR : true
}`

Thank you

@SvenMeyer
Copy link
Author

@cameel

  • How would I set --via-ir in a hardhat environment ?
  • What is --via-ir actually doing ?

@cameel
Copy link
Member

cameel commented Apr 17, 2022

@SvenMeyer
Copy link
Author

SvenMeyer commented Apr 18, 2022

@cameel
Thanks for that !

I added viaIR:true and was happy to see that contract size was reduced from ~24.132 to 23.555 .. then realized I had still set it to 0.8.12, switched it to 0.8.13, and it went up to 24.19 :-(

Any explanantion for that ?


  solidity: {
    compilers: [
      {
        version: "0.7.6",
        settings: {
          optimizer: {
            enabled: true,
            runs: 200,
          },
        },
      },
      {
        version: "0.8.13",
        settings: {
          metadata: {
            // Not including the metadata hash
            // https://github.com/paulrberg/solidity-template/issues/31
            bytecodeHash: "none",
          },
          optimizer: {
            enabled: true,
            runs: 800,
          },
          viaIR : true,
        },
      },
    ],
  },

Actually, version: "0.8.13" & viaIR: true gives me the largest code size :-(

solc viaIR code size
0.8.12 false 24.132
0.8.12 true 23.555
0.8.13 false 24.150
0.8.13 true 24.190

Still using version: "0.8.13" & viaIR: true , optimizer.runs makes a big difference (and may should):

runs code size
100 22.912
200 / not specified 23.299
800 24.190

Is it actually (still) advised to specify optimizer.runs ?

@cameel
Copy link
Member

cameel commented Apr 18, 2022

optimizer.runs is the way you tell the compiler what you care more about: the deployment cost or the runtime cost. The value is the number of times you expect it to run over its lifetime and the compiler makes the trade-off accordingly. The lower it is, the smaller your bytecode will be, even at the cost of making the execution more expensive. If you're going to use your contract only once, the deployment cost is just as important as runtime cost. If you're going to call it thousands of times, the deployment cost will be dwarfed by the runtime cost.

Any explanantion for that ?

We do lots of changes that affect the cost one way or another. Optimizations lower it, security-related changes often increase it so small differences like these are not uncommon.

For example we recently merged #12647, which slightly increases bytecode size in most cases.

Even if a change is an optimization that lowers the cost in most cases, there are pretty much always some corner cases where the cost goes up.

For viaIR you're likely going to see a significant improvement in one of the upcoming versions. We're currently working on making inlining more aggressive (#12731), which looks like it should reduce the bytecode size by 5-10% in many real-life projects. Maybe even more if we manage to completely remove function size limit.

@annaBlaize
Copy link

Enabling yul worked for me with next settings:

  solidity: {
    version: "0.8.11",
    settings: {
      optimizer: {
        runs: 200,
        enabled: true,
        details: {
          yul: true
        }
      },
    },
  },

@Alfa11917676
Copy link

@hrkrshnn I was getting

CompilerError: Stack too deep when compiling inline assembly: Variable value3 is 6 slot(s) too deep inside the stack.
Error HH600: Compilation failed

This error this was fixed by turning off yul optimiser

@cameel
Copy link
Member

cameel commented Aug 5, 2022

@Alfa11917676 Was it with viaIR: true? What does you code look like? Can you post a small snippet that reproduces this?

@0xAlfa
Copy link

0xAlfa commented Aug 6, 2022

@cameel my code was a on-chain metadata generator with a lot of traits coming into actions.

@cameel
Copy link
Member

cameel commented Aug 7, 2022

I mean, without seeing any code we can't really say anything just based on that error message. It simply says that the compiler ran out of available EVM slots but not why it happened.

And before jumping into the code, we should make sure it's not one of the cases where the problem is still known to exist. I.e. are you using viaIR: true? Is the function non-recursive? Are your assembly blocks "memory-safe"? If either is not true, you may still run into this and it's not really a bug in the compiler, more like a limitation of the compiler in overcoming the limitations of the EVM.

@CodeSandwich
Copy link

CodeSandwich commented Sep 22, 2022

I've been hit with this issue too, I've been able to boil it down to this:

struct Struct1 {
    uint256 field;
}

struct Struct2 {
    Struct1[] struct1;
}

contract Contract {
    function foo(
        uint256 arg1,
        uint256 arg2,
        uint256 arg3,
        uint256 arg4,
        Struct2[] memory struct2) external {}
}

I'm using Solidity 0.8.17. It doesn't matter if the optimizer is enabled, it always fails with Variable headStart is 2 slot(s) too deep inside the stack.. With viaIR it does compile, but this pipeline is still immature, so it's not a viable workaround.

A WORKAROUND I've found for this particular case is to change struct2 from memory to calldata. You can then copy it to memory if you need it in that form, but being calldata when sanity-checking cut the stack usage just enough to work.

@rkdud007
Copy link

rkdud007 commented Nov 3, 2022

I'm using solidity 0.8.12. I faced CompilerError: Stack too deep when compiling inline assembly: Variable headStart is 1 slot(s) too deep inside the stack. error. This is what I found out.

  1. optimizer enable doesn't matter
  2. yul should be true. if it is false, get stack error
  3. viaIR should be false. if it is true, it cannot coverage whole.
solidity: {
    version: "0.8.12",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
        details: {
          yul: true
        }
      },
      viaIR : false,
    },

@hrkrshnn
Copy link
Member

hrkrshnn commented Nov 6, 2022

@rkdud007 I think the coverage part is simply the tools not supporting via-ir.

@waqasideofuzion
Copy link

resolved my issue with viaIR to false for the first time but next time I got the error.

YulException: Variable _18 is 1 too deep in the stack [ _18 RET _3 _10 _12 _6 var_tokenDetails_offset _8 var_salt_mpos _7 _2 expr_address_4 _17 _6 _17 _17 _12 ]
No memoryguard was present. Consider using memory-safe assembly only and annotating it via 'assembly ("memory-safe") { ... }'.


Error HH600: Compilation failed

@windhustler
Copy link

windhustler commented Mar 9, 2023

I stumbled upon a really really strange stack too deep issue so sharing it here:

struct Payload {
        bytes body;
        uint8 action;
    }
    
struct ComplexStruct {
        uint256[] balances;
        address[] assets;
    }

function generate(Payload memory payload)
        external
        view
        returns (bytes[][] memory targets)
    {
        
        ComplexStruct memory info = abi.decode(payload, (ComplexStruct));
        targetsPerChain = new bytes[][](info.assets.length);
        // Here I have a lot of logic to map the payload into final targets
    }

In case I comment out all of this complex logic inside the generate function expect the first line ComplexStruct memory info = abi.decode(payload, (ComplexStruct)); the compilation FAILS with:
Screenshot 2023-03-09 at 22 45 13

If the logic is NOT commented out the compilation is successful.
How is this possible?

@trendespresso
Copy link

I had this issue today. Figured out a struct in my code was the culprit. Still unsure why or how the struct causes a Stack Too Deep error. Simply removed it from the code for now and am using raw value types only (uint, bool, etc). Any clarity why this error exists and why no line number is identified?

Incredibly frustrating when working with a large codebase and many structs.

@garrettmaring
Copy link

Latest settings here worked for me

solidity: {
  version: "0.8.18",
  settings: {
    viaIR: true,
    optimizer: {
      enabled: true,
      details: {
        yulDetails: {
          optimizerSteps: "u",
        },
      },
    },
  },
}

@github-actions
Copy link

github-actions bot commented Aug 2, 2023

This issue has been marked as stale due to inactivity for the last 90 days.
It will be automatically closed in 7 days.

@github-actions github-actions bot added the stale The issue/PR was marked as stale because it has been open for too long. label Aug 2, 2023
@github-actions
Copy link

github-actions bot commented Aug 9, 2023

Hi everyone! This issue has been automatically closed due to inactivity.
If you think this issue is still relevant in the latest Solidity version and you have something to contribute, feel free to reopen.
However, unless the issue is a concrete proposal that can be implemented, we recommend starting a language discussion on the forum instead.

@github-actions github-actions bot added the closed due inactivity The issue/PR was automatically closed due to inactivity. label Aug 9, 2023
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Aug 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
annoys users 😢 closed due inactivity The issue/PR was automatically closed due to inactivity. stale The issue/PR was marked as stale because it has been open for too long.
Projects
None yet
Development

No branches or pull requests