* Add initial support for fused adapter trampolines This commit lands a significant new piece of functionality to Wasmtime's implementation of the component model in the form of the implementation of fused adapter trampolines. Internally within a component core wasm modules can communicate with each other by having their exports `canon lift`'d to get `canon lower`'d into a different component. This signifies that two components are communicating through a statically known interface via the canonical ABI at this time. Previously Wasmtime was able to identify that this communication was happening but it simply panicked with `unimplemented!` upon seeing it. This commit is the beginning of filling out this panic location with an actual implementation. The implementation route chosen here for fused adapters is to use a WebAssembly module itself for the implementation. This means that, at compile time of a component, Wasmtime is generating core WebAssembly modules which then get recursively compiled within Wasmtime as well. The choice to use WebAssembly itself as the implementation of fused adapters stems from a few motivations: * This does not represent a significant increase in the "trusted compiler base" of Wasmtime. Getting the Wasm -> CLIF translation correct once is hard enough much less for an entirely different IR to CLIF. By generating WebAssembly no new interactions with Cranelift are added which drastically reduces the possibilities for mistakes. * Using WebAssembly means that component adapters are insulated from miscompilations and mistakes. If something goes wrong it's defined well within the WebAssembly specification how it goes wrong and what happens as a result. This means that the "blast zone" for a wrong adapter is the component instance but not the entire host itself. Accesses to linear memory are guaranteed to be in-bounds and otherwise handled via well-defined traps. * A fully-finished fused adapter compiler is expected to be a significant and quite complex component of Wasmtime. Functionality along these lines is expected to be needed for Web-based polyfills of the component model and by using core WebAssembly it provides the opportunity to share code between Wasmtime and these polyfills for the component model. * Finally the runtime implementation of managing WebAssembly modules is already implemented and quite easy to integrate with, so representing fused adapters with WebAssembly results in very little extra support necessary for the runtime implementation of instantiating and managing a component. The compiler added in this commit is dubbed Wasmtime's Fused Adapter Compiler of Trampolines (FACT) because who doesn't like deriving a name from an acronym. Currently the trampoline compiler is limited in its support for interface types and only supports a few primitives. I plan on filing future PRs to flesh out the support here for all the variants of `InterfaceType`. For now this PR is primarily focused on all of the other infrastructure for the addition of a trampoline compiler. With the choice to use core WebAssembly to implement fused adapters it means that adapters need to be inserted into a module. Unfortunately adapters cannot all go into a single WebAssembly module because adapters themselves have dependencies which may be provided transitively through instances that were instantiated with other adapters. This means that a significant chunk of this PR (`adapt.rs`) is dedicated to determining precisely which adapters go into precisely which adapter modules. This partitioning process attempts to make large modules wherever it can to cut down on core wasm instantiations but is likely not optimal as it's just a simple heuristic today. With all of this added together it's now possible to start writing `*.wast` tests that internally have adapted modules communicating with one another. A `fused.wast` test suite was added as part of this PR which is the beginning of tests for the support of the fused adapter compiler added in this PR. Currently this is primarily testing some various topologies of adapters along with direct/indirect modes. This will grow many more tests over time as more types are supported. Overall I'm not 100% satisfied with the testing story of this PR. When a test fails it's very difficult to debug since everything is written in the text format of WebAssembly meaning there's no "conveniences" to print out the state of the world when things go wrong and easily debug. I think this will become even more apparent as more tests are written for more types in subsequent PRs. At this time though I know of no better alternative other than leaning pretty heavily on fuzz-testing to ensure this is all exercised. * Fix an unused field warning * Fix tests in `wasmtime-runtime` * Add some more tests for compiled trampolines * Remap exports when injecting adapters The exports of a component were accidentally left unmapped which meant that they indexed the instance indexes pre-adapter module insertion. * Fix typo * Rebase conflicts
2413 lines
86 KiB
Rust
2413 lines
86 KiB
Rust
use super::{TypedFuncExt, REALLOC_AND_FREE};
|
|
use anyhow::Result;
|
|
use std::rc::Rc;
|
|
use std::sync::Arc;
|
|
use wasmtime::component::*;
|
|
use wasmtime::{Store, StoreContextMut, Trap, TrapCode};
|
|
|
|
const CANON_32BIT_NAN: u32 = 0b01111111110000000000000000000000;
|
|
const CANON_64BIT_NAN: u64 = 0b0111111111111000000000000000000000000000000000000000000000000000;
|
|
|
|
#[test]
|
|
fn thunks() -> Result<()> {
|
|
let component = r#"
|
|
(component
|
|
(core module $m
|
|
(func (export "thunk"))
|
|
(func (export "thunk-trap") unreachable)
|
|
)
|
|
(core instance $i (instantiate $m))
|
|
(func (export "thunk")
|
|
(canon lift (core func $i "thunk"))
|
|
)
|
|
(func (export "thunk-trap")
|
|
(canon lift (core func $i "thunk-trap"))
|
|
)
|
|
)
|
|
"#;
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
|
instance
|
|
.get_typed_func::<(), (), _>(&mut store, "thunk")?
|
|
.call_and_post_return(&mut store, ())?;
|
|
let err = instance
|
|
.get_typed_func::<(), (), _>(&mut store, "thunk-trap")?
|
|
.call(&mut store, ())
|
|
.unwrap_err();
|
|
assert!(err.downcast::<Trap>()?.trap_code() == Some(TrapCode::UnreachableCodeReached));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn typecheck() -> Result<()> {
|
|
let component = r#"
|
|
(component
|
|
(core module $m
|
|
(func (export "thunk"))
|
|
(func (export "take-string") (param i32 i32))
|
|
(func (export "two-args") (param i32 i32 i32))
|
|
(func (export "ret-one") (result i32) unreachable)
|
|
|
|
(memory (export "memory") 1)
|
|
(func (export "realloc") (param i32 i32 i32 i32) (result i32)
|
|
unreachable)
|
|
)
|
|
(core instance $i (instantiate (module $m)))
|
|
(func (export "thunk")
|
|
(canon lift (core func $i "thunk"))
|
|
)
|
|
(func (export "tuple-thunk") (param (tuple)) (result (tuple))
|
|
(canon lift (core func $i "thunk"))
|
|
)
|
|
(func (export "take-string") (param string)
|
|
(canon lift (core func $i "take-string") (memory $i "memory") (realloc (func $i "realloc")))
|
|
)
|
|
(func (export "take-two-args") (param s32) (param (list u8))
|
|
(canon lift (core func $i "two-args") (memory $i "memory") (realloc (func $i "realloc")))
|
|
)
|
|
(func (export "ret-tuple") (result (tuple u8 s8))
|
|
(canon lift (core func $i "ret-one") (memory $i "memory") (realloc (func $i "realloc")))
|
|
)
|
|
(func (export "ret-tuple1") (result (tuple u32))
|
|
(canon lift (core func $i "ret-one") (memory $i "memory") (realloc (func $i "realloc")))
|
|
)
|
|
(func (export "ret-string") (result string)
|
|
(canon lift (core func $i "ret-one") (memory $i "memory") (realloc (func $i "realloc")))
|
|
)
|
|
(func (export "ret-list-u8") (result (list u8))
|
|
(canon lift (core func $i "ret-one") (memory $i "memory") (realloc (func $i "realloc")))
|
|
)
|
|
)
|
|
"#;
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
|
let thunk = instance.get_func(&mut store, "thunk").unwrap();
|
|
let tuple_thunk = instance.get_func(&mut store, "tuple-thunk").unwrap();
|
|
let take_string = instance.get_func(&mut store, "take-string").unwrap();
|
|
let take_two_args = instance.get_func(&mut store, "take-two-args").unwrap();
|
|
let ret_tuple = instance.get_func(&mut store, "ret-tuple").unwrap();
|
|
let ret_tuple1 = instance.get_func(&mut store, "ret-tuple1").unwrap();
|
|
let ret_string = instance.get_func(&mut store, "ret-string").unwrap();
|
|
let ret_list_u8 = instance.get_func(&mut store, "ret-list-u8").unwrap();
|
|
assert!(thunk.typed::<(), u32, _>(&store).is_err());
|
|
assert!(thunk.typed::<(u32,), (), _>(&store).is_err());
|
|
assert!(thunk.typed::<(), (), _>(&store).is_ok());
|
|
assert!(tuple_thunk.typed::<(), (), _>(&store).is_err());
|
|
assert!(tuple_thunk.typed::<((),), (), _>(&store).is_ok());
|
|
assert!(take_string.typed::<(), (), _>(&store).is_err());
|
|
assert!(take_string.typed::<(String,), (), _>(&store).is_ok());
|
|
assert!(take_string.typed::<(&str,), (), _>(&store).is_ok());
|
|
assert!(take_string.typed::<(&[u8],), (), _>(&store).is_err());
|
|
assert!(take_two_args.typed::<(), (), _>(&store).is_err());
|
|
assert!(take_two_args.typed::<(i32, &[u8]), u32, _>(&store).is_err());
|
|
assert!(take_two_args.typed::<(u32, &[u8]), (), _>(&store).is_err());
|
|
assert!(take_two_args.typed::<(i32, &[u8]), (), _>(&store).is_ok());
|
|
assert!(ret_tuple.typed::<(), (), _>(&store).is_err());
|
|
assert!(ret_tuple.typed::<(), (u8,), _>(&store).is_err());
|
|
assert!(ret_tuple.typed::<(), (u8, i8), _>(&store).is_ok());
|
|
assert!(ret_tuple1.typed::<(), (u32,), _>(&store).is_ok());
|
|
assert!(ret_tuple1.typed::<(), u32, _>(&store).is_err());
|
|
assert!(ret_string.typed::<(), (), _>(&store).is_err());
|
|
assert!(ret_string.typed::<(), WasmStr, _>(&store).is_ok());
|
|
assert!(ret_list_u8.typed::<(), WasmList<u16>, _>(&store).is_err());
|
|
assert!(ret_list_u8.typed::<(), WasmList<i8>, _>(&store).is_err());
|
|
assert!(ret_list_u8.typed::<(), WasmList<u8>, _>(&store).is_ok());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn integers() -> Result<()> {
|
|
let component = r#"
|
|
(component
|
|
(core module $m
|
|
(func (export "take-i32-100") (param i32)
|
|
local.get 0
|
|
i32.const 100
|
|
i32.eq
|
|
br_if 0
|
|
unreachable
|
|
)
|
|
(func (export "take-i64-100") (param i64)
|
|
local.get 0
|
|
i64.const 100
|
|
i64.eq
|
|
br_if 0
|
|
unreachable
|
|
)
|
|
(func (export "ret-i32-0") (result i32) i32.const 0)
|
|
(func (export "ret-i64-0") (result i64) i64.const 0)
|
|
(func (export "ret-i32-minus-1") (result i32) i32.const -1)
|
|
(func (export "ret-i64-minus-1") (result i64) i64.const -1)
|
|
(func (export "ret-i32-100000") (result i32) i32.const 100000)
|
|
)
|
|
(core instance $i (instantiate (module $m)))
|
|
(func (export "take-u8") (param u8) (canon lift (core func $i "take-i32-100")))
|
|
(func (export "take-s8") (param s8) (canon lift (core func $i "take-i32-100")))
|
|
(func (export "take-u16") (param u16) (canon lift (core func $i "take-i32-100")))
|
|
(func (export "take-s16") (param s16) (canon lift (core func $i "take-i32-100")))
|
|
(func (export "take-u32") (param u32) (canon lift (core func $i "take-i32-100")))
|
|
(func (export "take-s32") (param s32) (canon lift (core func $i "take-i32-100")))
|
|
(func (export "take-u64") (param u64) (canon lift (core func $i "take-i64-100")))
|
|
(func (export "take-s64") (param s64) (canon lift (core func $i "take-i64-100")))
|
|
|
|
(func (export "ret-u8") (result u8) (canon lift (core func $i "ret-i32-0")))
|
|
(func (export "ret-s8") (result s8) (canon lift (core func $i "ret-i32-0")))
|
|
(func (export "ret-u16") (result u16) (canon lift (core func $i "ret-i32-0")))
|
|
(func (export "ret-s16") (result s16) (canon lift (core func $i "ret-i32-0")))
|
|
(func (export "ret-u32") (result u32) (canon lift (core func $i "ret-i32-0")))
|
|
(func (export "ret-s32") (result s32) (canon lift (core func $i "ret-i32-0")))
|
|
(func (export "ret-u64") (result u64) (canon lift (core func $i "ret-i64-0")))
|
|
(func (export "ret-s64") (result s64) (canon lift (core func $i "ret-i64-0")))
|
|
|
|
(func (export "retm1-u8") (result u8) (canon lift (core func $i "ret-i32-minus-1")))
|
|
(func (export "retm1-s8") (result s8) (canon lift (core func $i "ret-i32-minus-1")))
|
|
(func (export "retm1-u16") (result u16) (canon lift (core func $i "ret-i32-minus-1")))
|
|
(func (export "retm1-s16") (result s16) (canon lift (core func $i "ret-i32-minus-1")))
|
|
(func (export "retm1-u32") (result u32) (canon lift (core func $i "ret-i32-minus-1")))
|
|
(func (export "retm1-s32") (result s32) (canon lift (core func $i "ret-i32-minus-1")))
|
|
(func (export "retm1-u64") (result u64) (canon lift (core func $i "ret-i64-minus-1")))
|
|
(func (export "retm1-s64") (result s64) (canon lift (core func $i "ret-i64-minus-1")))
|
|
|
|
(func (export "retbig-u8") (result u8) (canon lift (core func $i "ret-i32-100000")))
|
|
(func (export "retbig-s8") (result s8) (canon lift (core func $i "ret-i32-100000")))
|
|
(func (export "retbig-u16") (result u16) (canon lift (core func $i "ret-i32-100000")))
|
|
(func (export "retbig-s16") (result s16) (canon lift (core func $i "ret-i32-100000")))
|
|
(func (export "retbig-u32") (result u32) (canon lift (core func $i "ret-i32-100000")))
|
|
(func (export "retbig-s32") (result s32) (canon lift (core func $i "ret-i32-100000")))
|
|
)
|
|
"#;
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let new_instance = |store: &mut Store<()>| Linker::new(&engine).instantiate(store, &component);
|
|
let instance = new_instance(&mut store)?;
|
|
|
|
// Passing in 100 is valid for all primitives
|
|
instance
|
|
.get_typed_func::<(u8,), (), _>(&mut store, "take-u8")?
|
|
.call_and_post_return(&mut store, (100,))?;
|
|
instance
|
|
.get_typed_func::<(i8,), (), _>(&mut store, "take-s8")?
|
|
.call_and_post_return(&mut store, (100,))?;
|
|
instance
|
|
.get_typed_func::<(u16,), (), _>(&mut store, "take-u16")?
|
|
.call_and_post_return(&mut store, (100,))?;
|
|
instance
|
|
.get_typed_func::<(i16,), (), _>(&mut store, "take-s16")?
|
|
.call_and_post_return(&mut store, (100,))?;
|
|
instance
|
|
.get_typed_func::<(u32,), (), _>(&mut store, "take-u32")?
|
|
.call_and_post_return(&mut store, (100,))?;
|
|
instance
|
|
.get_typed_func::<(i32,), (), _>(&mut store, "take-s32")?
|
|
.call_and_post_return(&mut store, (100,))?;
|
|
instance
|
|
.get_typed_func::<(u64,), (), _>(&mut store, "take-u64")?
|
|
.call_and_post_return(&mut store, (100,))?;
|
|
instance
|
|
.get_typed_func::<(i64,), (), _>(&mut store, "take-s64")?
|
|
.call_and_post_return(&mut store, (100,))?;
|
|
|
|
// This specific wasm instance traps if any value other than 100 is passed
|
|
new_instance(&mut store)?
|
|
.get_typed_func::<(u8,), (), _>(&mut store, "take-u8")?
|
|
.call(&mut store, (101,))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
new_instance(&mut store)?
|
|
.get_typed_func::<(i8,), (), _>(&mut store, "take-s8")?
|
|
.call(&mut store, (101,))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
new_instance(&mut store)?
|
|
.get_typed_func::<(u16,), (), _>(&mut store, "take-u16")?
|
|
.call(&mut store, (101,))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
new_instance(&mut store)?
|
|
.get_typed_func::<(i16,), (), _>(&mut store, "take-s16")?
|
|
.call(&mut store, (101,))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
new_instance(&mut store)?
|
|
.get_typed_func::<(u32,), (), _>(&mut store, "take-u32")?
|
|
.call(&mut store, (101,))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
new_instance(&mut store)?
|
|
.get_typed_func::<(i32,), (), _>(&mut store, "take-s32")?
|
|
.call(&mut store, (101,))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
new_instance(&mut store)?
|
|
.get_typed_func::<(u64,), (), _>(&mut store, "take-u64")?
|
|
.call(&mut store, (101,))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
new_instance(&mut store)?
|
|
.get_typed_func::<(i64,), (), _>(&mut store, "take-s64")?
|
|
.call(&mut store, (101,))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
|
|
// Zero can be returned as any integer
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), u8, _>(&mut store, "ret-u8")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i8, _>(&mut store, "ret-s8")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), u16, _>(&mut store, "ret-u16")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i16, _>(&mut store, "ret-s16")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), u32, _>(&mut store, "ret-u32")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i32, _>(&mut store, "ret-s32")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), u64, _>(&mut store, "ret-u64")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i64, _>(&mut store, "ret-s64")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
0
|
|
);
|
|
|
|
// Returning -1 should reinterpret the bytes as defined by each type.
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), u8, _>(&mut store, "retm1-u8")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
0xff
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i8, _>(&mut store, "retm1-s8")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
-1
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), u16, _>(&mut store, "retm1-u16")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
0xffff
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i16, _>(&mut store, "retm1-s16")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
-1
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), u32, _>(&mut store, "retm1-u32")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
0xffffffff
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i32, _>(&mut store, "retm1-s32")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
-1
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), u64, _>(&mut store, "retm1-u64")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
0xffffffff_ffffffff
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i64, _>(&mut store, "retm1-s64")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
-1
|
|
);
|
|
|
|
// Returning 100000 should chop off bytes as necessary
|
|
let ret: u32 = 100000;
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), u8, _>(&mut store, "retbig-u8")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
ret as u8,
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i8, _>(&mut store, "retbig-s8")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
ret as i8,
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), u16, _>(&mut store, "retbig-u16")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
ret as u16,
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i16, _>(&mut store, "retbig-s16")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
ret as i16,
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), u32, _>(&mut store, "retbig-u32")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
ret,
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i32, _>(&mut store, "retbig-s32")?
|
|
.call_and_post_return(&mut store, ())?,
|
|
ret as i32,
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn type_layers() -> Result<()> {
|
|
let component = r#"
|
|
(component
|
|
(core module $m
|
|
(func (export "take-i32-100") (param i32)
|
|
local.get 0
|
|
i32.const 2
|
|
i32.eq
|
|
br_if 0
|
|
unreachable
|
|
)
|
|
)
|
|
(core instance $i (instantiate $m))
|
|
(func (export "take-u32") (param u32) (canon lift (core func $i "take-i32-100")))
|
|
)
|
|
"#;
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
|
|
|
instance
|
|
.get_typed_func::<(Box<u32>,), (), _>(&mut store, "take-u32")?
|
|
.call_and_post_return(&mut store, (Box::new(2),))?;
|
|
instance
|
|
.get_typed_func::<(&u32,), (), _>(&mut store, "take-u32")?
|
|
.call_and_post_return(&mut store, (&2,))?;
|
|
instance
|
|
.get_typed_func::<(Rc<u32>,), (), _>(&mut store, "take-u32")?
|
|
.call_and_post_return(&mut store, (Rc::new(2),))?;
|
|
instance
|
|
.get_typed_func::<(Arc<u32>,), (), _>(&mut store, "take-u32")?
|
|
.call_and_post_return(&mut store, (Arc::new(2),))?;
|
|
instance
|
|
.get_typed_func::<(&Box<Arc<Rc<u32>>>,), (), _>(&mut store, "take-u32")?
|
|
.call_and_post_return(&mut store, (&Box::new(Arc::new(Rc::new(2))),))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn floats() -> Result<()> {
|
|
let component = r#"
|
|
(component
|
|
(core module $m
|
|
(func (export "i32.reinterpret_f32") (param f32) (result i32)
|
|
local.get 0
|
|
i32.reinterpret_f32
|
|
)
|
|
(func (export "i64.reinterpret_f64") (param f64) (result i64)
|
|
local.get 0
|
|
i64.reinterpret_f64
|
|
)
|
|
(func (export "f32.reinterpret_i32") (param i32) (result f32)
|
|
local.get 0
|
|
f32.reinterpret_i32
|
|
)
|
|
(func (export "f64.reinterpret_i64") (param i64) (result f64)
|
|
local.get 0
|
|
f64.reinterpret_i64
|
|
)
|
|
)
|
|
(core instance $i (instantiate $m))
|
|
|
|
(func (export "f32-to-u32") (param float32) (result u32)
|
|
(canon lift (core func $i "i32.reinterpret_f32"))
|
|
)
|
|
(func (export "f64-to-u64") (param float64) (result u64)
|
|
(canon lift (core func $i "i64.reinterpret_f64"))
|
|
)
|
|
(func (export "u32-to-f32") (param u32) (result float32)
|
|
(canon lift (core func $i "f32.reinterpret_i32"))
|
|
)
|
|
(func (export "u64-to-f64") (param u64) (result float64)
|
|
(canon lift (core func $i "f64.reinterpret_i64"))
|
|
)
|
|
)
|
|
"#;
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
|
let f32_to_u32 = instance.get_typed_func::<(f32,), u32, _>(&mut store, "f32-to-u32")?;
|
|
let f64_to_u64 = instance.get_typed_func::<(f64,), u64, _>(&mut store, "f64-to-u64")?;
|
|
let u32_to_f32 = instance.get_typed_func::<(u32,), f32, _>(&mut store, "u32-to-f32")?;
|
|
let u64_to_f64 = instance.get_typed_func::<(u64,), f64, _>(&mut store, "u64-to-f64")?;
|
|
|
|
assert_eq!(f32_to_u32.call(&mut store, (1.0,))?, 1.0f32.to_bits());
|
|
f32_to_u32.post_return(&mut store)?;
|
|
assert_eq!(f64_to_u64.call(&mut store, (2.0,))?, 2.0f64.to_bits());
|
|
f64_to_u64.post_return(&mut store)?;
|
|
assert_eq!(u32_to_f32.call(&mut store, (3.0f32.to_bits(),))?, 3.0);
|
|
u32_to_f32.post_return(&mut store)?;
|
|
assert_eq!(u64_to_f64.call(&mut store, (4.0f64.to_bits(),))?, 4.0);
|
|
u64_to_f64.post_return(&mut store)?;
|
|
|
|
assert_eq!(
|
|
u32_to_f32
|
|
.call(&mut store, (CANON_32BIT_NAN | 1,))?
|
|
.to_bits(),
|
|
CANON_32BIT_NAN
|
|
);
|
|
u32_to_f32.post_return(&mut store)?;
|
|
assert_eq!(
|
|
u64_to_f64
|
|
.call(&mut store, (CANON_64BIT_NAN | 1,))?
|
|
.to_bits(),
|
|
CANON_64BIT_NAN
|
|
);
|
|
u64_to_f64.post_return(&mut store)?;
|
|
|
|
assert_eq!(
|
|
f32_to_u32.call(&mut store, (f32::from_bits(CANON_32BIT_NAN | 1),))?,
|
|
CANON_32BIT_NAN
|
|
);
|
|
f32_to_u32.post_return(&mut store)?;
|
|
assert_eq!(
|
|
f64_to_u64.call(&mut store, (f64::from_bits(CANON_64BIT_NAN | 1),))?,
|
|
CANON_64BIT_NAN
|
|
);
|
|
f64_to_u64.post_return(&mut store)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn bools() -> Result<()> {
|
|
let component = r#"
|
|
(component
|
|
(core module $m
|
|
(func (export "pass") (param i32) (result i32) local.get 0)
|
|
)
|
|
(core instance $i (instantiate $m))
|
|
|
|
(func (export "u32-to-bool") (param u32) (result bool)
|
|
(canon lift (core func $i "pass"))
|
|
)
|
|
(func (export "bool-to-u32") (param bool) (result u32)
|
|
(canon lift (core func $i "pass"))
|
|
)
|
|
)
|
|
"#;
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
|
let u32_to_bool = instance.get_typed_func::<(u32,), bool, _>(&mut store, "u32-to-bool")?;
|
|
let bool_to_u32 = instance.get_typed_func::<(bool,), u32, _>(&mut store, "bool-to-u32")?;
|
|
|
|
assert_eq!(bool_to_u32.call(&mut store, (false,))?, 0);
|
|
bool_to_u32.post_return(&mut store)?;
|
|
assert_eq!(bool_to_u32.call(&mut store, (true,))?, 1);
|
|
bool_to_u32.post_return(&mut store)?;
|
|
assert_eq!(u32_to_bool.call(&mut store, (0,))?, false);
|
|
u32_to_bool.post_return(&mut store)?;
|
|
assert_eq!(u32_to_bool.call(&mut store, (1,))?, true);
|
|
u32_to_bool.post_return(&mut store)?;
|
|
assert_eq!(u32_to_bool.call(&mut store, (2,))?, true);
|
|
u32_to_bool.post_return(&mut store)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn chars() -> Result<()> {
|
|
let component = r#"
|
|
(component
|
|
(core module $m
|
|
(func (export "pass") (param i32) (result i32) local.get 0)
|
|
)
|
|
(core instance $i (instantiate $m))
|
|
|
|
(func (export "u32-to-char") (param u32) (result char)
|
|
(canon lift (core func $i "pass"))
|
|
)
|
|
(func (export "char-to-u32") (param char) (result u32)
|
|
(canon lift (core func $i "pass"))
|
|
)
|
|
)
|
|
"#;
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
|
let u32_to_char = instance.get_typed_func::<(u32,), char, _>(&mut store, "u32-to-char")?;
|
|
let char_to_u32 = instance.get_typed_func::<(char,), u32, _>(&mut store, "char-to-u32")?;
|
|
|
|
let mut roundtrip = |x: char| -> Result<()> {
|
|
assert_eq!(char_to_u32.call(&mut store, (x,))?, x as u32);
|
|
char_to_u32.post_return(&mut store)?;
|
|
assert_eq!(u32_to_char.call(&mut store, (x as u32,))?, x);
|
|
u32_to_char.post_return(&mut store)?;
|
|
Ok(())
|
|
};
|
|
|
|
roundtrip('x')?;
|
|
roundtrip('a')?;
|
|
roundtrip('\0')?;
|
|
roundtrip('\n')?;
|
|
roundtrip('💝')?;
|
|
|
|
let u32_to_char = |store: &mut Store<()>| {
|
|
Linker::new(&engine)
|
|
.instantiate(&mut *store, &component)?
|
|
.get_typed_func::<(u32,), char, _>(&mut *store, "u32-to-char")
|
|
};
|
|
let err = u32_to_char(&mut store)?
|
|
.call(&mut store, (0xd800,))
|
|
.unwrap_err();
|
|
assert!(err.to_string().contains("integer out of range"), "{}", err);
|
|
let err = u32_to_char(&mut store)?
|
|
.call(&mut store, (0xdfff,))
|
|
.unwrap_err();
|
|
assert!(err.to_string().contains("integer out of range"), "{}", err);
|
|
let err = u32_to_char(&mut store)?
|
|
.call(&mut store, (0x110000,))
|
|
.unwrap_err();
|
|
assert!(err.to_string().contains("integer out of range"), "{}", err);
|
|
let err = u32_to_char(&mut store)?
|
|
.call(&mut store, (u32::MAX,))
|
|
.unwrap_err();
|
|
assert!(err.to_string().contains("integer out of range"), "{}", err);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn tuple_result() -> Result<()> {
|
|
let component = r#"
|
|
(component
|
|
(core module $m
|
|
(memory (export "memory") 1)
|
|
(func (export "foo") (param i32 i32 f32 f64) (result i32)
|
|
(local $base i32)
|
|
(local.set $base (i32.const 8))
|
|
(i32.store8 offset=0 (local.get $base) (local.get 0))
|
|
(i32.store16 offset=2 (local.get $base) (local.get 1))
|
|
(f32.store offset=4 (local.get $base) (local.get 2))
|
|
(f64.store offset=8 (local.get $base) (local.get 3))
|
|
local.get $base
|
|
)
|
|
|
|
(func (export "invalid") (result i32)
|
|
i32.const -8
|
|
)
|
|
)
|
|
(core instance $i (instantiate $m))
|
|
|
|
(type $result (tuple s8 u16 float32 float64))
|
|
(func (export "tuple")
|
|
(param s8) (param u16) (param float32) (param float64) (result $result)
|
|
(canon lift (core func $i "foo") (memory $i "memory"))
|
|
)
|
|
(func (export "invalid") (result $result)
|
|
(canon lift (core func $i "invalid") (memory $i "memory"))
|
|
)
|
|
)
|
|
"#;
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
|
|
|
let input = (-1, 100, 3.0, 100.0);
|
|
let output = instance
|
|
.get_typed_func::<(i8, u16, f32, f64), (i8, u16, f32, f64), _>(&mut store, "tuple")?
|
|
.call_and_post_return(&mut store, input)?;
|
|
assert_eq!(input, output);
|
|
|
|
let invalid_func =
|
|
instance.get_typed_func::<(), (i8, u16, f32, f64), _>(&mut store, "invalid")?;
|
|
let err = invalid_func.call(&mut store, ()).err().unwrap();
|
|
assert!(
|
|
err.to_string().contains("pointer out of bounds of memory"),
|
|
"{}",
|
|
err
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn strings() -> Result<()> {
|
|
let component = format!(
|
|
r#"(component
|
|
(core module $m
|
|
(memory (export "memory") 1)
|
|
(func (export "roundtrip") (param i32 i32) (result i32)
|
|
(local $base i32)
|
|
(local.set $base
|
|
(call $realloc
|
|
(i32.const 0)
|
|
(i32.const 0)
|
|
(i32.const 4)
|
|
(i32.const 8)))
|
|
(i32.store offset=0
|
|
(local.get $base)
|
|
(local.get 0))
|
|
(i32.store offset=4
|
|
(local.get $base)
|
|
(local.get 1))
|
|
(local.get $base)
|
|
)
|
|
|
|
{REALLOC_AND_FREE}
|
|
)
|
|
(core instance $i (instantiate $m))
|
|
|
|
(func (export "list8-to-str") (param (list u8)) (result string)
|
|
(canon lift
|
|
(core func $i "roundtrip")
|
|
(memory $i "memory")
|
|
(realloc (func $i "realloc"))
|
|
)
|
|
)
|
|
(func (export "str-to-list8") (param string) (result (list u8))
|
|
(canon lift
|
|
(core func $i "roundtrip")
|
|
(memory $i "memory")
|
|
(realloc (func $i "realloc"))
|
|
)
|
|
)
|
|
(func (export "list16-to-str") (param (list u16)) (result string)
|
|
(canon lift
|
|
(core func $i "roundtrip")
|
|
string-encoding=utf16
|
|
(memory $i "memory")
|
|
(realloc (func $i "realloc"))
|
|
)
|
|
)
|
|
(func (export "str-to-list16") (param string) (result (list u16))
|
|
(canon lift
|
|
(core func $i "roundtrip")
|
|
string-encoding=utf16
|
|
(memory $i "memory")
|
|
(realloc (func $i "realloc"))
|
|
)
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
|
let list8_to_str =
|
|
instance.get_typed_func::<(&[u8],), WasmStr, _>(&mut store, "list8-to-str")?;
|
|
let str_to_list8 =
|
|
instance.get_typed_func::<(&str,), WasmList<u8>, _>(&mut store, "str-to-list8")?;
|
|
let list16_to_str =
|
|
instance.get_typed_func::<(&[u16],), WasmStr, _>(&mut store, "list16-to-str")?;
|
|
let str_to_list16 =
|
|
instance.get_typed_func::<(&str,), WasmList<u16>, _>(&mut store, "str-to-list16")?;
|
|
|
|
let mut roundtrip = |x: &str| -> Result<()> {
|
|
let ret = list8_to_str.call(&mut store, (x.as_bytes(),))?;
|
|
assert_eq!(ret.to_str(&store)?, x);
|
|
list8_to_str.post_return(&mut store)?;
|
|
|
|
let utf16 = x.encode_utf16().collect::<Vec<_>>();
|
|
let ret = list16_to_str.call(&mut store, (&utf16[..],))?;
|
|
assert_eq!(ret.to_str(&store)?, x);
|
|
list16_to_str.post_return(&mut store)?;
|
|
|
|
let ret = str_to_list8.call(&mut store, (x,))?;
|
|
assert_eq!(ret.iter(&store).collect::<Result<Vec<_>>>()?, x.as_bytes());
|
|
str_to_list8.post_return(&mut store)?;
|
|
|
|
let ret = str_to_list16.call(&mut store, (x,))?;
|
|
assert_eq!(ret.iter(&store).collect::<Result<Vec<_>>>()?, utf16,);
|
|
str_to_list16.post_return(&mut store)?;
|
|
|
|
Ok(())
|
|
};
|
|
|
|
roundtrip("")?;
|
|
roundtrip("foo")?;
|
|
roundtrip("hello there")?;
|
|
roundtrip("💝")?;
|
|
roundtrip("Löwe 老虎 Léopard")?;
|
|
|
|
let ret = list8_to_str.call(&mut store, (b"\xff",))?;
|
|
let err = ret.to_str(&store).unwrap_err();
|
|
assert!(err.to_string().contains("invalid utf-8"), "{}", err);
|
|
list8_to_str.post_return(&mut store)?;
|
|
|
|
let ret = list8_to_str.call(&mut store, (b"hello there \xff invalid",))?;
|
|
let err = ret.to_str(&store).unwrap_err();
|
|
assert!(err.to_string().contains("invalid utf-8"), "{}", err);
|
|
list8_to_str.post_return(&mut store)?;
|
|
|
|
let ret = list16_to_str.call(&mut store, (&[0xd800],))?;
|
|
let err = ret.to_str(&store).unwrap_err();
|
|
assert!(err.to_string().contains("unpaired surrogate"), "{}", err);
|
|
list16_to_str.post_return(&mut store)?;
|
|
|
|
let ret = list16_to_str.call(&mut store, (&[0xdfff],))?;
|
|
let err = ret.to_str(&store).unwrap_err();
|
|
assert!(err.to_string().contains("unpaired surrogate"), "{}", err);
|
|
list16_to_str.post_return(&mut store)?;
|
|
|
|
let ret = list16_to_str.call(&mut store, (&[0xd800, 0xff00],))?;
|
|
let err = ret.to_str(&store).unwrap_err();
|
|
assert!(err.to_string().contains("unpaired surrogate"), "{}", err);
|
|
list16_to_str.post_return(&mut store)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn many_parameters() -> Result<()> {
|
|
let component = format!(
|
|
r#"(component
|
|
(core module $m
|
|
(memory (export "memory") 1)
|
|
(func (export "foo") (param i32) (result i32)
|
|
(local $base i32)
|
|
|
|
;; Allocate space for the return
|
|
(local.set $base
|
|
(call $realloc
|
|
(i32.const 0)
|
|
(i32.const 0)
|
|
(i32.const 4)
|
|
(i32.const 12)))
|
|
|
|
;; Store the pointer/length of the entire linear memory
|
|
;; so we have access to everything.
|
|
(i32.store offset=0
|
|
(local.get $base)
|
|
(i32.const 0))
|
|
(i32.store offset=4
|
|
(local.get $base)
|
|
(i32.mul
|
|
(memory.size)
|
|
(i32.const 65536)))
|
|
|
|
;; And also store our pointer parameter
|
|
(i32.store offset=8
|
|
(local.get $base)
|
|
(local.get 0))
|
|
|
|
(local.get $base)
|
|
)
|
|
|
|
{REALLOC_AND_FREE}
|
|
)
|
|
(core instance $i (instantiate $m))
|
|
|
|
(type $result (tuple (list u8) u32))
|
|
(type $t (func
|
|
(param s8) ;; offset 0, size 1
|
|
(param u64) ;; offset 8, size 8
|
|
(param float32) ;; offset 16, size 4
|
|
(param u8) ;; offset 20, size 1
|
|
(param unit) ;; offset 21, size 0
|
|
(param s16) ;; offset 22, size 2
|
|
(param string) ;; offset 24, size 8
|
|
(param (list u32)) ;; offset 32, size 8
|
|
(param bool) ;; offset 40, size 1
|
|
(param bool) ;; offset 41, size 1
|
|
(param char) ;; offset 44, size 4
|
|
(param (list bool)) ;; offset 48, size 8
|
|
(param (list char)) ;; offset 56, size 8
|
|
(param (list string)) ;; offset 64, size 8
|
|
|
|
(result $result)
|
|
))
|
|
(func (export "many-param") (type $t)
|
|
(canon lift
|
|
(core func $i "foo")
|
|
(memory $i "memory")
|
|
(realloc (func $i "realloc"))
|
|
)
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
|
let func = instance.get_typed_func::<(
|
|
i8,
|
|
u64,
|
|
f32,
|
|
u8,
|
|
(),
|
|
i16,
|
|
&str,
|
|
&[u32],
|
|
bool,
|
|
bool,
|
|
char,
|
|
&[bool],
|
|
&[char],
|
|
&[&str],
|
|
), (WasmList<u8>, u32), _>(&mut store, "many-param")?;
|
|
|
|
let input = (
|
|
-100,
|
|
u64::MAX / 2,
|
|
f32::from_bits(CANON_32BIT_NAN | 1),
|
|
38,
|
|
(),
|
|
18831,
|
|
"this is the first string",
|
|
[1, 2, 3, 4, 5, 6, 7, 8].as_slice(),
|
|
true,
|
|
false,
|
|
'🚩',
|
|
[false, true, false, true, true].as_slice(),
|
|
['🍌', '🥐', '🍗', '🍙', '🍡'].as_slice(),
|
|
[
|
|
"the quick",
|
|
"brown fox",
|
|
"was too lazy",
|
|
"to jump over the dog",
|
|
"what a demanding dog",
|
|
]
|
|
.as_slice(),
|
|
);
|
|
let (memory, pointer) = func.call(&mut store, input)?;
|
|
let memory = memory.as_le_slice(&store);
|
|
|
|
let mut actual = &memory[pointer as usize..][..72];
|
|
assert_eq!(i8::from_le_bytes(*actual.take_n::<1>()), input.0);
|
|
actual.skip::<7>();
|
|
assert_eq!(u64::from_le_bytes(*actual.take_n::<8>()), input.1);
|
|
assert_eq!(u32::from_le_bytes(*actual.take_n::<4>()), CANON_32BIT_NAN);
|
|
assert_eq!(u8::from_le_bytes(*actual.take_n::<1>()), input.3);
|
|
actual.skip::<1>();
|
|
assert_eq!(i16::from_le_bytes(*actual.take_n::<2>()), input.5);
|
|
assert_eq!(actual.ptr_len(memory, 1), input.6.as_bytes());
|
|
let mut mem = actual.ptr_len(memory, 4);
|
|
for expected in input.7.iter() {
|
|
assert_eq!(u32::from_le_bytes(*mem.take_n::<4>()), *expected);
|
|
}
|
|
assert!(mem.is_empty());
|
|
assert_eq!(actual.take_n::<1>(), &[input.8 as u8]);
|
|
assert_eq!(actual.take_n::<1>(), &[input.9 as u8]);
|
|
actual.skip::<2>();
|
|
assert_eq!(u32::from_le_bytes(*actual.take_n::<4>()), input.10 as u32);
|
|
|
|
// (list bool)
|
|
mem = actual.ptr_len(memory, 1);
|
|
for expected in input.11.iter() {
|
|
assert_eq!(mem.take_n::<1>(), &[*expected as u8]);
|
|
}
|
|
assert!(mem.is_empty());
|
|
|
|
// (list char)
|
|
mem = actual.ptr_len(memory, 4);
|
|
for expected in input.12.iter() {
|
|
assert_eq!(u32::from_le_bytes(*mem.take_n::<4>()), *expected as u32);
|
|
}
|
|
assert!(mem.is_empty());
|
|
|
|
// (list string)
|
|
mem = actual.ptr_len(memory, 8);
|
|
for expected in input.13.iter() {
|
|
let actual = mem.ptr_len(memory, 1);
|
|
assert_eq!(actual, expected.as_bytes());
|
|
}
|
|
assert!(mem.is_empty());
|
|
assert!(actual.is_empty());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn some_traps() -> Result<()> {
|
|
let middle_of_memory = (i32::MAX / 2) & (!0xff);
|
|
let component = format!(
|
|
r#"(component
|
|
(core module $m
|
|
(memory (export "memory") 1)
|
|
(func (export "take-many") (param i32))
|
|
(func (export "take-list") (param i32 i32))
|
|
|
|
(func (export "realloc") (param i32 i32 i32 i32) (result i32)
|
|
unreachable)
|
|
)
|
|
(core instance $i (instantiate $m))
|
|
|
|
(func (export "take-list-unreachable") (param (list u8))
|
|
(canon lift (core func $i "take-list") (memory $i "memory") (realloc (func $i "realloc")))
|
|
)
|
|
(func (export "take-string-unreachable") (param string)
|
|
(canon lift (core func $i "take-list") (memory $i "memory") (realloc (func $i "realloc")))
|
|
)
|
|
|
|
(type $t (func
|
|
(param string)
|
|
(param string)
|
|
(param string)
|
|
(param string)
|
|
(param string)
|
|
(param string)
|
|
(param string)
|
|
(param string)
|
|
(param string)
|
|
(param string)
|
|
))
|
|
(func (export "take-many-unreachable") (type $t)
|
|
(canon lift (core func $i "take-many") (memory $i "memory") (realloc (func $i "realloc")))
|
|
)
|
|
|
|
(core module $m2
|
|
(memory (export "memory") 1)
|
|
(func (export "take-many") (param i32))
|
|
(func (export "take-list") (param i32 i32))
|
|
|
|
(func (export "realloc") (param i32 i32 i32 i32) (result i32)
|
|
i32.const {middle_of_memory})
|
|
)
|
|
(core instance $i2 (instantiate $m2))
|
|
|
|
(func (export "take-list-base-oob") (param (list u8))
|
|
(canon lift (core func $i2 "take-list") (memory $i2 "memory") (realloc (func $i2 "realloc")))
|
|
)
|
|
(func (export "take-string-base-oob") (param string)
|
|
(canon lift (core func $i2 "take-list") (memory $i2 "memory") (realloc (func $i2 "realloc")))
|
|
)
|
|
(func (export "take-many-base-oob") (type $t)
|
|
(canon lift (core func $i2 "take-many") (memory $i2 "memory") (realloc (func $i2 "realloc")))
|
|
)
|
|
|
|
(core module $m3
|
|
(memory (export "memory") 1)
|
|
(func (export "take-many") (param i32))
|
|
(func (export "take-list") (param i32 i32))
|
|
|
|
(func (export "realloc") (param i32 i32 i32 i32) (result i32)
|
|
i32.const 65532)
|
|
)
|
|
(core instance $i3 (instantiate $m3))
|
|
|
|
(func (export "take-list-end-oob") (param (list u8))
|
|
(canon lift (core func $i3 "take-list") (memory $i3 "memory") (realloc (func $i3 "realloc")))
|
|
)
|
|
(func (export "take-string-end-oob") (param string)
|
|
(canon lift (core func $i3 "take-list") (memory $i3 "memory") (realloc (func $i3 "realloc")))
|
|
)
|
|
(func (export "take-many-end-oob") (type $t)
|
|
(canon lift (core func $i3 "take-many") (memory $i3 "memory") (realloc (func $i3 "realloc")))
|
|
)
|
|
|
|
(core module $m4
|
|
(memory (export "memory") 1)
|
|
(func (export "take-many") (param i32))
|
|
|
|
(global $cnt (mut i32) (i32.const 0))
|
|
(func (export "realloc") (param i32 i32 i32 i32) (result i32)
|
|
global.get $cnt
|
|
if (result i32)
|
|
i32.const 100000
|
|
else
|
|
i32.const 1
|
|
global.set $cnt
|
|
i32.const 0
|
|
end
|
|
)
|
|
)
|
|
(core instance $i4 (instantiate $m4))
|
|
|
|
(func (export "take-many-second-oob") (type $t)
|
|
(canon lift (core func $i4 "take-many") (memory $i4 "memory") (realloc (func $i4 "realloc")))
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = |store: &mut Store<()>| Linker::new(&engine).instantiate(store, &component);
|
|
|
|
// This should fail when calling the allocator function for the argument
|
|
let err = instance(&mut store)?
|
|
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-unreachable")?
|
|
.call(&mut store, (&[],))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
assert_eq!(err.trap_code(), Some(TrapCode::UnreachableCodeReached));
|
|
|
|
// This should fail when calling the allocator function for the argument
|
|
let err = instance(&mut store)?
|
|
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-unreachable")?
|
|
.call(&mut store, ("",))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
assert_eq!(err.trap_code(), Some(TrapCode::UnreachableCodeReached));
|
|
|
|
// This should fail when calling the allocator function for the space
|
|
// to store the arguments (before arguments are even lowered)
|
|
let err = instance(&mut store)?
|
|
.get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>(
|
|
&mut store,
|
|
"take-many-unreachable",
|
|
)?
|
|
.call(&mut store, ("", "", "", "", "", "", "", "", "", ""))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
assert_eq!(err.trap_code(), Some(TrapCode::UnreachableCodeReached));
|
|
|
|
// Assert that when the base pointer returned by malloc is out of bounds
|
|
// that errors are reported as such. Both empty and lists with contents
|
|
// should all be invalid here.
|
|
//
|
|
// FIXME(WebAssembly/component-model#32) confirm the semantics here are
|
|
// what's desired.
|
|
#[track_caller]
|
|
fn assert_oob(err: &anyhow::Error) {
|
|
assert!(
|
|
err.to_string()
|
|
.contains("realloc return: beyond end of memory"),
|
|
"{:?}",
|
|
err,
|
|
);
|
|
}
|
|
let err = instance(&mut store)?
|
|
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-base-oob")?
|
|
.call(&mut store, (&[],))
|
|
.unwrap_err();
|
|
assert_oob(&err);
|
|
let err = instance(&mut store)?
|
|
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-base-oob")?
|
|
.call(&mut store, (&[1],))
|
|
.unwrap_err();
|
|
assert_oob(&err);
|
|
let err = instance(&mut store)?
|
|
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-base-oob")?
|
|
.call(&mut store, ("",))
|
|
.unwrap_err();
|
|
assert_oob(&err);
|
|
let err = instance(&mut store)?
|
|
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-base-oob")?
|
|
.call(&mut store, ("x",))
|
|
.unwrap_err();
|
|
assert_oob(&err);
|
|
let err = instance(&mut store)?
|
|
.get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>(
|
|
&mut store,
|
|
"take-many-base-oob",
|
|
)?
|
|
.call(&mut store, ("", "", "", "", "", "", "", "", "", ""))
|
|
.unwrap_err();
|
|
assert_oob(&err);
|
|
|
|
// Test here that when the returned pointer from malloc is one byte from the
|
|
// end of memory that empty things are fine, but larger things are not.
|
|
|
|
instance(&mut store)?
|
|
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")?
|
|
.call_and_post_return(&mut store, (&[],))?;
|
|
instance(&mut store)?
|
|
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")?
|
|
.call_and_post_return(&mut store, (&[1, 2, 3, 4],))?;
|
|
let err = instance(&mut store)?
|
|
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")?
|
|
.call(&mut store, (&[1, 2, 3, 4, 5],))
|
|
.unwrap_err();
|
|
assert_oob(&err);
|
|
instance(&mut store)?
|
|
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")?
|
|
.call_and_post_return(&mut store, ("",))?;
|
|
instance(&mut store)?
|
|
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")?
|
|
.call_and_post_return(&mut store, ("abcd",))?;
|
|
let err = instance(&mut store)?
|
|
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")?
|
|
.call(&mut store, ("abcde",))
|
|
.unwrap_err();
|
|
assert_oob(&err);
|
|
let err = instance(&mut store)?
|
|
.get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>(
|
|
&mut store,
|
|
"take-many-end-oob",
|
|
)?
|
|
.call(&mut store, ("", "", "", "", "", "", "", "", "", ""))
|
|
.unwrap_err();
|
|
assert_oob(&err);
|
|
|
|
// For this function the first allocation, the space to store all the
|
|
// arguments, is in-bounds but then all further allocations, such as for
|
|
// each individual string, are all out of bounds.
|
|
let err = instance(&mut store)?
|
|
.get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>(
|
|
&mut store,
|
|
"take-many-second-oob",
|
|
)?
|
|
.call(&mut store, ("", "", "", "", "", "", "", "", "", ""))
|
|
.unwrap_err();
|
|
assert_oob(&err);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn char_bool_memory() -> Result<()> {
|
|
let component = format!(
|
|
r#"(component
|
|
(core module $m
|
|
(memory (export "memory") 1)
|
|
(func (export "ret-tuple") (param i32 i32) (result i32)
|
|
(local $base i32)
|
|
|
|
;; Allocate space for the return
|
|
(local.set $base
|
|
(call $realloc
|
|
(i32.const 0)
|
|
(i32.const 0)
|
|
(i32.const 4)
|
|
(i32.const 8)))
|
|
|
|
;; store the boolean
|
|
(i32.store offset=0
|
|
(local.get $base)
|
|
(local.get 0))
|
|
|
|
;; store the char
|
|
(i32.store offset=4
|
|
(local.get $base)
|
|
(local.get 1))
|
|
|
|
(local.get $base)
|
|
)
|
|
|
|
{REALLOC_AND_FREE}
|
|
)
|
|
(core instance $i (instantiate $m))
|
|
|
|
(func (export "ret-tuple") (param u32) (param u32) (result (tuple bool char))
|
|
(canon lift (core func $i "ret-tuple")
|
|
(memory $i "memory")
|
|
(realloc (func $i "realloc")))
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
|
let func = instance.get_typed_func::<(u32, u32), (bool, char), _>(&mut store, "ret-tuple")?;
|
|
|
|
let ret = func.call(&mut store, (0, 'a' as u32))?;
|
|
assert_eq!(ret, (false, 'a'));
|
|
func.post_return(&mut store)?;
|
|
|
|
let ret = func.call(&mut store, (1, '🍰' as u32))?;
|
|
assert_eq!(ret, (true, '🍰'));
|
|
func.post_return(&mut store)?;
|
|
|
|
let ret = func.call(&mut store, (2, 'a' as u32))?;
|
|
assert_eq!(ret, (true, 'a'));
|
|
func.post_return(&mut store)?;
|
|
|
|
assert!(func.call(&mut store, (0, 0xd800)).is_err());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn string_list_oob() -> Result<()> {
|
|
let component = format!(
|
|
r#"(component
|
|
(core module $m
|
|
(memory (export "memory") 1)
|
|
(func (export "ret-list") (result i32)
|
|
(local $base i32)
|
|
|
|
;; Allocate space for the return
|
|
(local.set $base
|
|
(call $realloc
|
|
(i32.const 0)
|
|
(i32.const 0)
|
|
(i32.const 4)
|
|
(i32.const 8)))
|
|
|
|
(i32.store offset=0
|
|
(local.get $base)
|
|
(i32.const 100000))
|
|
(i32.store offset=4
|
|
(local.get $base)
|
|
(i32.const 1))
|
|
|
|
(local.get $base)
|
|
)
|
|
|
|
{REALLOC_AND_FREE}
|
|
)
|
|
(core instance $i (instantiate $m))
|
|
|
|
(func (export "ret-list-u8") (result (list u8))
|
|
(canon lift (core func $i "ret-list")
|
|
(memory $i "memory")
|
|
(realloc (func $i "realloc"))
|
|
)
|
|
)
|
|
(func (export "ret-string") (result string)
|
|
(canon lift (core func $i "ret-list")
|
|
(memory $i "memory")
|
|
(realloc (func $i "realloc"))
|
|
)
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let ret_list_u8 = Linker::new(&engine)
|
|
.instantiate(&mut store, &component)?
|
|
.get_typed_func::<(), WasmList<u8>, _>(&mut store, "ret-list-u8")?;
|
|
let ret_string = Linker::new(&engine)
|
|
.instantiate(&mut store, &component)?
|
|
.get_typed_func::<(), WasmStr, _>(&mut store, "ret-string")?;
|
|
|
|
let err = ret_list_u8.call(&mut store, ()).err().unwrap();
|
|
assert!(err.to_string().contains("out of bounds"), "{}", err);
|
|
|
|
let err = ret_string.call(&mut store, ()).err().unwrap();
|
|
assert!(err.to_string().contains("out of bounds"), "{}", err);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn tuples() -> Result<()> {
|
|
let component = format!(
|
|
r#"(component
|
|
(core module $m
|
|
(memory (export "memory") 1)
|
|
(func (export "foo")
|
|
(param i32 f64 i32)
|
|
(result i32)
|
|
|
|
local.get 0
|
|
i32.const 0
|
|
i32.ne
|
|
if unreachable end
|
|
|
|
local.get 1
|
|
f64.const 1
|
|
f64.ne
|
|
if unreachable end
|
|
|
|
local.get 2
|
|
i32.const 2
|
|
i32.ne
|
|
if unreachable end
|
|
|
|
i32.const 3
|
|
)
|
|
)
|
|
(core instance $i (instantiate $m))
|
|
|
|
(func (export "foo")
|
|
(param (tuple s32 float64))
|
|
(param (tuple s8))
|
|
(result (tuple u16))
|
|
(canon lift (core func $i "foo"))
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
|
let foo = instance.get_typed_func::<((i32, f64), (i8,)), (u16,), _>(&mut store, "foo")?;
|
|
assert_eq!(foo.call(&mut store, ((0, 1.0), (2,)))?, (3,));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn option() -> Result<()> {
|
|
let component = format!(
|
|
r#"(component
|
|
(core module $m
|
|
(memory (export "memory") 1)
|
|
(func (export "pass0") (param i32) (result i32)
|
|
local.get 0
|
|
)
|
|
(func (export "pass1") (param i32 i32) (result i32)
|
|
(local $base i32)
|
|
(local.set $base
|
|
(call $realloc
|
|
(i32.const 0)
|
|
(i32.const 0)
|
|
(i32.const 4)
|
|
(i32.const 8)))
|
|
|
|
(i32.store offset=0
|
|
(local.get $base)
|
|
(local.get 0))
|
|
(i32.store offset=4
|
|
(local.get $base)
|
|
(local.get 1))
|
|
|
|
(local.get $base)
|
|
)
|
|
(func (export "pass2") (param i32 i32 i32) (result i32)
|
|
(local $base i32)
|
|
(local.set $base
|
|
(call $realloc
|
|
(i32.const 0)
|
|
(i32.const 0)
|
|
(i32.const 4)
|
|
(i32.const 12)))
|
|
|
|
(i32.store offset=0
|
|
(local.get $base)
|
|
(local.get 0))
|
|
(i32.store offset=4
|
|
(local.get $base)
|
|
(local.get 1))
|
|
(i32.store offset=8
|
|
(local.get $base)
|
|
(local.get 2))
|
|
|
|
(local.get $base)
|
|
)
|
|
|
|
{REALLOC_AND_FREE}
|
|
)
|
|
(core instance $i (instantiate $m))
|
|
|
|
(func (export "option-unit-to-u32") (param (option unit)) (result u32)
|
|
(canon lift (core func $i "pass0"))
|
|
)
|
|
(func (export "option-u8-to-tuple") (param (option u8)) (result (tuple u32 u32))
|
|
(canon lift (core func $i "pass1") (memory $i "memory"))
|
|
)
|
|
(func (export "option-u32-to-tuple") (param (option u32)) (result (tuple u32 u32))
|
|
(canon lift (core func $i "pass1") (memory $i "memory"))
|
|
)
|
|
(func (export "option-string-to-tuple") (param (option string)) (result (tuple u32 string))
|
|
(canon lift
|
|
(core func $i "pass2")
|
|
(memory $i "memory")
|
|
(realloc (func $i "realloc"))
|
|
)
|
|
)
|
|
(func (export "to-option-unit") (param u32) (result (option unit))
|
|
(canon lift (core func $i "pass0"))
|
|
)
|
|
(func (export "to-option-u8") (param u32) (param u32) (result (option u8))
|
|
(canon lift (core func $i "pass1") (memory $i "memory"))
|
|
)
|
|
(func (export "to-option-u32") (param u32) (param u32) (result (option u32))
|
|
(canon lift
|
|
(core func $i "pass1")
|
|
(memory $i "memory")
|
|
)
|
|
)
|
|
(func (export "to-option-string") (param u32) (param string) (result (option string))
|
|
(canon lift
|
|
(core func $i "pass2")
|
|
(memory $i "memory")
|
|
(realloc (func $i "realloc"))
|
|
)
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let linker = Linker::new(&engine);
|
|
let instance = linker.instantiate(&mut store, &component)?;
|
|
let option_unit_to_u32 =
|
|
instance.get_typed_func::<(Option<()>,), u32, _>(&mut store, "option-unit-to-u32")?;
|
|
assert_eq!(option_unit_to_u32.call(&mut store, (None,))?, 0);
|
|
option_unit_to_u32.post_return(&mut store)?;
|
|
assert_eq!(option_unit_to_u32.call(&mut store, (Some(()),))?, 1);
|
|
option_unit_to_u32.post_return(&mut store)?;
|
|
|
|
let option_u8_to_tuple = instance
|
|
.get_typed_func::<(Option<u8>,), (u32, u32), _>(&mut store, "option-u8-to-tuple")?;
|
|
assert_eq!(option_u8_to_tuple.call(&mut store, (None,))?, (0, 0));
|
|
option_u8_to_tuple.post_return(&mut store)?;
|
|
assert_eq!(option_u8_to_tuple.call(&mut store, (Some(0),))?, (1, 0));
|
|
option_u8_to_tuple.post_return(&mut store)?;
|
|
assert_eq!(option_u8_to_tuple.call(&mut store, (Some(100),))?, (1, 100));
|
|
option_u8_to_tuple.post_return(&mut store)?;
|
|
|
|
let option_u32_to_tuple = instance
|
|
.get_typed_func::<(Option<u32>,), (u32, u32), _>(&mut store, "option-u32-to-tuple")?;
|
|
assert_eq!(option_u32_to_tuple.call(&mut store, (None,))?, (0, 0));
|
|
option_u32_to_tuple.post_return(&mut store)?;
|
|
assert_eq!(option_u32_to_tuple.call(&mut store, (Some(0),))?, (1, 0));
|
|
option_u32_to_tuple.post_return(&mut store)?;
|
|
assert_eq!(
|
|
option_u32_to_tuple.call(&mut store, (Some(100),))?,
|
|
(1, 100)
|
|
);
|
|
option_u32_to_tuple.post_return(&mut store)?;
|
|
|
|
let option_string_to_tuple = instance.get_typed_func::<(Option<&str>,), (u32, WasmStr), _>(
|
|
&mut store,
|
|
"option-string-to-tuple",
|
|
)?;
|
|
let (a, b) = option_string_to_tuple.call(&mut store, (None,))?;
|
|
assert_eq!(a, 0);
|
|
assert_eq!(b.to_str(&store)?, "");
|
|
option_string_to_tuple.post_return(&mut store)?;
|
|
let (a, b) = option_string_to_tuple.call(&mut store, (Some(""),))?;
|
|
assert_eq!(a, 1);
|
|
assert_eq!(b.to_str(&store)?, "");
|
|
option_string_to_tuple.post_return(&mut store)?;
|
|
let (a, b) = option_string_to_tuple.call(&mut store, (Some("hello"),))?;
|
|
assert_eq!(a, 1);
|
|
assert_eq!(b.to_str(&store)?, "hello");
|
|
option_string_to_tuple.post_return(&mut store)?;
|
|
|
|
let instance = linker.instantiate(&mut store, &component)?;
|
|
let to_option_unit =
|
|
instance.get_typed_func::<(u32,), Option<()>, _>(&mut store, "to-option-unit")?;
|
|
assert_eq!(to_option_unit.call(&mut store, (0,))?, None);
|
|
to_option_unit.post_return(&mut store)?;
|
|
assert_eq!(to_option_unit.call(&mut store, (1,))?, Some(()));
|
|
to_option_unit.post_return(&mut store)?;
|
|
let err = to_option_unit.call(&mut store, (2,)).unwrap_err();
|
|
assert!(err.to_string().contains("invalid option"), "{}", err);
|
|
|
|
let instance = linker.instantiate(&mut store, &component)?;
|
|
let to_option_u8 =
|
|
instance.get_typed_func::<(u32, u32), Option<u8>, _>(&mut store, "to-option-u8")?;
|
|
assert_eq!(to_option_u8.call(&mut store, (0x00_00, 0))?, None);
|
|
to_option_u8.post_return(&mut store)?;
|
|
assert_eq!(to_option_u8.call(&mut store, (0x00_01, 0))?, Some(0));
|
|
to_option_u8.post_return(&mut store)?;
|
|
assert_eq!(to_option_u8.call(&mut store, (0xfd_01, 0))?, Some(0xfd));
|
|
to_option_u8.post_return(&mut store)?;
|
|
assert!(to_option_u8.call(&mut store, (0x00_02, 0)).is_err());
|
|
|
|
let instance = linker.instantiate(&mut store, &component)?;
|
|
let to_option_u32 =
|
|
instance.get_typed_func::<(u32, u32), Option<u32>, _>(&mut store, "to-option-u32")?;
|
|
assert_eq!(to_option_u32.call(&mut store, (0, 0))?, None);
|
|
to_option_u32.post_return(&mut store)?;
|
|
assert_eq!(to_option_u32.call(&mut store, (1, 0))?, Some(0));
|
|
to_option_u32.post_return(&mut store)?;
|
|
assert_eq!(
|
|
to_option_u32.call(&mut store, (1, 0x1234fead))?,
|
|
Some(0x1234fead)
|
|
);
|
|
to_option_u32.post_return(&mut store)?;
|
|
assert!(to_option_u32.call(&mut store, (2, 0)).is_err());
|
|
|
|
let instance = linker.instantiate(&mut store, &component)?;
|
|
let to_option_string = instance
|
|
.get_typed_func::<(u32, &str), Option<WasmStr>, _>(&mut store, "to-option-string")?;
|
|
let ret = to_option_string.call(&mut store, (0, ""))?;
|
|
assert!(ret.is_none());
|
|
to_option_string.post_return(&mut store)?;
|
|
let ret = to_option_string.call(&mut store, (1, ""))?;
|
|
assert_eq!(ret.unwrap().to_str(&store)?, "");
|
|
to_option_string.post_return(&mut store)?;
|
|
let ret = to_option_string.call(&mut store, (1, "cheesecake"))?;
|
|
assert_eq!(ret.unwrap().to_str(&store)?, "cheesecake");
|
|
to_option_string.post_return(&mut store)?;
|
|
assert!(to_option_string.call(&mut store, (2, "")).is_err());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn expected() -> Result<()> {
|
|
let component = format!(
|
|
r#"(component
|
|
(core module $m
|
|
(memory (export "memory") 1)
|
|
(func (export "pass0") (param i32) (result i32)
|
|
local.get 0
|
|
)
|
|
(func (export "pass1") (param i32 i32) (result i32)
|
|
(local $base i32)
|
|
(local.set $base
|
|
(call $realloc
|
|
(i32.const 0)
|
|
(i32.const 0)
|
|
(i32.const 4)
|
|
(i32.const 8)))
|
|
|
|
(i32.store offset=0
|
|
(local.get $base)
|
|
(local.get 0))
|
|
(i32.store offset=4
|
|
(local.get $base)
|
|
(local.get 1))
|
|
|
|
(local.get $base)
|
|
)
|
|
(func (export "pass2") (param i32 i32 i32) (result i32)
|
|
(local $base i32)
|
|
(local.set $base
|
|
(call $realloc
|
|
(i32.const 0)
|
|
(i32.const 0)
|
|
(i32.const 4)
|
|
(i32.const 12)))
|
|
|
|
(i32.store offset=0
|
|
(local.get $base)
|
|
(local.get 0))
|
|
(i32.store offset=4
|
|
(local.get $base)
|
|
(local.get 1))
|
|
(i32.store offset=8
|
|
(local.get $base)
|
|
(local.get 2))
|
|
|
|
(local.get $base)
|
|
)
|
|
|
|
{REALLOC_AND_FREE}
|
|
)
|
|
(core instance $i (instantiate $m))
|
|
|
|
(func (export "take-expected-unit") (param (expected unit unit)) (result u32)
|
|
(canon lift (core func $i "pass0"))
|
|
)
|
|
(func (export "take-expected-u8-f32") (param (expected u8 float32)) (result (tuple u32 u32))
|
|
(canon lift (core func $i "pass1") (memory $i "memory"))
|
|
)
|
|
(type $list (list u8))
|
|
(func (export "take-expected-string") (param (expected string $list)) (result (tuple u32 string))
|
|
(canon lift
|
|
(core func $i "pass2")
|
|
(memory $i "memory")
|
|
(realloc (func $i "realloc"))
|
|
)
|
|
)
|
|
(func (export "to-expected-unit") (param u32) (result (expected unit unit))
|
|
(canon lift (core func $i "pass0"))
|
|
)
|
|
(func (export "to-expected-s16-f32") (param u32) (param u32) (result (expected s16 float32))
|
|
(canon lift
|
|
(core func $i "pass1")
|
|
(memory $i "memory")
|
|
(realloc (func $i "realloc"))
|
|
)
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let linker = Linker::new(&engine);
|
|
let instance = linker.instantiate(&mut store, &component)?;
|
|
let take_expected_unit =
|
|
instance.get_typed_func::<(Result<(), ()>,), u32, _>(&mut store, "take-expected-unit")?;
|
|
assert_eq!(take_expected_unit.call(&mut store, (Ok(()),))?, 0);
|
|
take_expected_unit.post_return(&mut store)?;
|
|
assert_eq!(take_expected_unit.call(&mut store, (Err(()),))?, 1);
|
|
take_expected_unit.post_return(&mut store)?;
|
|
|
|
let take_expected_u8_f32 = instance
|
|
.get_typed_func::<(Result<u8, f32>,), (u32, u32), _>(&mut store, "take-expected-u8-f32")?;
|
|
assert_eq!(take_expected_u8_f32.call(&mut store, (Ok(1),))?, (0, 1));
|
|
take_expected_u8_f32.post_return(&mut store)?;
|
|
assert_eq!(
|
|
take_expected_u8_f32.call(&mut store, (Err(2.0),))?,
|
|
(1, 2.0f32.to_bits())
|
|
);
|
|
take_expected_u8_f32.post_return(&mut store)?;
|
|
|
|
let take_expected_string = instance
|
|
.get_typed_func::<(Result<&str, &[u8]>,), (u32, WasmStr), _>(
|
|
&mut store,
|
|
"take-expected-string",
|
|
)?;
|
|
let (a, b) = take_expected_string.call(&mut store, (Ok("hello"),))?;
|
|
assert_eq!(a, 0);
|
|
assert_eq!(b.to_str(&store)?, "hello");
|
|
take_expected_string.post_return(&mut store)?;
|
|
let (a, b) = take_expected_string.call(&mut store, (Err(b"goodbye"),))?;
|
|
assert_eq!(a, 1);
|
|
assert_eq!(b.to_str(&store)?, "goodbye");
|
|
take_expected_string.post_return(&mut store)?;
|
|
|
|
let instance = linker.instantiate(&mut store, &component)?;
|
|
let to_expected_unit =
|
|
instance.get_typed_func::<(u32,), Result<(), ()>, _>(&mut store, "to-expected-unit")?;
|
|
assert_eq!(to_expected_unit.call(&mut store, (0,))?, Ok(()));
|
|
to_expected_unit.post_return(&mut store)?;
|
|
assert_eq!(to_expected_unit.call(&mut store, (1,))?, Err(()));
|
|
to_expected_unit.post_return(&mut store)?;
|
|
let err = to_expected_unit.call(&mut store, (2,)).unwrap_err();
|
|
assert!(err.to_string().contains("invalid expected"), "{}", err);
|
|
|
|
let instance = linker.instantiate(&mut store, &component)?;
|
|
let to_expected_s16_f32 = instance
|
|
.get_typed_func::<(u32, u32), Result<i16, f32>, _>(&mut store, "to-expected-s16-f32")?;
|
|
assert_eq!(to_expected_s16_f32.call(&mut store, (0, 0))?, Ok(0));
|
|
to_expected_s16_f32.post_return(&mut store)?;
|
|
assert_eq!(to_expected_s16_f32.call(&mut store, (0, 100))?, Ok(100));
|
|
to_expected_s16_f32.post_return(&mut store)?;
|
|
assert_eq!(
|
|
to_expected_s16_f32.call(&mut store, (1, 1.0f32.to_bits()))?,
|
|
Err(1.0)
|
|
);
|
|
to_expected_s16_f32.post_return(&mut store)?;
|
|
let ret = to_expected_s16_f32.call(&mut store, (1, CANON_32BIT_NAN | 1))?;
|
|
assert_eq!(ret.unwrap_err().to_bits(), CANON_32BIT_NAN);
|
|
to_expected_s16_f32.post_return(&mut store)?;
|
|
assert!(to_expected_s16_f32.call(&mut store, (2, 0)).is_err());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn fancy_list() -> Result<()> {
|
|
let component = format!(
|
|
r#"(component
|
|
(core module $m
|
|
(memory (export "memory") 1)
|
|
(func (export "take") (param i32 i32) (result i32)
|
|
(local $base i32)
|
|
(local.set $base
|
|
(call $realloc
|
|
(i32.const 0)
|
|
(i32.const 0)
|
|
(i32.const 4)
|
|
(i32.const 16)))
|
|
|
|
(i32.store offset=0
|
|
(local.get $base)
|
|
(local.get 0))
|
|
(i32.store offset=4
|
|
(local.get $base)
|
|
(local.get 1))
|
|
(i32.store offset=8
|
|
(local.get $base)
|
|
(i32.const 0))
|
|
(i32.store offset=12
|
|
(local.get $base)
|
|
(i32.mul
|
|
(memory.size)
|
|
(i32.const 65536)))
|
|
|
|
(local.get $base)
|
|
)
|
|
|
|
{REALLOC_AND_FREE}
|
|
)
|
|
(core instance $i (instantiate $m))
|
|
|
|
(type $a (option u8))
|
|
(type $b (expected unit string))
|
|
(type $input (list (tuple $a $b)))
|
|
(type $output (tuple u32 u32 (list u8)))
|
|
(func (export "take") (param $input) (result $output)
|
|
(canon lift
|
|
(core func $i "take")
|
|
(memory $i "memory")
|
|
(realloc (func $i "realloc"))
|
|
)
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
|
|
|
let func = instance
|
|
.get_typed_func::<(&[(Option<u8>, Result<(), &str>)],), (u32, u32, WasmList<u8>), _>(
|
|
&mut store, "take",
|
|
)?;
|
|
|
|
let input = [
|
|
(None, Ok(())),
|
|
(Some(2), Err("hello there")),
|
|
(Some(200), Err("general kenobi")),
|
|
];
|
|
let (ptr, len, list) = func.call(&mut store, (&input,))?;
|
|
let memory = list.as_le_slice(&store);
|
|
let ptr = usize::try_from(ptr).unwrap();
|
|
let len = usize::try_from(len).unwrap();
|
|
let mut array = &memory[ptr..][..len * 16];
|
|
|
|
for (a, b) in input.iter() {
|
|
match a {
|
|
Some(val) => {
|
|
assert_eq!(*array.take_n::<2>(), [1, *val]);
|
|
}
|
|
None => {
|
|
assert_eq!(*array.take_n::<1>(), [0]);
|
|
array.skip::<1>();
|
|
}
|
|
}
|
|
array.skip::<2>();
|
|
match b {
|
|
Ok(()) => {
|
|
assert_eq!(*array.take_n::<1>(), [0]);
|
|
array.skip::<11>();
|
|
}
|
|
Err(s) => {
|
|
assert_eq!(*array.take_n::<1>(), [1]);
|
|
array.skip::<3>();
|
|
assert_eq!(array.ptr_len(memory, 1), s.as_bytes());
|
|
}
|
|
}
|
|
}
|
|
assert!(array.is_empty());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
trait SliceExt<'a> {
|
|
fn take_n<const N: usize>(&mut self) -> &'a [u8; N];
|
|
|
|
fn skip<const N: usize>(&mut self) {
|
|
self.take_n::<N>();
|
|
}
|
|
|
|
fn ptr_len<'b>(&mut self, all_memory: &'b [u8], size: usize) -> &'b [u8] {
|
|
let ptr = u32::from_le_bytes(*self.take_n::<4>());
|
|
let len = u32::from_le_bytes(*self.take_n::<4>());
|
|
let ptr = usize::try_from(ptr).unwrap();
|
|
let len = usize::try_from(len).unwrap();
|
|
&all_memory[ptr..][..len * size]
|
|
}
|
|
}
|
|
|
|
impl<'a> SliceExt<'a> for &'a [u8] {
|
|
fn take_n<const N: usize>(&mut self) -> &'a [u8; N] {
|
|
let (a, b) = self.split_at(N);
|
|
*self = b;
|
|
a.try_into().unwrap()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_alignment() -> Result<()> {
|
|
let component = format!(
|
|
r#"(component
|
|
(core module $m
|
|
(memory (export "memory") 1)
|
|
(func (export "realloc") (param i32 i32 i32 i32) (result i32)
|
|
i32.const 1)
|
|
|
|
(func (export "take-i32") (param i32))
|
|
(func (export "ret-1") (result i32) i32.const 1)
|
|
(func (export "ret-unaligned-list") (result i32)
|
|
(i32.store offset=0 (i32.const 8) (i32.const 1))
|
|
(i32.store offset=4 (i32.const 8) (i32.const 1))
|
|
i32.const 8)
|
|
)
|
|
(core instance $i (instantiate $m))
|
|
|
|
(func (export "many-params")
|
|
(param string) (param string) (param string) (param string)
|
|
(param string) (param string) (param string) (param string)
|
|
(param string) (param string) (param string) (param string)
|
|
(canon lift
|
|
(core func $i "take-i32")
|
|
(memory $i "memory")
|
|
(realloc (func $i "realloc"))
|
|
)
|
|
)
|
|
(func (export "string-ret") (result string)
|
|
(canon lift
|
|
(core func $i "ret-1")
|
|
(memory $i "memory")
|
|
(realloc (func $i "realloc"))
|
|
)
|
|
)
|
|
(func (export "list-u32-ret") (result (list u32))
|
|
(canon lift
|
|
(core func $i "ret-unaligned-list")
|
|
(memory $i "memory")
|
|
(realloc (func $i "realloc"))
|
|
)
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = |store: &mut Store<()>| Linker::new(&engine).instantiate(store, &component);
|
|
|
|
let err = instance(&mut store)?
|
|
.get_typed_func::<(
|
|
&str,
|
|
&str,
|
|
&str,
|
|
&str,
|
|
&str,
|
|
&str,
|
|
&str,
|
|
&str,
|
|
&str,
|
|
&str,
|
|
&str,
|
|
&str,
|
|
), (), _>(&mut store, "many-params")?
|
|
.call(&mut store, ("", "", "", "", "", "", "", "", "", "", "", ""))
|
|
.unwrap_err();
|
|
assert!(
|
|
err.to_string()
|
|
.contains("realloc return: result not aligned"),
|
|
"{}",
|
|
err
|
|
);
|
|
|
|
let err = instance(&mut store)?
|
|
.get_typed_func::<(), WasmStr, _>(&mut store, "string-ret")?
|
|
.call(&mut store, ())
|
|
.err()
|
|
.unwrap();
|
|
assert!(
|
|
err.to_string().contains("return pointer not aligned"),
|
|
"{}",
|
|
err
|
|
);
|
|
|
|
let err = instance(&mut store)?
|
|
.get_typed_func::<(), WasmList<u32>, _>(&mut store, "list-u32-ret")?
|
|
.call(&mut store, ())
|
|
.err()
|
|
.unwrap();
|
|
assert!(
|
|
err.to_string().contains("list pointer is not aligned"),
|
|
"{}",
|
|
err
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn drop_component_still_works() -> Result<()> {
|
|
let component = r#"
|
|
(component
|
|
(import "f" (func $f))
|
|
|
|
(core func $f_lower
|
|
(canon lower (func $f))
|
|
)
|
|
(core module $m
|
|
(import "" "" (func $f))
|
|
|
|
(func $f2
|
|
call $f
|
|
call $f
|
|
)
|
|
|
|
(export "f" (func $f2))
|
|
)
|
|
(core instance $i (instantiate $m
|
|
(with "" (instance
|
|
(export "" (func $f_lower))
|
|
))
|
|
))
|
|
(func (export "f")
|
|
(canon lift
|
|
(core func $i "f")
|
|
)
|
|
)
|
|
)
|
|
"#;
|
|
|
|
let (mut store, instance) = {
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, 0);
|
|
let mut linker = Linker::new(&engine);
|
|
linker
|
|
.root()
|
|
.func_wrap("f", |mut store: StoreContextMut<'_, u32>| -> Result<()> {
|
|
*store.data_mut() += 1;
|
|
Ok(())
|
|
})?;
|
|
let instance = linker.instantiate(&mut store, &component)?;
|
|
(store, instance)
|
|
};
|
|
|
|
let f = instance.get_typed_func::<(), (), _>(&mut store, "f")?;
|
|
assert_eq!(*store.data(), 0);
|
|
f.call(&mut store, ())?;
|
|
assert_eq!(*store.data(), 2);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn raw_slice_of_various_types() -> Result<()> {
|
|
let component = r#"
|
|
(component
|
|
(core module $m
|
|
(memory (export "memory") 1)
|
|
|
|
(func (export "list8") (result i32)
|
|
(call $setup_list (i32.const 16))
|
|
)
|
|
(func (export "list16") (result i32)
|
|
(call $setup_list (i32.const 8))
|
|
)
|
|
(func (export "list32") (result i32)
|
|
(call $setup_list (i32.const 4))
|
|
)
|
|
(func (export "list64") (result i32)
|
|
(call $setup_list (i32.const 2))
|
|
)
|
|
|
|
(func $setup_list (param i32) (result i32)
|
|
(i32.store offset=0 (i32.const 100) (i32.const 8))
|
|
(i32.store offset=4 (i32.const 100) (local.get 0))
|
|
i32.const 100
|
|
)
|
|
|
|
(data (i32.const 8) "\00\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f")
|
|
)
|
|
(core instance $i (instantiate $m))
|
|
(func (export "list-u8") (result (list u8))
|
|
(canon lift (core func $i "list8") (memory $i "memory"))
|
|
)
|
|
(func (export "list-i8") (result (list s8))
|
|
(canon lift (core func $i "list8") (memory $i "memory"))
|
|
)
|
|
(func (export "list-u16") (result (list u16))
|
|
(canon lift (core func $i "list16") (memory $i "memory"))
|
|
)
|
|
(func (export "list-i16") (result (list s16))
|
|
(canon lift (core func $i "list16") (memory $i "memory"))
|
|
)
|
|
(func (export "list-u32") (result (list u32))
|
|
(canon lift (core func $i "list32") (memory $i "memory"))
|
|
)
|
|
(func (export "list-i32") (result (list s32))
|
|
(canon lift (core func $i "list32") (memory $i "memory"))
|
|
)
|
|
(func (export "list-u64") (result (list u64))
|
|
(canon lift (core func $i "list64") (memory $i "memory"))
|
|
)
|
|
(func (export "list-i64") (result (list s64))
|
|
(canon lift (core func $i "list64") (memory $i "memory"))
|
|
)
|
|
)
|
|
"#;
|
|
|
|
let (mut store, instance) = {
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
|
(store, instance)
|
|
};
|
|
|
|
let list = instance
|
|
.get_typed_func::<(), WasmList<u8>, _>(&mut store, "list-u8")?
|
|
.call_and_post_return(&mut store, ())?;
|
|
assert_eq!(
|
|
list.as_le_slice(&store),
|
|
[
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
|
|
0x0e, 0x0f,
|
|
]
|
|
);
|
|
let list = instance
|
|
.get_typed_func::<(), WasmList<i8>, _>(&mut store, "list-i8")?
|
|
.call_and_post_return(&mut store, ())?;
|
|
assert_eq!(
|
|
list.as_le_slice(&store),
|
|
[
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
|
|
0x0e, 0x0f,
|
|
]
|
|
);
|
|
|
|
let list = instance
|
|
.get_typed_func::<(), WasmList<u16>, _>(&mut store, "list-u16")?
|
|
.call_and_post_return(&mut store, ())?;
|
|
assert_eq!(
|
|
list.as_le_slice(&store),
|
|
[
|
|
u16::to_le(0x01_00),
|
|
u16::to_le(0x03_02),
|
|
u16::to_le(0x05_04),
|
|
u16::to_le(0x07_06),
|
|
u16::to_le(0x09_08),
|
|
u16::to_le(0x0b_0a),
|
|
u16::to_le(0x0d_0c),
|
|
u16::to_le(0x0f_0e),
|
|
]
|
|
);
|
|
let list = instance
|
|
.get_typed_func::<(), WasmList<i16>, _>(&mut store, "list-i16")?
|
|
.call_and_post_return(&mut store, ())?;
|
|
assert_eq!(
|
|
list.as_le_slice(&store),
|
|
[
|
|
i16::to_le(0x01_00),
|
|
i16::to_le(0x03_02),
|
|
i16::to_le(0x05_04),
|
|
i16::to_le(0x07_06),
|
|
i16::to_le(0x09_08),
|
|
i16::to_le(0x0b_0a),
|
|
i16::to_le(0x0d_0c),
|
|
i16::to_le(0x0f_0e),
|
|
]
|
|
);
|
|
let list = instance
|
|
.get_typed_func::<(), WasmList<u32>, _>(&mut store, "list-u32")?
|
|
.call_and_post_return(&mut store, ())?;
|
|
assert_eq!(
|
|
list.as_le_slice(&store),
|
|
[
|
|
u32::to_le(0x03_02_01_00),
|
|
u32::to_le(0x07_06_05_04),
|
|
u32::to_le(0x0b_0a_09_08),
|
|
u32::to_le(0x0f_0e_0d_0c),
|
|
]
|
|
);
|
|
let list = instance
|
|
.get_typed_func::<(), WasmList<i32>, _>(&mut store, "list-i32")?
|
|
.call_and_post_return(&mut store, ())?;
|
|
assert_eq!(
|
|
list.as_le_slice(&store),
|
|
[
|
|
i32::to_le(0x03_02_01_00),
|
|
i32::to_le(0x07_06_05_04),
|
|
i32::to_le(0x0b_0a_09_08),
|
|
i32::to_le(0x0f_0e_0d_0c),
|
|
]
|
|
);
|
|
let list = instance
|
|
.get_typed_func::<(), WasmList<u64>, _>(&mut store, "list-u64")?
|
|
.call_and_post_return(&mut store, ())?;
|
|
assert_eq!(
|
|
list.as_le_slice(&store),
|
|
[
|
|
u64::to_le(0x07_06_05_04_03_02_01_00),
|
|
u64::to_le(0x0f_0e_0d_0c_0b_0a_09_08),
|
|
]
|
|
);
|
|
let list = instance
|
|
.get_typed_func::<(), WasmList<i64>, _>(&mut store, "list-i64")?
|
|
.call_and_post_return(&mut store, ())?;
|
|
assert_eq!(
|
|
list.as_le_slice(&store),
|
|
[
|
|
i64::to_le(0x07_06_05_04_03_02_01_00),
|
|
i64::to_le(0x0f_0e_0d_0c_0b_0a_09_08),
|
|
]
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn lower_then_lift() -> Result<()> {
|
|
// First test simple integers when the import/export ABI happen to line up
|
|
let component = r#"
|
|
(component $c
|
|
(import "f" (func $f (result u32)))
|
|
|
|
(core func $f_lower
|
|
(canon lower (func $f))
|
|
)
|
|
(func $f2 (result s32)
|
|
(canon lift (core func $f_lower))
|
|
)
|
|
(export "f" (func $f2))
|
|
)
|
|
"#;
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let mut linker = Linker::new(&engine);
|
|
linker.root().func_wrap("f", || Ok(2u32))?;
|
|
let instance = linker.instantiate(&mut store, &component)?;
|
|
|
|
let f = instance.get_typed_func::<(), i32, _>(&mut store, "f")?;
|
|
assert_eq!(f.call(&mut store, ())?, 2);
|
|
|
|
// First test strings when the import/export ABI happen to line up
|
|
let component = format!(
|
|
r#"
|
|
(component $c
|
|
(import "s" (func $f (param string)))
|
|
|
|
(core module $libc
|
|
(memory (export "memory") 1)
|
|
{REALLOC_AND_FREE}
|
|
)
|
|
(core instance $libc (instantiate $libc))
|
|
|
|
(core func $f_lower
|
|
(canon lower (func $f) (memory $libc "memory"))
|
|
)
|
|
(func $f2 (param string)
|
|
(canon lift (core func $f_lower)
|
|
(memory $libc "memory")
|
|
(realloc (func $libc "realloc"))
|
|
)
|
|
)
|
|
(export "f" (func $f2))
|
|
)
|
|
"#
|
|
);
|
|
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
linker
|
|
.root()
|
|
.func_wrap("s", |store: StoreContextMut<'_, ()>, x: WasmStr| {
|
|
assert_eq!(x.to_str(&store)?, "hello");
|
|
Ok(())
|
|
})?;
|
|
let instance = linker.instantiate(&mut store, &component)?;
|
|
|
|
let f = instance.get_typed_func::<(&str,), (), _>(&mut store, "f")?;
|
|
f.call(&mut store, ("hello",))?;
|
|
|
|
// Next test "type punning" where return values are reinterpreted just
|
|
// because the return ABI happens to line up.
|
|
let component = format!(
|
|
r#"
|
|
(component $c
|
|
(import "s2" (func $f (param string) (result u32)))
|
|
|
|
(core module $libc
|
|
(memory (export "memory") 1)
|
|
{REALLOC_AND_FREE}
|
|
)
|
|
(core instance $libc (instantiate $libc))
|
|
|
|
(core func $f_lower
|
|
(canon lower (func $f) (memory $libc "memory"))
|
|
)
|
|
(func $f2 (param string) (result string)
|
|
(canon lift (core func $f_lower)
|
|
(memory $libc "memory")
|
|
(realloc (func $libc "realloc"))
|
|
)
|
|
)
|
|
(export "f" (func $f2))
|
|
)
|
|
"#
|
|
);
|
|
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
linker
|
|
.root()
|
|
.func_wrap("s2", |store: StoreContextMut<'_, ()>, x: WasmStr| {
|
|
assert_eq!(x.to_str(&store)?, "hello");
|
|
Ok(u32::MAX)
|
|
})?;
|
|
let instance = linker.instantiate(&mut store, &component)?;
|
|
|
|
let f = instance.get_typed_func::<(&str,), WasmStr, _>(&mut store, "f")?;
|
|
let err = f.call(&mut store, ("hello",)).err().unwrap();
|
|
assert!(
|
|
err.to_string().contains("return pointer not aligned"),
|
|
"{}",
|
|
err
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn errors_that_poison_instance() -> Result<()> {
|
|
let component = format!(
|
|
r#"
|
|
(component $c
|
|
(core module $m1
|
|
(func (export "f1") unreachable)
|
|
(func (export "f2"))
|
|
)
|
|
(core instance $m1 (instantiate $m1))
|
|
(func (export "f1") (canon lift (core func $m1 "f1")))
|
|
(func (export "f2") (canon lift (core func $m1 "f2")))
|
|
|
|
(core module $m2
|
|
(func (export "f") (param i32 i32))
|
|
(func (export "r") (param i32 i32 i32 i32) (result i32) unreachable)
|
|
(memory (export "m") 1)
|
|
)
|
|
(core instance $m2 (instantiate $m2))
|
|
(func (export "f3") (param string)
|
|
(canon lift (core func $m2 "f") (realloc (func $m2 "r")) (memory $m2 "m"))
|
|
)
|
|
|
|
(core module $m3
|
|
(func (export "f") (result i32) i32.const 1)
|
|
(memory (export "m") 1)
|
|
)
|
|
(core instance $m3 (instantiate $m3))
|
|
(func (export "f4") (result string)
|
|
(canon lift (core func $m3 "f") (memory $m3 "m"))
|
|
)
|
|
)
|
|
"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let linker = Linker::new(&engine);
|
|
let instance = linker.instantiate(&mut store, &component)?;
|
|
let f1 = instance.get_typed_func::<(), (), _>(&mut store, "f1")?;
|
|
let f2 = instance.get_typed_func::<(), (), _>(&mut store, "f2")?;
|
|
assert_unreachable(f1.call(&mut store, ()));
|
|
assert_poisoned(f1.call(&mut store, ()));
|
|
assert_poisoned(f2.call(&mut store, ()));
|
|
|
|
let instance = linker.instantiate(&mut store, &component)?;
|
|
let f3 = instance.get_typed_func::<(&str,), (), _>(&mut store, "f3")?;
|
|
assert_unreachable(f3.call(&mut store, ("x",)));
|
|
assert_poisoned(f3.call(&mut store, ("x",)));
|
|
|
|
let instance = linker.instantiate(&mut store, &component)?;
|
|
let f4 = instance.get_typed_func::<(), WasmStr, _>(&mut store, "f4")?;
|
|
assert!(f4.call(&mut store, ()).is_err());
|
|
assert_poisoned(f4.call(&mut store, ()));
|
|
|
|
return Ok(());
|
|
|
|
#[track_caller]
|
|
fn assert_unreachable<T>(err: Result<T>) {
|
|
let err = match err {
|
|
Ok(_) => panic!("expected an error"),
|
|
Err(e) => e,
|
|
};
|
|
assert_eq!(
|
|
err.downcast::<Trap>().unwrap().trap_code(),
|
|
Some(TrapCode::UnreachableCodeReached)
|
|
);
|
|
}
|
|
|
|
#[track_caller]
|
|
fn assert_poisoned<T>(err: Result<T>) {
|
|
let err = match err {
|
|
Ok(_) => panic!("expected an error"),
|
|
Err(e) => e,
|
|
};
|
|
assert!(
|
|
err.to_string()
|
|
.contains("cannot reenter component instance"),
|
|
"{}",
|
|
err,
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn run_export_with_internal_adapter() -> Result<()> {
|
|
let component = r#"
|
|
(component
|
|
(type $t (func (param u32) (result u32)))
|
|
(component $a
|
|
(core module $m
|
|
(func (export "add-five") (param i32) (result i32)
|
|
local.get 0
|
|
i32.const 5
|
|
i32.add)
|
|
)
|
|
(core instance $m (instantiate $m))
|
|
(func (export "add-five") (type $t) (canon lift (core func $m "add-five")))
|
|
)
|
|
(component $b
|
|
(import "interface-0.1.0" (instance $i
|
|
(export "add-five" (func (type $t)))))
|
|
(core module $m
|
|
(func $add-five (import "interface-0.1.0" "add-five") (param i32) (result i32))
|
|
(func) ;; causes index out of bounds
|
|
(func (export "run") (result i32) i32.const 0 call $add-five)
|
|
)
|
|
(core func $add-five (canon lower (func $i "add-five")))
|
|
(core instance $i (instantiate 0
|
|
(with "interface-0.1.0" (instance
|
|
(export "add-five" (func $add-five))
|
|
))
|
|
))
|
|
(func (result u32) (canon lift (core func $i "run")))
|
|
(export "run" (func 1))
|
|
)
|
|
(instance $a (instantiate $a))
|
|
(instance $b (instantiate $b (with "interface-0.1.0" (instance $a))))
|
|
(export "run" (func $b "run"))
|
|
)
|
|
"#;
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let linker = Linker::new(&engine);
|
|
let instance = linker.instantiate(&mut store, &component)?;
|
|
let run = instance.get_typed_func::<(), u32, _>(&mut store, "run")?;
|
|
assert_eq!(run.call(&mut store, ())?, 5);
|
|
Ok(())
|
|
}
|