Skip to content

Commit f0bcaba

Browse files
committed
perf: inline common memory-zero load offsets
1 parent c1e65ca commit f0bcaba

5 files changed

Lines changed: 114 additions & 3 deletions

File tree

‎crates/parser/src/emitter.rs‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ use crate::{
44
ParseError, ParserOptions, Result, conversion::FunctionLoweringContext, selection, visit::FunctionDataBuilder,
55
};
66
use alloc::vec::Vec;
7-
use tinywasm_types::{BranchTableOperand, ExceptionCatch, ExceptionHandler, Instruction, Operand128, ValueCounts};
7+
use tinywasm_types::{
8+
BranchTableOperand, ExceptionCatch, ExceptionHandler, Instruction, MemoryOperand, Operand128, Operand128Idx,
9+
ValueCounts,
10+
};
811

912
const LOOKBEHIND: usize = 3;
1013

@@ -308,6 +311,25 @@ impl Emitter {
308311
return Err(ParseError::Other("exception handler range out of bounds".into()));
309312
}
310313
}
314+
// Run after fusion so only surviving memory-0 loads become inline-offset instructions.
315+
if self.select {
316+
let inline_offset = |index: Operand128Idx<MemoryOperand>| {
317+
let arg = data.operand128(index);
318+
if arg.memory() == 0 { u32::try_from(arg.offset()).ok() } else { None }
319+
};
320+
for instruction in &mut self.instructions {
321+
use Instruction::*;
322+
let replacement = match *instruction {
323+
I32Load(index) => inline_offset(index).map(I32LoadInline),
324+
I32Load8U(index) => inline_offset(index).map(I32Load8UInline),
325+
I32Load16S(index) => inline_offset(index).map(I32Load16SInline),
326+
_ => None,
327+
};
328+
if let Some(replacement) = replacement {
329+
*instruction = replacement;
330+
}
331+
}
332+
}
311333
Ok(self.instructions)
312334
}
313335

‎crates/tinywasm/src/interpreter/executor/instructions.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,12 +357,15 @@ macro_rules! instruction_handlers {
357357
I64Store16(idx) => executor.exec_mem_store::<i64, i16, 2>(idx.resolve(&executor.func.data), #[inline(always)] |v| v as i16)?,
358358
I64Store32(idx) => executor.exec_mem_store::<i64, i32, 4>(idx.resolve(&executor.func.data), #[inline(always)] |v| v as i32)?,
359359
I32Load(idx) => executor.exec_mem_load::<i32, 4, _>(idx.resolve(&executor.func.data), identity)?,
360+
I32LoadInline(offset) => executor.exec_mem_load::<i32, 4, _>(Operand128::<MemoryOperand>::new(u64::from(*offset), 0), identity)?,
360361
I64Load(idx) => executor.exec_mem_load::<i64, 8, _>(idx.resolve(&executor.func.data), identity)?,
361362
F32Load(idx) => executor.exec_mem_load::<f32, 4, _>(idx.resolve(&executor.func.data), identity)?,
362363
F64Load(idx) => executor.exec_mem_load::<f64, 8, _>(idx.resolve(&executor.func.data), identity)?,
363364
I32Load8S(idx) => executor.exec_mem_load::<i8, 1, _>(idx.resolve(&executor.func.data), i32::from)?,
364365
I32Load8U(idx) => executor.exec_mem_load::<u8, 1, _>(idx.resolve(&executor.func.data), i32::from)?,
366+
I32Load8UInline(offset) => executor.exec_mem_load::<u8, 1, _>(Operand128::<MemoryOperand>::new(u64::from(*offset), 0), i32::from)?,
365367
I32Load16S(idx) => executor.exec_mem_load::<i16, 2, _>(idx.resolve(&executor.func.data), i32::from)?,
368+
I32Load16SInline(offset) => executor.exec_mem_load::<i16, 2, _>(Operand128::<MemoryOperand>::new(u64::from(*offset), 0), i32::from)?,
366369
I32Load16U(idx) => executor.exec_mem_load::<u16, 2, _>(idx.resolve(&executor.func.data), i32::from)?,
367370
I64Load8S(idx) => executor.exec_mem_load::<i8, 1, _>(idx.resolve(&executor.func.data), i64::from)?,
368371
I64Load8U(idx) => executor.exec_mem_load::<u8, 1, _>(idx.resolve(&executor.func.data), i64::from)?,

‎crates/tinywasm/tests/memory.rs‎

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,86 @@ fn resource_limiter_allows_guest_memory_grow_by_default() -> TestResult {
200200
assert_eq!(grow.call(&mut store, ())?, 1);
201201
Ok(())
202202
}
203+
204+
#[test]
205+
fn inline_memory_immediates_preserve_multi_memory_behavior() -> TestResult {
206+
use tinywasm::parser::{Parser, ParserOptions};
207+
use tinywasm::types::Instruction;
208+
209+
let wasm = wat::parse_str(
210+
r#"
211+
(module
212+
(memory 1)
213+
(memory $other 1)
214+
(data (memory $other) (i32.const 1) "\7f")
215+
(func (export "inline") (result i32)
216+
i32.const 0
217+
i32.const 0x12345678
218+
i32.store offset=4
219+
i32.const 0
220+
i32.load offset=4
221+
drop
222+
i32.const 0
223+
i32.const -2
224+
i32.store16 offset=8
225+
i32.const 0
226+
i32.load16_s offset=8
227+
drop
228+
i32.const 0
229+
i32.load8_u offset=4)
230+
(func (export "other") (result i32)
231+
i32.const 0
232+
i32.load8_u $other offset=1))
233+
"#,
234+
)?;
235+
let optimized = Parser::default().parse_module_bytes(&wasm)?;
236+
let instructions = &optimized.funcs[0].instructions;
237+
assert!(instructions.iter().any(|op| matches!(op, Instruction::I32LoadInline(4))));
238+
assert!(instructions.iter().any(|op| matches!(op, Instruction::I32Load16SInline(8))));
239+
assert!(instructions.iter().any(|op| matches!(op, Instruction::I32Load8UInline(4))));
240+
assert!(optimized.funcs[1].instructions.iter().any(|op| matches!(op, Instruction::I32Load8U(_))));
241+
242+
for optimize in [true, false] {
243+
let module = Parser::new(ParserOptions::new().with_optimize(optimize)).parse_module_bytes(&wasm)?;
244+
let mut store = Store::default();
245+
let instance = ModuleInstance::instantiate(&mut store, &module, None)?;
246+
assert_eq!(instance.func::<(), i32>(&store, "inline")?.call(&mut store, ())?, 0x78);
247+
assert_eq!(instance.func::<(), i32>(&store, "other")?.call(&mut store, ())?, 0x7f);
248+
}
249+
Ok(())
250+
}
251+
252+
#[test]
253+
fn inline_memory_loads_preserve_memory64_and_wide_offsets() -> TestResult {
254+
use tinywasm::parser::{Parser, ParserOptions};
255+
use tinywasm::types::Instruction;
256+
257+
let wasm = wat::parse_str(
258+
r#"
259+
(module
260+
(memory i64 1)
261+
(data (i64.const 0) "\2a")
262+
(func (export "small") (result i32)
263+
i64.const 0
264+
i32.load8_u)
265+
(func (export "wide") (result i32)
266+
i64.const 0
267+
i32.load8_u offset=4294967296))
268+
"#,
269+
)?;
270+
let optimized = Parser::default().parse_module_bytes(&wasm)?;
271+
assert!(optimized.funcs[0].instructions.iter().any(|op| matches!(op, Instruction::I32Load8UInline(0))));
272+
assert!(optimized.funcs[1].instructions.iter().any(|op| matches!(op, Instruction::I32Load8U(_))));
273+
274+
for optimize in [true, false] {
275+
let module = Parser::new(ParserOptions::new().with_optimize(optimize)).parse_module_bytes(&wasm)?;
276+
let mut store = Store::default();
277+
let instance = ModuleInstance::instantiate(&mut store, &module, None)?;
278+
assert_eq!(instance.func::<(), i32>(&store, "small")?.call(&mut store, ())?, 42);
279+
assert!(matches!(
280+
instance.func::<(), i32>(&store, "wide")?.call(&mut store, ()),
281+
Err(tinywasm::Error::Trap(Trap::MemoryOutOfBounds { .. }))
282+
));
283+
}
284+
Ok(())
285+
}

‎crates/types/src/archive.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::Module;
77
#[rustfmt::skip]
88
const TWASM_MAGIC: [u8; 16] = [ TWASM_MAGIC_PREFIX[0], TWASM_MAGIC_PREFIX[1], TWASM_MAGIC_PREFIX[2], TWASM_MAGIC_PREFIX[3], TWASM_VERSION[0], TWASM_VERSION[1], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
99
const TWASM_MAGIC_PREFIX: &[u8; 4] = b"TWAS";
10-
const TWASM_VERSION: &[u8; 2] = b"06";
10+
const TWASM_VERSION: &[u8; 2] = b"07";
1111

1212
fn validate_magic(wasm: &[u8]) -> Result<usize, TwasmError> {
1313
if wasm.len() < TWASM_MAGIC.len() || &wasm[..TWASM_MAGIC_PREFIX.len()] != TWASM_MAGIC_PREFIX {
@@ -98,7 +98,7 @@ mod tests {
9898
let module = Module::from(ModuleInner { funcs: Box::new([Shared::new(function)]), ..ModuleInner::default() });
9999

100100
let archive = module.serialize_twasm().expect("serialize archive");
101-
assert_eq!(&archive[..6], b"TWAS06");
101+
assert_eq!(&archive[..6], b"TWAS07");
102102
let decoded = Module::try_from_twasm(&archive).expect("deserialize archive");
103103
let function = &decoded.funcs[0];
104104
assert!(function.max_stack == ValueCounts { c32: 2, c64: 3, c128: 4 });

‎crates/types/src/instructions.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,9 @@ define_instructions! {
903903
I32x4RelaxedDotI8x16I7x16AddS,
904904

905905
SelectStore32(Operand128Idx<MemoryOperand>), SelectStore64(Operand128Idx<MemoryOperand>),
906+
907+
// Selected memory-0 operations carry a 32-bit static offset in the instruction.
908+
I32LoadInline(u32), I32Load8UInline(u32), I32Load16SInline(u32),
906909
}
907910

908911
const _: () = assert!(core::mem::size_of::<Instruction>() == 8);

0 commit comments

Comments
 (0)