@@ -137,12 +137,14 @@ impl Default for LicmSolver {
137137#[ cfg( test) ]
138138mod 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}
0 commit comments