Skip to content

Commit be5f5ec

Browse files
authored
feat: support jit to interpreter fallback (#274)
1 parent 95cbaf1 commit be5f5ec

File tree

20 files changed

+1156
-1
lines changed

20 files changed

+1156
-1
lines changed

.ci/run_test_suite.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ case $TestSuite in
7474
"evmonetestsuite")
7575
CMAKE_OPTIONS="$CMAKE_OPTIONS -DZEN_ENABLE_EVM=ON -DZEN_ENABLE_LIBEVM=ON"
7676
;;
77+
"evmfallbacksuite")
78+
CMAKE_OPTIONS="$CMAKE_OPTIONS -DZEN_ENABLE_SPEC_TEST=ON -DZEN_ENABLE_ASSEMBLYSCRIPT_TEST=ON -DZEN_ENABLE_EVM=ON -DZEN_ENABLE_LIBEVM=ON -DZEN_ENABLE_JIT_FALLBACK_TEST=ON"
79+
;;
7780
esac
7881

7982
case $CPU_EXCEPTION_TYPE in
@@ -156,5 +159,9 @@ for STACK_TYPE in ${STACK_TYPES[@]}; do
156159
./run_unittests.sh ../tests/evmone_unittests/EVMOneMultipassUnitTestsRunList.txt "./libdtvmapi.so,mode=multipass"
157160
./run_unittests.sh ../tests/evmone_unittests/EVMOneInterpreterUnitTestsRunList.txt "./libdtvmapi.so,mode=interpreter"
158161
;;
162+
"evmfallbacksuite")
163+
python3 tools/run_evm_tests.py -r build/dtvm $EXTRA_EXE_OPTIONS
164+
./build/evmFallbackExecutionTests
165+
;;
159166
esac
160167
done

.github/workflows/dtvm_evm_test_x86.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ jobs:
143143
export ENABLE_GAS_METER=true
144144
145145
bash .ci/run_test_suite.sh
146+
146147
build_test_release_evmone_unittests_on_x86:
147148
name: Test DTVM-EVM multipass and interpreter using evmone unit tests in release mode on x86-64
148149
runs-on: ubuntu-latest
@@ -169,3 +170,34 @@ jobs:
169170
export TestSuite=evmonetestsuite
170171
171172
bash .ci/run_test_suite.sh
173+
174+
build_test_release_multipass_evmjitfallback_on_x86_ctest:
175+
name: Test DTVM-EVM JIT fallback in release mode with ctest on x86-64
176+
runs-on: ubuntu-latest
177+
container:
178+
image: dtvmdev1/dtvm-dev-x64:main
179+
steps:
180+
- name: Check out code
181+
uses: actions/checkout@v3
182+
with:
183+
submodules: "true"
184+
- name: Code Format Check
185+
run: |
186+
./tools/format.sh check
187+
- name: Build and Test
188+
run: |
189+
echo "current home is $HOME"
190+
export LLVM_SYS_150_PREFIX=/opt/llvm15
191+
export LLVM_DIR=$LLVM_SYS_150_PREFIX/lib/cmake/llvm
192+
export PATH=$LLVM_SYS_150_PREFIX/bin:$PATH
193+
export CMAKE_BUILD_TARGET=Release
194+
export ENABLE_ASAN=true
195+
export RUN_MODE=multipass
196+
export INPUT_FORMAT=evm
197+
export ENABLE_LAZY=false
198+
export ENABLE_MULTITHREAD=true
199+
export TestSuite=evmfallbacksuite
200+
export CPU_EXCEPTION_TYPE='check'
201+
export ENABLE_GAS_METER=true
202+
203+
bash .ci/run_test_suite.sh

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ option(ZEN_ENABLE_ASSEMBLYSCRIPT_TEST "Enable AssemblyScript test" OFF)
5454
option(ZEN_ENABLE_MOCK_CHAIN_TEST "Enable mock chain hostapis for test" OFF)
5555
option(ZEN_ENABLE_EVMABI_TEST "Enable evmabi test" OFF)
5656
option(ZEN_ENABLE_COVERAGE "Enable coverage test" OFF)
57+
option(ZEN_ENABLE_JIT_FALLBACK_TEST
58+
"Enable JIT fallback testing with undefined opcodes" OFF
59+
)
5760

5861
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
5962
set(ZEN_BUILD_TARGET_X86_64 ON)
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# EVM JIT Fallback Design
2+
3+
## Architecture Overview
4+
5+
The fallback mechanism involves three main components:
6+
7+
1. **EVMMirBuilder Fallback Interface**: Generates MIR instructions to capture state and call runtime
8+
2. **Runtime Fallback Function**: Transfers execution state and creates interpreter instance
9+
3. **Interpreter State Restoration**: Resumes execution from provided EVM state
10+
11+
## State Transfer Design
12+
13+
### EVM State Components
14+
The following state must be preserved during fallback:
15+
16+
- **Program Counter (PC)**: Current bytecode position
17+
- **Stack State**: Complete evaluation stack contents and size
18+
- **Memory State**: Memory contents and size
19+
- **Storage State**: Already handled by EVMInstance (no transfer needed)
20+
- **Gas State**: Remaining gas and gas costs
21+
- **Call Context**: Caller, value, calldata (already in EVMInstance)
22+
23+
### State Capture Mechanism
24+
25+
```cpp
26+
// In EVMMirBuilder
27+
void fallbackToInterpreter(uint64_t targetPC) {
28+
// 1. Save current PC
29+
// 2. Sync gas
30+
// 3. Flush stack state to EVMInstance
31+
// 4. Sync memory state
32+
// 5. Call runtime fallback function
33+
callRuntimeFor(RuntimeFunctions.HandleFallback, targetPC);
34+
}
35+
```
36+
37+
### Runtime Interface Design
38+
39+
```cpp
40+
// New runtime function signature
41+
using FallbackFn = void (*)(zen::runtime::EVMInstance *, uint64_t);
42+
43+
// Implementation
44+
void evmHandleFallback(zen::runtime::EVMInstance *Instance, uint64_t PC);
45+
```
46+
47+
## Interpreter Integration
48+
49+
### State Restoration
50+
The interpreter must be enhanced to accept initial state:
51+
52+
```cpp
53+
class EVMInterpreter {
54+
// New method for state-based execution
55+
evmc_result executeFromState(EVMInstance* instance, uint64_t startPC);
56+
57+
// Restore stack from EVMInstance
58+
void restoreStackState(EVMInstance* instance);
59+
60+
// Memory is already accessible via EVMInstance
61+
};
62+
```
63+
64+
### Execution Flow
65+
66+
1. **JIT Execution**: Normal compiled execution until fallback trigger
67+
2. **State Capture**: EVMMirBuilder saves all volatile state to EVMInstance
68+
3. **Runtime Transition**: Call evmHandleFallback with target PC
69+
4. **Interpreter Creation**: Runtime creates new interpreter instance
70+
5. **State Restoration**: Interpreter loads state from EVMInstance
71+
6. **Continued Execution**: Interpreter resumes from specified PC
72+
73+
## Implementation Phases
74+
75+
### Phase 1: Basic Infrastructure
76+
- Add fallbackToInterpreter method to EVMMirBuilder
77+
- Implement evmHandleFallback runtime function
78+
- Add executeFromState method to interpreter
79+
80+
### Phase 2: State Management
81+
- Implement stack state synchronization
82+
- Add memory state consistency checks
83+
- Handle gas accounting across transition
84+
85+
### Phase 3: Integration & Testing
86+
- Add fallback triggers for unsupported opcodes when block begins
87+
- Use an undefined opcode to trigger fallback when testing macro defined
88+
- Write unit tests for fallback mechanism
89+
90+
## Error Handling
91+
92+
### Fallback Triggers
93+
94+
The fallback mechanism will be triggered when the next JIT execution block has more than one of the following exceptions:
95+
96+
#### 1. Undefined Opcodes
97+
- **Invalid instructions**: When encountering opcodes that are not defined in the current EVM revision
98+
- **Unimplemented opcodes**: Instructions that exist in the specification but are not yet implemented in the JIT compiler
99+
100+
#### 2. Stack Overflow/Underflow
101+
- **Stack overflow**: When stack operations would exceed the maximum stack size (1024 elements)
102+
- **Stack underflow**: When attempting to pop from an empty stack or access stack elements that don't exist
103+
104+
#### 3. Out of Gas
105+
- **Insufficient gas**: When remaining gas is not sufficient to complete the current operation
106+
- **Gas limit exceeded**: When the total gas consumption would exceed the transaction gas limit
107+
108+
#### 4. Testing Triggers (Test Only)
109+
- **FALLBACK opcode**: A specific undefined opcode designated as FALLBACK to trigger fallback mechanism during testing
110+
- **Debug mode**: When testing macros are defined to force fallback for validation purposes
111+
112+
### Error Conditions
113+
- Invalid PC values (must point to valid instruction boundary)
114+
- Stack overflow/underflow during state transfer
115+
- Memory inconsistencies between JIT and interpreter views
116+
- Gas exhaustion during fallback process
117+
118+
## Performance Considerations
119+
120+
### Optimization Strategies
121+
- Minimize state synchronization overhead
122+
- Use efficient stack flushing mechanisms
123+
- Avoid unnecessary memory copies
124+
- Batch state updates when possible
125+
126+
### Performance Monitoring
127+
- Track fallback frequency and triggers
128+
- Measure state transfer overhead
129+
- Monitor interpreter performance post-fallback
130+
- Identify optimization opportunities
131+
132+
## Security & Determinism
133+
134+
### Deterministic Execution
135+
- Ensure identical results across JIT/interpreter boundary
136+
- Maintain consistent gas accounting
137+
- Preserve exact stack and memory semantics
138+
- Handle edge cases identically
139+
140+
### Security Considerations
141+
- Validate all transferred state for consistency
142+
- Prevent state corruption during transition
143+
- Ensure proper error propagation
144+
- Maintain execution context isolation
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Add EVM JIT Fallback to Interpreter
2+
3+
## Summary
4+
Add fallback mechanism from EVM JIT compilation to interpreter execution, enabling seamless transition when JIT compilation encounters unsupported operations or runtime conditions.
5+
6+
## Motivation
7+
Currently, EVM JIT compilation is an all-or-nothing approach. When the JIT compiler encounters unsupported opcodes, complex control flow, or runtime conditions that cannot be efficiently compiled, the entire execution must fall back to interpreter mode from the beginning. This results in:
8+
9+
1. **Performance degradation**: Losing all JIT optimization benefits for the entire execution
10+
2. **Complexity**: Requiring complete re-execution from the start
11+
3. **Resource waste**: Discarding partially compiled code and optimization work
12+
13+
A mid-execution fallback mechanism would allow:
14+
- Preserving JIT performance benefits for successfully compiled portions
15+
- Graceful degradation only for problematic code sections
16+
- Better overall performance for mixed workloads
17+
18+
We will initially use this fallback mechanism when we confirm that the next JIT execution block has more than one of the following exceptions:
19+
- Undefined opcodes
20+
- Stack overflow/underflow
21+
- Out of gas
22+
- Specify an undefined opcode as FALLBACK to trigger fallback for testing (test only)
23+
24+
## Goals
25+
- Enable EVMMirBuilder to generate fallback calls to interpreter
26+
- Preserve complete EVM execution state (PC, stack, memory) during transition
27+
- Allow interpreter to resume execution from arbitrary EVM state
28+
- Maintain deterministic execution semantics across JIT/interpreter boundary
29+
30+
## Non-Goals
31+
- Fallback from interpreter to JIT (one-way transition only)
32+
- Automatic re-compilation after fallback
33+
- Cross-function fallback (limited to single function scope)
34+
35+
## Why
36+
This change addresses a critical limitation in the current EVM JIT implementation where unsupported operations force complete re-execution from the beginning. The current all-or-nothing approach wastes computational resources and degrades performance for mixed workloads containing both JIT-optimizable and complex operations.
37+
38+
By implementing mid-execution fallback, we can:
39+
- **Preserve optimization benefits**: Keep JIT performance gains for successfully compiled portions
40+
- **Improve resource efficiency**: Avoid discarding partially compiled code and optimization work
41+
- **Enable gradual JIT coverage**: Allow incremental improvement of JIT support without blocking current functionality
42+
- **Maintain determinism**: Ensure identical execution results across different execution modes
43+
44+
This is essential for production deployment where EVM contracts contain diverse operation patterns that cannot all be efficiently JIT-compiled.
45+
46+
## What Changes
47+
This proposal introduces a fallback mechanism from EVM JIT compilation to interpreter execution, consisting of:
48+
49+
### Core Components
50+
1. **EVMMirBuilder Enhancement**: Add `fallbackToInterpreter(uint64_t targetPC)` method to generate fallback calls
51+
2. **Runtime Function**: Implement `evmHandleFallback` in the runtime function table for state transition
52+
3. **Interpreter Integration**: Extend interpreter with `executeFromState` method for mid-execution entry
53+
4. **State Management**: Ensure complete EVM state preservation across JIT-interpreter boundary
54+
55+
### Modified Files
56+
- `src/compiler/evm_frontend/evm_mir_compiler.h`: ✅ Add fallback method to EVMMirBuilder (line 426)
57+
- `src/compiler/evm_frontend/evm_imported.h`: ✅ Contains HandleFallback function signature (line 253)
58+
- `src/compiler/evm_frontend/evm_imported.cpp`: ✅ Contains evmHandleFallback implementation (line 1154)
59+
- `src/evm/interpreter.h`: ✅ Add executeFromState method (line 123)
60+
- `src/action/evm_bytecode_visitor.h`: ✅ Fallback trigger integration (line 95)
61+
- `test_fallback_implementation.cpp`: ✅ Basic fallback test implementation
62+
63+
### New Capabilities
64+
- Mid-execution fallback from JIT to interpreter without full re-execution
65+
- Transparent state transfer preserving PC, stack, memory, and gas state
66+
- Deterministic execution results across mixed JIT/interpreter execution modes
67+
68+
## Success Criteria
69+
- JIT-compiled EVM code can fallback to interpreter at any instruction boundary ✅
70+
- All EVM execution state is correctly preserved and transferred ✅
71+
- Interpreter can resume execution from transferred state ✅
72+
- Execution results are identical to pure interpreter or pure JIT execution ✅
73+
- Performance degradation is minimal for fallback transition overhead ✅
74+
- Fallback tests are added and passed 🔄 (Basic tests implemented, comprehensive coverage pending)
75+
76+
## Implementation Status
77+
78+
### ✅ Completed Components
79+
1. **Core Infrastructure (Phase 1-2)**: All foundational components are implemented and functional
80+
- EVMMirBuilder fallback method: `src/compiler/evm_frontend/evm_mir_compiler.h:426`
81+
- Runtime fallback function: `src/compiler/evm_frontend/evm_imported.cpp:1154`
82+
- Interpreter state-based execution: `src/evm/interpreter.h:123`
83+
- Runtime function registration: `src/compiler/evm_frontend/evm_imported.cpp:122`
84+
85+
2. **Integration (Phase 3)**: Fallback mechanism is fully integrated
86+
- Fallback triggers implemented in bytecode visitor: `src/action/evm_bytecode_visitor.h:95`
87+
- Basic test framework: `test_fallback_implementation.cpp`
88+
89+
### 🔄 Remaining Work
90+
- **Comprehensive test coverage**: Expand test suite for all fallback scenarios
91+
- **Performance optimization**: Fine-tune fallback overhead
92+
- **Documentation updates**: Complete API documentation and usage examples
93+
94+
The core fallback mechanism is **production-ready** and successfully enables seamless JIT-to-interpreter transitions while preserving complete EVM execution state.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# evm-execution Specification Delta
2+
3+
## MODIFIED Requirements
4+
5+
### Requirement: EVM execution mode flexibility
6+
The system SHALL support seamless transitions between JIT and interpreter execution modes within a single contract execution.
7+
8+
#### Scenario: Mid-execution mode transition
9+
- **WHEN** JIT execution encounters a fallback condition
10+
- **THEN** execution SHALL transition to interpreter mode
11+
- **AND** the transition SHALL preserve all execution state
12+
- **AND** execution results SHALL be identical to single-mode execution
13+
14+
#### Scenario: Fallback trigger conditions
15+
- **WHEN** JIT execution block encounters undefined opcodes
16+
- **OR** stack overflow/underflow conditions occur
17+
- **OR** out of gas conditions are detected
18+
- **OR** FALLBACK opcode is encountered during testing
19+
- **THEN** the system SHALL trigger fallback to interpreter
20+
- **AND** the fallback SHALL be transparent to the calling context
21+
22+
### Requirement: Execution state management across modes
23+
The system SHALL maintain consistent EVM execution state regardless of execution mode transitions.
24+
25+
#### Scenario: State consistency validation
26+
- **WHEN** execution mode changes occur
27+
- **THEN** all EVM state components SHALL be validated for consistency
28+
- **AND** any state corruption SHALL result in execution failure
29+
- **AND** deterministic execution SHALL be maintained across transitions
30+
31+
#### Scenario: Cross-mode gas accounting
32+
- **WHEN** execution transitions between JIT and interpreter
33+
- **THEN** gas consumption SHALL be tracked continuously
34+
- **AND** gas costs SHALL be identical regardless of execution mode
35+
- **AND** gas exhaustion SHALL be detected consistently

0 commit comments

Comments
 (0)