Skip to content

Commit 983c303

Browse files
g-r-a-n-tGrant  Wuerker
andauthored
evm: treat keccak256 as memory read (#196)
Co-authored-by: Grant Wuerker <grantwuerker@pop-os.localdomain>
1 parent a254403 commit 983c303

File tree

2 files changed

+75
-2
lines changed

2 files changed

+75
-2
lines changed

crates/codegen/src/optim/licm.rs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,14 @@ impl Default for LicmSolver {
137137
#[cfg(test)]
138138
mod tests {
139139
use sonatina_ir::{
140-
ControlFlowGraph, Type,
140+
ControlFlowGraph, I256, Type,
141141
builder::test_util::*,
142142
inst::{
143143
arith::Add,
144-
cmp::Slt,
144+
cmp::{Lt, Slt},
145145
control_flow::{Br, Jump, Phi, Return},
146+
data::Mstore,
147+
evm::EvmKeccak256,
146148
},
147149
prelude::*,
148150
};
@@ -205,4 +207,74 @@ mod tests {
205207
assert_eq!(func.layout.iter_block().count(), 3);
206208
});
207209
}
210+
211+
#[test]
212+
fn licm_does_not_hoist_keccak256_across_memory_writes() {
213+
// Regression: `evm_keccak256` reads linear memory and must not be treated as a pure op.
214+
//
215+
// If `evm_keccak256` is considered pure, LICM may hoist it out of loops where the
216+
// pointer/length are loop-invariant, even though the hashed bytes are written inside
217+
// the loop (e.g. `mstore` + `keccak256(ptr, 64)` in Merkle hashing code).
218+
let mb = test_module_builder();
219+
let (evm, mut builder) = test_func_builder(&mb, &[], Type::Unit);
220+
let is = evm.inst_set();
221+
222+
let preheader = builder.append_block();
223+
let header = builder.append_block();
224+
let body = builder.append_block();
225+
let exit = builder.append_block();
226+
227+
builder.switch_to_block(preheader);
228+
let ptr = builder.make_imm_value(I256::from(0x2000u64));
229+
let len = builder.make_imm_value(I256::from(64u64));
230+
builder.insert_inst_no_result_with(|| Jump::new(is, header));
231+
232+
builder.switch_to_block(header);
233+
let idx = builder.insert_inst_with(|| Phi::new(is, vec![]), Type::I256);
234+
let limit = builder.make_imm_value(I256::from(2u64));
235+
let cond = builder.insert_inst_with(|| Lt::new(is, idx, limit), Type::I1);
236+
builder.insert_inst_no_result_with(|| Br::new(is, cond, body, exit));
237+
238+
builder.switch_to_block(body);
239+
// A trivially loop-invariant pure op: should hoist.
240+
let pure_invariant = builder.insert_inst_with(|| Add::new(is, ptr, len), Type::I256);
241+
// A loop write to memory that `keccak256(ptr, len)` depends on.
242+
builder.insert_inst_no_result_with(|| Mstore::new(is, ptr, idx, Type::I256));
243+
// Must remain in the loop body (not safe to hoist).
244+
let keccak = builder.insert_inst_with(|| EvmKeccak256::new(is, ptr, len), Type::I256);
245+
246+
let one = builder.make_imm_value(I256::from(1u64));
247+
let idx_next = builder.insert_inst_with(|| Add::new(is, idx, one), Type::I256);
248+
builder.insert_inst_no_result_with(|| Jump::new(is, header));
249+
builder.append_phi_arg(idx, idx_next, body);
250+
251+
builder.switch_to_block(exit);
252+
builder.insert_inst_no_result_with(|| Return::new(is, None));
253+
254+
builder.seal_all();
255+
builder.finish();
256+
257+
let module = mb.build();
258+
let func_ref = module.funcs()[0];
259+
module.func_store.modify(func_ref, |func| {
260+
let pure_invariant_inst = func.dfg.value_inst(pure_invariant).unwrap();
261+
let keccak_inst = func.dfg.value_inst(keccak).unwrap();
262+
assert_eq!(func.layout.inst_block(pure_invariant_inst), body);
263+
assert_eq!(func.layout.inst_block(keccak_inst), body);
264+
265+
let mut cfg = ControlFlowGraph::default();
266+
cfg.compute(func);
267+
let mut domtree = DomTree::default();
268+
domtree.compute(&cfg);
269+
let mut lpt = LoopTree::default();
270+
lpt.compute(&cfg, &domtree);
271+
272+
LicmSolver::new().run(func, &mut cfg, &mut lpt);
273+
274+
// Sanity: LICM runs and hoists pure invariants.
275+
assert_eq!(func.layout.inst_block(pure_invariant_inst), preheader);
276+
// Regression: keccak must not be hoisted out of the loop.
277+
assert_eq!(func.layout.inst_block(keccak_inst), body);
278+
});
279+
}
208280
}

crates/ir/src/inst/evm/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ pub struct EvmClz {
6969
}
7070

7171
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Inst)]
72+
#[inst(side_effect(crate::inst::SideEffect::Read))]
7273
pub struct EvmKeccak256 {
7374
addr: ValueId,
7475
len: ValueId,

0 commit comments

Comments
 (0)