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

Unexpected SLOADs during storing tightly packed struct #12460

Closed
k06a opened this issue Dec 28, 2021 · 12 comments
Closed

Unexpected SLOADs during storing tightly packed struct #12460

k06a opened this issue Dec 28, 2021 · 12 comments
Labels
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

@k06a
Copy link

k06a commented Dec 28, 2021

Description

Unexpected SLOADs during storing tightly packed struct. The following code should produce 0 SLOADs and 2 SSTOREs from my point of view. But somehow it executes 1 SLOAD before SSTOREs.

Environment

  • Compiler version: 0.8.11
  • Target EVM version (as per compiler settings): default (runs=1000000)
  • Framework/IDE (e.g. Truffle or Remix): Remix, Hardhat

Steps to Reproduce

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.11;

contract BitPacking {
    struct Data {
        uint48 a;
        uint40 b;
        uint40 c;
        uint128 d;
        uint128 e;
        uint128 f;
    }

    Data public data;

    function func(uint48 _1, uint40 _2, uint40 _3, uint128 _4) external {
        data = Data(_1, _2, _3, _4, _4, _4);
    }
}
@chriseth
Copy link
Contributor

Solidity is still not very good at optimizing across memory stores (Data(...) creates the struct in memory). For now, the more efficient (and maybe also safer) way to do this is to use individual assignments - data.a = _1; data.b = _2; ... - since this does not create any copies in memory.

It is weird, though, that slot 0 is even read when doing individual assignments.

@k06a
Copy link
Author

k06a commented Dec 29, 2021

@chriseth I tried this code too, even before using structs. I have the same SLOADs and SSTOREs counts but no MLOADs and MSTOREs :)

@hrkrshnn
Copy link
Member

hrkrshnn commented Jan 3, 2022

Looked at the IR. The issue is that the function calls for writing to storage, corresponding to d, e and f are not getting inlined. I'd assume the same issue for the legacy codegen. I think improving the inlining heuristics should fix this.

@hrkrshnn hrkrshnn closed this as completed Jan 3, 2022
@hrkrshnn hrkrshnn reopened this Jan 3, 2022
@k06a
Copy link
Author

k06a commented Jan 3, 2022

@hrkrshnn what do you think about adding tests for such cases?

@chriseth
Copy link
Contributor

chriseth commented Jan 3, 2022

I don't think we have an issue with inlining in the old codegen, because most routines are always inlined at code gen phase already. I would still like the sload of zero to be investigated.

@chriseth
Copy link
Contributor

chriseth commented Jan 3, 2022

@k06a we already have tests for that and we know that the gas consumption is far from ideal.

I just re-checked the version where we store in struct members directly, i.e.

contract BitPacking {
    struct Data {
        uint48 a;
        uint80 b;
        uint128 c;
    }

    Data data;

    function func(uint48 _1, uint80 _2, uint128 _3) external {
        data.a = _1;
        data.b = _2;
        data.c = _3;
    }
}

And the code generated via ir does perform multiple stores, but it does not read from the storage slot for the final store (the multiple stores will be fixed by #11352 ).
The code via the old codegen does read from slot 0, but not if the first two members are combined. So I think this might just be a limitation of the old optimizer regarding code complexity.

@chriseth
Copy link
Contributor

chriseth commented Jan 3, 2022

For reference, IR code:

            function fun_func(var, var_1, var_)
            {
                let _1 := and(var, 0xffffffffffff)
                let _2 := sload(0x00)
                sstore(0x00, or(and(_2, not(0xffffffffffff)), _1))
                let toInsert := and(shl(48, var_1), 0xffffffffffffffffffff000000000000)
                let _3 := not(0xffffffffffffffffffffffffffffffff)
                sstore(0x00, or(or(and(_2, _3), _1), toInsert))
                sstore(0x00, or(or(toInsert, _1), and(shl(128, var_), _3)))
            }

@k06a
Copy link
Author

k06a commented Jan 14, 2022

@chriseth what do you think about fixing and adding tests for such cases to avoid future regressions?

@ekpyron
Copy link
Member

ekpyron commented Jan 14, 2022

@chriseth what do you think about fixing and adding tests for such cases to avoid future regressions?

That's what we're doing :-). #11352 will fix the issue and will have appropriate tests, there are just some minor issues yet to be worked out to assure that it's always correct.

@k06a
Copy link
Author

k06a commented Jan 15, 2022

@ekpyron thanks, amazing!

@github-actions
Copy link

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 Mar 22, 2023
@github-actions
Copy link

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 Mar 29, 2023
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Mar 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
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

4 participants