EVM Legacy Assembly translator
There are two Solidity IRs used in our pipeline: Yul and EVM legacy assembly. The former is used for older versions of Solidity, more precisely <=0.7.
EVM legacy assembly is very challenging to translate to LLVM IR, since it obfuscates the control flow of the program and uses a lot of dynamic jumps. Most of the jumps can be translated to static ones by using a static analysis of EVM assembly, but some of jumps are impossible to resolve statically. For example, internal function pointers can be written to memory or storage, and then loaded and called. Recursion is another case we have skipped for now, as there is another stack frame allocated on every iteration, preventing the static analyzer from resolving the jumps.
Both issues are being worked on in our fork of the Solidity compiler, where we are changing the codegen to remove the dynamic jumps and add the necessary metadata.
Below you can see a minimal example of a Solidity contract and its EVM legacy assembly translated to LLVM IR which is eventually compiled to EraVM assembly.
Source Code
contract Example {
function main() public pure returns (uint256 result) {
result = 42;
}
}
EVM Legacy Assembly
Produced by the upstream Solidity compiler v0.7.6.
| Line | Instruction | Value/Tag |
| ---- | ------------ | --------- |
| 000 | PUSH | 80 |
| 001 | PUSH | 40 |
| 002 | MSTORE | |
| 003 | CALLVALUE | |
| 004 | DUP1 | |
| 005 | ISZERO | |
| 006 | PUSH | [tag] 1 |
| 007 | JUMPI | |
| 008 | PUSH | 0 |
| 009 | DUP1 | |
| 010 | REVERT | |
| 011 | Tag 1 | |
| 012 | JUMPDEST | |
| 013 | POP | |
| 014 | PUSH | 4 |
| 015 | CALLDATASIZE | |
| 016 | LT | |
| 017 | PUSH | [tag] 2 |
| 018 | JUMPI | |
| 019 | PUSH | 0 |
| 020 | CALLDATALOAD | |
| 021 | PUSH | E0 |
| 022 | SHR | |
| 023 | DUP1 | |
| 024 | PUSH | 5A8AC02D |
| 025 | EQ | |
| 026 | PUSH | [tag] 3 |
| 027 | JUMPI | |
| 028 | Tag 2 | |
| 029 | JUMPDEST | |
| 030 | PUSH | 0 |
| 031 | DUP1 | |
| 032 | REVERT | |
| 033 | Tag 3 | |
| 034 | JUMPDEST | |
| 035 | PUSH | [tag] 4 |
| 036 | PUSH | [tag] 5 |
| 037 | JUMP | [in] |
| 038 | Tag 4 | |
| 039 | JUMPDEST | |
| 040 | PUSH | 40 |
| 041 | DUP1 | |
| 042 | MLOAD | |
| 043 | SWAP2 | |
| 044 | DUP3 | |
| 045 | MSTORE | |
| 046 | MLOAD | |
| 047 | SWAP1 | |
| 048 | DUP2 | |
| 049 | SWAP1 | |
| 050 | SUB | |
| 051 | PUSH | 20 |
| 052 | ADD | |
| 053 | SWAP1 | |
| 054 | RETURN | |
| 055 | Tag 5 | |
| 056 | JUMPDEST | |
| 057 | PUSH | 2A |
| 058 | SWAP1 | |
| 059 | JUMP | [out] |
EthIR
EthIR (Ethereal IR) is a special IR used by our translator to represent EVM legacy assembly and prepare it for the translation to LLVM IR. The IR solves several purposes:
- Tracking the stack state to extract jump destinations.
- Duplicating blocks that are reachable with different stack states.
- Restoring the complete control-flow graph of the contract using the abovementioned data.
- Resolving dependencies and static data chunks.
Data format:
V_<name>
- value returned by an instruction<name>
.T_<tag>
- tag of a block<tag>
.40
- hexadecimal constant.tests/solidity/simple/default.sol:Test
- contract definition.
Stack format: [ V_CALLVALUE ]
(current values) - [ V_CALLVALUE ]
(popped values) + [ V_ISZERO ]
(pushed values)
// The default entry function of the contract.
function main {
// The maximum stack size in the function.
stack_usage: 6
block_dt_0/0: // Deploy Code Tag 0, Instance 0.
// PUSHed 0x80 onto the stack.
PUSH 80 [ ] + [ 80 ]
// PUSHed 0x40 onto the stack.
PUSH 40 [ 80 ] + [ 40 ]
// POPped 0x40 at 0x80 from the stack to store 0x80 at 0x40.
MSTORE [ ] - [ 80 | 40 ]
// PUSHed CALLVALUE onto the stack.
CALLVALUE [ ] + [ V_CALLVALUE ]
DUP1 [ V_CALLVALUE ] + [ V_CALLVALUE ]
ISZERO [ V_CALLVALUE ] - [ V_CALLVALUE ] + [ V_ISZERO ]
PUSH [tag] 1 [ V_CALLVALUE | V_ISZERO ] + [ T_1 ]
// JUMPI schedules rt_0/0 for analysis with the current stack state.
JUMPI [ V_CALLVALUE ] - [ V_ISZERO | T_1 ]
PUSH 0 [ V_CALLVALUE ] + [ 0 ]
DUP1 [ V_CALLVALUE | 0 ] + [ 0 ]
REVERT [ V_CALLVALUE ] - [ 0 | 0 ]
block_dt_1/0: (predecessors: dt_0/0) // Deploy Code Tag 1, Instance 0; the only predecessor of this block is dt_0/0.
// JUMPDESTs are ignored as we are only interested in the stack state and tag destinations.
JUMPDEST [ V_CALLVALUE ]
POP [ ] - [ V_CALLVALUE ]
PUSH #[$] tests/solidity/simple/default.sol:Test [ ] + [ tests/solidity/simple/default.sol:Test ]
DUP1 [ tests/solidity/simple/default.sol:Test ] + [ tests/solidity/simple/default.sol:Test ]
PUSH [$] tests/solidity/simple/default.sol:Test [ tests/solidity/simple/default.sol:Test | tests/solidity/simple/default.sol:Test ] + [ tests/solidity/simple/default.sol:Test ]
PUSH 0 [ tests/solidity/simple/default.sol:Test | tests/solidity/simple/default.sol:Test | tests/solidity/simple/default.sol:Test ] + [ 0 ]
CODECOPY [ tests/solidity/simple/default.sol:Test ] - [ tests/solidity/simple/default.sol:Test | tests/solidity/simple/default.sol:Test | 0 ]
PUSH 0 [ tests/solidity/simple/default.sol:Test ] + [ 0 ]
RETURN [ ] - [ tests/solidity/simple/default.sol:Test | 0 ]
// The runtime code is analyzed in the same control-flow graph as the deploy code, as it is possible to call its functions from the constructor.
block_rt_0/0: // Deploy Code Tag 0, Instance 0.
PUSH 80 [ ] + [ 80 ]
PUSH 40 [ 80 ] + [ 40 ]
MSTORE [ ] - [ 80 | 40 ]
CALLVALUE [ ] + [ V_CALLVALUE ]
DUP1 [ V_CALLVALUE ] + [ V_CALLVALUE ]
ISZERO [ V_CALLVALUE ] - [ V_CALLVALUE ] + [ V_ISZERO ]
PUSH [tag] 1 [ V_CALLVALUE | V_ISZERO ] + [ T_1 ]
JUMPI [ V_CALLVALUE ] - [ V_ISZERO | T_1 ]
PUSH 0 [ V_CALLVALUE ] + [ 0 ]
DUP1 [ V_CALLVALUE | 0 ] + [ 0 ]
REVERT [ V_CALLVALUE ] - [ 0 | 0 ]
block_rt_1/0: (predecessors: rt_0/0) // Runtime Code Tag 1, Instance 0; the only predecessor of this block is rt_0/0.
JUMPDEST [ V_CALLVALUE ]
POP [ ] - [ V_CALLVALUE ]
PUSH 4 [ ] + [ 4 ]
CALLDATASIZE [ 4 ] + [ V_CALLDATASIZE ]
LT [ ] - [ 4 | V_CALLDATASIZE ] + [ V_LT ]
PUSH [tag] 2 [ V_LT ] + [ T_2 ]
JUMPI [ ] - [ V_LT | T_2 ]
PUSH 0 [ ] + [ 0 ]
CALLDATALOAD [ ] - [ 0 ] + [ V_CALLDATALOAD ]
PUSH E0 [ V_CALLDATALOAD ] + [ E0 ]
SHR [ ] - [ V_CALLDATALOAD | E0 ] + [ V_SHR ]
DUP1 [ V_SHR ] + [ V_SHR ]
PUSH 5A8AC02D [ V_SHR | V_SHR ] + [ 5A8AC02D ]
EQ [ V_SHR ] - [ V_SHR | 5A8AC02D ] + [ V_EQ ]
PUSH [tag] 3 [ V_SHR | V_EQ ] + [ T_3 ]
JUMPI [ V_SHR ] - [ V_EQ | T_3 ]
Tag 2 [ V_SHR ]
// This instance is called with a different stack state using the JUMPI above.
block_rt_2/0: (predecessors: rt_1/0) // Runtime Code Tag 2, Instance 0.
JUMPDEST [ ]
PUSH 0 [ ] + [ 0 ]
DUP1 [ 0 ] + [ 0 ]
REVERT [ ] - [ 0 | 0 ]
// This instance is also called from rt_1/0, but using a fallthrough 'Tag 2'.
// Given different stack states, we create a new instance of the block operating on different data
// and potentially different tag destinations, although usually such blocks are merged back by LLVM.
block_rt_2/1: (predecessors: rt_1/0) // Runtime Code Tag 2, Instance 1.
JUMPDEST [ V_SHR ]
PUSH 0 [ V_SHR ] + [ 0 ]
DUP1 [ V_SHR | 0 ] + [ 0 ]
REVERT [ V_SHR ] - [ 0 | 0 ]
block_rt_3/0: (predecessors: rt_1/0) // Runtime Code Tag 3, Instance 0.
JUMPDEST [ V_SHR ]
PUSH [tag] 4 [ V_SHR ] + [ T_4 ]
PUSH [tag] 5 [ V_SHR | T_4 ] + [ T_5 ]
JUMP [in] [ V_SHR | T_4 ] - [ T_5 ]
block_rt_4/0: (predecessors: rt_5/0) // Runtime Code Tag 4, Instance 0.
JUMPDEST [ V_SHR | 2A ]
PUSH 40 [ V_SHR | 2A ] + [ 40 ]
DUP1 [ V_SHR | 2A | 40 ] + [ 40 ]
MLOAD [ V_SHR | 2A | 40 ] - [ 40 ] + [ V_MLOAD ]
SWAP2 [ V_SHR | V_MLOAD | 40 | 2A ]
DUP3 [ V_SHR | V_MLOAD | 40 | 2A ] + [ V_MLOAD ]
MSTORE [ V_SHR | V_MLOAD | 40 ] - [ 2A | V_MLOAD ]
MLOAD [ V_SHR | V_MLOAD ] - [ 40 ] + [ V_MLOAD ]
SWAP1 [ V_SHR | V_MLOAD | V_MLOAD ]
DUP2 [ V_SHR | V_MLOAD | V_MLOAD ] + [ V_MLOAD ]
SWAP1 [ V_SHR | V_MLOAD | V_MLOAD | V_MLOAD ]
SUB [ V_SHR | V_MLOAD ] - [ V_MLOAD | V_MLOAD ] + [ V_SUB ]
PUSH 20 [ V_SHR | V_MLOAD | V_SUB ] + [ 20 ]
ADD [ V_SHR | V_MLOAD ] - [ V_SUB | 20 ] + [ V_ADD ]
SWAP1 [ V_SHR | V_ADD | V_MLOAD ]
RETURN [ V_SHR ] - [ V_ADD | V_MLOAD ]
block_rt_5/0: (predecessors: rt_3/0) // Runtime Code Tag 5, Instance 0.
JUMPDEST [ V_SHR | T_4 ]
PUSH 2A [ V_SHR | T_4 ] + [ 2A ]
SWAP1 [ V_SHR | 2A | T_4 ]
// JUMP [out] is usually a return statement
JUMP [out] [ V_SHR | 2A ] - [ T_4 ]
Unoptimized LLVM IR
In LLVM IR, the necessary stack space is allocated at the beginning of the function.
Every stack operation interacts with a statically known stack pointer with an offset from EthIR.
; Function Attrs: nofree null_pointer_is_valid
define i256 @__entry(ptr addrspace(3) %0, i256 %1, i256 %2, i256 %3, i256 %4, i256 %5, i256 %6, i256 %7, i256 %8, i256 %9, i256 %10, i256 %11) #8 personality ptr @__personality {
entry:
store i256 0, ptr @memory_pointer, align 32
store i256 0, ptr @calldatasize, align 32
store i256 0, ptr @returndatasize, align 32
store i256 0, ptr @call_flags, align 32
store [10 x i256] zeroinitializer, ptr @extra_abi_data, align 32
store ptr addrspace(3) %0, ptr @ptr_calldata, align 32
%abi_pointer_value = ptrtoint ptr addrspace(3) %0 to i256
%abi_pointer_value_shifted = lshr i256 %abi_pointer_value, 96
%abi_length_value = and i256 %abi_pointer_value_shifted, 4294967295
store i256 %abi_length_value, ptr @calldatasize, align 32
%calldatasize = load i256, ptr @calldatasize, align 32
%return_data_abi_initializer = getelementptr i8, ptr addrspace(3) %0, i256 %calldatasize
store ptr addrspace(3) %return_data_abi_initializer, ptr @ptr_return_data, align 32
store ptr addrspace(3) %return_data_abi_initializer, ptr @ptr_active, align 32
store i256 %1, ptr @call_flags, align 32
store i256 %2, ptr @extra_abi_data, align 32
store i256 %3, ptr getelementptr inbounds ([10 x i256], ptr @extra_abi_data, i256 0, i32 1), align 32
store i256 %4, ptr getelementptr inbounds ([10 x i256], ptr @extra_abi_data, i256 0, i32 2), align 32
store i256 %5, ptr getelementptr inbounds ([10 x i256], ptr @extra_abi_data, i256 0, i32 3), align 32
store i256 %6, ptr getelementptr inbounds ([10 x i256], ptr @extra_abi_data, i256 0, i32 4), align 32
store i256 %7, ptr getelementptr inbounds ([10 x i256], ptr @extra_abi_data, i256 0, i32 5), align 32
store i256 %8, ptr getelementptr inbounds ([10 x i256], ptr @extra_abi_data, i256 0, i32 6), align 32
store i256 %9, ptr getelementptr inbounds ([10 x i256], ptr @extra_abi_data, i256 0, i32 7), align 32
store i256 %10, ptr getelementptr inbounds ([10 x i256], ptr @extra_abi_data, i256 0, i32 8), align 32
store i256 %11, ptr getelementptr inbounds ([10 x i256], ptr @extra_abi_data, i256 0, i32 9), align 32
%is_deploy_code_call_flag_truncated = and i256 %1, 1
%is_deploy_code_call_flag = icmp eq i256 %is_deploy_code_call_flag_truncated, 1
br i1 %is_deploy_code_call_flag, label %deploy_code_call_block, label %runtime_code_call_block
return: ; preds = %runtime_code_call_block, %deploy_code_call_block
ret i256 0
deploy_code_call_block: ; preds = %entry
call void @__deploy()
br label %return
runtime_code_call_block: ; preds = %entry
call void @__runtime()
br label %return
}
; Function Attrs: nofree null_pointer_is_valid
define private void @__deploy() #8 personality ptr @__personality {
entry:
call void @main(i1 true)
br label %return
return: ; preds = %entry
ret void
}
; Function Attrs: nofree null_pointer_is_valid
define private void @__runtime() #8 personality ptr @__personality {
entry:
call void @main(i1 false)
br label %return
return: ; preds = %entry
ret void
}
; Function Attrs: nofree null_pointer_is_valid
define private void @main(i1 %0) #8 personality ptr @__personality { ; 6 cells are allocated at the beginning of the function.
entry:
%stack_var_000 = alloca i256, align 32
store i256 0, ptr %stack_var_000, align 32
%stack_var_001 = alloca i256, align 32
store i256 0, ptr %stack_var_001, align 32
%stack_var_002 = alloca i256, align 32
store i256 0, ptr %stack_var_002, align 32
%stack_var_003 = alloca i256, align 32
store i256 0, ptr %stack_var_003, align 32
%stack_var_004 = alloca i256, align 32
store i256 0, ptr %stack_var_004, align 32
%stack_var_005 = alloca i256, align 32
store i256 0, ptr %stack_var_005, align 32
br i1 %0, label %"block_dt_0/0", label %"block_rt_0/0"
return: ; No predecessors!
ret void
"block_dt_0/0": ; preds = %entry
store i256 128, ptr %stack_var_000, align 32
store i256 64, ptr %stack_var_001, align 32
%argument_0 = load i256, ptr %stack_var_001, align 32
%argument_1 = load i256, ptr %stack_var_000, align 32
%memory_store_pointer = inttoptr i256 %argument_0 to ptr addrspace(1)
store i256 %argument_1, ptr addrspace(1) %memory_store_pointer, align 1
%get_u128_value = call i256 @llvm.syncvm.getu128()
store i256 %get_u128_value, ptr %stack_var_000, align 32
%dup1 = load i256, ptr %stack_var_000, align 32
store i256 %dup1, ptr %stack_var_001, align 32
%argument_01 = load i256, ptr %stack_var_001, align 32
%comparison_result = icmp eq i256 %argument_01, 0
%comparison_result_extended = zext i1 %comparison_result to i256
store i256 %comparison_result_extended, ptr %stack_var_001, align 32
store i256 1, ptr %stack_var_002, align 32
%conditional_dt_1_condition = load i256, ptr %stack_var_001, align 32
%conditional_dt_1_condition_compared = icmp ne i256 %conditional_dt_1_condition, 0
br i1 %conditional_dt_1_condition_compared, label %"block_dt_1/0", label %conditional_dt_1_join_block
"block_dt_1/0": ; preds = %"block_dt_0/0"
store i256 0, ptr %stack_var_000, align 32
%dup15 = load i256, ptr %stack_var_000, align 32
store i256 %dup15, ptr %stack_var_001, align 32
store i256 0, ptr %stack_var_002, align 32
store i256 0, ptr %stack_var_003, align 32
%argument_06 = load i256, ptr %stack_var_003, align 32
%argument_17 = load i256, ptr %stack_var_002, align 32
%argument_2 = load i256, ptr %stack_var_001, align 32
%calldata_copy_destination_pointer = inttoptr i256 %argument_06 to ptr addrspace(1)
%calldata_pointer = load ptr addrspace(3), ptr @ptr_calldata, align 32
%calldata_source_pointer = getelementptr i8, ptr addrspace(3) %calldata_pointer, i256 %argument_17
call void @llvm.memcpy.p1.p3.i256(ptr addrspace(1) align 1 %calldata_copy_destination_pointer, ptr addrspace(3) align 1 %calldata_source_pointer, i256 %argument_2, i1 false)
store i256 0, ptr %stack_var_001, align 32
%argument_08 = load i256, ptr %stack_var_001, align 32
%argument_19 = load i256, ptr %stack_var_000, align 32
store i256 32, ptr addrspace(2) inttoptr (i256 256 to ptr addrspace(2)), align 1
store i256 0, ptr addrspace(2) inttoptr (i256 288 to ptr addrspace(2)), align 1
call void @__return(i256 256, i256 64, i256 2)
unreachable
"block_rt_0/0": ; preds = %entry
store i256 128, ptr %stack_var_000, align 32
store i256 64, ptr %stack_var_001, align 32
%argument_010 = load i256, ptr %stack_var_001, align 32
%argument_111 = load i256, ptr %stack_var_000, align 32
%memory_store_pointer12 = inttoptr i256 %argument_010 to ptr addrspace(1)
store i256 %argument_111, ptr addrspace(1) %memory_store_pointer12, align 1
%get_u128_value13 = call i256 @llvm.syncvm.getu128()
store i256 %get_u128_value13, ptr %stack_var_000, align 32
%dup114 = load i256, ptr %stack_var_000, align 32
store i256 %dup114, ptr %stack_var_001, align 32
%argument_015 = load i256, ptr %stack_var_001, align 32
%comparison_result16 = icmp eq i256 %argument_015, 0
%comparison_result_extended17 = zext i1 %comparison_result16 to i256
store i256 %comparison_result_extended17, ptr %stack_var_001, align 32
store i256 1, ptr %stack_var_002, align 32
%conditional_rt_1_condition = load i256, ptr %stack_var_001, align 32
%conditional_rt_1_condition_compared = icmp ne i256 %conditional_rt_1_condition, 0
br i1 %conditional_rt_1_condition_compared, label %"block_rt_1/0", label %conditional_rt_1_join_block
"block_rt_1/0": ; preds = %"block_rt_0/0"
store i256 4, ptr %stack_var_000, align 32
%calldatasize = load i256, ptr @calldatasize, align 32
store i256 %calldatasize, ptr %stack_var_001, align 32
%argument_021 = load i256, ptr %stack_var_001, align 32
%argument_122 = load i256, ptr %stack_var_000, align 32
%comparison_result23 = icmp ult i256 %argument_021, %argument_122
%comparison_result_extended24 = zext i1 %comparison_result23 to i256
store i256 %comparison_result_extended24, ptr %stack_var_000, align 32
store i256 2, ptr %stack_var_001, align 32
%conditional_rt_2_condition = load i256, ptr %stack_var_000, align 32
%conditional_rt_2_condition_compared = icmp ne i256 %conditional_rt_2_condition, 0
br i1 %conditional_rt_2_condition_compared, label %"block_rt_2/0", label %conditional_rt_2_join_block
"block_rt_2/0": ; preds = %"block_rt_1/0"
store i256 0, ptr %stack_var_000, align 32
%dup134 = load i256, ptr %stack_var_000, align 32
store i256 %dup134, ptr %stack_var_001, align 32
%argument_035 = load i256, ptr %stack_var_001, align 32
%argument_136 = load i256, ptr %stack_var_000, align 32
call void @__revert(i256 %argument_035, i256 %argument_136, i256 0)
unreachable
"block_rt_2/1": ; preds = %conditional_rt_3_join_block
store i256 0, ptr %stack_var_001, align 32
%dup137 = load i256, ptr %stack_var_001, align 32
store i256 %dup137, ptr %stack_var_002, align 32
%argument_038 = load i256, ptr %stack_var_002, align 32
%argument_139 = load i256, ptr %stack_var_001, align 32
call void @__revert(i256 %argument_038, i256 %argument_139, i256 0)
unreachable
"block_rt_3/0": ; preds = %conditional_rt_2_join_block
store i256 4, ptr %stack_var_001, align 32
store i256 5, ptr %stack_var_002, align 32
br label %"block_rt_5/0"
"block_rt_4/0": ; preds = %"block_rt_5/0"
store i256 64, ptr %stack_var_002, align 32
%dup140 = load i256, ptr %stack_var_002, align 32
store i256 %dup140, ptr %stack_var_003, align 32
%argument_041 = load i256, ptr %stack_var_003, align 32
%memory_load_pointer = inttoptr i256 %argument_041 to ptr addrspace(1)
%memory_load_result = load i256, ptr addrspace(1) %memory_load_pointer, align 1
store i256 %memory_load_result, ptr %stack_var_003, align 32
%swap2_top_value = load i256, ptr %stack_var_003, align 32
%swap2_swap_value = load i256, ptr %stack_var_001, align 32
store i256 %swap2_swap_value, ptr %stack_var_003, align 32
store i256 %swap2_top_value, ptr %stack_var_001, align 32
%dup3 = load i256, ptr %stack_var_001, align 32
store i256 %dup3, ptr %stack_var_004, align 32
%argument_042 = load i256, ptr %stack_var_004, align 32
%argument_143 = load i256, ptr %stack_var_003, align 32
%memory_store_pointer44 = inttoptr i256 %argument_042 to ptr addrspace(1)
store i256 %argument_143, ptr addrspace(1) %memory_store_pointer44, align 1
%argument_045 = load i256, ptr %stack_var_002, align 32
%memory_load_pointer46 = inttoptr i256 %argument_045 to ptr addrspace(1)
%memory_load_result47 = load i256, ptr addrspace(1) %memory_load_pointer46, align 1
store i256 %memory_load_result47, ptr %stack_var_002, align 32
%swap1_top_value = load i256, ptr %stack_var_002, align 32
%swap1_swap_value = load i256, ptr %stack_var_001, align 32
store i256 %swap1_swap_value, ptr %stack_var_002, align 32
store i256 %swap1_top_value, ptr %stack_var_001, align 32
%dup2 = load i256, ptr %stack_var_001, align 32
store i256 %dup2, ptr %stack_var_003, align 32
%swap1_top_value48 = load i256, ptr %stack_var_003, align 32
%swap1_swap_value49 = load i256, ptr %stack_var_002, align 32
store i256 %swap1_swap_value49, ptr %stack_var_003, align 32
store i256 %swap1_top_value48, ptr %stack_var_002, align 32
%argument_050 = load i256, ptr %stack_var_003, align 32
%argument_151 = load i256, ptr %stack_var_002, align 32
%subtraction_result = sub i256 %argument_050, %argument_151
store i256 %subtraction_result, ptr %stack_var_002, align 32
store i256 32, ptr %stack_var_003, align 32
%argument_052 = load i256, ptr %stack_var_003, align 32
%argument_153 = load i256, ptr %stack_var_002, align 32
%addition_result = add i256 %argument_052, %argument_153
store i256 %addition_result, ptr %stack_var_002, align 32
%swap1_top_value54 = load i256, ptr %stack_var_002, align 32
%swap1_swap_value55 = load i256, ptr %stack_var_001, align 32
store i256 %swap1_swap_value55, ptr %stack_var_002, align 32
store i256 %swap1_top_value54, ptr %stack_var_001, align 32
%argument_056 = load i256, ptr %stack_var_002, align 32
%argument_157 = load i256, ptr %stack_var_001, align 32
call void @__return(i256 %argument_056, i256 %argument_157, i256 0)
unreachable
"block_rt_5/0": ; preds = %"block_rt_3/0"
store i256 42, ptr %stack_var_002, align 32
%swap1_top_value58 = load i256, ptr %stack_var_002, align 32
%swap1_swap_value59 = load i256, ptr %stack_var_001, align 32
store i256 %swap1_swap_value59, ptr %stack_var_002, align 32
store i256 %swap1_top_value58, ptr %stack_var_001, align 32
br label %"block_rt_4/0"
conditional_dt_1_join_block: ; preds = %"block_dt_0/0"
store i256 0, ptr %stack_var_001, align 32
%dup12 = load i256, ptr %stack_var_001, align 32
store i256 %dup12, ptr %stack_var_002, align 32
%argument_03 = load i256, ptr %stack_var_002, align 32
%argument_14 = load i256, ptr %stack_var_001, align 32
call void @__revert(i256 %argument_03, i256 %argument_14, i256 0)
unreachable
conditional_rt_1_join_block: ; preds = %"block_rt_0/0"
store i256 0, ptr %stack_var_001, align 32
%dup118 = load i256, ptr %stack_var_001, align 32
store i256 %dup118, ptr %stack_var_002, align 32
%argument_019 = load i256, ptr %stack_var_002, align 32
%argument_120 = load i256, ptr %stack_var_001, align 32
call void @__revert(i256 %argument_019, i256 %argument_120, i256 0)
unreachable
conditional_rt_2_join_block: ; preds = %"block_rt_1/0"
store i256 0, ptr %stack_var_000, align 32
%argument_025 = load i256, ptr %stack_var_000, align 32
%calldata_pointer26 = load ptr addrspace(3), ptr @ptr_calldata, align 32
%calldata_pointer_with_offset = getelementptr i8, ptr addrspace(3) %calldata_pointer26, i256 %argument_025
%calldata_value = load i256, ptr addrspace(3) %calldata_pointer_with_offset, align 32
store i256 %calldata_value, ptr %stack_var_000, align 32
store i256 224, ptr %stack_var_001, align 32
%argument_027 = load i256, ptr %stack_var_001, align 32
%argument_128 = load i256, ptr %stack_var_000, align 32
%shr_call = call i256 @__shr(i256 %argument_027, i256 %argument_128)
store i256 %shr_call, ptr %stack_var_000, align 32
%dup129 = load i256, ptr %stack_var_000, align 32
store i256 %dup129, ptr %stack_var_001, align 32
store i256 1519042605, ptr %stack_var_002, align 32
%argument_030 = load i256, ptr %stack_var_002, align 32
%argument_131 = load i256, ptr %stack_var_001, align 32
%comparison_result32 = icmp eq i256 %argument_030, %argument_131
%comparison_result_extended33 = zext i1 %comparison_result32 to i256
store i256 %comparison_result_extended33, ptr %stack_var_001, align 32
store i256 3, ptr %stack_var_002, align 32
%conditional_rt_3_condition = load i256, ptr %stack_var_001, align 32
%conditional_rt_3_condition_compared = icmp ne i256 %conditional_rt_3_condition, 0
br i1 %conditional_rt_3_condition_compared, label %"block_rt_3/0", label %conditional_rt_3_join_block
conditional_rt_3_join_block: ; preds = %conditional_rt_2_join_block
br label %"block_rt_2/1"
}
attributes #0 = { nounwind }
attributes #1 = { nounwind readnone }
attributes #2 = { nounwind readonly }
attributes #3 = { writeonly }
attributes #4 = { argmemonly nocallback nofree nounwind willreturn }
attributes #5 = { noprofile }
attributes #6 = { mustprogress nofree nounwind null_pointer_is_valid readnone willreturn }
attributes #7 = { mustprogress nofree nounwind null_pointer_is_valid willreturn }
attributes #8 = { nofree null_pointer_is_valid }
Optimized LLVM IR
The redundancy is optimized by LLVM, resulting in the optimized LLVM IR below.
; Function Attrs: nofree noreturn null_pointer_is_valid
define i256 @__entry(ptr addrspace(3) %0, i256 %1, i256 %2, i256 %3, i256 %4, i256 %5, i256 %6, i256 %7, i256 %8, i256 %9, i256 %10, i256 %11) local_unnamed_addr #1 personality ptr @__personality {
entry:
store ptr addrspace(3) %0, ptr @ptr_calldata, align 32
%abi_pointer_value = ptrtoint ptr addrspace(3) %0 to i256
%abi_pointer_value_shifted = lshr i256 %abi_pointer_value, 96
%abi_length_value = and i256 %abi_pointer_value_shifted, 4294967295
store i256 %abi_length_value, ptr @calldatasize, align 32
%is_deploy_code_call_flag_truncated = and i256 %1, 1
%is_deploy_code_call_flag.not = icmp eq i256 %is_deploy_code_call_flag_truncated, 0
store i256 128, ptr addrspace(1) inttoptr (i256 64 to ptr addrspace(1)), align 64
%get_u128_value.i.i1 = tail call i256 @llvm.syncvm.getu128()
br i1 %is_deploy_code_call_flag.not, label %runtime_code_call_block, label %deploy_code_call_block
deploy_code_call_block: ; preds = %entry
%comparison_result.i.i = icmp eq i256 %get_u128_value.i.i1, 0
br i1 %comparison_result.i.i, label %"block_dt_1/0.i.i", label %"block_rt_2/0.i.i"
"block_dt_1/0.i.i": ; preds = %deploy_code_call_block
store i256 32, ptr addrspace(2) inttoptr (i256 256 to ptr addrspace(2)), align 256
store i256 0, ptr addrspace(2) inttoptr (i256 288 to ptr addrspace(2)), align 32
tail call void @llvm.syncvm.return(i256 53919893334301279589334030174039261352344891250716429051063678533632)
unreachable
"block_rt_2/0.i.i": ; preds = %runtime_code_call_block, %conditional_rt_2_join_block.i.i, %deploy_code_call_block
tail call void @llvm.syncvm.revert(i256 0)
unreachable
runtime_code_call_block: ; preds = %entry
%comparison_result.i.i2 = icmp ne i256 %get_u128_value.i.i1, 0
%calldatasize.i.i = load i256, ptr @calldatasize, align 32
%comparison_result23.i.i = icmp ult i256 %calldatasize.i.i, 4
%or.cond.i.i = select i1 %comparison_result.i.i2, i1 true, i1 %comparison_result23.i.i
br i1 %or.cond.i.i, label %"block_rt_2/0.i.i", label %conditional_rt_2_join_block.i.i
"block_rt_3/0.i.i": ; preds = %conditional_rt_2_join_block.i.i
%memory_load_result.i.i = load i256, ptr addrspace(1) inttoptr (i256 64 to ptr addrspace(1)), align 64
%memory_store_pointer44.i.i = inttoptr i256 %memory_load_result.i.i to ptr addrspace(1)
store i256 42, ptr addrspace(1) %memory_store_pointer44.i.i, align 1
%memory_load_result47.i.i = load i256, ptr addrspace(1) inttoptr (i256 64 to ptr addrspace(1)), align 64
%subtraction_result.i.i = add i256 %memory_load_result.i.i, 32
%addition_result.i.i = sub i256 %subtraction_result.i.i, %memory_load_result47.i.i
%12 = tail call i256 @llvm.umin.i256(i256 %memory_load_result47.i.i, i256 4294967295)
%13 = tail call i256 @llvm.umin.i256(i256 %addition_result.i.i, i256 4294967295)
%offset_shifted.i.i.i.i = shl nuw nsw i256 %12, 64
%length_shifted.i.i.i.i = shl nuw nsw i256 %13, 96
%tmp.i.i.i.i = or i256 %length_shifted.i.i.i.i, %offset_shifted.i.i.i.i
tail call void @llvm.syncvm.return(i256 %tmp.i.i.i.i)
unreachable
conditional_rt_2_join_block.i.i: ; preds = %runtime_code_call_block
%calldata_pointer26.i.i = load ptr addrspace(3), ptr @ptr_calldata, align 32
%calldata_value.i.i = load i256, ptr addrspace(3) %calldata_pointer26.i.i, align 32
%shift_res.i.mask.i.i = and i256 %calldata_value.i.i, -26959946667150639794667015087019630673637144422540572481103610249216
%comparison_result32.i.i = icmp eq i256 %shift_res.i.mask.i.i, 40953307615929575801107647705360601464619672688377251939886941387873771847680
br i1 %comparison_result32.i.i, label %"block_rt_3/0.i.i", label %"block_rt_2/0.i.i"
}
attributes #0 = { nounwind }
attributes #1 = { nofree noreturn null_pointer_is_valid }
attributes #2 = { noreturn nounwind }
attributes #3 = { nocallback nofree nosync nounwind readnone speculatable willreturn }
EraVM Assembly
The optimized LLVM IR is translated into EraVM assembly below, allowing the size comparable to the Yul pipeline.
.text
.file "default.sol:Test"
.globl __entry
__entry:
.func_begin0:
ptr.add r1, r0, stack[@ptr_calldata]
shr.s 96, r1, r1
and @CPI0_0[0], r1, stack[@calldatasize]
add 128, r0, r1
st.1 64, r1
context.get_context_u128 r1
and! 1, r2, r2
jump.ne @.BB0_1
sub! r1, r0, r1
jump.ne @.BB0_3
add stack[@calldatasize], r0, r1
sub.s! 4, r1, r1
jump.lt @.BB0_3
ptr.add stack[@ptr_calldata], r0, r1
ld r1, r1
and @CPI0_2[0], r1, r1
sub.s! @CPI0_3[0], r1, r1
jump.ne @.BB0_3
ld.1 64, r1
add 42, r0, r2
st.1 r1, r2
ld.1 64, r2
sub r1, r2, r1
add 32, r1, r1
add @CPI0_0[0], r0, r3
sub.s! @CPI0_0[0], r1, r4
add.ge r3, r0, r1
sub.s! @CPI0_0[0], r2, r4
add.ge r3, r0, r2
shl.s 64, r2, r2
shl.s 96, r1, r1
or r1, r2, r1
ret.ok.to_label r1, @DEFAULT_FAR_RETURN
.BB0_1:
sub! r1, r0, r1
jump.ne @.BB0_3
add 32, r0, r1
st.2 256, r1
st.2 288, r0
add @CPI0_1[0], r0, r1
ret.ok.to_label r1, @DEFAULT_FAR_RETURN
.BB0_3:
add r0, r0, r1
ret.revert.to_label r1, @DEFAULT_FAR_REVERT
.func_end0:
.data
.p2align 5
calldatasize:
.cell 0
.p2align 5
ptr_calldata:
.cell 0
.note.GNU-stack
.rodata
CPI0_0:
.cell 4294967295
CPI0_1:
.cell 53919893334301279589334030174039261352344891250716429051063678533632
CPI0_2:
.cell -26959946667150639794667015087019630673637144422540572481103610249216
CPI0_3:
.cell 40953307615929575801107647705360601464619672688377251939886941387873771847680
For comparison, the Yul pipeline of solc v0.8.21 produces the following EraVM assembly:
.text
.file "default.sol:Test"
.globl __entry
__entry:
.func_begin0:
ptr.add r1, r0, stack[@ptr_calldata]
shr.s 96, r1, r1
and @CPI0_0[0], r1, stack[@calldatasize]
add 128, r0, r1
st.1 64, r1
and! 1, r2, r1
jump.ne @.BB0_1
add stack[@calldatasize], r0, r1
sub.s! 4, r1, r1
jump.lt @.BB0_2
ptr.add stack[@ptr_calldata], r0, r1
ld r1, r1
and @CPI0_2[0], r1, r1
sub.s! @CPI0_3[0], r1, r1
jump.ne @.BB0_2
context.get_context_u128 r1
sub! r1, r0, r1
jump.ne @.BB0_2
sub.s 4, r0, r1
add stack[@calldatasize], r1, r1
add @CPI0_4[0], r0, r2
sub! r1, r0, r3
add r0, r0, r3
add.lt r2, r0, r3
and @CPI0_4[0], r1, r1
sub! r1, r0, r4
add.le r0, r0, r2
sub.s! @CPI0_4[0], r1, r1
add r3, r0, r1
add.eq r2, r0, r1
sub! r1, r0, r1
jump.ne @.BB0_2
add 42, r0, r1
st.1 128, r1
add @CPI0_5[0], r0, r1
ret.ok.to_label r1, @DEFAULT_FAR_RETURN
.BB0_1:
context.get_context_u128 r1
sub! r1, r0, r1
jump.ne @.BB0_2
add 32, r0, r1
st.2 256, r1
st.2 288, r0
add @CPI0_1[0], r0, r1
ret.ok.to_label r1, @DEFAULT_FAR_RETURN
.BB0_2:
add r0, r0, r1
ret.revert.to_label r1, @DEFAULT_FAR_REVERT
.func_end0:
.data
.p2align 5
calldatasize:
.cell 0
.p2align 5
ptr_calldata:
.cell 0
.note.GNU-stack
.rodata
CPI0_0:
.cell 4294967295
CPI0_1:
.cell 53919893334301279589334030174039261352344891250716429051063678533632
CPI0_2:
.cell -26959946667150639794667015087019630673637144422540572481103610249216
CPI0_3:
.cell 40953307615929575801107647705360601464619672688377251939886941387873771847680
CPI0_4:
.cell -57896044618658097711785492504343953926634992332820282019728792003956564819968
CPI0_5:
.cell 2535301202817642044428229017600