Skip to content

Commit 056ad88

Browse files
avrabeclaude
andcommitted
feat: wire bulk memory ops into ISLE pipeline, eliminating skip block
- Add MemoryFill, MemoryCopy, MemoryInit, DataDrop ValueData variants, constructors, and simplify rules (side-effectful, no constant folding) - Wire through instructions_to_terms (as side effects) and terms_to_instructions - Remove all bulk memory ops from has_unsupported_isle_instructions - Skip block now contains ONLY Unknown instructions Every standard WebAssembly instruction is now fully wired into the ISLE optimization pipeline. Functions are only skipped if they contain Unknown (unrecognized) opcodes. This means all functions in standard-compliant WASM files get full optimization coverage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f50dd04 commit 056ad88

3 files changed

Lines changed: 265 additions & 35 deletions

File tree

loom-core/src/lib.rs

Lines changed: 148 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3593,17 +3593,17 @@ pub mod terms {
35933593
use super::{BlockType, FunctionSignature, Instruction, Module, Value, ValueType};
35943594
use anyhow::{Result, anyhow};
35953595
use loom_isle::{
3596-
Imm32, Imm64, ImmF32, ImmF64, block, br, br_if, br_table, call, call_indirect, drop_instr,
3597-
f32_convert_i32_s, f32_convert_i32_u, f32_convert_i64_s, f32_convert_i64_u, f32_demote_f64,
3598-
f32_load, f32_reinterpret_i32, f32_store, f64_convert_i32_s, f64_convert_i32_u,
3599-
f64_convert_i64_s, f64_convert_i64_u, f64_load, f64_promote_f32, f64_reinterpret_i64,
3600-
f64_store, fabs32, fabs64, fadd32, fadd64, fceil32, fceil64, fconst32, fconst64,
3601-
fcopysign32, fcopysign64, fdiv32, fdiv64, feq32, feq64, ffloor32, ffloor64, fge32, fge64,
3602-
fgt32, fgt64, fle32, fle64, flt32, flt64, fmax32, fmax64, fmin32, fmin64, fmul32, fmul64,
3603-
fne32, fne64, fnearest32, fnearest64, fneg32, fneg64, fsqrt32, fsqrt64, fsub32, fsub64,
3604-
ftrunc32, ftrunc64, global_get, global_set, i32_extend8_s, i32_extend16_s, i32_load,
3605-
i32_load8_s, i32_load8_u, i32_load16_s, i32_load16_u, i32_reinterpret_f32, i32_store,
3606-
i32_store8, i32_store16, i32_trunc_f32_s, i32_trunc_f32_u, i32_trunc_f64_s,
3596+
Imm32, Imm64, ImmF32, ImmF64, block, br, br_if, br_table, call, call_indirect, data_drop,
3597+
drop_instr, f32_convert_i32_s, f32_convert_i32_u, f32_convert_i64_s, f32_convert_i64_u,
3598+
f32_demote_f64, f32_load, f32_reinterpret_i32, f32_store, f64_convert_i32_s,
3599+
f64_convert_i32_u, f64_convert_i64_s, f64_convert_i64_u, f64_load, f64_promote_f32,
3600+
f64_reinterpret_i64, f64_store, fabs32, fabs64, fadd32, fadd64, fceil32, fceil64, fconst32,
3601+
fconst64, fcopysign32, fcopysign64, fdiv32, fdiv64, feq32, feq64, ffloor32, ffloor64,
3602+
fge32, fge64, fgt32, fgt64, fle32, fle64, flt32, flt64, fmax32, fmax64, fmin32, fmin64,
3603+
fmul32, fmul64, fne32, fne64, fnearest32, fnearest64, fneg32, fneg64, fsqrt32, fsqrt64,
3604+
fsub32, fsub64, ftrunc32, ftrunc64, global_get, global_set, i32_extend8_s, i32_extend16_s,
3605+
i32_load, i32_load8_s, i32_load8_u, i32_load16_s, i32_load16_u, i32_reinterpret_f32,
3606+
i32_store, i32_store8, i32_store16, i32_trunc_f32_s, i32_trunc_f32_u, i32_trunc_f64_s,
36073607
i32_trunc_f64_u, i32_trunc_sat_f32_s, i32_trunc_sat_f32_u, i32_trunc_sat_f64_s,
36083608
i32_trunc_sat_f64_u, i32_wrap_i64, i64_extend_i32_s, i64_extend_i32_u, i64_extend8_s,
36093609
i64_extend16_s, i64_extend32_s, i64_load, i64_load8_s, i64_load8_u, i64_load16_s,
@@ -3616,8 +3616,8 @@ pub mod terms {
36163616
iles64, ileu32, ileu64, ilts32, ilts64, iltu32, iltu64, imul32, imul64, ine32, ine64,
36173617
ior32, ior64, ipopcnt32, ipopcnt64, irems32, irems64, iremu32, iremu64, irotl32, irotl64,
36183618
irotr32, irotr64, ishl32, ishl64, ishrs32, ishrs64, ishru32, ishru64, isub32, isub64,
3619-
ixor32, ixor64, local_get, local_set, local_tee, loop_construct, memory_grow, memory_size,
3620-
nop, return_val, select_instr, unreachable,
3619+
ixor32, ixor64, local_get, local_set, local_tee, loop_construct, memory_copy, memory_fill,
3620+
memory_grow, memory_init, memory_size, nop, return_val, select_instr, unreachable,
36213621
};
36223622

36233623
/// Owned context for function signature lookup during ISLE term conversion.
@@ -5196,12 +5196,45 @@ pub mod terms {
51965196
// They are passed through unchanged in the encoding phase
51975197
}
51985198

5199-
// Bulk memory instructions - pass through unchanged
5200-
Instruction::MemoryFill(_)
5201-
| Instruction::MemoryCopy { .. }
5202-
| Instruction::MemoryInit { .. }
5203-
| Instruction::DataDrop(_) => {
5204-
// These don't have ISLE term representations yet
5199+
// Bulk memory instructions - side-effectful, no stack output
5200+
Instruction::MemoryFill(mem) => {
5201+
let len = stack
5202+
.pop()
5203+
.ok_or_else(|| anyhow!("Stack underflow for memory.fill len"))?;
5204+
let val = stack
5205+
.pop()
5206+
.ok_or_else(|| anyhow!("Stack underflow for memory.fill val"))?;
5207+
let dst = stack
5208+
.pop()
5209+
.ok_or_else(|| anyhow!("Stack underflow for memory.fill dst"))?;
5210+
side_effects.push(memory_fill(dst, val, len, *mem));
5211+
}
5212+
Instruction::MemoryCopy { dst_mem, src_mem } => {
5213+
let len = stack
5214+
.pop()
5215+
.ok_or_else(|| anyhow!("Stack underflow for memory.copy len"))?;
5216+
let src = stack
5217+
.pop()
5218+
.ok_or_else(|| anyhow!("Stack underflow for memory.copy src"))?;
5219+
let dst = stack
5220+
.pop()
5221+
.ok_or_else(|| anyhow!("Stack underflow for memory.copy dst"))?;
5222+
side_effects.push(memory_copy(dst, src, len, *dst_mem, *src_mem));
5223+
}
5224+
Instruction::MemoryInit { mem, data_idx } => {
5225+
let len = stack
5226+
.pop()
5227+
.ok_or_else(|| anyhow!("Stack underflow for memory.init len"))?;
5228+
let src = stack
5229+
.pop()
5230+
.ok_or_else(|| anyhow!("Stack underflow for memory.init src"))?;
5231+
let dst = stack
5232+
.pop()
5233+
.ok_or_else(|| anyhow!("Stack underflow for memory.init dst"))?;
5234+
side_effects.push(memory_init(dst, src, len, *mem, *data_idx));
5235+
}
5236+
Instruction::DataDrop(data_idx) => {
5237+
side_effects.push(data_drop(*data_idx));
52055238
}
52065239
}
52075240
}
@@ -5698,6 +5731,46 @@ pub mod terms {
56985731
term_to_instructions_recursive(val, instructions)?;
56995732
instructions.push(Instruction::MemoryGrow(*mem));
57005733
}
5734+
// Bulk memory operations (side-effectful)
5735+
ValueData::MemoryFill { dst, val, len, mem } => {
5736+
term_to_instructions_recursive(dst, instructions)?;
5737+
term_to_instructions_recursive(val, instructions)?;
5738+
term_to_instructions_recursive(len, instructions)?;
5739+
instructions.push(Instruction::MemoryFill(*mem));
5740+
}
5741+
ValueData::MemoryCopy {
5742+
dst,
5743+
src,
5744+
len,
5745+
dst_mem,
5746+
src_mem,
5747+
} => {
5748+
term_to_instructions_recursive(dst, instructions)?;
5749+
term_to_instructions_recursive(src, instructions)?;
5750+
term_to_instructions_recursive(len, instructions)?;
5751+
instructions.push(Instruction::MemoryCopy {
5752+
dst_mem: *dst_mem,
5753+
src_mem: *src_mem,
5754+
});
5755+
}
5756+
ValueData::MemoryInit {
5757+
dst,
5758+
src,
5759+
len,
5760+
mem,
5761+
data_idx,
5762+
} => {
5763+
term_to_instructions_recursive(dst, instructions)?;
5764+
term_to_instructions_recursive(src, instructions)?;
5765+
term_to_instructions_recursive(len, instructions)?;
5766+
instructions.push(Instruction::MemoryInit {
5767+
mem: *mem,
5768+
data_idx: *data_idx,
5769+
});
5770+
}
5771+
ValueData::DataDrop { data_idx } => {
5772+
instructions.push(Instruction::DataDrop(*data_idx));
5773+
}
57015774
// Sign extension operations
57025775
ValueData::I32Extend8S { val } => {
57035776
term_to_instructions_recursive(val, instructions)?;
@@ -6530,12 +6603,9 @@ pub mod optimize {
65306603

65316604
/// Helper: Check if a function contains instructions not supported by ISLE term conversion
65326605
///
6533-
/// The instructions_to_terms function only handles a subset of WASM instructions.
6534-
/// For unsupported instructions (floats, conversions, rotations, etc.), it doesn't
6535-
/// properly simulate stack effects, which causes stack underflow errors.
6536-
///
6537-
/// This function identifies functions that should skip ISLE-based optimization
6538-
/// to avoid corrupting the stack simulation.
6606+
/// Currently only `Unknown` instructions are unsupported — all standard WASM
6607+
/// instructions (integer, float, conversion, memory, control flow, call_indirect,
6608+
/// br_table, bulk memory) are fully wired into the ISLE pipeline.
65396609
fn has_unsupported_isle_instructions(func: &Function) -> bool {
65406610
has_unsupported_isle_instructions_in_block(&func.instructions)
65416611
}
@@ -6544,27 +6614,25 @@ pub mod optimize {
65446614
for instr in instructions {
65456615
match instr {
65466616
// Recursively check nested blocks
6547-
Instruction::Block { body, .. }
6548-
| Instruction::Loop { body, .. } => {
6617+
Instruction::Block { body, .. } | Instruction::Loop { body, .. } => {
65496618
if has_unsupported_isle_instructions_in_block(body) {
65506619
return true;
65516620
}
65526621
}
6553-
Instruction::If { then_body, else_body, .. } => {
6622+
Instruction::If {
6623+
then_body,
6624+
else_body,
6625+
..
6626+
} => {
65546627
if has_unsupported_isle_instructions_in_block(then_body)
65556628
|| has_unsupported_isle_instructions_in_block(else_body)
65566629
{
65576630
return true;
65586631
}
65596632
}
65606633

6561-
// Bulk memory operations (no ISLE term representation yet)
6562-
Instruction::MemoryFill(_)
6563-
| Instruction::MemoryCopy { .. }
6564-
| Instruction::MemoryInit { .. }
6565-
| Instruction::DataDrop(_)
6566-
// Unknown instructions
6567-
| Instruction::Unknown(_) => {
6634+
// Unknown instructions (opaque — cannot model stack effects)
6635+
Instruction::Unknown(_) => {
65686636
return true;
65696637
}
65706638

@@ -14517,4 +14585,49 @@ mod tests {
1451714585
func.instructions
1451814586
);
1451914587
}
14588+
14589+
#[test]
14590+
fn test_bulk_memory_not_skipped() {
14591+
// Functions with bulk memory ops should NOT be skipped from optimization
14592+
let wat = r#"
14593+
(module
14594+
(memory 1)
14595+
(data $d "hello")
14596+
(func $bulk_test
14597+
i32.const 1
14598+
i32.const 1
14599+
i32.add
14600+
i32.const 0
14601+
i32.const 5
14602+
memory.fill
14603+
)
14604+
)
14605+
"#;
14606+
14607+
let mut module = parse::parse_wat(wat).expect("Failed to parse WAT");
14608+
optimize::optimize_module(&mut module).expect("Failed to optimize");
14609+
14610+
let func = &module.functions[0];
14611+
// i32.const 1 + i32.const 1 should be folded to i32.const 2
14612+
assert!(
14613+
func.instructions.contains(&Instruction::I32Const(2)),
14614+
"Expected constant folding to occur in function with memory.fill: {:?}",
14615+
func.instructions
14616+
);
14617+
// memory.fill should still be present
14618+
assert!(
14619+
func.instructions.contains(&Instruction::MemoryFill(0)),
14620+
"Expected memory.fill to be preserved: {:?}",
14621+
func.instructions
14622+
);
14623+
}
14624+
14625+
#[test]
14626+
fn test_data_drop_round_trip() {
14627+
// data.drop goes through ISLE terms and back unchanged
14628+
let instructions = vec![Instruction::DataDrop(0), Instruction::End];
14629+
let terms = terms::instructions_to_terms(&instructions).expect("Failed to convert");
14630+
let result = terms::terms_to_instructions(&terms).expect("Failed to convert back");
14631+
assert_eq!(result, vec![Instruction::DataDrop(0)]);
14632+
}
1452014633
}

loom-shared/isle/wasm_terms.isle

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,12 @@
298298
;; Memory operations
299299
(MemorySize (mem u32))
300300
(MemoryGrow (val Value) (mem u32))
301+
302+
;; Bulk memory operations (side-effectful)
303+
(MemoryFill (dst Value) (val Value) (len Value) (mem u32))
304+
(MemoryCopy (dst Value) (src Value) (len Value) (dst_mem u32) (src_mem u32))
305+
(MemoryInit (dst Value) (src Value) (len Value) (mem u32) (data_idx u32))
306+
(DataDrop (data_idx u32))
301307
))
302308

303309
;; Value is a boxed pointer to ValueData
@@ -803,6 +809,16 @@
803809
(decl memory_grow (Value u32) Value)
804810
(extern constructor memory_grow memory_grow)
805811

812+
;; Bulk memory operations
813+
(decl memory_fill (Value Value Value u32) Value)
814+
(extern constructor memory_fill memory_fill)
815+
(decl memory_copy (Value Value Value u32 u32) Value)
816+
(extern constructor memory_copy memory_copy)
817+
(decl memory_init (Value Value Value u32 u32) Value)
818+
(extern constructor memory_init memory_init)
819+
(decl data_drop (u32) Value)
820+
(extern constructor data_drop data_drop)
821+
806822
;; BlockType constructors
807823
(decl block_type_empty () BlockType)
808824
(extern constructor block_type_empty block_type_empty)

loom-shared/src/lib.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,37 @@ pub enum ValueData {
941941
mem: u32,
942942
},
943943

944+
// ========================================================================
945+
// Bulk Memory Operations (side-effectful, no stack output)
946+
// ========================================================================
947+
/// memory.fill - fill memory region with a byte value
948+
MemoryFill {
949+
dst: Value,
950+
val: Value,
951+
len: Value,
952+
mem: u32,
953+
},
954+
/// memory.copy - copy memory region from src to dst
955+
MemoryCopy {
956+
dst: Value,
957+
src: Value,
958+
len: Value,
959+
dst_mem: u32,
960+
src_mem: u32,
961+
},
962+
/// memory.init - initialize memory from a data segment
963+
MemoryInit {
964+
dst: Value,
965+
src: Value,
966+
len: Value,
967+
mem: u32,
968+
data_idx: u32,
969+
},
970+
/// data.drop - drop a data segment (no stack operands)
971+
DataDrop {
972+
data_idx: u32,
973+
},
974+
944975
// ========================================================================
945976
// Sign Extension Operations (in-place sign extension)
946977
// ========================================================================
@@ -2137,6 +2168,34 @@ pub fn memory_grow(val: Value, mem: u32) -> Value {
21372168
Value(Box::new(ValueData::MemoryGrow { val, mem }))
21382169
}
21392170

2171+
// ============================================================================
2172+
// Bulk Memory Constructors
2173+
// ============================================================================
2174+
pub fn memory_fill(dst: Value, val: Value, len: Value, mem: u32) -> Value {
2175+
Value(Box::new(ValueData::MemoryFill { dst, val, len, mem }))
2176+
}
2177+
pub fn memory_copy(dst: Value, src: Value, len: Value, dst_mem: u32, src_mem: u32) -> Value {
2178+
Value(Box::new(ValueData::MemoryCopy {
2179+
dst,
2180+
src,
2181+
len,
2182+
dst_mem,
2183+
src_mem,
2184+
}))
2185+
}
2186+
pub fn memory_init(dst: Value, src: Value, len: Value, mem: u32, data_idx: u32) -> Value {
2187+
Value(Box::new(ValueData::MemoryInit {
2188+
dst,
2189+
src,
2190+
len,
2191+
mem,
2192+
data_idx,
2193+
}))
2194+
}
2195+
pub fn data_drop(data_idx: u32) -> Value {
2196+
Value(Box::new(ValueData::DataDrop { data_idx }))
2197+
}
2198+
21402199
// ============================================================================
21412200
// Sign Extension Constructors
21422201
// ============================================================================
@@ -5020,6 +5079,48 @@ fn simplify_stateless(val: Value) -> Value {
50205079
memory_grow(v, *mem)
50215080
}
50225081

5082+
// ====================================================================
5083+
// Bulk Memory — side-effectful, cannot fold, simplify children
5084+
// ====================================================================
5085+
ValueData::MemoryFill {
5086+
dst,
5087+
val: v,
5088+
len,
5089+
mem,
5090+
} => memory_fill(
5091+
simplify(dst.clone()),
5092+
simplify(v.clone()),
5093+
simplify(len.clone()),
5094+
*mem,
5095+
),
5096+
ValueData::MemoryCopy {
5097+
dst,
5098+
src,
5099+
len,
5100+
dst_mem,
5101+
src_mem,
5102+
} => memory_copy(
5103+
simplify(dst.clone()),
5104+
simplify(src.clone()),
5105+
simplify(len.clone()),
5106+
*dst_mem,
5107+
*src_mem,
5108+
),
5109+
ValueData::MemoryInit {
5110+
dst,
5111+
src,
5112+
len,
5113+
mem,
5114+
data_idx,
5115+
} => memory_init(
5116+
simplify(dst.clone()),
5117+
simplify(src.clone()),
5118+
simplify(len.clone()),
5119+
*mem,
5120+
*data_idx,
5121+
),
5122+
ValueData::DataDrop { .. } => val, // no children to simplify
5123+
50235124
// Constants are already in simplest form
50245125
_ => val,
50255126
}

0 commit comments

Comments
 (0)