cargo-makepad, makepad-builder (with an Android step on every OS), CI tools and tiles, and the remaining libraries. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
75 lines
2.7 KiB
Rust
75 lines
2.7 KiB
Rust
//! A host function called in a loop must not grow the native stack.
|
|
//!
|
|
//! Every threaded instruction ends in a sibling call to the next one. A
|
|
//! handler that keeps something alive in its frame across that call leaves
|
|
//! the frame behind, so a module that calls a host function many times in one
|
|
//! invocation runs out of native stack.
|
|
|
|
use makepad_stitch::{Engine, Func, Linker, Module, Store, Val};
|
|
|
|
const CALLS: i32 = 1_000_000;
|
|
|
|
const WAT: &str = r#"
|
|
(module
|
|
(type $t (func (param i32) (result i32)))
|
|
(import "env" "tick" (func $tick (type $t)))
|
|
(table 1 funcref)
|
|
(elem (i32.const 0) $tick)
|
|
(func (export "direct") (param $n i32) (result i32)
|
|
(local $i i32) (local $acc i32)
|
|
(block $done
|
|
(loop $next
|
|
(br_if $done (i32.ge_u (local.get $i) (local.get $n)))
|
|
(local.set $acc (call $tick (local.get $acc)))
|
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
(br $next)))
|
|
(local.get $acc))
|
|
(func (export "indirect") (param $n i32) (result i32)
|
|
(local $i i32) (local $acc i32)
|
|
(block $done
|
|
(loop $next
|
|
(br_if $done (i32.ge_u (local.get $i) (local.get $n)))
|
|
(local.set $acc (call_indirect (type $t) (local.get $acc) (i32.const 0)))
|
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
(br $next)))
|
|
(local.get $acc)))
|
|
"#;
|
|
|
|
/// Calls `export(CALLS)` on a thread with a 1 MiB stack: a leak of even a
|
|
/// few hundred bytes per host call overflows it long before the loop ends.
|
|
fn run(export: &'static str) -> i32 {
|
|
std::thread::Builder::new()
|
|
.stack_size(1 << 20)
|
|
.spawn(move || {
|
|
let buf = wast::parser::ParseBuffer::new(WAT).unwrap();
|
|
let mut wat: wast::Wat = wast::parser::parse(&buf).unwrap();
|
|
let bytes = wat.encode().unwrap();
|
|
let engine = Engine::new();
|
|
let mut store = Store::new(engine);
|
|
let module = Module::new(store.engine(), &bytes).unwrap();
|
|
let mut linker = Linker::new();
|
|
let tick = Func::wrap(&mut store, |x: i32| -> i32 { x + 1 });
|
|
linker.define("env", "tick", tick);
|
|
let instance = linker.instantiate(&mut store, &module).unwrap();
|
|
let mut results = [Val::I32(0)];
|
|
instance
|
|
.exported_func(export)
|
|
.unwrap()
|
|
.call(&mut store, &[Val::I32(CALLS)], &mut results)
|
|
.unwrap();
|
|
results[0].to_i32().unwrap()
|
|
})
|
|
.unwrap()
|
|
.join()
|
|
.unwrap()
|
|
}
|
|
|
|
#[test]
|
|
fn host_calls_in_a_loop_keep_the_native_stack_flat() {
|
|
assert_eq!(run("direct"), CALLS);
|
|
}
|
|
|
|
#[test]
|
|
fn indirect_host_calls_in_a_loop_keep_the_native_stack_flat() {
|
|
assert_eq!(run("indirect"), CALLS);
|
|
}
|