Skip to main content

revmc_codegen/tests/
meta.rs

1use super::with_evm_context;
2use crate::{Backend, EvmCompiler};
3use revm_bytecode::opcode as op;
4use revm_interpreter::InstructionResult;
5use revm_primitives::{U256, hardfork::SpecId};
6
7// Also tests multiple functions in the same module.
8matrix_tests!(
9    translate_then_compile = |compiler| {
10        let bytecode: &[u8] = &[];
11        let spec_id = SpecId::CANCUN;
12        compiler.gas_metering(false);
13        let gas_id = compiler.translate("test1", bytecode, spec_id).unwrap();
14        compiler.gas_metering(true);
15        let no_gas_id = compiler.translate("test2", bytecode, spec_id).unwrap();
16        let gas_fn = unsafe { compiler.jit_function(gas_id) }.unwrap();
17        let no_gas_fn = unsafe { compiler.jit_function(no_gas_id) }.unwrap();
18        with_evm_context(bytecode, spec_id, |ecx, stack, stack_len| {
19            let r = unsafe { gas_fn.call(stack, stack_len, ecx) };
20            assert_eq!(r, InstructionResult::Stop);
21            let r = unsafe { no_gas_fn.call(stack, stack_len, ecx) };
22            assert_eq!(r, InstructionResult::Stop);
23        });
24    }
25);
26
27// Tests that calling `clear_ir` between two compilations works: the first function remains
28// callable after the IR is cleared, and the second function compiles and runs correctly.
29matrix_tests!(
30    clear_ir_between_compiles = |compiler| {
31        use revm_bytecode::opcode as op;
32
33        let spec_id = SpecId::CANCUN;
34        compiler.inspect_stack(true);
35
36        // First function: PUSH1 42, STOP.
37        let bytecode1: &[u8] = &[op::PUSH1, 42];
38        let f1 = unsafe { compiler.jit("clear_ir_1", bytecode1, spec_id) }.unwrap();
39
40        compiler.clear_ir().unwrap();
41
42        // Second function: PUSH1 42, PUSH1 0, MSTORE, PUSH1 1, PUSH1 2, ADD, STOP.
43        // Uses MSTORE to exercise a builtin being re-declared in the new module.
44        let bytecode2: &[u8] =
45            &[op::PUSH1, 42, op::PUSH1, 0, op::MSTORE, op::PUSH1, 1, op::PUSH1, 2, op::ADD];
46        let f2 = unsafe { compiler.jit("clear_ir_2", bytecode2, spec_id) }.unwrap();
47
48        // First function still works after clear_ir + second compilation.
49        with_evm_context(bytecode1, spec_id, |ecx, stack, stack_len| {
50            let r = unsafe { f1.call(stack, stack_len, ecx) };
51            assert_eq!(r, InstructionResult::Stop);
52            assert_eq!(*stack_len, 1);
53            assert_eq!(unsafe { stack.as_slice(*stack_len) }[0].to_u256(), U256::from(42));
54        });
55
56        // Second function works.
57        with_evm_context(bytecode2, spec_id, |ecx, stack, stack_len| {
58            let r = unsafe { f2.call(stack, stack_len, ecx) };
59            assert_eq!(r, InstructionResult::Stop);
60            assert_eq!(*stack_len, 1);
61            assert_eq!(unsafe { stack.as_slice(*stack_len) }[0].to_u256(), U256::from(3));
62        });
63    }
64);
65
66/// Simple bytecode: `PUSH1 <value>, STOP`.
67fn push_stop(value: u8) -> [u8; 3] {
68    [op::PUSH1, value, op::STOP]
69}
70
71/// JIT-compile, call, and verify a PUSH1+STOP function returns the expected value.
72fn jit_and_verify<B: Backend>(
73    compiler: &mut EvmCompiler<B>,
74    name: &str,
75    code: &[u8],
76    expected: U256,
77) -> B::FuncId {
78    compiler.inspect_stack(true);
79    let id = compiler.translate(name, code, super::DEF_SPEC).unwrap();
80    let f = unsafe { compiler.jit_function(id) }.unwrap();
81
82    with_evm_context(code, super::DEF_SPEC, |ecx, stack, stack_len| {
83        let r = unsafe { f.call(stack, stack_len, ecx) };
84        assert_eq!(r, InstructionResult::Stop, "{name}: unexpected return");
85        assert_eq!(*stack_len, 1, "{name}: expected 1 stack element");
86        assert_eq!(
87            unsafe { stack.as_slice(*stack_len) }[0].to_u256(),
88            expected,
89            "{name}: wrong value"
90        );
91    });
92    id
93}
94
95// Free a single committed function, then compile and run a new one.
96matrix_tests!(
97    free_single = |compiler| {
98        let code = push_stop(0x42);
99        let id = jit_and_verify(compiler, "f1", &code, U256::from(0x42));
100
101        unsafe { compiler.free_function(id) }.unwrap();
102
103        // Compile a new function after freeing — the module should still be usable.
104        compiler.clear_ir().unwrap();
105        let code2 = push_stop(0x69);
106        jit_and_verify(compiler, "f2", &code2, U256::from(0x69));
107    }
108);
109
110// Free all functions via `clear`, then compile new ones.
111matrix_tests!(
112    free_all = |compiler| {
113        let code = push_stop(0x10);
114        jit_and_verify(compiler, "g1", &code, U256::from(0x10));
115
116        unsafe { compiler.clear() }.unwrap();
117
118        let code2 = push_stop(0x20);
119        jit_and_verify(compiler, "g2", &code2, U256::from(0x20));
120    }
121);
122
123// Repeated clear/recompile cycles. Regression test for resource tracker UAF:
124// `JITDylib::clear` must run before dropping `ResourceTracker` handles.
125matrix_tests!(
126    repeated_clear_recompile = |compiler| {
127        for i in 0..1000u32 {
128            let code = push_stop((i & 0xFF) as u8);
129            jit_and_verify(compiler, "f", &code, U256::from(i & 0xFF));
130            unsafe { compiler.clear() }.unwrap();
131        }
132    }
133);
134
135// Free one function, then free all remaining via `clear`.
136matrix_tests!(
137    free_single_then_clear = |compiler| {
138        let code_a = push_stop(0xAA);
139        let id_a = jit_and_verify(compiler, "h1", &code_a, U256::from(0xAA));
140
141        compiler.clear_ir().unwrap();
142        let code_b = push_stop(0xBB);
143        jit_and_verify(compiler, "h2", &code_b, U256::from(0xBB));
144
145        // Free only the first function.
146        unsafe { compiler.free_function(id_a) }.unwrap();
147
148        // Clear everything.
149        unsafe { compiler.clear() }.unwrap();
150
151        // Compile again.
152        let code_c = push_stop(0xCC);
153        jit_and_verify(compiler, "h3", &code_c, U256::from(0xCC));
154    }
155);
156
157// Compile multiple functions, free them individually.
158matrix_tests!(
159    free_multiple_individually = |compiler| {
160        let code_a = push_stop(0x11);
161        let id_a = jit_and_verify(compiler, "m1", &code_a, U256::from(0x11));
162
163        compiler.clear_ir().unwrap();
164        let code_b = push_stop(0x22);
165        let id_b = jit_and_verify(compiler, "m2", &code_b, U256::from(0x22));
166
167        // Free both.
168        unsafe { compiler.free_function(id_a) }.unwrap();
169        unsafe { compiler.free_function(id_b) }.unwrap();
170
171        // Compile again.
172        compiler.clear_ir().unwrap();
173        let code_c = push_stop(0x33);
174        jit_and_verify(compiler, "m3", &code_c, U256::from(0x33));
175    }
176);
177
178// Verify that jit_memory_usage() reflects live JIT code: non-zero after compilation,
179// decreasing after free_function, and back to baseline after freeing all.
180#[cfg(feature = "llvm")]
181#[test]
182fn jit_memory_usage_tracking() {
183    use super::with_jit_compiler;
184    use revmc_llvm::jit_memory_usage;
185
186    with_jit_compiler(revmc_backend::OptimizationLevel::default(), |compiler| {
187        let baseline = jit_memory_usage().map(|u| u.total_bytes()).unwrap_or(0);
188
189        let code_a = push_stop(0x42);
190        let id_a = jit_and_verify(compiler, "mem_a", &code_a, U256::from(0x42));
191        let after_a = jit_memory_usage().unwrap().total_bytes();
192        assert!(after_a > baseline, "expected memory increase after first compile");
193
194        compiler.clear_ir().unwrap();
195        let code_b = push_stop(0x69);
196        let id_b = jit_and_verify(compiler, "mem_b", &code_b, U256::from(0x69));
197        let after_b = jit_memory_usage().unwrap().total_bytes();
198        assert!(after_b > after_a, "expected memory increase after second compile");
199
200        unsafe { compiler.free_function(id_a) }.unwrap();
201        let after_free_a = jit_memory_usage().unwrap().total_bytes();
202        assert!(after_free_a < after_b, "expected memory decrease after freeing first function");
203
204        unsafe { compiler.free_function(id_b) }.unwrap();
205        let after_free_b = jit_memory_usage().unwrap().total_bytes();
206        assert!(
207            after_free_b <= after_free_a,
208            "expected memory decrease after freeing second function"
209        );
210    });
211}
212
213// Reuse the same symbol name after freeing. If ORC resources weren't actually
214// removed, re-defining the symbol would fail with a duplicate symbol error.
215matrix_tests!(
216    free_reuse_name = |compiler| {
217        let code_a = push_stop(0x42);
218        let id = jit_and_verify(compiler, "reuse", &code_a, U256::from(0x42));
219
220        unsafe { compiler.free_function(id) }.unwrap();
221        compiler.clear_ir().unwrap();
222
223        // Same name, different value.
224        let code_b = push_stop(0x69);
225        jit_and_verify(compiler, "reuse", &code_b, U256::from(0x69));
226    }
227);