* components: Implement the ability to call component exports This commit is an implementation of the typed method of calling component exports. This is intended to represent the most efficient way of calling a component in Wasmtime, similar to what `TypedFunc` represents today for core wasm. Internally this contains all the traits and implementations necessary to invoke component exports with any type signature (e.g. arbitrary parameters and/or results). The expectation is that for results we'll reuse all of this infrastructure except in reverse (arguments and results will be swapped when defining imports). Some features of this implementation are: * Arbitrary type hierarchies are supported * The Rust-standard `Option`, `Result`, `String`, `Vec<T>`, and tuple types all map down to the corresponding type in the component model. * Basic utf-16 string support is implemented as proof-of-concept to show what handling might look like. This will need further testing and benchmarking. * Arguments can be behind "smart pointers", so for example `&Rc<Arc<[u8]>>` corresponds to `list<u8>` in interface types. * Bulk copies from linear memory never happen unless explicitly instructed to do so. The goal of this commit is to create the ability to actually invoke wasm components. This represents what is expected to be the performance threshold for these calls where it ideally should be optimal how WebAssembly is invoked. One major missing piece of this is a `#[derive]` of some sort to generate Rust types for arbitrary `*.wit` types such as custom records, variants, flags, unions, etc. The current trait impls for tuples and `Result<T, E>` are expected to have fleshed out most of what such a derive would look like. There are some downsides and missing pieces to this commit and method of calling components, however, such as: * Passing `&[u8]` to WebAssembly is currently not optimal. Ideally this compiles down to a `memcpy`-equivalent somewhere but that currently doesn't happen due to all the bounds checks of copying data into memory. I have been unsuccessful so far at getting these bounds checks to be removed. * There is no finalization at this time (the "post return" functionality in the canonical ABI). Implementing this should be relatively straightforward but at this time requires `wasmparser` changes to catch up with the current canonical ABI. * There is no guarantee that results of a wasm function will be validated. As results are consumed they are validated but this means that if function returns an invalid string which the host doesn't look at then no trap will be generated. This is probably not the intended semantics of hosts in the component model. * At this time there's no support for memory64 memories, just a bunch of `FIXME`s to get around to. It's expected that this won't be too onerous, however. Some extra care will need to ensure that the various methods related to size/alignment all optimize to the same thing they do today (e.g. constants). * The return value of a typed component function is either `T` or `Value<T>`, and it depends on the ABI details of `T` and whether it takes up more than one return value slot or not. This is an ABI-implementation detail which is being forced through to the API layer which is pretty unfortunate. For example if you say the return value of a function is `(u8, u32)` then it's a runtime type-checking error. I don't know of a great way to solve this at this time. Overall I'm feeling optimistic about this trajectory of implementing value lifting/lowering in Wasmtime. While there are a number of downsides none seem completely insurmountable. There's naturally still a good deal of work with the component model but this should be a significant step up towards implementing and testing the component model. * Review comments * Write tests for calling functions This commit adds a new test file for actually executing functions and testing their results. This is not written as a `*.wast` test yet since it's not 100% clear if that's the best way to do that for now (given that dynamic signatures aren't supported yet). The tests themselves could all largely be translated to `*.wast` testing in the future, though, if supported. Along the way a number of minor issues were fixed with lowerings with the bugs exposed here. * Fix an endian mistake * Fix a typo and the `memory.fill` instruction
1935 lines
68 KiB
Rust
1935 lines
68 KiB
Rust
use anyhow::Result;
|
|
use std::rc::Rc;
|
|
use std::sync::Arc;
|
|
use wasmtime::component::*;
|
|
use wasmtime::{Store, Trap, TrapCode};
|
|
|
|
const CANON_32BIT_NAN: u32 = 0b01111111110000000000000000000000;
|
|
const CANON_64BIT_NAN: u64 = 0b0111111111111000000000000000000000000000000000000000000000000000;
|
|
|
|
// A simple bump allocator which can be used with modules below
|
|
const REALLOC_AND_FREE: &str = r#"
|
|
(global $last (mut i32) (i32.const 8))
|
|
(func $realloc (export "canonical_abi_realloc")
|
|
(param $old_ptr i32)
|
|
(param $old_size i32)
|
|
(param $align i32)
|
|
(param $new_size i32)
|
|
(result i32)
|
|
|
|
;; Test if the old pointer is non-null
|
|
local.get $old_ptr
|
|
if
|
|
;; If the old size is bigger than the new size then
|
|
;; this is a shrink and transparently allow it
|
|
local.get $old_size
|
|
local.get $new_size
|
|
i32.gt_u
|
|
if
|
|
local.get $old_ptr
|
|
return
|
|
end
|
|
|
|
;; ... otherwise this is unimplemented
|
|
unreachable
|
|
end
|
|
|
|
;; align up `$last`
|
|
(global.set $last
|
|
(i32.and
|
|
(i32.add
|
|
(global.get $last)
|
|
(i32.add
|
|
(local.get $align)
|
|
(i32.const -1)))
|
|
(i32.xor
|
|
(i32.add
|
|
(local.get $align)
|
|
(i32.const -1))
|
|
(i32.const -1))))
|
|
|
|
;; save the current value of `$last` as the return value
|
|
global.get $last
|
|
|
|
;; ensure anything necessary is set to valid data by spraying a bit
|
|
;; pattern that is invalid
|
|
global.get $last
|
|
i32.const 0xde
|
|
local.get $new_size
|
|
memory.fill
|
|
|
|
;; bump our pointer
|
|
(global.set $last
|
|
(i32.add
|
|
(global.get $last)
|
|
(local.get $new_size)))
|
|
)
|
|
|
|
(func (export "canonical_abi_free") (param i32 i32 i32))
|
|
"#;
|
|
|
|
#[test]
|
|
fn thunks() -> Result<()> {
|
|
let component = r#"
|
|
(component
|
|
(module $m
|
|
(func (export "thunk"))
|
|
(func (export "thunk-trap") unreachable)
|
|
)
|
|
(instance $i (instantiate (module $m)))
|
|
(func (export "thunk")
|
|
(canon.lift (func) (func $i "thunk"))
|
|
)
|
|
(func (export "thunk-trap")
|
|
(canon.lift (func) (func $i "thunk-trap"))
|
|
)
|
|
)
|
|
"#;
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Instance::new(&mut store, &component)?;
|
|
instance
|
|
.get_typed_func::<(), (), _>(&mut store, "thunk")?
|
|
.call(&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
|
|
(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 "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32)
|
|
unreachable)
|
|
(func (export "canonical_abi_free") (param i32 i32 i32)
|
|
unreachable)
|
|
)
|
|
(instance $i (instantiate (module $m)))
|
|
(func (export "thunk")
|
|
(canon.lift (func) (func $i "thunk"))
|
|
)
|
|
(func (export "take-string")
|
|
(canon.lift (func (param string)) (into $i) (func $i "take-string"))
|
|
)
|
|
(func (export "take-two-args")
|
|
(canon.lift (func (param s32) (param (list u8))) (into $i) (func $i "two-args"))
|
|
)
|
|
(func (export "ret-tuple")
|
|
(canon.lift (func (result (tuple u8 s8))) (into $i) (func $i "ret-one"))
|
|
)
|
|
(func (export "ret-tuple1")
|
|
(canon.lift (func (result (tuple u32))) (into $i) (func $i "ret-one"))
|
|
)
|
|
)
|
|
"#;
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Instance::new(&mut store, &component)?;
|
|
let thunk = instance.get_func(&mut store, "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();
|
|
assert!(thunk.typed::<(), u32, _>(&store).is_err());
|
|
assert!(thunk.typed::<(u32,), (), _>(&store).is_err());
|
|
assert!(thunk.typed::<(), (), _>(&store).is_ok());
|
|
assert!(take_string.typed::<(), (), _>(&store).is_err());
|
|
assert!(take_string.typed::<(), Value<String>, _>(&store).is_err());
|
|
assert!(take_string
|
|
.typed::<(String, String), Value<String>, _>(&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!(take_two_args
|
|
.typed::<(i32, &[u8]), Value<()>, _>(&store)
|
|
.is_err());
|
|
assert!(ret_tuple.typed::<(), (), _>(&store).is_err());
|
|
assert!(ret_tuple.typed::<(), (u8,), _>(&store).is_err());
|
|
assert!(ret_tuple.typed::<(), (u8, i8), _>(&store).is_err());
|
|
assert!(ret_tuple.typed::<(), Value<(u8, i8)>, _>(&store).is_ok());
|
|
assert!(ret_tuple1.typed::<(), (u32,), _>(&store).is_ok());
|
|
assert!(ret_tuple1.typed::<(), u32, _>(&store).is_err());
|
|
assert!(ret_tuple1.typed::<(), Value<u32>, _>(&store).is_err());
|
|
assert!(ret_tuple1.typed::<(), Value<(u32,)>, _>(&store).is_err());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn integers() -> Result<()> {
|
|
let component = r#"
|
|
(component
|
|
(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)
|
|
)
|
|
(instance $i (instantiate (module $m)))
|
|
(func (export "take-u8") (canon.lift (func (param u8)) (func $i "take-i32-100")))
|
|
(func (export "take-s8") (canon.lift (func (param s8)) (func $i "take-i32-100")))
|
|
(func (export "take-u16") (canon.lift (func (param u16)) (func $i "take-i32-100")))
|
|
(func (export "take-s16") (canon.lift (func (param s16)) (func $i "take-i32-100")))
|
|
(func (export "take-u32") (canon.lift (func (param u32)) (func $i "take-i32-100")))
|
|
(func (export "take-s32") (canon.lift (func (param s32)) (func $i "take-i32-100")))
|
|
(func (export "take-u64") (canon.lift (func (param u64)) (func $i "take-i64-100")))
|
|
(func (export "take-s64") (canon.lift (func (param s64)) (func $i "take-i64-100")))
|
|
|
|
(func (export "ret-u8") (canon.lift (func (result u8)) (func $i "ret-i32-0")))
|
|
(func (export "ret-s8") (canon.lift (func (result s8)) (func $i "ret-i32-0")))
|
|
(func (export "ret-u16") (canon.lift (func (result u16)) (func $i "ret-i32-0")))
|
|
(func (export "ret-s16") (canon.lift (func (result s16)) (func $i "ret-i32-0")))
|
|
(func (export "ret-u32") (canon.lift (func (result u32)) (func $i "ret-i32-0")))
|
|
(func (export "ret-s32") (canon.lift (func (result s32)) (func $i "ret-i32-0")))
|
|
(func (export "ret-u64") (canon.lift (func (result u64)) (func $i "ret-i64-0")))
|
|
(func (export "ret-s64") (canon.lift (func (result s64)) (func $i "ret-i64-0")))
|
|
|
|
(func (export "retm1-u8") (canon.lift (func (result u8)) (func $i "ret-i32-minus-1")))
|
|
(func (export "retm1-s8") (canon.lift (func (result s8)) (func $i "ret-i32-minus-1")))
|
|
(func (export "retm1-u16") (canon.lift (func (result u16)) (func $i "ret-i32-minus-1")))
|
|
(func (export "retm1-s16") (canon.lift (func (result s16)) (func $i "ret-i32-minus-1")))
|
|
(func (export "retm1-u32") (canon.lift (func (result u32)) (func $i "ret-i32-minus-1")))
|
|
(func (export "retm1-s32") (canon.lift (func (result s32)) (func $i "ret-i32-minus-1")))
|
|
(func (export "retm1-u64") (canon.lift (func (result u64)) (func $i "ret-i64-minus-1")))
|
|
(func (export "retm1-s64") (canon.lift (func (result s64)) (func $i "ret-i64-minus-1")))
|
|
|
|
(func (export "retbig-u8") (canon.lift (func (result u8)) (func $i "ret-i32-100000")))
|
|
(func (export "retbig-s8") (canon.lift (func (result s8)) (func $i "ret-i32-100000")))
|
|
(func (export "retbig-u16") (canon.lift (func (result u16)) (func $i "ret-i32-100000")))
|
|
(func (export "retbig-s16") (canon.lift (func (result s16)) (func $i "ret-i32-100000")))
|
|
(func (export "retbig-u32") (canon.lift (func (result u32)) (func $i "ret-i32-100000")))
|
|
(func (export "retbig-s32") (canon.lift (func (result s32)) (func $i "ret-i32-100000")))
|
|
)
|
|
"#;
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Instance::new(&mut store, &component)?;
|
|
|
|
// Passing in 100 is valid for all primitives
|
|
instance
|
|
.get_typed_func::<(u8,), (), _>(&mut store, "take-u8")?
|
|
.call(&mut store, (100,))?;
|
|
instance
|
|
.get_typed_func::<(i8,), (), _>(&mut store, "take-s8")?
|
|
.call(&mut store, (100,))?;
|
|
instance
|
|
.get_typed_func::<(u16,), (), _>(&mut store, "take-u16")?
|
|
.call(&mut store, (100,))?;
|
|
instance
|
|
.get_typed_func::<(i16,), (), _>(&mut store, "take-s16")?
|
|
.call(&mut store, (100,))?;
|
|
instance
|
|
.get_typed_func::<(u32,), (), _>(&mut store, "take-u32")?
|
|
.call(&mut store, (100,))?;
|
|
instance
|
|
.get_typed_func::<(i32,), (), _>(&mut store, "take-s32")?
|
|
.call(&mut store, (100,))?;
|
|
instance
|
|
.get_typed_func::<(u64,), (), _>(&mut store, "take-u64")?
|
|
.call(&mut store, (100,))?;
|
|
instance
|
|
.get_typed_func::<(i64,), (), _>(&mut store, "take-s64")?
|
|
.call(&mut store, (100,))?;
|
|
|
|
// This specific wasm instance traps if any value other than 100 is passed
|
|
instance
|
|
.get_typed_func::<(u8,), (), _>(&mut store, "take-u8")?
|
|
.call(&mut store, (101,))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
instance
|
|
.get_typed_func::<(i8,), (), _>(&mut store, "take-s8")?
|
|
.call(&mut store, (101,))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
instance
|
|
.get_typed_func::<(u16,), (), _>(&mut store, "take-u16")?
|
|
.call(&mut store, (101,))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
instance
|
|
.get_typed_func::<(i16,), (), _>(&mut store, "take-s16")?
|
|
.call(&mut store, (101,))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
instance
|
|
.get_typed_func::<(u32,), (), _>(&mut store, "take-u32")?
|
|
.call(&mut store, (101,))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
instance
|
|
.get_typed_func::<(i32,), (), _>(&mut store, "take-s32")?
|
|
.call(&mut store, (101,))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
instance
|
|
.get_typed_func::<(u64,), (), _>(&mut store, "take-u64")?
|
|
.call(&mut store, (101,))
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
instance
|
|
.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(&mut store, ())?,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i8, _>(&mut store, "ret-s8")?
|
|
.call(&mut store, ())?,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), u16, _>(&mut store, "ret-u16")?
|
|
.call(&mut store, ())?,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i16, _>(&mut store, "ret-s16")?
|
|
.call(&mut store, ())?,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), u32, _>(&mut store, "ret-u32")?
|
|
.call(&mut store, ())?,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i32, _>(&mut store, "ret-s32")?
|
|
.call(&mut store, ())?,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), u64, _>(&mut store, "ret-u64")?
|
|
.call(&mut store, ())?,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i64, _>(&mut store, "ret-s64")?
|
|
.call(&mut store, ())?,
|
|
0
|
|
);
|
|
|
|
// Returning -1 should fail for u8 and u16, but succeed for all other types.
|
|
let err = instance
|
|
.get_typed_func::<(), u8, _>(&mut store, "retm1-u8")?
|
|
.call(&mut store, ())
|
|
.unwrap_err();
|
|
assert!(
|
|
err.to_string().contains("out of range integral type"),
|
|
"{}",
|
|
err
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i8, _>(&mut store, "retm1-s8")?
|
|
.call(&mut store, ())?,
|
|
-1
|
|
);
|
|
let err = instance
|
|
.get_typed_func::<(), u16, _>(&mut store, "retm1-u16")?
|
|
.call(&mut store, ())
|
|
.unwrap_err();
|
|
assert!(
|
|
err.to_string().contains("out of range integral type"),
|
|
"{}",
|
|
err
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i16, _>(&mut store, "retm1-s16")?
|
|
.call(&mut store, ())?,
|
|
-1
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), u32, _>(&mut store, "retm1-u32")?
|
|
.call(&mut store, ())?,
|
|
0xffffffff
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i32, _>(&mut store, "retm1-s32")?
|
|
.call(&mut store, ())?,
|
|
-1
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), u64, _>(&mut store, "retm1-u64")?
|
|
.call(&mut store, ())?,
|
|
0xffffffff_ffffffff
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i64, _>(&mut store, "retm1-s64")?
|
|
.call(&mut store, ())?,
|
|
-1
|
|
);
|
|
|
|
// Returning 100000 should fail for small primitives but succeed for 32-bit.
|
|
let err = instance
|
|
.get_typed_func::<(), u8, _>(&mut store, "retbig-u8")?
|
|
.call(&mut store, ())
|
|
.unwrap_err();
|
|
assert!(
|
|
err.to_string().contains("out of range integral type"),
|
|
"{}",
|
|
err
|
|
);
|
|
let err = instance
|
|
.get_typed_func::<(), i8, _>(&mut store, "retbig-s8")?
|
|
.call(&mut store, ())
|
|
.unwrap_err();
|
|
assert!(
|
|
err.to_string().contains("out of range integral type"),
|
|
"{}",
|
|
err
|
|
);
|
|
let err = instance
|
|
.get_typed_func::<(), u16, _>(&mut store, "retbig-u16")?
|
|
.call(&mut store, ())
|
|
.unwrap_err();
|
|
assert!(
|
|
err.to_string().contains("out of range integral type"),
|
|
"{}",
|
|
err
|
|
);
|
|
let err = instance
|
|
.get_typed_func::<(), i16, _>(&mut store, "retbig-s16")?
|
|
.call(&mut store, ())
|
|
.unwrap_err();
|
|
assert!(
|
|
err.to_string().contains("out of range integral type"),
|
|
"{}",
|
|
err
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), u32, _>(&mut store, "retbig-u32")?
|
|
.call(&mut store, ())?,
|
|
100000
|
|
);
|
|
assert_eq!(
|
|
instance
|
|
.get_typed_func::<(), i32, _>(&mut store, "retbig-s32")?
|
|
.call(&mut store, ())?,
|
|
100000
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn type_layers() -> Result<()> {
|
|
let component = r#"
|
|
(component
|
|
(module $m
|
|
(func (export "take-i32-100") (param i32)
|
|
local.get 0
|
|
i32.const 2
|
|
i32.eq
|
|
br_if 0
|
|
unreachable
|
|
)
|
|
)
|
|
(instance $i (instantiate (module $m)))
|
|
(func (export "take-u32") (canon.lift (func (param u32)) (func $i "take-i32-100")))
|
|
)
|
|
"#;
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Instance::new(&mut store, &component)?;
|
|
|
|
instance
|
|
.get_typed_func::<(Box<u32>,), (), _>(&mut store, "take-u32")?
|
|
.call(&mut store, (Box::new(2),))?;
|
|
instance
|
|
.get_typed_func::<(&u32,), (), _>(&mut store, "take-u32")?
|
|
.call(&mut store, (&2,))?;
|
|
instance
|
|
.get_typed_func::<(Rc<u32>,), (), _>(&mut store, "take-u32")?
|
|
.call(&mut store, (Rc::new(2),))?;
|
|
instance
|
|
.get_typed_func::<(Arc<u32>,), (), _>(&mut store, "take-u32")?
|
|
.call(&mut store, (Arc::new(2),))?;
|
|
instance
|
|
.get_typed_func::<(&Box<Arc<Rc<u32>>>,), (), _>(&mut store, "take-u32")?
|
|
.call(&mut store, (&Box::new(Arc::new(Rc::new(2))),))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn floats() -> Result<()> {
|
|
let component = r#"
|
|
(component
|
|
(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
|
|
)
|
|
)
|
|
(instance $i (instantiate (module $m)))
|
|
|
|
(func (export "f32-to-u32")
|
|
(canon.lift (func (param float32) (result u32)) (func $i "i32.reinterpret_f32"))
|
|
)
|
|
(func (export "f64-to-u64")
|
|
(canon.lift (func (param float64) (result u64)) (func $i "i64.reinterpret_f64"))
|
|
)
|
|
(func (export "u32-to-f32")
|
|
(canon.lift (func (param u32) (result float32)) (func $i "f32.reinterpret_i32"))
|
|
)
|
|
(func (export "u64-to-f64")
|
|
(canon.lift (func (param u64) (result float64)) (func $i "f64.reinterpret_i64"))
|
|
)
|
|
)
|
|
"#;
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Instance::new(&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());
|
|
assert_eq!(f64_to_u64.call(&mut store, (2.0,))?, 2.0f64.to_bits());
|
|
assert_eq!(u32_to_f32.call(&mut store, (3.0f32.to_bits(),))?, 3.0);
|
|
assert_eq!(u64_to_f64.call(&mut store, (4.0f64.to_bits(),))?, 4.0);
|
|
|
|
assert_eq!(
|
|
u32_to_f32
|
|
.call(&mut store, (CANON_32BIT_NAN | 1,))?
|
|
.to_bits(),
|
|
CANON_32BIT_NAN
|
|
);
|
|
assert_eq!(
|
|
u64_to_f64
|
|
.call(&mut store, (CANON_64BIT_NAN | 1,))?
|
|
.to_bits(),
|
|
CANON_64BIT_NAN
|
|
);
|
|
|
|
assert_eq!(
|
|
f32_to_u32.call(&mut store, (f32::from_bits(CANON_32BIT_NAN | 1),))?,
|
|
CANON_32BIT_NAN
|
|
);
|
|
assert_eq!(
|
|
f64_to_u64.call(&mut store, (f64::from_bits(CANON_64BIT_NAN | 1),))?,
|
|
CANON_64BIT_NAN
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn bools() -> Result<()> {
|
|
let component = r#"
|
|
(component
|
|
(module $m
|
|
(func (export "pass") (param i32) (result i32) local.get 0)
|
|
)
|
|
(instance $i (instantiate (module $m)))
|
|
|
|
(func (export "u32-to-bool")
|
|
(canon.lift (func (param u32) (result bool)) (func $i "pass"))
|
|
)
|
|
(func (export "bool-to-u32")
|
|
(canon.lift (func (param bool) (result u32)) (func $i "pass"))
|
|
)
|
|
)
|
|
"#;
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Instance::new(&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);
|
|
assert_eq!(bool_to_u32.call(&mut store, (true,))?, 1);
|
|
assert_eq!(u32_to_bool.call(&mut store, (0,))?, false);
|
|
assert_eq!(u32_to_bool.call(&mut store, (1,))?, true);
|
|
let err = u32_to_bool.call(&mut store, (2,)).unwrap_err();
|
|
assert!(err.to_string().contains("invalid boolean"), "{}", err);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn chars() -> Result<()> {
|
|
let component = r#"
|
|
(component
|
|
(module $m
|
|
(func (export "pass") (param i32) (result i32) local.get 0)
|
|
)
|
|
(instance $i (instantiate (module $m)))
|
|
|
|
(func (export "u32-to-char")
|
|
(canon.lift (func (param u32) (result char)) (func $i "pass"))
|
|
)
|
|
(func (export "char-to-u32")
|
|
(canon.lift (func (param char) (result u32)) (func $i "pass"))
|
|
)
|
|
)
|
|
"#;
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Instance::new(&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);
|
|
assert_eq!(u32_to_char.call(&mut store, (x as u32,))?, x);
|
|
Ok(())
|
|
};
|
|
|
|
roundtrip('x')?;
|
|
roundtrip('a')?;
|
|
roundtrip('\0')?;
|
|
roundtrip('\n')?;
|
|
roundtrip('💝')?;
|
|
|
|
let err = u32_to_char.call(&mut store, (0xd800,)).unwrap_err();
|
|
assert!(err.to_string().contains("integer out of range"), "{}", err);
|
|
let err = u32_to_char.call(&mut store, (0xdfff,)).unwrap_err();
|
|
assert!(err.to_string().contains("integer out of range"), "{}", err);
|
|
let err = u32_to_char.call(&mut store, (0x110000,)).unwrap_err();
|
|
assert!(err.to_string().contains("integer out of range"), "{}", err);
|
|
let err = u32_to_char.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
|
|
(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 -1
|
|
)
|
|
|
|
(func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32)
|
|
unreachable)
|
|
(func (export "canonical_abi_free") (param i32 i32 i32)
|
|
unreachable)
|
|
)
|
|
(instance $i (instantiate (module $m)))
|
|
|
|
(type $result (tuple s8 u16 float32 float64))
|
|
(func (export "tuple")
|
|
(canon.lift
|
|
(func (param s8) (param u16) (param float32) (param float64) (result $result))
|
|
(into $i)
|
|
(func $i "foo")
|
|
)
|
|
)
|
|
(func (export "invalid")
|
|
(canon.lift (func (result $result)) (into $i) (func $i "invalid"))
|
|
)
|
|
)
|
|
"#;
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Instance::new(&mut store, &component)?;
|
|
let result = instance
|
|
.get_typed_func::<(i8, u16, f32, f64), Value<(i8, u16, f32, f64)>, _>(&mut store, "tuple")?
|
|
.call(&mut store, (-1, 100, 3.0, 100.0))?;
|
|
let cursor = result.cursor(&store);
|
|
assert_eq!(cursor.a1().get(), -1);
|
|
assert_eq!(cursor.a2().get(), 100);
|
|
assert_eq!(cursor.a3().get(), 3.0);
|
|
assert_eq!(cursor.a4().get(), 100.0);
|
|
|
|
let err = instance
|
|
.get_typed_func::<(i8, u16, f32, f64), (i8, u16, f32, f64), _>(&mut store, "tuple")
|
|
.err()
|
|
.unwrap();
|
|
let err = format!("{:?}", err);
|
|
assert!(err.contains("is returned indirectly"), "{}", err);
|
|
|
|
let invalid_func =
|
|
instance.get_typed_func::<(), Value<(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
|
|
(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}
|
|
)
|
|
(instance $i (instantiate (module $m)))
|
|
|
|
(func (export "list8-to-str")
|
|
(canon.lift
|
|
(func (param (list u8)) (result string))
|
|
(into $i)
|
|
(func $i "roundtrip")
|
|
)
|
|
)
|
|
(func (export "str-to-list8")
|
|
(canon.lift
|
|
(func (param string) (result (list u8)))
|
|
(into $i)
|
|
(func $i "roundtrip")
|
|
)
|
|
)
|
|
(func (export "list16-to-str")
|
|
(canon.lift
|
|
(func (param (list u16)) (result string))
|
|
string=utf16
|
|
(into $i)
|
|
(func $i "roundtrip")
|
|
)
|
|
)
|
|
(func (export "str-to-list16")
|
|
(canon.lift
|
|
(func (param string) (result (list u16)))
|
|
string=utf16
|
|
(into $i)
|
|
(func $i "roundtrip")
|
|
)
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Instance::new(&mut store, &component)?;
|
|
let list8_to_str =
|
|
instance.get_typed_func::<(&[u8],), Value<String>, _>(&mut store, "list8-to-str")?;
|
|
let str_to_list8 =
|
|
instance.get_typed_func::<(&str,), Value<Vec<u8>>, _>(&mut store, "str-to-list8")?;
|
|
let list16_to_str =
|
|
instance.get_typed_func::<(&[u16],), Value<String>, _>(&mut store, "list16-to-str")?;
|
|
let str_to_list16 =
|
|
instance.get_typed_func::<(&str,), Value<Vec<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.cursor(&store).to_str()?, x);
|
|
|
|
let utf16 = x.encode_utf16().collect::<Vec<_>>();
|
|
let ret = list16_to_str.call(&mut store, (&utf16[..],))?;
|
|
assert_eq!(ret.cursor(&store).to_str()?, x);
|
|
|
|
let ret = str_to_list8.call(&mut store, (x,))?;
|
|
assert_eq!(
|
|
ret.cursor(&store)
|
|
.iter()?
|
|
.map(|s| s.get())
|
|
.collect::<Vec<_>>(),
|
|
x.as_bytes()
|
|
);
|
|
|
|
let ret = str_to_list16.call(&mut store, (x,))?;
|
|
assert_eq!(
|
|
ret.cursor(&store)
|
|
.iter()?
|
|
.map(|s| s.get())
|
|
.collect::<Vec<_>>(),
|
|
utf16,
|
|
);
|
|
|
|
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.cursor(&store).to_str().unwrap_err();
|
|
assert!(err.to_string().contains("invalid utf-8"), "{}", err);
|
|
|
|
let ret = list8_to_str.call(&mut store, (b"hello there \xff invalid",))?;
|
|
let err = ret.cursor(&store).to_str().unwrap_err();
|
|
assert!(err.to_string().contains("invalid utf-8"), "{}", err);
|
|
|
|
let ret = list16_to_str.call(&mut store, (&[0xd800],))?;
|
|
let err = ret.cursor(&store).to_str().unwrap_err();
|
|
assert!(err.to_string().contains("unpaired surrogate"), "{}", err);
|
|
|
|
let ret = list16_to_str.call(&mut store, (&[0xdfff],))?;
|
|
let err = ret.cursor(&store).to_str().unwrap_err();
|
|
assert!(err.to_string().contains("unpaired surrogate"), "{}", err);
|
|
|
|
let ret = list16_to_str.call(&mut store, (&[0xd800, 0xff00],))?;
|
|
let err = ret.cursor(&store).to_str().unwrap_err();
|
|
assert!(err.to_string().contains("unpaired surrogate"), "{}", err);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn many_parameters() -> Result<()> {
|
|
let component = format!(
|
|
r#"(component
|
|
(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}
|
|
)
|
|
(instance $i (instantiate (module $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")
|
|
(canon.lift (type $t) (into $i) (func $i "foo"))
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Instance::new(&mut store, &component)?;
|
|
let func = instance.get_typed_func::<(
|
|
i8,
|
|
u64,
|
|
f32,
|
|
u8,
|
|
(),
|
|
i16,
|
|
&str,
|
|
&[u32],
|
|
bool,
|
|
bool,
|
|
char,
|
|
&[bool],
|
|
&[char],
|
|
&[&str],
|
|
), Value<(Vec<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 result = func.call(&mut store, input)?;
|
|
let cursor = result.cursor(&store);
|
|
let memory = cursor.a1().as_slice()?;
|
|
let pointer = usize::try_from(cursor.a2().get()).unwrap();
|
|
|
|
let mut actual = &memory[pointer..][..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;
|
|
let component = format!(
|
|
r#"(component
|
|
(module $m
|
|
(memory (export "memory") 1)
|
|
(func (export "take-many") (param i32))
|
|
(func (export "take-list") (param i32 i32))
|
|
|
|
(func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32)
|
|
unreachable)
|
|
(func (export "canonical_abi_free") (param i32 i32 i32)
|
|
unreachable)
|
|
)
|
|
(instance $i (instantiate (module $m)))
|
|
|
|
(func (export "take-list-unreachable")
|
|
(canon.lift (func (param (list u8))) (into $i) (func $i "take-list"))
|
|
)
|
|
(func (export "take-string-unreachable")
|
|
(canon.lift (func (param string)) (into $i) (func $i "take-list"))
|
|
)
|
|
|
|
(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")
|
|
(canon.lift (type $t) (into $i) (func $i "take-many"))
|
|
)
|
|
|
|
(module $m2
|
|
(memory (export "memory") 1)
|
|
(func (export "take-many") (param i32))
|
|
(func (export "take-list") (param i32 i32))
|
|
|
|
(func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32)
|
|
i32.const {middle_of_memory})
|
|
(func (export "canonical_abi_free") (param i32 i32 i32)
|
|
unreachable)
|
|
)
|
|
(instance $i2 (instantiate (module $m2)))
|
|
|
|
(func (export "take-list-base-oob")
|
|
(canon.lift (func (param (list u8))) (into $i2) (func $i2 "take-list"))
|
|
)
|
|
(func (export "take-string-base-oob")
|
|
(canon.lift (func (param string)) (into $i2) (func $i2 "take-list"))
|
|
)
|
|
(func (export "take-many-base-oob")
|
|
(canon.lift (type $t) (into $i2) (func $i2 "take-many"))
|
|
)
|
|
|
|
(module $m3
|
|
(memory (export "memory") 1)
|
|
(func (export "take-many") (param i32))
|
|
(func (export "take-list") (param i32 i32))
|
|
|
|
(func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32)
|
|
i32.const 65535)
|
|
(func (export "canonical_abi_free") (param i32 i32 i32)
|
|
unreachable)
|
|
)
|
|
(instance $i3 (instantiate (module $m3)))
|
|
|
|
(func (export "take-list-end-oob")
|
|
(canon.lift (func (param (list u8))) (into $i3) (func $i3 "take-list"))
|
|
)
|
|
(func (export "take-string-end-oob")
|
|
(canon.lift (func (param string)) (into $i3) (func $i3 "take-list"))
|
|
)
|
|
(func (export "take-many-end-oob")
|
|
(canon.lift (type $t) (into $i3) (func $i3 "take-many"))
|
|
)
|
|
|
|
(module $m4
|
|
(memory (export "memory") 1)
|
|
(func (export "take-many") (param i32))
|
|
|
|
(global $cnt (mut i32) (i32.const 0))
|
|
(func (export "canonical_abi_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
|
|
)
|
|
(func (export "canonical_abi_free") (param i32 i32 i32)
|
|
unreachable)
|
|
)
|
|
(instance $i4 (instantiate (module $m4)))
|
|
|
|
(func (export "take-many-second-oob")
|
|
(canon.lift (type $t) (into $i4) (func $i4 "take-many"))
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Instance::new(&mut store, &component)?;
|
|
|
|
// This should fail when calling the allocator function for the argument
|
|
let err = instance
|
|
.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
|
|
.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
|
|
.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
|
|
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-base-oob")?
|
|
.call(&mut store, (&[],))
|
|
.unwrap_err();
|
|
assert_oob(&err);
|
|
let err = instance
|
|
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-base-oob")?
|
|
.call(&mut store, (&[1],))
|
|
.unwrap_err();
|
|
assert_oob(&err);
|
|
let err = instance
|
|
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-base-oob")?
|
|
.call(&mut store, ("",))
|
|
.unwrap_err();
|
|
assert_oob(&err);
|
|
let err = instance
|
|
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-base-oob")?
|
|
.call(&mut store, ("x",))
|
|
.unwrap_err();
|
|
assert_oob(&err);
|
|
let err = instance
|
|
.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
|
|
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")?
|
|
.call(&mut store, (&[],))?;
|
|
instance
|
|
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")?
|
|
.call(&mut store, (&[1],))?;
|
|
assert_oob(&err);
|
|
let err = instance
|
|
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")?
|
|
.call(&mut store, (&[1, 2],))
|
|
.unwrap_err();
|
|
assert_oob(&err);
|
|
instance
|
|
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")?
|
|
.call(&mut store, ("",))?;
|
|
instance
|
|
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")?
|
|
.call(&mut store, ("x",))?;
|
|
let err = instance
|
|
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")?
|
|
.call(&mut store, ("xy",))
|
|
.unwrap_err();
|
|
assert_oob(&err);
|
|
let err = instance
|
|
.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
|
|
.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
|
|
(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}
|
|
)
|
|
(instance $i (instantiate (module $m)))
|
|
|
|
(func (export "ret-tuple")
|
|
(canon.lift (func (param u32) (param u32) (result (tuple bool char))) (into $i) (func $i "ret-tuple"))
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Instance::new(&mut store, &component)?;
|
|
let func =
|
|
instance.get_typed_func::<(u32, u32), Value<(bool, char)>, _>(&mut store, "ret-tuple")?;
|
|
|
|
let ret = func.call(&mut store, (0, 'a' as u32))?;
|
|
assert_eq!(ret.cursor(&store).a1().get()?, false);
|
|
assert_eq!(ret.cursor(&store).a2().get()?, 'a');
|
|
|
|
let ret = func.call(&mut store, (1, '🍰' as u32))?;
|
|
assert_eq!(ret.cursor(&store).a1().get()?, true);
|
|
assert_eq!(ret.cursor(&store).a2().get()?, '🍰');
|
|
|
|
let ret = func.call(&mut store, (2, 'a' as u32))?;
|
|
assert!(ret.cursor(&store).a1().get().is_err());
|
|
|
|
let ret = func.call(&mut store, (0, 0xd800))?;
|
|
assert!(ret.cursor(&store).a2().get().is_err());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn string_list_oob() -> Result<()> {
|
|
let component = format!(
|
|
r#"(component
|
|
(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}
|
|
)
|
|
(instance $i (instantiate (module $m)))
|
|
|
|
(func (export "ret-list-u8")
|
|
(canon.lift (func (result (list u8))) (into $i) (func $i "ret-list"))
|
|
)
|
|
(func (export "ret-string")
|
|
(canon.lift (func (result string)) (into $i) (func $i "ret-list"))
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Instance::new(&mut store, &component)?;
|
|
let ret_list_u8 =
|
|
instance.get_typed_func::<(), Value<Vec<u8>>, _>(&mut store, "ret-list-u8")?;
|
|
let ret_string = instance.get_typed_func::<(), Value<String>, _>(&mut store, "ret-string")?;
|
|
|
|
let list = ret_list_u8.call(&mut store, ())?;
|
|
let err = list.cursor(&store).iter().err().unwrap();
|
|
assert!(err.to_string().contains("list out of bounds"), "{}", err);
|
|
|
|
let ret = ret_string.call(&mut store, ())?;
|
|
let err = ret.cursor(&store).to_str().unwrap_err();
|
|
assert!(err.to_string().contains("string out of bounds"), "{}", err);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn tuples() -> Result<()> {
|
|
let component = format!(
|
|
r#"(component
|
|
(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
|
|
)
|
|
|
|
(func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32)
|
|
unreachable)
|
|
(func (export "canonical_abi_free") (param i32 i32 i32)
|
|
unreachable)
|
|
)
|
|
(instance $i (instantiate (module $m)))
|
|
|
|
(func (export "foo")
|
|
(canon.lift
|
|
(func
|
|
(param (tuple s32 float64))
|
|
(param (tuple s8))
|
|
(result (tuple u16))
|
|
)
|
|
(func $i "foo")
|
|
)
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Instance::new(&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
|
|
(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}
|
|
)
|
|
(instance $i (instantiate (module $m)))
|
|
|
|
(func (export "option-unit-to-u32")
|
|
(canon.lift
|
|
(func (param (option unit)) (result u32))
|
|
(func $i "pass0")
|
|
)
|
|
)
|
|
(func (export "option-u8-to-tuple")
|
|
(canon.lift
|
|
(func (param (option u8)) (result (tuple u32 u32)))
|
|
(into $i)
|
|
(func $i "pass1")
|
|
)
|
|
)
|
|
(func (export "option-u32-to-tuple")
|
|
(canon.lift
|
|
(func (param (option u32)) (result (tuple u32 u32)))
|
|
(into $i)
|
|
(func $i "pass1")
|
|
)
|
|
)
|
|
(func (export "option-string-to-tuple")
|
|
(canon.lift
|
|
(func (param (option string)) (result (tuple u32 string)))
|
|
(into $i)
|
|
(func $i "pass2")
|
|
)
|
|
)
|
|
(func (export "to-option-unit")
|
|
(canon.lift
|
|
(func (param u32) (result (option unit)))
|
|
(func $i "pass0")
|
|
)
|
|
)
|
|
(func (export "to-option-u8")
|
|
(canon.lift
|
|
(func (param u32) (param u32) (result (option u8)))
|
|
(into $i)
|
|
(func $i "pass1")
|
|
)
|
|
)
|
|
(func (export "to-option-u32")
|
|
(canon.lift
|
|
(func (param u32) (param u32) (result (option u32)))
|
|
(into $i)
|
|
(func $i "pass1")
|
|
)
|
|
)
|
|
(func (export "to-option-string")
|
|
(canon.lift
|
|
(func (param u32) (param string) (result (option string)))
|
|
(into $i)
|
|
(func $i "pass2")
|
|
)
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Instance::new(&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);
|
|
assert_eq!(option_unit_to_u32.call(&mut store, (Some(()),))?, 1);
|
|
|
|
let option_u8_to_tuple = instance
|
|
.get_typed_func::<(Option<u8>,), Value<(u32, u32)>, _>(&mut store, "option-u8-to-tuple")?;
|
|
let ret = option_u8_to_tuple.call(&mut store, (None,))?;
|
|
assert_eq!(ret.cursor(&store).a1().get(), 0);
|
|
assert_eq!(ret.cursor(&store).a2().get(), 0);
|
|
let ret = option_u8_to_tuple.call(&mut store, (Some(0),))?;
|
|
assert_eq!(ret.cursor(&store).a1().get(), 1);
|
|
assert_eq!(ret.cursor(&store).a2().get(), 0);
|
|
let ret = option_u8_to_tuple.call(&mut store, (Some(100),))?;
|
|
assert_eq!(ret.cursor(&store).a1().get(), 1);
|
|
assert_eq!(ret.cursor(&store).a2().get(), 100);
|
|
|
|
let option_u32_to_tuple = instance.get_typed_func::<(Option<u32>,), Value<(u32, u32)>, _>(
|
|
&mut store,
|
|
"option-u32-to-tuple",
|
|
)?;
|
|
let ret = option_u32_to_tuple.call(&mut store, (None,))?;
|
|
assert_eq!(ret.cursor(&store).a1().get(), 0);
|
|
assert_eq!(ret.cursor(&store).a2().get(), 0);
|
|
let ret = option_u32_to_tuple.call(&mut store, (Some(0),))?;
|
|
assert_eq!(ret.cursor(&store).a1().get(), 1);
|
|
assert_eq!(ret.cursor(&store).a2().get(), 0);
|
|
let ret = option_u32_to_tuple.call(&mut store, (Some(100),))?;
|
|
assert_eq!(ret.cursor(&store).a1().get(), 1);
|
|
assert_eq!(ret.cursor(&store).a2().get(), 100);
|
|
|
|
let option_string_to_tuple = instance
|
|
.get_typed_func::<(Option<&str>,), Value<(u32, String)>, _>(
|
|
&mut store,
|
|
"option-string-to-tuple",
|
|
)?;
|
|
let ret = option_string_to_tuple.call(&mut store, (None,))?;
|
|
assert_eq!(ret.cursor(&store).a1().get(), 0);
|
|
assert_eq!(ret.cursor(&store).a2().to_str()?, "");
|
|
let ret = option_string_to_tuple.call(&mut store, (Some(""),))?;
|
|
assert_eq!(ret.cursor(&store).a1().get(), 1);
|
|
assert_eq!(ret.cursor(&store).a2().to_str()?, "");
|
|
let ret = option_string_to_tuple.call(&mut store, (Some("hello"),))?;
|
|
assert_eq!(ret.cursor(&store).a1().get(), 1);
|
|
assert_eq!(ret.cursor(&store).a2().to_str()?, "hello");
|
|
|
|
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);
|
|
assert_eq!(to_option_unit.call(&mut store, (1,))?, Some(()));
|
|
let err = to_option_unit.call(&mut store, (2,)).unwrap_err();
|
|
assert!(err.to_string().contains("invalid option"), "{}", err);
|
|
|
|
let to_option_u8 =
|
|
instance.get_typed_func::<(u32, u32), Value<Option<u8>>, _>(&mut store, "to-option-u8")?;
|
|
let ret = to_option_u8.call(&mut store, (0x00_00, 0))?;
|
|
assert!(ret.cursor(&store).get()?.is_none());
|
|
let ret = to_option_u8.call(&mut store, (0x00_01, 0))?;
|
|
assert_eq!(ret.cursor(&store).get()?.unwrap().get(), 0x00);
|
|
let ret = to_option_u8.call(&mut store, (0xfd_01, 0))?;
|
|
assert_eq!(ret.cursor(&store).get()?.unwrap().get(), 0xfd);
|
|
let ret = to_option_u8.call(&mut store, (0x00_02, 0))?;
|
|
assert!(ret.cursor(&store).get().is_err());
|
|
|
|
let to_option_u32 = instance
|
|
.get_typed_func::<(u32, u32), Value<Option<u32>>, _>(&mut store, "to-option-u32")?;
|
|
let ret = to_option_u32.call(&mut store, (0, 0))?;
|
|
assert!(ret.cursor(&store).get()?.is_none());
|
|
let ret = to_option_u32.call(&mut store, (1, 0))?;
|
|
assert_eq!(ret.cursor(&store).get()?.unwrap().get(), 0);
|
|
let ret = to_option_u32.call(&mut store, (1, 0x1234fead))?;
|
|
assert_eq!(ret.cursor(&store).get()?.unwrap().get(), 0x1234fead);
|
|
let ret = to_option_u32.call(&mut store, (2, 0))?;
|
|
assert!(ret.cursor(&store).get().is_err());
|
|
|
|
let to_option_string = instance
|
|
.get_typed_func::<(u32, &str), Value<Option<String>>, _>(&mut store, "to-option-string")?;
|
|
let ret = to_option_string.call(&mut store, (0, ""))?;
|
|
assert!(ret.cursor(&store).get()?.is_none());
|
|
let ret = to_option_string.call(&mut store, (1, ""))?;
|
|
assert_eq!(ret.cursor(&store).get()?.unwrap().to_str()?, "");
|
|
let ret = to_option_string.call(&mut store, (1, "cheesecake"))?;
|
|
assert_eq!(ret.cursor(&store).get()?.unwrap().to_str()?, "cheesecake");
|
|
let ret = to_option_string.call(&mut store, (2, ""))?;
|
|
assert!(ret.cursor(&store).get().is_err());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn expected() -> Result<()> {
|
|
let component = format!(
|
|
r#"(component
|
|
(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}
|
|
)
|
|
(instance $i (instantiate (module $m)))
|
|
|
|
(func (export "take-expected-unit")
|
|
(canon.lift
|
|
(func (param (expected unit unit)) (result u32))
|
|
(func $i "pass0")
|
|
)
|
|
)
|
|
(func (export "take-expected-u8-f32")
|
|
(canon.lift
|
|
(func (param (expected u8 float32)) (result (tuple u32 u32)))
|
|
(into $i)
|
|
(func $i "pass1")
|
|
)
|
|
)
|
|
(type $list (list u8))
|
|
(func (export "take-expected-string")
|
|
(canon.lift
|
|
(func (param (expected string $list)) (result (tuple u32 string)))
|
|
(into $i)
|
|
(func $i "pass2")
|
|
)
|
|
)
|
|
(func (export "to-expected-unit")
|
|
(canon.lift
|
|
(func (param u32) (result (expected unit unit)))
|
|
(func $i "pass0")
|
|
)
|
|
)
|
|
(func (export "to-expected-s16-f32")
|
|
(canon.lift
|
|
(func (param u32) (param u32) (result (expected s16 float32)))
|
|
(into $i)
|
|
(func $i "pass1")
|
|
)
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Instance::new(&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);
|
|
assert_eq!(take_expected_unit.call(&mut store, (Err(()),))?, 1);
|
|
|
|
let take_expected_u8_f32 = instance
|
|
.get_typed_func::<(Result<u8, f32>,), Value<(u32, u32)>, _>(
|
|
&mut store,
|
|
"take-expected-u8-f32",
|
|
)?;
|
|
let ret = take_expected_u8_f32.call(&mut store, (Ok(1),))?;
|
|
assert_eq!(ret.cursor(&store).a1().get(), 0);
|
|
assert_eq!(ret.cursor(&store).a2().get(), 1);
|
|
let ret = take_expected_u8_f32.call(&mut store, (Err(2.0),))?;
|
|
assert_eq!(ret.cursor(&store).a1().get(), 1);
|
|
assert_eq!(ret.cursor(&store).a2().get(), 2.0f32.to_bits());
|
|
|
|
let take_expected_string = instance
|
|
.get_typed_func::<(Result<&str, &[u8]>,), Value<(u32, String)>, _>(
|
|
&mut store,
|
|
"take-expected-string",
|
|
)?;
|
|
let ret = take_expected_string.call(&mut store, (Ok("hello"),))?;
|
|
assert_eq!(ret.cursor(&store).a1().get(), 0);
|
|
assert_eq!(ret.cursor(&store).a2().to_str()?, "hello");
|
|
let ret = take_expected_string.call(&mut store, (Err(b"goodbye"),))?;
|
|
assert_eq!(ret.cursor(&store).a1().get(), 1);
|
|
assert_eq!(ret.cursor(&store).a2().to_str()?, "goodbye");
|
|
|
|
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(()));
|
|
assert_eq!(to_expected_unit.call(&mut store, (1,))?, Err(()));
|
|
let err = to_expected_unit.call(&mut store, (2,)).unwrap_err();
|
|
assert!(err.to_string().contains("invalid expected"), "{}", err);
|
|
|
|
let to_expected_s16_f32 = instance.get_typed_func::<(u32, u32), Value<Result<i16, f32>>, _>(
|
|
&mut store,
|
|
"to-expected-s16-f32",
|
|
)?;
|
|
let ret = to_expected_s16_f32.call(&mut store, (0, 0))?;
|
|
assert_eq!(ret.cursor(&store).get()?.ok().unwrap().get(), 0);
|
|
let ret = to_expected_s16_f32.call(&mut store, (0, 100))?;
|
|
assert_eq!(ret.cursor(&store).get()?.ok().unwrap().get(), 100);
|
|
let ret = to_expected_s16_f32.call(&mut store, (1, 1.0f32.to_bits()))?;
|
|
assert_eq!(ret.cursor(&store).get()?.err().unwrap().get(), 1.0);
|
|
let ret = to_expected_s16_f32.call(&mut store, (1, CANON_32BIT_NAN | 1))?;
|
|
assert_eq!(
|
|
ret.cursor(&store).get()?.err().unwrap().get().to_bits(),
|
|
CANON_32BIT_NAN
|
|
);
|
|
let ret = to_expected_s16_f32.call(&mut store, (2, 0))?;
|
|
assert!(ret.cursor(&store).get().is_err());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn fancy_list() -> Result<()> {
|
|
let component = format!(
|
|
r#"(component
|
|
(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}
|
|
)
|
|
(instance $i (instantiate (module $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")
|
|
(canon.lift
|
|
(func (param $input) (result $output))
|
|
(into $i)
|
|
(func $i "take")
|
|
)
|
|
)
|
|
)"#
|
|
);
|
|
|
|
let engine = super::engine();
|
|
let component = Component::new(&engine, component)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let instance = Instance::new(&mut store, &component)?;
|
|
|
|
let func = instance
|
|
.get_typed_func::<(&[(Option<u8>, Result<(), &str>)],), Value<(u32, u32, Vec<u8>)>, _>(
|
|
&mut store, "take",
|
|
)?;
|
|
|
|
let input = [
|
|
(None, Ok(())),
|
|
(Some(2), Err("hello there")),
|
|
(Some(200), Err("general kenobi")),
|
|
];
|
|
let ret = func.call(&mut store, (&input,))?;
|
|
let ret = ret.cursor(&store);
|
|
let memory = ret.a3().as_slice()?;
|
|
let ptr = usize::try_from(ret.a1().get()).unwrap();
|
|
let len = usize::try_from(ret.a2().get()).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()
|
|
}
|
|
}
|