test: Cycle 43 — funcref table dispatch, concurrent build, memory.copy, error traits

- module_with_funcref_table_works: call_indirect through funcref table dispatches correctly
- concurrent_build_from_shared_engine: 4 threads build actors from same engine
- guest_uses_memory_copy_for_response: bulk memory.copy for message relay
- wasm_actor_error_is_send_and_sync: compile-time trait check

All 184 tests pass. No new bugs found.

Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
Claude 2026-02-13 09:51:31 +00:00
parent c8bf78231b
commit a6fea775ac

View file

@ -6535,3 +6535,141 @@ fn empty_wasm_bytes_error() {
let result = WasmActorBuilder::new(engine, Vec::<u8>::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::<ByteMessage>().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::<ByteMessage>().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::<ByteMessage>().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<T: Send + Sync>() {}
assert_send_sync::<WasmActorError>();
}