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

Reuse optimized IR/bytecode for bytecode dependencies #15179

Open
cameel opened this issue Jun 4, 2024 · 0 comments
Open

Reuse optimized IR/bytecode for bytecode dependencies #15179

cameel opened this issue Jun 4, 2024 · 0 comments
Assignees
Labels
high impact Changes are very prominent and affect users or the project in a major way. medium effort Default level of effort must have Something we consider an essential part of Solidity 1.0. optimizer performance 🐎

Comments

@cameel
Copy link
Member

cameel commented Jun 4, 2024

Abstract

While the IR code generator does reuse the IR that the current contract depends on (via new, .runtimeCode or .creationCode) it's only the unoptimized IR that is reused. There is no reuse of optimized IR, which means that we're reoptimizing dependencies (and their dependencies, recursively) from scratch every time.

Motivation

This is likely a significant slowdown for contracts that contain a lot of bytecode dependencies.

Benchmarking is needed to determine how much of an effect it has on compilation times in practice, but we have already established that some popular projects do have a lot of bytecode dependencies (especially when using Foundry test framework) so some attempt at code reuse here is expected to be beneficial.

Details

As can be seen in CompilerStack, the compiler takes unoptimized IR of all contracts and later passes it into IRGenerator:

for (auto const& pair: m_contracts)
otherYulSources.emplace(pair.second.contract, pair.second.yulIR);

IRGenerator selects sources of contract's bytecode dependencies and embeds their unoptimized IR as subobjects of the contract being compiled:

Then that object is optimized as a whole, without an attempt at reusing the already optimized IR of other contracts:

Possible solutions

A quick and easy way to address this at Yul IR level would be to modify YulStack::optimize() to receive optimized IR of other contracts and substitute it whenever it encounters a corresponding subobject.

The downside of this, however, is that it would only address IR reuse. We'd still be doing Yul->EVM transform separately for each subobject. It also breaks encapsulation by having YulStack assume that the unoptimized subobject really comes from the bytecode dependency and has not been modified between code generation and optimization.

A better approach might be to defer subobject embedding and introduce a linking stage. We could have IRGenerator generate code only for the current contract and insert the code dependency later. The optimizer already works on each assembly separately, and knowledge about assemblies is abstracted away using builtins like datasize()/dataoffset()/datacopy() so this should be feasible.

The upside of this solution is that linking could be done even at the bytecode level, reusing the results of EVM->Yul transform and maybe even EVM asm optimization. The downside is that the change is more invasive and we also have to prepare compiler for dealing with unlinked (i.e. incomplete) bytecode in most of the pipeline. To avoid showing such incomplete artifacts to the user we'd also need to be prepared to do some rudimentary linking at any stage where output can be requested.

Backwards Compatibility

This should be completely transparent to the users, unless we decide to cut corners, e.g. by outputting unlinked artifacts at intermediate stages.

@cameel cameel added performance 🐎 optimizer medium effort Default level of effort high impact Changes are very prominent and affect users or the project in a major way. must have Something we consider an essential part of Solidity 1.0. labels Jun 4, 2024
@cameel cameel self-assigned this Jun 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
high impact Changes are very prominent and affect users or the project in a major way. medium effort Default level of effort must have Something we consider an essential part of Solidity 1.0. optimizer performance 🐎
Projects
None yet
Development

No branches or pull requests

1 participant