Skip to content

Commit efb95c4

Browse files
authored
Merge pull request #57 from refcell/feat/mem-region-symbolic-allocation
feat: symbolic MemRegion memory allocation
2 parents 441780b + 252398d commit efb95c4

35 files changed

Lines changed: 1209 additions & 1116 deletions

crates/codegen/src/expr_compiler.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,15 @@ impl<'a> ExprCompiler<'a> {
146146
}
147147
// Multi-arg Arg should be accessed via Get(Arg, i), not bare.
148148
}
149+
EvmExpr::MemRegion(id, _size) => {
150+
// MemRegion should have been resolved to a concrete offset by
151+
// assign_memory_offsets(). If we get here, it's a bug.
152+
panic!(
153+
"MemRegion({id}) reached codegen without being resolved to a concrete offset. \
154+
Run assign_memory_offsets() after egglog extraction."
155+
);
156+
}
157+
149158
EvmExpr::Empty(_, _) | EvmExpr::StorageField(_, _, _) => {
150159
// Empty: unit — no value on stack.
151160
// StorageField: declarations don't emit code.
@@ -959,6 +968,15 @@ impl<'a> ExprCompiler<'a> {
959968
self.asm.emit_op(Opcode::CallDataCopy);
960969
self.stack_depth -= 3; // pops 3, pushes 0
961970
}
971+
EvmTernaryOp::Mcopy => {
972+
// Mcopy(dest, src, size)
973+
// EVM stack order: MCOPY(dest, src, length) — pops 3, pushes 0
974+
self.compile_expr(c); // size
975+
self.compile_expr(b); // src
976+
self.compile_expr(a); // dest
977+
self.asm.emit_op(Opcode::MCopy);
978+
self.stack_depth -= 3; // pops 3, pushes 0
979+
}
962980
EvmTernaryOp::Select => {
963981
// Select(cond, true_val, false_val) → if cond then true_val else false_val
964982
let else_label = self.asm.fresh_label("select_else");
@@ -1184,7 +1202,8 @@ impl<'a> ExprCompiler<'a> {
11841202
| EvmTernaryOp::TStore
11851203
| EvmTernaryOp::MStore
11861204
| EvmTernaryOp::MStore8
1187-
| EvmTernaryOp::CalldataCopy => 0,
1205+
| EvmTernaryOp::CalldataCopy
1206+
| EvmTernaryOp::Mcopy => 0,
11881207
EvmTernaryOp::Keccak256 | EvmTernaryOp::Select => 1,
11891208
},
11901209
// If: both branches should push the same count

crates/driver/src/compiler.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ impl Compiler {
179179
}
180180

181181
// IR lowering + optimization
182+
let t_ir = std::time::Instant::now();
182183
let ir_program = edge_ir::lower_and_optimize(
183184
&ast,
184185
self.session.config.optimization_level,
@@ -197,6 +198,8 @@ impl Compiler {
197198
CompileError::Aborted
198199
})?;
199200

201+
tracing::info!("IR lowering + optimization: {:?}", t_ir.elapsed());
202+
200203
// Emit any warnings from IR lowering
201204
for warning in &ir_program.warnings {
202205
self.session.emit_warning(warning.clone());
@@ -262,6 +265,7 @@ impl Compiler {
262265
}
263266

264267
// Code generation — compile each contract individually
268+
let t_codegen = std::time::Instant::now();
265269
let mut all_bytecodes: IndexMap<String, Vec<u8>> = IndexMap::new();
266270
for contract in &ir_program.contracts {
267271
let single_program = edge_ir::EvmProgram {
@@ -305,6 +309,8 @@ impl Compiler {
305309
});
306310
}
307311

312+
tracing::info!("Codegen: {:?}", t_codegen.elapsed());
313+
308314
let last_bytecode = all_bytecodes.values().last().cloned();
309315

310316
Ok(CompileOutput {

crates/e2e/tests/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#![allow(missing_docs)]
22

3+
#[path = "suites/helpers.rs"]
4+
mod helpers;
5+
36
#[path = "suites/access_exec.rs"]
47
mod access_exec;
58
#[path = "suites/calldata_args.rs"]

crates/e2e/tests/suites/features_exec.rs

Lines changed: 22 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -5,48 +5,22 @@
55
//!
66
//! Every test runs at O0, O1, O2, and O3 to catch optimizer bugs.
77
8-
use std::path::PathBuf;
9-
10-
use edge_driver::{
11-
compiler::Compiler,
12-
config::{CompilerConfig, EmitKind},
13-
};
148
use revm::{
159
context::{Context, TxEnv},
1610
database::{CacheDB, EmptyDB},
17-
handler::{MainBuilder, MainnetContext},
11+
handler::MainnetContext,
1812
primitives::{Address, Bytes, TxKind, U256},
1913
state::AccountInfo,
20-
ExecuteCommitEvm, MainContext, MainnetEvm,
14+
ExecuteCommitEvm, MainBuilder, MainContext, MainnetEvm,
2115
};
2216
use tiny_keccak::{Hasher, Keccak};
2317

18+
use crate::helpers::{calldata, compile_contract_opt, decode_u256, encode_u256, selector, CALLER};
19+
2420
// =============================================================================
25-
// Shared helpers
21+
// Shared helpers (features-specific — EvmHandle returns CallResult with logs)
2622
// =============================================================================
2723

28-
fn workspace_root() -> PathBuf {
29-
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..")
30-
}
31-
32-
fn compile_contract_opt(relative_path: &str, opt_level: u8) -> Vec<u8> {
33-
let path = workspace_root().join(relative_path);
34-
let mut config = CompilerConfig::new(path);
35-
config.emit = EmitKind::Bytecode;
36-
config.optimization_level = opt_level;
37-
let mut compiler = Compiler::new(config).expect("compiler init failed");
38-
let output = compiler.compile().expect("compile failed");
39-
output.bytecode.expect("no bytecode produced")
40-
}
41-
42-
fn selector(sig: &str) -> [u8; 4] {
43-
let mut h = Keccak::v256();
44-
h.update(sig.as_bytes());
45-
let mut out = [0u8; 32];
46-
h.finalize(&mut out);
47-
[out[0], out[1], out[2], out[3]]
48-
}
49-
5024
fn event_sig(sig: &str) -> [u8; 32] {
5125
let mut h = Keccak::v256();
5226
h.update(sig.as_bytes());
@@ -55,30 +29,12 @@ fn event_sig(sig: &str) -> [u8; 32] {
5529
out
5630
}
5731

58-
fn encode_u256(val: u64) -> [u8; 32] {
59-
let mut out = [0u8; 32];
60-
out[24..].copy_from_slice(&val.to_be_bytes());
61-
out
62-
}
63-
6432
const fn encode_addr(suffix: u8) -> [u8; 32] {
6533
let mut out = [0u8; 32];
6634
out[31] = suffix;
6735
out
6836
}
6937

70-
fn decode_u256(output: &[u8]) -> u64 {
71-
assert!(
72-
output.len() >= 32,
73-
"return value too short: {} bytes",
74-
output.len()
75-
);
76-
assert_eq!(&output[0..24], &[0u8; 24], "u256 too large for u64");
77-
u64::from_be_bytes(output[24..32].try_into().unwrap())
78-
}
79-
80-
const CALLER: Address = Address::ZERO;
81-
8238
type TestDb = CacheDB<EmptyDB>;
8339
type TestEvm = MainnetEvm<MainnetContext<TestDb>>;
8440

@@ -164,20 +120,23 @@ struct CallResult {
164120
logs: Vec<LogEntry>,
165121
}
166122

167-
fn calldata(sel: [u8; 4], args: &[[u8; 32]]) -> Vec<u8> {
168-
let mut cd = sel.to_vec();
169-
for a in args {
170-
cd.extend_from_slice(a);
171-
}
172-
cd
173-
}
174-
175-
fn for_all_opt_levels(contract_path: &str, test_fn: impl Fn(&mut EvmHandle, u8)) {
176-
for opt in 0..=3 {
177-
let bc = compile_contract_opt(contract_path, opt);
178-
let mut h = EvmHandle::new(bc);
179-
test_fn(&mut h, opt);
180-
}
123+
fn for_all_opt_levels(contract_path: &str, test_fn: impl Fn(&mut EvmHandle, u8) + Sync) {
124+
std::thread::scope(|s| {
125+
let handles: Vec<_> = (0..=3)
126+
.map(|opt| {
127+
let test_fn = &test_fn;
128+
let path = contract_path;
129+
s.spawn(move || {
130+
let bc = compile_contract_opt(path, opt);
131+
let mut h = EvmHandle::new(bc);
132+
test_fn(&mut h, opt);
133+
})
134+
})
135+
.collect();
136+
for h in handles {
137+
h.join().unwrap();
138+
}
139+
});
181140
}
182141

183142
// =============================================================================

crates/e2e/tests/suites/generics_exec.rs

Lines changed: 1 addition & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -4,135 +4,7 @@
44
//!
55
//! Every test runs at O0, O1, O2, and O3 to catch optimizer bugs.
66
7-
use std::path::PathBuf;
8-
9-
use edge_driver::{
10-
compiler::Compiler,
11-
config::{CompilerConfig, EmitKind},
12-
};
13-
use revm::{
14-
context::{Context, TxEnv},
15-
database::{CacheDB, EmptyDB},
16-
handler::{MainBuilder, MainnetContext},
17-
primitives::{Address, Bytes, TxKind, U256},
18-
state::AccountInfo,
19-
ExecuteCommitEvm, MainContext, MainnetEvm,
20-
};
21-
use tiny_keccak::{Hasher, Keccak};
22-
23-
// =============================================================================
24-
// Shared helpers
25-
// =============================================================================
26-
27-
fn workspace_root() -> PathBuf {
28-
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..")
29-
}
30-
31-
fn compile_contract_opt(relative_path: &str, opt_level: u8) -> Vec<u8> {
32-
let path = workspace_root().join(relative_path);
33-
let mut config = CompilerConfig::new(path);
34-
config.emit = EmitKind::Bytecode;
35-
config.optimization_level = opt_level;
36-
let mut compiler = Compiler::new(config).expect("compiler init failed");
37-
let output = compiler.compile().expect("compile failed");
38-
output.bytecode.expect("no bytecode produced")
39-
}
40-
41-
fn selector(sig: &str) -> [u8; 4] {
42-
let mut h = Keccak::v256();
43-
h.update(sig.as_bytes());
44-
let mut out = [0u8; 32];
45-
h.finalize(&mut out);
46-
[out[0], out[1], out[2], out[3]]
47-
}
48-
49-
fn decode_u256(output: &[u8]) -> u64 {
50-
assert!(
51-
output.len() >= 32,
52-
"return value too short: {} bytes",
53-
output.len()
54-
);
55-
assert_eq!(&output[0..24], &[0u8; 24], "u256 too large for u64");
56-
u64::from_be_bytes(output[24..32].try_into().unwrap())
57-
}
58-
59-
const CALLER: Address = Address::ZERO;
60-
61-
type TestDb = CacheDB<EmptyDB>;
62-
type TestEvm = MainnetEvm<MainnetContext<TestDb>>;
63-
64-
struct EvmHandle {
65-
evm: TestEvm,
66-
contract: Address,
67-
nonce: u64,
68-
}
69-
70-
impl EvmHandle {
71-
fn new(deploy_bytecode: Vec<u8>) -> Self {
72-
let mut db = CacheDB::<EmptyDB>::default();
73-
db.insert_account_info(
74-
CALLER,
75-
AccountInfo {
76-
balance: U256::from(u64::MAX),
77-
nonce: 0,
78-
..Default::default()
79-
},
80-
);
81-
82-
let mut evm = Context::mainnet().with_db(db).build_mainnet();
83-
84-
let tx = TxEnv::builder()
85-
.caller(CALLER)
86-
.kind(TxKind::Create)
87-
.data(Bytes::from(deploy_bytecode))
88-
.gas_limit(10_000_000)
89-
.nonce(0)
90-
.build()
91-
.unwrap();
92-
93-
let result = evm.transact_commit(tx).unwrap();
94-
assert!(result.is_success(), "Deployment failed: {result:#?}");
95-
96-
let contract = CALLER.create(0);
97-
Self {
98-
evm,
99-
contract,
100-
nonce: 1,
101-
}
102-
}
103-
104-
fn call(&mut self, calldata: Vec<u8>) -> (bool, Vec<u8>) {
105-
let tx = TxEnv::builder()
106-
.caller(CALLER)
107-
.kind(TxKind::Call(self.contract))
108-
.data(Bytes::from(calldata))
109-
.nonce(self.nonce)
110-
.gas_limit(10_000_000)
111-
.build()
112-
.unwrap();
113-
let result = self.evm.transact_commit(tx).unwrap();
114-
self.nonce += 1;
115-
let success = result.is_success();
116-
let output = result.output().map(|b| b.to_vec()).unwrap_or_default();
117-
(success, output)
118-
}
119-
}
120-
121-
fn calldata(sel: [u8; 4], args: &[[u8; 32]]) -> Vec<u8> {
122-
let mut cd = sel.to_vec();
123-
for a in args {
124-
cd.extend_from_slice(a);
125-
}
126-
cd
127-
}
128-
129-
fn for_all_opt_levels(contract_path: &str, test_fn: impl Fn(&mut EvmHandle, u8)) {
130-
for opt in 0..=3 {
131-
let bc = compile_contract_opt(contract_path, opt);
132-
let mut h = EvmHandle::new(bc);
133-
test_fn(&mut h, opt);
134-
}
135-
}
7+
use crate::helpers::*;
1368

1379
// =============================================================================
13810
// Generic function tests (examples/test_generics.edge)

0 commit comments

Comments
 (0)