EVM Assembly Translator
Our toolchain uses two Solidity code generators:
Name | solc support | solc default |
---|---|---|
EVM Assembly | >=0.4.12 | <0.8 |
Yul | >=0.8.0 | >=0.8 |
ZKsync Fork of solc
EVM assembly is challenging to translate to LLVM IR because it obscures the contract’s control flow and relies heavily on dynamic jumps.
solc's EVM assembly representation introduces several challenges for our LLVM IR translator:
- Internal function pointers are stored in memory or storage, then dynamically loaded and called.
- Each iteration of local recursion allocates an additional stack frame.
- Some try-catch patterns leave values on the stack, complicating stack analysis.
All of these issues have been resolved in our fork of solc, where we removed dynamic jumps and added the necessary metadata in the code generation process.
Source Code
In this and the following sections, you will find a minimal example of a Solidity contract, its EVM assembly, and its translation to LLVM IR, which is then compiled into EraVM bytecode.
contract Example {
function main() public pure returns (uint256 result) {
result = 42;
}
}
EVM Legacy Assembly
Produced by solc 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 an intermediate representation developed specifically for our translator. It serves several key purposes:
- Tracking the stack state to identify jump destinations.
- Duplicating blocks that are reachable from predecessors with different stack states.
- Reconstructing the complete control-flow graph of the contract using the aforementioned data.
- Resolving dependencies and static data chunks.
Meaning of EthIR data:
V_<name>
- a value returned by an instruction.T_<tag>
- the tag of an assembly block.40
- a hexadecimal constant.tests/solidity/simple/default.sol:Test
- a contract full path definition.
Stack legend format: [ <current_1> | <current_2> | ... | <current_N> ] - [ <popped_1> | <popped_2> | ... | <popped_N> ] + [ <pushed_1> | <pushed_2> | ... | <pushed_N> ]
.
// 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 required stack space is allocated at the start of the main
function, and every stack operation uses a statically known stack pointer with an offset derived 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) #7 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
%calldatasize = load i256, ptr @calldatasize, align 32
%ptr_calldata = load ptr addrspace(3), ptr @ptr_calldata, align 32
%calldata_end_pointer = getelementptr i8, ptr addrspace(3) %ptr_calldata, i256 %calldatasize
store ptr addrspace(3) %calldata_end_pointer, ptr @ptr_return_data, align 32
store ptr addrspace(3) %calldata_end_pointer, ptr @ptr_decommit, align 32
%calldatasize1 = load i256, ptr @calldatasize, align 32
%ptr_calldata2 = load ptr addrspace(3), ptr @ptr_calldata, align 32
%calldata_end_pointer3 = getelementptr i8, ptr addrspace(3) %ptr_calldata2, i256 %calldatasize1
store ptr addrspace(3) %calldata_end_pointer3, ptr @ptr_active, align 32
store ptr addrspace(3) %calldata_end_pointer3, ptr getelementptr inbounds ([16 x ptr addrspace(3)], ptr @ptr_active, i256 0, i256 1), align 32
store ptr addrspace(3) %calldata_end_pointer3, ptr getelementptr inbounds ([16 x ptr addrspace(3)], ptr @ptr_active, i256 0, i256 2), align 32
store ptr addrspace(3) %calldata_end_pointer3, ptr getelementptr inbounds ([16 x ptr addrspace(3)], ptr @ptr_active, i256 0, i256 3), align 32
store ptr addrspace(3) %calldata_end_pointer3, ptr getelementptr inbounds ([16 x ptr addrspace(3)], ptr @ptr_active, i256 0, i256 4), align 32
store ptr addrspace(3) %calldata_end_pointer3, ptr getelementptr inbounds ([16 x ptr addrspace(3)], ptr @ptr_active, i256 0, i256 5), align 32
store ptr addrspace(3) %calldata_end_pointer3, ptr getelementptr inbounds ([16 x ptr addrspace(3)], ptr @ptr_active, i256 0, i256 6), align 32
store ptr addrspace(3) %calldata_end_pointer3, ptr getelementptr inbounds ([16 x ptr addrspace(3)], ptr @ptr_active, i256 0, i256 7), align 32
store ptr addrspace(3) %calldata_end_pointer3, ptr getelementptr inbounds ([16 x ptr addrspace(3)], ptr @ptr_active, i256 0, i256 8), align 32
store ptr addrspace(3) %calldata_end_pointer3, ptr getelementptr inbounds ([16 x ptr addrspace(3)], ptr @ptr_active, i256 0, i256 9), align 32
store ptr addrspace(3) %calldata_end_pointer3, ptr getelementptr inbounds ([16 x ptr addrspace(3)], ptr @ptr_active, i256 0, i256 10), align 32
store ptr addrspace(3) %calldata_end_pointer3, ptr getelementptr inbounds ([16 x ptr addrspace(3)], ptr @ptr_active, i256 0, i256 11), align 32
store ptr addrspace(3) %calldata_end_pointer3, ptr getelementptr inbounds ([16 x ptr addrspace(3)], ptr @ptr_active, i256 0, i256 12), align 32
store ptr addrspace(3) %calldata_end_pointer3, ptr getelementptr inbounds ([16 x ptr addrspace(3)], ptr @ptr_active, i256 0, i256 13), align 32
store ptr addrspace(3) %calldata_end_pointer3, ptr getelementptr inbounds ([16 x ptr addrspace(3)], ptr @ptr_active, i256 0, i256 14), align 32
store ptr addrspace(3) %calldata_end_pointer3, ptr getelementptr inbounds ([16 x ptr addrspace(3)], ptr @ptr_active, i256 0, i256 15), 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() #7 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() #7 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) #7 personality ptr @__personality {
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
%stack_var_006 = alloca i256, align 32
store i256 0, ptr %stack_var_006, 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.eravm.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 2, ptr %stack_var_000, align 32
br label %"block_dt_2/0"
"block_dt_2/0": ; preds = %"block_dt_1/0"
store i256 0, ptr %stack_var_000, align 32
%dup14 = load i256, ptr %stack_var_000, align 32
store i256 %dup14, 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_05 = load i256, ptr %stack_var_003, align 32
%argument_16 = 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_05 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_16
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_07 = load i256, ptr %stack_var_001, align 32
%argument_18 = 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_09 = load i256, ptr %stack_var_001, align 32
%argument_110 = load i256, ptr %stack_var_000, align 32
%memory_store_pointer11 = inttoptr i256 %argument_09 to ptr addrspace(1)
store i256 %argument_110, ptr addrspace(1) %memory_store_pointer11, align 1
%get_u128_value12 = call i256 @llvm.eravm.getu128()
store i256 %get_u128_value12, ptr %stack_var_000, align 32
%dup113 = load i256, ptr %stack_var_000, align 32
store i256 %dup113, ptr %stack_var_001, align 32
%argument_014 = load i256, ptr %stack_var_001, align 32
%comparison_result15 = icmp eq i256 %argument_014, 0
%comparison_result_extended16 = zext i1 %comparison_result15 to i256
store i256 %comparison_result_extended16, 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_019 = load i256, ptr %stack_var_001, align 32
%argument_120 = load i256, ptr %stack_var_000, align 32
%comparison_result21 = icmp ult i256 %argument_019, %argument_120
%comparison_result_extended22 = zext i1 %comparison_result21 to i256
store i256 %comparison_result_extended22, 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
store i256 0, ptr %stack_var_001, align 32
%argument_032 = load i256, ptr %stack_var_001, align 32
%argument_133 = load i256, ptr %stack_var_000, align 32
call void @__revert(i256 %argument_032, i256 %argument_133, i256 0)
unreachable
"block_rt_2/1": ; preds = %conditional_rt_3_join_block
store i256 0, ptr %stack_var_001, align 32
store i256 0, ptr %stack_var_002, align 32
%argument_034 = load i256, ptr %stack_var_002, align 32
%argument_135 = load i256, ptr %stack_var_001, align 32
call void @__revert(i256 %argument_034, i256 %argument_135, 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_6/0"
store i256 64, ptr %stack_var_002, align 32
%argument_036 = load i256, ptr %stack_var_002, align 32
%memory_load_pointer = inttoptr i256 %argument_036 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_002, align 32
%dup137 = load i256, ptr %stack_var_002, align 32
store i256 %dup137, ptr %stack_var_003, align 32
%dup3 = load i256, ptr %stack_var_001, align 32
store i256 %dup3, ptr %stack_var_004, align 32
%dup2 = load i256, ptr %stack_var_003, align 32
store i256 %dup2, ptr %stack_var_005, align 32
%argument_038 = load i256, ptr %stack_var_005, align 32
%argument_139 = load i256, ptr %stack_var_004, align 32
%memory_store_pointer40 = inttoptr i256 %argument_038 to ptr addrspace(1)
store i256 %argument_139, ptr addrspace(1) %memory_store_pointer40, align 1
store i256 32, ptr %stack_var_004, align 32
%argument_041 = load i256, ptr %stack_var_004, align 32
%argument_142 = load i256, ptr %stack_var_003, align 32
%addition_result = add i256 %argument_041, %argument_142
store i256 %addition_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
store i256 64, ptr %stack_var_002, align 32
%argument_043 = load i256, ptr %stack_var_002, align 32
%memory_load_pointer44 = inttoptr i256 %argument_043 to ptr addrspace(1)
%memory_load_result45 = load i256, ptr addrspace(1) %memory_load_pointer44, align 1
store i256 %memory_load_result45, ptr %stack_var_002, align 32
%dup146 = load i256, ptr %stack_var_002, align 32
store i256 %dup146, ptr %stack_var_003, align 32
%swap2_top_value47 = load i256, ptr %stack_var_003, align 32
%swap2_swap_value48 = load i256, ptr %stack_var_001, align 32
store i256 %swap2_swap_value48, ptr %stack_var_003, align 32
store i256 %swap2_top_value47, ptr %stack_var_001, align 32
%argument_049 = load i256, ptr %stack_var_003, align 32
%argument_150 = load i256, ptr %stack_var_002, align 32
%subtraction_result = sub i256 %argument_049, %argument_150
store i256 %subtraction_result, 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
%argument_051 = load i256, ptr %stack_var_002, align 32
%argument_152 = load i256, ptr %stack_var_001, align 32
call void @__return(i256 %argument_051, i256 %argument_152, i256 0)
unreachable
"block_rt_5/0": ; preds = %"block_rt_3/0"
store i256 0, ptr %stack_var_002, align 32
store i256 42, ptr %stack_var_003, align 32
%swap1_top_value53 = load i256, ptr %stack_var_003, align 32
%swap1_swap_value54 = load i256, ptr %stack_var_002, align 32
store i256 %swap1_swap_value54, ptr %stack_var_003, align 32
store i256 %swap1_top_value53, ptr %stack_var_002, align 32
%dup155 = load i256, ptr %stack_var_002, align 32
store i256 %dup155, ptr %stack_var_003, align 32
br label %"block_rt_6/0"
"block_rt_6/0": ; preds = %"block_rt_5/0"
%swap1_top_value56 = load i256, ptr %stack_var_002, align 32
%swap1_swap_value57 = load i256, ptr %stack_var_001, align 32
store i256 %swap1_swap_value57, ptr %stack_var_002, align 32
store i256 %swap1_top_value56, 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
store i256 0, ptr %stack_var_002, align 32
%argument_02 = load i256, ptr %stack_var_002, align 32
%argument_13 = load i256, ptr %stack_var_001, align 32
call void @__revert(i256 %argument_02, i256 %argument_13, i256 0)
unreachable
conditional_rt_1_join_block: ; preds = %"block_rt_0/0"
store i256 0, ptr %stack_var_001, align 32
store i256 0, ptr %stack_var_002, align 32
%argument_017 = load i256, ptr %stack_var_002, align 32
%argument_118 = load i256, ptr %stack_var_001, align 32
call void @__revert(i256 %argument_017, i256 %argument_118, i256 0)
unreachable
conditional_rt_2_join_block: ; preds = %"block_rt_1/0"
store i256 0, ptr %stack_var_000, align 32
%argument_023 = load i256, ptr %stack_var_000, align 32
%calldata_pointer24 = load ptr addrspace(3), ptr @ptr_calldata, align 32
%calldata_pointer_with_offset = getelementptr i8, ptr addrspace(3) %calldata_pointer24, i256 %argument_023
%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_025 = load i256, ptr %stack_var_001, align 32
%argument_126 = load i256, ptr %stack_var_000, align 32
%shr_call = call i256 @__shr(i256 %argument_025, i256 %argument_126)
store i256 %shr_call, ptr %stack_var_000, align 32
%dup127 = load i256, ptr %stack_var_000, align 32
store i256 %dup127, ptr %stack_var_001, align 32
store i256 3758009808, ptr %stack_var_002, align 32
%argument_028 = load i256, ptr %stack_var_002, align 32
%argument_129 = load i256, ptr %stack_var_001, align 32
%comparison_result30 = icmp eq i256 %argument_028, %argument_129
%comparison_result_extended31 = zext i1 %comparison_result30 to i256
store i256 %comparison_result_extended31, 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
store i256 2, ptr %stack_var_001, align 32
br label %"block_rt_2/1"
}
attributes #0 = { cold noreturn nounwind }
attributes #1 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
attributes #2 = { nocallback nofree nosync nounwind willreturn memory(none) }
attributes #3 = { nounwind willreturn memory(inaccessiblemem: readwrite) }
attributes #4 = { nounwind }
attributes #5 = { nounwind willreturn memory(none) }
attributes #6 = { nomerge nounwind willreturn memory(inaccessiblemem: readwrite) }
attributes #7 = { nofree null_pointer_is_valid }
Optimized LLVM IR
LLVM optimizes away the redundancy, resulting in the LLVM IR shown 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:
%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.i4 = tail call i256 @llvm.eravm.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.i4, 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.eravm.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.eravm.revert(i256 0)
unreachable
runtime_code_call_block: ; preds = %entry
%abi_pointer_value = ptrtoint ptr addrspace(3) %0 to i256
%comparison_result.i.i5 = icmp ne i256 %get_u128_value.i.i4, 0
%12 = and i256 %abi_pointer_value, 340282366604025813406317257057592410112
%comparison_result21.i.i = icmp eq i256 %12, 0
%or.cond.i = select i1 %comparison_result.i.i5, i1 true, i1 %comparison_result21.i.i
br i1 %or.cond.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
store i256 42, ptr addrspace(1) inttoptr (i256 128 to ptr addrspace(1)), align 128
tail call void @llvm.eravm.return(i256 2535301202817642044428229017600)
unreachable
conditional_rt_2_join_block.i.i: ; preds = %runtime_code_call_block
%calldata_value.i.i = load i256, ptr addrspace(3) %0, align 32
%shift_res.i.mask.i.i = and i256 %calldata_value.i.i, -26959946667150639794667015087019630673637144422540572481103610249216
%comparison_result30.i.i = icmp eq i256 %shift_res.i.mask.i.i, -14476345239007179661737236217584162293203948892620596377535322027150077329408
br i1 %comparison_result30.i.i, label %"block_rt_3/0.i.i", label %"block_rt_2/0.i.i"
}
; Function Attrs: noreturn nounwind
declare void @llvm.eravm.revert(i256) #2
; Function Attrs: noreturn nounwind
declare void @llvm.eravm.return(i256) #2
attributes #0 = { mustprogress nofree nosync nounwind willreturn memory(none) }
attributes #1 = { nofree noreturn null_pointer_is_valid }
attributes #2 = { noreturn nounwind }
EraVM Assembly
The optimized LLVM IR is then compiled into EraVM assembly, resulting in a bytecode size comparable to that produced via the Yul pipeline.
.text
.file "test.sol:Example"
.globl __entry
__entry:
.func_begin0:
add 128, r0, r3
stm.h 64, r3
ldvl r3
and! 1, r2, r0
jump.ne @.BB0_1
sub! r3, r0, r0
jump.ne @.BB0_2
and! code[@CPI0_1], r1, r0
jump.eq @.BB0_2
ldp r1, r1
and code[@CPI0_2], r1, r1
sub.s! code[@CPI0_3], r1, r0
jump.ne @.BB0_2
add 42, r0, r1
stm.h 128, r1
add code[@CPI0_4], r0, r1
retl @DEFAULT_FAR_RETURN
.BB0_1:
sub! r3, r0, r0
jump.ne @.BB0_2
add 32, r0, r1
stm.ah 256, r1
stm.ah 288, r0
add code[@CPI0_0], r0, r1
retl @DEFAULT_FAR_RETURN
.BB0_2:
add r0, r0, r1
revl @DEFAULT_FAR_REVERT
.func_end0:
.rodata
CPI0_0:
.cell 53919893334301279589334030174039261352344891250716429051063678533632
CPI0_1:
.cell 340282366604025813406317257057592410112
CPI0_2:
.cell -26959946667150639794667015087019630673637144422540572481103610249216
CPI0_3:
.cell -14476345239007179661737236217584162293203948892620596377535322027150077329408
CPI0_4:
.cell 2535301202817642044428229017600
.text
DEFAULT_UNWIND:
pncl @DEFAULT_UNWIND
DEFAULT_FAR_RETURN:
retl @DEFAULT_FAR_RETURN
DEFAULT_FAR_REVERT:
revl @DEFAULT_FAR_REVERT
For comparison, the Yul pipeline of solc v0.8.28 generates the following EraVM assembly:
.text
.file "test.sol:Example"
.globl __entry
__entry:
.func_begin0:
add 128, r0, r3
stm.h 64, r3
and! 1, r2, r0
jump.ne @.BB0_1
and! code[@CPI0_1], r1, r0
jump.eq @.BB0_7
ldp r1, r1
and code[@CPI0_2], r1, r1
sub.s! code[@CPI0_3], r1, r0
jump.ne @.BB0_7
ldvl r1
sub! r1, r0, r0
jump.ne @.BB0_7
add 42, r0, r1
stm.h 128, r1
add code[@CPI0_4], r0, r1
retl @DEFAULT_FAR_RETURN
.BB0_1:
ldvl r1
sub! r1, r0, r0
jump.ne @.BB0_7
add 32, r0, r1
stm.ah 256, r1
stm.ah 288, r0
add code[@CPI0_0], r0, r1
retl @DEFAULT_FAR_RETURN
.BB0_7:
add r0, r0, r1
revl @DEFAULT_FAR_REVERT
.func_end0:
.rodata
CPI0_0:
.cell 53919893334301279589334030174039261352344891250716429051063678533632
CPI0_1:
.cell 340282366604025813406317257057592410112
CPI0_2:
.cell -26959946667150639794667015087019630673637144422540572481103610249216
CPI0_3:
.cell -14476345239007179661737236217584162293203948892620596377535322027150077329408
CPI0_4:
.cell 2535301202817642044428229017600
.text
DEFAULT_UNWIND:
pncl @DEFAULT_UNWIND
DEFAULT_FAR_RETURN:
retl @DEFAULT_FAR_RETURN
DEFAULT_FAR_REVERT:
revl @DEFAULT_FAR_REVERT