diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index c18dfd0..10dfb72 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -6535,3 +6535,141 @@ fn empty_wasm_bytes_error() { let result = WasmActorBuilder::new(engine, Vec::::new()).build(); assert!(result.is_err(), "empty bytes should fail"); } + +// ── Guest table with funcref (call_indirect already tested, but table.get) ── + +#[test] +fn module_with_funcref_table_works() { + // Table of function references used for indirect dispatch. + // Reference types are disabled, but funcref tables should work + // since they're part of the MVP spec. + let wat = r#" + (module + (import "swactor" "send" (func $send (param i32 i32 i32))) + (memory (export "memory") 1) + (type $handler (func (param i32 i32 i32))) + + (func $send_A (param $dest i32) (param $payload i32) (param $len i32) + (i32.store8 (i32.const 200) (i32.const 65)) ;; 'A' + (call $send (local.get $dest) (i32.const 200) (i32.const 1)) + ) + (func $send_B (param $dest i32) (param $payload i32) (param $len i32) + (i32.store8 (i32.const 200) (i32.const 66)) ;; 'B' + (call $send (local.get $dest) (i32.const 200) (i32.const 1)) + ) + + (table 2 funcref) + (elem (i32.const 0) $send_A $send_B) + + (func (export "alloc") (param i32) (result i32) i32.const 4096) + (func (export "handle") (param $ptr i32) (param $len i32) + ;; Dispatch based on first payload byte: 0→send_A, 1→send_B + (call_indirect (type $handler) + (local.get $ptr) ;; dest + (i32.const 0) ;; unused payload + (i32.const 0) ;; unused len + (i32.load8_u (i32.add (local.get $ptr) (i32.const 32))) ;; table index + ) + ) + ) + "#; + let wasm = wat::parse_str(wat).unwrap(); + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, wasm).build().unwrap(); + + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + rt.send_to(addr, framed_msg(inbox.addr(), &[0])).unwrap(); // → 'A' + rt.send_to(addr, framed_msg(inbox.addr(), &[1])).unwrap(); // → 'B' + rt.tick(); + + let msg1 = inbox.try_recv().unwrap(); + let msg2 = inbox.try_recv().unwrap(); + assert_eq!(msg1.0, b"A"); + assert_eq!(msg2.0, b"B"); +} + +// ── Concurrent build from multiple threads ────────────────────────────────── + +#[test] +fn concurrent_build_from_shared_engine() { + use std::thread; + + let engine = SharedEngine::new().unwrap(); + let wasm_bytes = guest_wasm("echo"); + + let handles: Vec<_> = (0..4) + .map(|_| { + let e = engine.clone(); + let w = wasm_bytes.clone(); + thread::spawn(move || { + WasmActorBuilder::new(e, w).build().unwrap() + }) + }) + .collect(); + + let actors: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect(); + + // All 4 actors should work on the same runtime + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + for actor in actors { + let addr = rt.spawn(actor).unwrap(); + rt.send_to(addr, framed_msg(inbox.addr(), b"concurrent")).unwrap(); + } + rt.tick(); + + let mut count = 0; + while let Some(_) = inbox.try_recv() { + count += 1; + } + assert_eq!(count, 4, "all 4 concurrently-built actors should work"); +} + +// ── Guest uses memory.copy for bulk data move ─────────────────────────────── + +#[test] +fn guest_uses_memory_copy_for_response() { + // Guest copies the entire message to a response buffer using memory.copy, + // then sends the payload portion back. + let wat = r#" + (module + (import "swactor" "send" (func $send (param i32 i32 i32))) + (memory (export "memory") 1) + (func (export "alloc") (param i32) (result i32) i32.const 4096) + (func (export "handle") (param $ptr i32) (param $len i32) + ;; Copy entire message to offset 8192 + (memory.copy (i32.const 8192) (local.get $ptr) (local.get $len)) + ;; Send payload (offset 8192+32) back to dest (offset 8192) + (call $send + (i32.const 8192) + (i32.add (i32.const 8192) (i32.const 32)) + (i32.sub (local.get $len) (i32.const 32)) + ) + ) + ) + "#; + let wasm = wat::parse_str(wat).unwrap(); + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, wasm).build().unwrap(); + + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + rt.send_to(addr, framed_msg(inbox.addr(), b"bulk-copy-test")).unwrap(); + rt.tick(); + + let msg = inbox.try_recv().unwrap(); + assert_eq!(msg.0, b"bulk-copy-test"); +} + +// ── WasmActorError is Send + Sync ────────────────────────────────────────── + +#[test] +fn wasm_actor_error_is_send_and_sync() { + fn assert_send_sync() {} + assert_send_sync::(); +}