From 1145753506da082140b343197e04b7f56aa6bf90 Mon Sep 17 00:00:00 2001 From: Owais Jamil Date: Mon, 8 Jun 2026 11:33:44 -0500 Subject: [PATCH] test: wasmtime tests --- crates/core/src/ir/lowerer.rs | 9 +- crates/core/src/wasm.rs | 83 ++++++++++++++++++- .../architecture/runtime_representation.md | 6 +- .../tasks/12_runtime_memory_and_semantics.md | 4 +- 4 files changed, 89 insertions(+), 13 deletions(-) diff --git a/crates/core/src/ir/lowerer.rs b/crates/core/src/ir/lowerer.rs index 6ddf44e..d363696 100644 --- a/crates/core/src/ir/lowerer.rs +++ b/crates/core/src/ir/lowerer.rs @@ -142,9 +142,8 @@ impl Lowerer { Diagnostic::new( DiagnosticCode::LoweringError, format!( - "function `{}` has generic type `{}` that cannot be lowered without monomorphization", - function.name.text, - format!("{:?}", function.type_) + "function `{}` has generic type `{:?}` that cannot be lowered without monomorphization", + function.name.text, function.type_ ), ) .with_label(Label::primary(function.name.span, "generic function type here")), @@ -157,8 +156,8 @@ impl Lowerer { Diagnostic::new( DiagnosticCode::LoweringError, format!( - "expression has generic type `{}` that cannot be lowered without monomorphization", - format!("{:?}", expression.type_) + "expression has generic type `{:?}` that cannot be lowered without monomorphization", + expression.type_ ), ) .with_label(Label::primary(expression.span, "generic expression type here")), diff --git a/crates/core/src/wasm.rs b/crates/core/src/wasm.rs index 209f3fa..733aa7c 100644 --- a/crates/core/src/wasm.rs +++ b/crates/core/src/wasm.rs @@ -2724,6 +2724,78 @@ pub fn same() { "hi" == "hi" } assert_eq!(&bytes[8..10], b"ab"); } + #[test] + fn runtime_allocation_failure_writes_structured_panic_payload() { + let instance = runtime_helper_instance_with_memory( + r#" + (data (i32.const 2048) "ab") + (func $too_large (export "too_large") (result i32) + i32.const 2048 + i32.const 70000 + call $__string_new) +"#, + " (memory (export \"memory\") 1 1)", + ); + let (engine, mut store, instance) = instance; + let _engine = engine; + let too_large = instance + .get_typed_func::<(), i32>(&mut store, "too_large") + .expect("get too_large export"); + assert!(too_large.call(&mut store, ()).is_err()); + + let last_panic = instance + .get_typed_func::<(), i32>(&mut store, "__last_panic") + .expect("get __last_panic export"); + let pointer = last_panic.call(&mut store, ()).expect("call __last_panic") as usize; + assert_eq!(pointer, 64); + + let memory = instance.get_memory(&mut store, "memory").expect("memory export"); + let mut bytes = [0; 28]; + memory.read(&store, pointer, &mut bytes).expect("read allocation panic"); + assert_eq!(u32::from_le_bytes(bytes[0..4].try_into().unwrap()), 10); + assert_eq!(u32::from_le_bytes(bytes[4..8].try_into().unwrap()), 2); + assert_eq!(u32::from_le_bytes(bytes[8..12].try_into().unwrap()), 1); + assert_eq!(u64::from_le_bytes(bytes[12..20].try_into().unwrap()), 70008); + assert_eq!(u64::from_le_bytes(bytes[20..28].try_into().unwrap()), 4096); + } + + #[test] + fn runtime_host_borrowed_pointer_stays_stable_across_growth() { + let instance = runtime_helper_instance( + r#" + (data (i32.const 2048) "ab") + (func $borrow (export "borrow") (result i32) + i32.const 2048 + i32.const 2 + call $__string_new) + (func $grow (export "grow") + i32.const 2048 + i32.const 70000 + call $__string_new + drop) +"#, + ); + let (engine, mut store, instance) = instance; + let _engine = engine; + let borrow = instance + .get_typed_func::<(), i32>(&mut store, "borrow") + .expect("get borrow export"); + let pointer = borrow.call(&mut store, ()).expect("call borrow") as usize; + assert_eq!(pointer, runtime::RuntimeConfig::DEFAULT.heap_start as usize); + + let grow = instance + .get_typed_func::<(), ()>(&mut store, "grow") + .expect("get grow export"); + grow.call(&mut store, ()).expect("call grow"); + + let memory = instance.get_memory(&mut store, "memory").expect("memory export"); + let mut bytes = [0; 16]; + memory.read(&store, pointer, &mut bytes).expect("read borrowed pointer"); + assert_eq!(u32::from_le_bytes(bytes[0..4].try_into().unwrap()), 1); + assert_eq!(u32::from_le_bytes(bytes[4..8].try_into().unwrap()), 2); + assert_eq!(&bytes[8..10], b"ab"); + } + #[test] fn runtime_helpers_compare_nested_managed_values_structurally() { let instance = runtime_helper_instance( @@ -2956,10 +3028,13 @@ pub fn same() { "hi" == "hi" } } fn runtime_helper_instance(extra_wat: &str) -> (Engine, Store<()>, Instance) { - let wat = format!( - "(module\n{}{extra_wat})\n", - runtime_prelude(runtime::RuntimeConfig::DEFAULT) - ); + runtime_helper_instance_with_memory(extra_wat, " (memory (export \"memory\") 1)") + } + + fn runtime_helper_instance_with_memory(extra_wat: &str, memory_wat: &str) -> (Engine, Store<()>, Instance) { + let prelude = + runtime_prelude(runtime::RuntimeConfig::DEFAULT).replace(" (memory (export \"memory\") 1)", memory_wat); + let wat = format!("(module\n{prelude}{extra_wat})\n"); let bytes = wat::parse_str(&wat).expect("parse runtime helper wat"); let engine = Engine::default(); let module = Module::new(&engine, bytes).expect("compile helper module"); diff --git a/docs/src/internal/development/architecture/runtime_representation.md b/docs/src/internal/development/architecture/runtime_representation.md index c69aaa9..0e68b56 100644 --- a/docs/src/internal/development/architecture/runtime_representation.md +++ b/docs/src/internal/development/architecture/runtime_representation.md @@ -92,8 +92,10 @@ slot 1 is the heap pointer before allocation. Internal calls pass scalars as raw WASM values and managed values as pointers. Host ABI support is target-specific. For Wasmtime, managed pointers are borrowed -for the duration of the call unless an adapter documents transfer of ownership. -No object is freed by the guest today, so borrowed pointers remain stable. +by the host; ownership stays with the guest runtime unless an adapter documents +transfer. A borrowed pointer may be read after the exporting call returns and +remains stable until the Wasm instance is reset or a future explicit arena reset +runs. Hosts must not retain pointers across those reset boundaries. ## Tests diff --git a/docs/src/internal/tasks/12_runtime_memory_and_semantics.md b/docs/src/internal/tasks/12_runtime_memory_and_semantics.md index f640a76..845dac0 100644 --- a/docs/src/internal/tasks/12_runtime_memory_and_semantics.md +++ b/docs/src/internal/tasks/12_runtime_memory_and_semantics.md @@ -12,8 +12,8 @@ Complete runtime memory management and allocation failure behavior. - [x] Implement checked allocation and heap growth for every allocator path. - [x] Define allocation failure as a structured runtime panic payload. - [x] Keep managed objects non-moving until instance reset or arena reset. -- [ ] Document host pointers as borrowed and stable until reset. -- [ ] Add Wasmtime tests for growth, failed growth, and pointer stability. +- [x] Document host pointers as borrowed and stable until reset. +- [x] Add Wasmtime tests for growth, failed growth, and pointer stability. ## Done when -- 2.54.0