#![cfg(not(miri))]
use wasmtime::Result;
use wasmtime::component::*;
use wasmtime::{Config, Engine, Store, Trap};
#[test]
fn host_resource_types() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(import "t" (type $t (sub resource)))
(import "u" (type $u (sub resource)))
(export "t1" (type $t))
(export "t2" (type $t))
(export "u1" (type $u))
(export "u2" (type $u))
(component $c
(import "r" (type $r (sub resource)))
(export "r1" (type $r))
)
(instance $i1 (instantiate $c (with "r" (type $t))))
(instance $i2 (instantiate $c (with "r" (type $t))))
(export "t3" (type $i1 "r1"))
(export "t4" (type $i2 "r1"))
)
"#,
)?;
struct T;
struct U;
assert!(ResourceType::host::<T>() != ResourceType::host::<U>());
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t", ResourceType::host::<T>(), |_, _| Ok(()))?;
linker
.root()
.resource("u", ResourceType::host::<U>(), |_, _| Ok(()))?;
let i = linker.instantiate(&mut store, &c)?;
let t1 = i.get_resource(&mut store, "t1").unwrap();
let t2 = i.get_resource(&mut store, "t2").unwrap();
let t3 = i.get_resource(&mut store, "t3").unwrap();
let t4 = i.get_resource(&mut store, "t4").unwrap();
let u1 = i.get_resource(&mut store, "u1").unwrap();
let u2 = i.get_resource(&mut store, "u2").unwrap();
assert_eq!(t1, ResourceType::host::<T>());
assert_eq!(t2, ResourceType::host::<T>());
assert_eq!(t3, ResourceType::host::<T>());
assert_eq!(t4, ResourceType::host::<T>());
assert_eq!(u1, ResourceType::host::<U>());
assert_eq!(u2, ResourceType::host::<U>());
Ok(())
}
#[test]
fn guest_resource_types() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(type $t (resource (rep i32)))
(type $u (resource (rep i32)))
(export "t1" (type $t))
(export "t2" (type $t))
(export "u1" (type $u))
(export "u2" (type $u))
(component $c
(import "r" (type $r (sub resource)))
(export "r1" (type $r))
)
(instance $i1 (instantiate $c (with "r" (type $t))))
(instance $i2 (instantiate $c (with "r" (type $t))))
(export "t3" (type $i1 "r1"))
(export "t4" (type $i2 "r1"))
)
"#,
)?;
let mut store = Store::new(&engine, ());
let linker = Linker::new(&engine);
let i = linker.instantiate(&mut store, &c)?;
let t1 = i.get_resource(&mut store, "t1").unwrap();
let t2 = i.get_resource(&mut store, "t2").unwrap();
let t3 = i.get_resource(&mut store, "t3").unwrap();
let t4 = i.get_resource(&mut store, "t4").unwrap();
let u1 = i.get_resource(&mut store, "u1").unwrap();
let u2 = i.get_resource(&mut store, "u2").unwrap();
assert_ne!(t1, u1);
assert_eq!(t1, t2);
assert_eq!(t1, t3);
assert_eq!(t1, t4);
assert_eq!(u1, u2);
Ok(())
}
#[test]
fn resource_any() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(type $t' (resource (rep i32)))
(type $u' (resource (rep i32)))
(export $t "t" (type $t'))
(export $u "u" (type $u'))
(core func $t_ctor (canon resource.new $t))
(core func $u_ctor (canon resource.new $u))
(func (export "[constructor]t") (param "x" u32) (result (own $t))
(canon lift (core func $t_ctor)))
(func (export "[constructor]u") (param "x" u32) (result (own $u))
(canon lift (core func $u_ctor)))
(core func $t_drop (canon resource.drop $t))
(core func $u_drop (canon resource.drop $u))
(func (export "drop-t") (param "x" (own $t))
(canon lift (core func $t_drop)))
(func (export "drop-u") (param "x" (own $u))
(canon lift (core func $u_drop)))
)
"#,
)?;
let linker = Linker::new(&engine);
{
let mut store = Store::new(&engine, ());
let i = linker.instantiate(&mut store, &c)?;
let t = i.get_resource(&mut store, "t").unwrap();
let u = i.get_resource(&mut store, "u").unwrap();
assert_ne!(t, u);
let t_ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "[constructor]t")?;
let u_ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "[constructor]u")?;
let t_dtor = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "drop-t")?;
let u_dtor = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "drop-u")?;
let (t1,) = t_ctor.call(&mut store, (100,))?;
let (t2,) = t_ctor.call(&mut store, (200,))?;
let (u1,) = u_ctor.call(&mut store, (300,))?;
let (u2,) = u_ctor.call(&mut store, (400,))?;
assert_eq!(t1.ty(), t);
assert_eq!(t2.ty(), t);
assert_eq!(u1.ty(), u);
assert_eq!(u2.ty(), u);
u_dtor.call(&mut store, (u2,))?;
u_dtor.call(&mut store, (u1,))?;
t_dtor.call(&mut store, (t1,))?;
t_dtor.call(&mut store, (t2,))?;
}
{
let mut store = Store::new(&engine, ());
let i = linker.instantiate(&mut store, &c)?;
let t_ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "[constructor]t")?;
let u_ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "[constructor]u")?;
let t_dtor = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "drop-t")?;
let (t,) = t_ctor.call(&mut store, (100,))?;
t_dtor.call(&mut store, (t,))?;
let (_u,) = u_ctor.call(&mut store, (100,))?;
assert_eq!(
t_dtor.call(&mut store, (t,)).unwrap_err().to_string(),
"host-owned resource is being used with the wrong type"
);
}
Ok(())
}
#[test]
fn mismatch_intrinsics() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(type $t' (resource (rep i32)))
(type $u' (resource (rep i32)))
(export $t "t" (type $t'))
(export $u "u" (type $u'))
;; note the mismatch where this is an intrinsic for `u` but
;; we're typing it as `t`
(core func $t_ctor (canon resource.new $u))
(func (export "ctor") (param "x" u32) (result (own $t))
(canon lift (core func $t_ctor)))
)
"#,
)?;
let mut store = Store::new(&engine, ());
let i = Linker::new(&engine).instantiate(&mut store, &c)?;
let ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "ctor")?;
assert_eq!(
ctor.call(&mut store, (100,)).unwrap_err().to_string(),
"handle index 1 used with the wrong type, expected guest-defined \
resource but found a different guest-defined resource",
);
Ok(())
}
#[test]
fn mismatch_resource_types() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(type $t' (resource (rep i32)))
(type $u' (resource (rep i32)))
(export $t "t" (type $t'))
(export $u "u" (type $u'))
(core func $t_ctor (canon resource.new $t))
(func (export "ctor") (param "x" u32) (result (own $t))
(canon lift (core func $t_ctor)))
(core func $u_dtor (canon resource.drop $u))
(func (export "dtor") (param "x" (own $u))
(canon lift (core func $u_dtor)))
)
"#,
)?;
let mut store = Store::new(&engine, ());
let i = Linker::new(&engine).instantiate(&mut store, &c)?;
let ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "ctor")?;
let dtor = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "dtor")?;
let (t,) = ctor.call(&mut store, (100,))?;
assert_eq!(
dtor.call(&mut store, (t,)).unwrap_err().to_string(),
"mismatched resource types"
);
Ok(())
}
#[test]
fn drop_in_different_places() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(type $t' (resource (rep i32)))
(export $t "t" (type $t'))
(core func $ctor (canon resource.new $t))
(func (export "ctor") (param "x" u32) (result (own $t))
(canon lift (core func $ctor)))
(core func $dtor (canon resource.drop $t))
(func (export "dtor1") (param "x" (own $t))
(canon lift (core func $dtor)))
(component $c
(import "t" (type $t (sub resource)))
(core func $dtor (canon resource.drop $t))
(func (export "dtor") (param "x" (own $t))
(canon lift (core func $dtor)))
)
(instance $i1 (instantiate $c (with "t" (type $t))))
(instance $i2 (instantiate $c (with "t" (type $t))))
(export "dtor2" (func $i1 "dtor"))
(export "dtor3" (func $i2 "dtor"))
)
"#,
)?;
let mut store = Store::new(&engine, ());
let i = Linker::new(&engine).instantiate(&mut store, &c)?;
let ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "ctor")?;
let dtor1 = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "dtor1")?;
let dtor2 = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "dtor2")?;
let dtor3 = i.get_typed_func::<(ResourceAny,), ()>(&mut store, "dtor3")?;
let (t,) = ctor.call(&mut store, (100,))?;
dtor1.call(&mut store, (t,))?;
let (t,) = ctor.call(&mut store, (200,))?;
dtor2.call(&mut store, (t,))?;
let (t,) = ctor.call(&mut store, (300,))?;
dtor3.call(&mut store, (t,))?;
Ok(())
}
#[test]
fn drop_guest_twice() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(type $t' (resource (rep i32)))
(export $t "t" (type $t'))
(core func $ctor (canon resource.new $t))
(func (export "ctor") (param "x" u32) (result (own $t))
(canon lift (core func $ctor)))
(core func $dtor (canon resource.drop $t))
(func (export "dtor") (param "x" (own $t))
(canon lift (core func $dtor)))
)
"#,
)?;
let mut store = Store::new(&engine, ());
let i = Linker::new(&engine).instantiate(&mut store, &c)?;
let ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "ctor")?;
let dtor = i.get_typed_func::<(&ResourceAny,), ()>(&mut store, "dtor")?;
let (t,) = ctor.call(&mut store, (100,))?;
dtor.call(&mut store, (&t,))?;
assert_eq!(
dtor.call(&mut store, (&t,)).unwrap_err().to_string(),
"unknown handle index 1"
);
Ok(())
}
#[test]
fn drop_host_twice() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(import "t" (type $t (sub resource)))
(core func $dtor (canon resource.drop $t))
(func (export "dtor") (param "x" (own $t))
(canon lift (core func $dtor)))
)
"#,
)?;
struct MyType;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t", ResourceType::host::<MyType>(), |_, _| Ok(()))?;
let i = linker.instantiate(&mut store, &c)?;
let dtor = i.get_typed_func::<(&Resource<MyType>,), ()>(&mut store, "dtor")?;
let t = Resource::new_own(100);
dtor.call(&mut store, (&t,))?;
assert_eq!(
dtor.call(&mut store, (&t,)).unwrap_err().to_string(),
"host resource already consumed"
);
Ok(())
}
#[test]
fn manually_destroy() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(import "t1" (type $t1 (sub resource)))
(core module $m
(global $drops (mut i32) i32.const 0)
(global $last-drop (mut i32) i32.const 0)
(func (export "dtor") (param i32)
(global.set $drops (i32.add (global.get $drops) (i32.const 1)))
(global.set $last-drop (local.get 0))
)
(func (export "drops") (result i32) global.get $drops)
(func (export "last-drop") (result i32) global.get $last-drop)
(func (export "pass") (param i32) (result i32) local.get 0)
)
(core instance $i (instantiate $m))
(type $t2' (resource (rep i32) (dtor (func $i "dtor"))))
(export $t2 "t2" (type $t2'))
(core func $ctor (canon resource.new $t2))
(func (export "[constructor]t2") (param "rep" u32) (result (own $t2))
(canon lift (core func $ctor)))
(func (export "[static]t2.drops") (result u32)
(canon lift (core func $i "drops")))
(func (export "[static]t2.last-drop") (result u32)
(canon lift (core func $i "last-drop")))
(func (export "t1-pass") (param "t" (own $t1)) (result (own $t1))
(canon lift (core func $i "pass")))
)
"#,
)?;
struct MyType;
#[derive(Default)]
struct Data {
drops: u32,
last_drop: Option<u32>,
}
let mut store = Store::new(&engine, Data::default());
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t1", ResourceType::host::<MyType>(), |mut cx, rep| {
let data: &mut Data = cx.data_mut();
data.drops += 1;
data.last_drop = Some(rep);
Ok(())
})?;
let i = linker.instantiate(&mut store, &c)?;
let t2_ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "[constructor]t2")?;
let t2_drops = i.get_typed_func::<(), (u32,)>(&mut store, "[static]t2.drops")?;
let t2_last_drop = i.get_typed_func::<(), (u32,)>(&mut store, "[static]t2.last-drop")?;
let t1_pass = i.get_typed_func::<(Resource<MyType>,), (ResourceAny,)>(&mut store, "t1-pass")?;
let t1 = Resource::new_own(100);
let (t1,) = t1_pass.call(&mut store, (t1,))?;
assert_eq!(store.data().drops, 0);
assert_eq!(store.data().last_drop, None);
t1.resource_drop(&mut store)?;
assert_eq!(store.data().drops, 1);
assert_eq!(store.data().last_drop, Some(100));
let (t2,) = t2_ctor.call(&mut store, (200,))?;
assert_eq!(t2_drops.call(&mut store, ())?, (0,));
assert_eq!(t2_last_drop.call(&mut store, ())?, (0,));
t2.resource_drop(&mut store)?;
assert_eq!(t2_drops.call(&mut store, ())?, (1,));
assert_eq!(t2_last_drop.call(&mut store, ())?, (200,));
assert_eq!(store.data().drops, 1);
assert_eq!(store.data().last_drop, Some(100));
Ok(())
}
#[test]
fn dynamic_type() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(import "t1" (type $t1 (sub resource)))
(type $t2' (resource (rep i32)))
(export $t2 "t2" (type $t2'))
(core func $f (canon resource.drop $t2))
(func (export "a") (param "x" (own $t1))
(canon lift (core func $f)))
(func (export "b") (param "x" (tuple (own $t2)))
(canon lift (core func $f)))
)
"#,
)?;
struct MyType;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t1", ResourceType::host::<MyType>(), |_, _| Ok(()))?;
let i = linker.instantiate(&mut store, &c)?;
let a = i.get_func(&mut store, "a").unwrap();
let b = i.get_func(&mut store, "b").unwrap();
let t2 = i.get_resource(&mut store, "t2").unwrap();
let aty = a.ty(&store);
assert_eq!(
aty.params().next().unwrap(),
("x", Type::Own(ResourceType::host::<MyType>()))
);
let bty = b.ty(&store);
match bty.params().next().unwrap() {
(name, Type::Tuple(t)) => {
assert_eq!(name, "x");
assert_eq!(t.types().len(), 1);
let t0 = t.types().next().unwrap();
assert_eq!(t0, Type::Own(t2));
}
_ => unreachable!(),
}
Ok(())
}
#[test]
fn dynamic_val() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(import "t1" (type $t1 (sub resource)))
(type $t2' (resource (rep i32)))
(export $t2 "t2" (type $t2'))
(core func $f (canon resource.new $t2))
(core module $m
(func (export "pass") (param i32) (result i32)
(local.get 0)))
(core instance $i (instantiate $m))
(func (export "a") (param "x" (own $t1)) (result (own $t1))
(canon lift (core func $i "pass")))
(func (export "b") (param "x" u32) (result (own $t2))
(canon lift (core func $f)))
)
"#,
)?;
struct MyType;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t1", ResourceType::host::<MyType>(), |_, _| Ok(()))?;
let i_pre = linker.instantiate_pre(&c)?;
let i = i_pre.instantiate(&mut store)?;
let a = i.get_func(&mut store, "a").unwrap();
let a_typed = i.get_typed_func::<(Resource<MyType>,), (ResourceAny,)>(&mut store, "a")?;
let a_typed_result =
i.get_typed_func::<(Resource<MyType>,), (Resource<MyType>,)>(&mut store, "a")?;
let b = i.get_func(&mut store, "b").unwrap();
let t2 = i.get_resource(&mut store, "t2").unwrap();
let t1 = Resource::new_own(100);
let (t1,) = a_typed.call(&mut store, (t1,))?;
assert_eq!(t1.ty(), ResourceType::host::<MyType>());
let mut results = [Val::Bool(false)];
a.call(&mut store, &[Val::Resource(t1)], &mut results)?;
match &results[0] {
Val::Resource(resource) => {
assert_eq!(resource.ty(), ResourceType::host::<MyType>());
assert!(resource.owned());
let resource = resource.try_into_resource::<MyType>(&mut store)?;
assert_eq!(resource.rep(), 100);
assert!(resource.owned());
let resource = resource.try_into_resource_any(&mut store)?;
assert_eq!(resource.ty(), ResourceType::host::<MyType>());
assert!(resource.owned());
}
_ => unreachable!(),
}
let t1_any = Resource::<MyType>::new_own(100).try_into_resource_any(&mut store)?;
let mut results = [Val::Bool(false)];
a.call(&mut store, &[Val::Resource(t1_any)], &mut results)?;
match &results[0] {
Val::Resource(resource) => {
assert_eq!(resource.ty(), ResourceType::host::<MyType>());
assert!(resource.owned());
let resource = resource.try_into_resource::<MyType>(&mut store)?;
assert_eq!(resource.rep(), 100);
assert!(resource.owned());
let resource = resource.try_into_resource_any(&mut store)?;
assert_eq!(resource.ty(), ResourceType::host::<MyType>());
assert!(resource.owned());
}
_ => unreachable!(),
}
let t1 = Resource::<MyType>::new_own(100)
.try_into_resource_any(&mut store)?
.try_into_resource(&mut store)?;
let (t1,) = a_typed_result.call(&mut store, (t1,))?;
assert_eq!(t1.rep(), 100);
assert!(t1.owned());
let t1_any = t1
.try_into_resource_any(&mut store)?
.try_into_resource::<MyType>(&mut store)?
.try_into_resource_any(&mut store)?;
let mut results = [Val::Bool(false)];
a.call(&mut store, &[Val::Resource(t1_any)], &mut results)?;
match &results[0] {
Val::Resource(resource) => {
assert_eq!(resource.ty(), ResourceType::host::<MyType>());
assert!(resource.owned());
let resource = resource.try_into_resource::<MyType>(&mut store)?;
assert_eq!(resource.rep(), 100);
assert!(resource.owned());
let resource = resource.try_into_resource_any(&mut store)?;
assert_eq!(resource.ty(), ResourceType::host::<MyType>());
assert!(resource.owned());
}
_ => unreachable!(),
}
b.call(&mut store, &[Val::U32(200)], &mut results)?;
match &results[0] {
Val::Resource(resource) => {
assert_eq!(resource.ty(), t2);
}
_ => unreachable!(),
}
Ok(())
}
#[test]
fn cannot_reenter_during_import() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(import "f" (func $f))
(core func $f (canon lower (func $f)))
(core module $m
(import "" "f" (func $f))
(func (export "call") call $f)
(func (export "dtor") (param i32) unreachable)
)
(core instance $i (instantiate $m
(with "" (instance
(export "f" (func $f))
))
))
(type $t2' (resource (rep i32) (dtor (func $i "dtor"))))
(export $t2 "t" (type $t2'))
(core func $ctor (canon resource.new $t2))
(func (export "ctor") (param "x" u32) (result (own $t2))
(canon lift (core func $ctor)))
(func (export "call") (canon lift (core func $i "call")))
)
"#,
)?;
let mut store = Store::new(&engine, None);
let mut linker = Linker::new(&engine);
linker.root().func_wrap("f", |mut cx, ()| {
let data: &mut Option<ResourceAny> = cx.data_mut();
let err = data.take().unwrap().resource_drop(cx).unwrap_err();
assert_eq!(
err.downcast_ref(),
Some(&Trap::CannotEnterComponent),
"bad error: {err:?}"
);
Ok(())
})?;
let i = linker.instantiate(&mut store, &c)?;
let ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "ctor")?;
let call = i.get_typed_func::<(), ()>(&mut store, "call")?;
let (resource,) = ctor.call(&mut store, (100,))?;
*store.data_mut() = Some(resource);
call.call(&mut store, ())?;
Ok(())
}
#[test]
fn active_borrows_at_end_of_call() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(import "t" (type $t (sub resource)))
(core module $m
(func (export "f") (param i32))
)
(core instance $i (instantiate $m))
(func (export "f") (param "x" (borrow $t))
(canon lift (core func $i "f")))
)
"#,
)?;
struct MyType;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t", ResourceType::host::<MyType>(), |_, _| Ok(()))?;
let i = linker.instantiate(&mut store, &c)?;
let f = i.get_typed_func::<(&Resource<MyType>,), ()>(&mut store, "f")?;
let resource = Resource::new_own(1);
let err = f.call(&mut store, (&resource,)).unwrap_err();
assert_eq!(
err.to_string(),
"borrow handles still remain at the end of the call",
);
Ok(())
}
#[test]
fn thread_through_borrow() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(import "t" (type $t (sub resource)))
(import "f" (func $f (param "x" (borrow $t))))
(core func $f (canon lower (func $f)))
(core func $drop (canon resource.drop $t))
(core module $m
(import "" "f" (func $f (param i32)))
(import "" "drop" (func $drop (param i32)))
(func (export "f2") (param i32)
(call $f (local.get 0))
(call $f (local.get 0))
(call $drop (local.get 0))
)
)
(core instance $i (instantiate $m
(with "" (instance
(export "f" (func $f))
(export "drop" (func $drop))
))
))
(func (export "f2") (param "x" (borrow $t))
(canon lift (core func $i "f2")))
)
"#,
)?;
struct MyType;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t", ResourceType::host::<MyType>(), |_, _| Ok(()))?;
linker
.root()
.func_wrap("f", |_cx, (r,): (Resource<MyType>,)| {
assert!(!r.owned());
assert_eq!(r.rep(), 100);
Ok(())
})?;
let i = linker.instantiate(&mut store, &c)?;
let f = i.get_typed_func::<(&Resource<MyType>,), ()>(&mut store, "f2")?;
let resource = Resource::new_own(100);
f.call(&mut store, (&resource,))?;
Ok(())
}
#[test]
fn borrows_must_be_dropped_before_lifting() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(import "t" (type $t (sub resource)))
(core module $m
(func (export "f") (param i32) (result i32)
;; this is a `borrow $t`, and we're returning it as an
;; `own $t`. This should have an error about remaining
;; borrows rather than a mismatch of own/borrow.
local.get 0
)
)
(core instance $i (instantiate $m))
(func (export "f") (param "x" (borrow $t)) (result (own $t))
(canon lift (core func $i "f")))
)
"#,
)?;
struct MyType;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t", ResourceType::host::<MyType>(), |_, _| Ok(()))?;
let i = linker.instantiate(&mut store, &c)?;
let f = i.get_typed_func::<(&Resource<MyType>,), (Resource<MyType>,)>(&mut store, "f")?;
let resource = Resource::new_own(100);
let err = f.call(&mut store, (&resource,)).unwrap_err();
assert_eq!(
err.to_string(),
"borrow handles still remain at the end of the call"
);
Ok(())
}
#[test]
fn cannot_use_borrow_for_own() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(import "t" (type $t (sub resource)))
(import "f" (func $f (param "x" (own $t))))
(core module $m
(import "" "f" (func $f (param i32)))
(func (export "f") (param i32)
local.get 0
call $f
)
)
(core func $f (canon lower (func $f)))
(core instance $i (instantiate $m
(with "" (instance
(export "f" (func $f))
))
))
(func (export "f") (param "x" (borrow $t))
(canon lift (core func $i "f")))
)
"#,
)?;
struct MyType;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t", ResourceType::host::<MyType>(), |_, _| Ok(()))?;
linker
.root()
.func_wrap("f", |_, _: (Resource<MyType>,)| Ok(()))?;
let i = linker.instantiate(&mut store, &c)?;
let f = i.get_typed_func::<(&Resource<MyType>,), ()>(&mut store, "f")?;
let resource = Resource::new_own(100);
let err = f.call(&mut store, (&resource,)).unwrap_err();
assert!(
format!("{err:?}").contains("cannot lift own resource from a borrow"),
"bad error: {err:?}",
);
Ok(())
}
#[test]
fn can_use_own_for_borrow() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(import "t" (type $t (sub resource)))
(core func $drop (canon resource.drop $t))
(core module $m
(import "" "drop" (func $drop (param i32)))
(func (export "f") (param i32)
(call $drop (local.get 0))
)
)
(core instance $i (instantiate $m
(with "" (instance
(export "drop" (func $drop))
))
))
(func (export "f") (param "x" (borrow $t))
(canon lift (core func $i "f")))
)
"#,
)?;
struct MyType;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t", ResourceType::host::<MyType>(), |_, _| Ok(()))?;
let i_pre = linker.instantiate_pre(&c)?;
let i = i_pre.instantiate(&mut store)?;
let f = i.get_func(&mut store, "f").unwrap();
let f_typed = i.get_typed_func::<(&Resource<MyType>,), ()>(&mut store, "f")?;
let resource = Resource::new_own(100);
f_typed.call(&mut store, (&resource,))?;
let resource = Resource::new_borrow(200);
f_typed.call(&mut store, (&resource,))?;
let resource = Resource::<MyType>::new_own(300).try_into_resource_any(&mut store)?;
f.call(&mut store, &[Val::Resource(resource)], &mut [])?;
resource.resource_drop(&mut store)?;
Ok(())
}
#[test]
fn passthrough_wrong_type() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(import "t" (type $t (sub resource)))
(import "f" (func $f (param "a" (borrow $t)) (result (own $t))))
(core func $f (canon lower (func $f)))
(core module $m
(import "" "f" (func $f (param i32) (result i32)))
(func (export "f2") (param i32)
(drop (call $f (local.get 0)))
)
)
(core instance $i (instantiate $m
(with "" (instance
(export "f" (func $f))
))
))
(func (export "f2") (param "x" (borrow $t))
(canon lift (core func $i "f2")))
)
"#,
)?;
struct MyType;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t", ResourceType::host::<MyType>(), |_, _| Ok(()))?;
linker
.root()
.func_wrap("f", |_cx, (r,): (Resource<MyType>,)| Ok((r,)))?;
let i = linker.instantiate(&mut store, &c)?;
let f = i.get_typed_func::<(&Resource<MyType>,), ()>(&mut store, "f2")?;
let resource = Resource::new_own(100);
let err = f.call(&mut store, (&resource,)).unwrap_err();
assert!(
format!("{err:?}").contains("cannot lower a `borrow` resource into an `own`"),
"bad error: {err:?}"
);
Ok(())
}
#[test]
fn pass_moved_resource() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(import "t" (type $t (sub resource)))
(core module $m
(func (export "f") (param i32 i32))
)
(core instance $i (instantiate $m))
(func (export "f") (param "x" (own $t)) (param "y" (borrow $t))
(canon lift (core func $i "f")))
)
"#,
)?;
struct MyType;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t", ResourceType::host::<MyType>(), |_, _| Ok(()))?;
let i = linker.instantiate(&mut store, &c)?;
let f = i.get_typed_func::<(&Resource<MyType>, &Resource<MyType>), ()>(&mut store, "f")?;
let resource = Resource::new_own(100);
let err = f.call(&mut store, (&resource, &resource)).unwrap_err();
assert!(
format!("{err:?}").contains("host resource already consumed"),
"bad error: {err:?}"
);
Ok(())
}
#[test]
fn type_mismatch() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(type $t' (resource (rep i32)))
(export $t "t" (type $t'))
(core func $drop (canon resource.drop $t))
(func (export "f1") (param "x" (own $t))
(canon lift (core func $drop)))
(func (export "f2") (param "x" (borrow $t))
(canon lift (core func $drop)))
(func (export "f3") (param "x" u32)
(canon lift (core func $drop)))
)
"#,
)?;
struct MyType;
let mut store = Store::new(&engine, ());
let i = Linker::new(&engine).instantiate(&mut store, &c)?;
assert!(
i.get_typed_func::<(&Resource<MyType>,), ()>(&mut store, "f1")
.is_err()
);
assert!(
i.get_typed_func::<(&ResourceAny,), ()>(&mut store, "f1")
.is_ok()
);
assert!(
i.get_typed_func::<(&Resource<MyType>,), ()>(&mut store, "f2")
.is_err()
);
assert!(
i.get_typed_func::<(&ResourceAny,), ()>(&mut store, "f2")
.is_ok()
);
assert!(
i.get_typed_func::<(&Resource<MyType>,), ()>(&mut store, "f3")
.is_err()
);
assert!(
i.get_typed_func::<(&ResourceAny,), ()>(&mut store, "f3")
.is_err()
);
assert!(i.get_typed_func::<(u32,), ()>(&mut store, "f3").is_ok());
Ok(())
}
#[test]
fn drop_no_dtor() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(type $t' (resource (rep i32)))
(export $t "t" (type $t'))
(core func $ctor (canon resource.new $t))
(func (export "ctor") (param "x" u32) (result (own $t))
(canon lift (core func $ctor)))
)
"#,
)?;
let mut store = Store::new(&engine, ());
let i = Linker::new(&engine).instantiate(&mut store, &c)?;
let ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "ctor")?;
let (resource,) = ctor.call(&mut store, (100,))?;
resource.resource_drop(&mut store)?;
Ok(())
}
#[test]
fn host_borrow_as_resource_any() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(import "t" (type $t (sub resource)))
(import "f" (func $f (param "f" (borrow $t))))
(core func $f (canon lower (func $f)))
(core func $drop (canon resource.drop $t))
(core module $m
(import "" "f" (func $f (param i32)))
(import "" "drop" (func $drop (param i32)))
(func (export "f2") (param i32)
(call $f (local.get 0))
(call $drop (local.get 0))
)
)
(core instance $i (instantiate $m
(with "" (instance
(export "f" (func $f))
(export "drop" (func $drop))
))
))
(func (export "f2") (param "x" (borrow $t))
(canon lift (core func $i "f2")))
)
"#,
)?;
struct MyType;
let mut store = Store::new(&engine, ());
{
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t", ResourceType::host::<MyType>(), |_, _| Ok(()))?;
linker
.root()
.func_wrap("f", |mut cx, (r,): (ResourceAny,)| {
r.resource_drop(&mut cx)?;
Ok(())
})?;
let i = linker.instantiate(&mut store, &c)?;
let f = i.get_typed_func::<(&Resource<MyType>,), ()>(&mut store, "f2")?;
let resource = Resource::new_own(100);
f.call(&mut store, (&resource,))?;
}
{
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t", ResourceType::host::<MyType>(), |_, _| Ok(()))?;
linker.root().func_wrap("f", |_cx, (_r,): (ResourceAny,)| {
Ok(())
})?;
let i = linker.instantiate(&mut store, &c)?;
let f = i.get_typed_func::<(&Resource<MyType>,), ()>(&mut store, "f2")?;
let resource = Resource::new_own(100);
let err = f.call(&mut store, (&resource,)).unwrap_err();
assert!(
format!("{err:?}").contains("borrow handles still remain at the end of the call"),
"bad error: {err:?}"
);
}
Ok(())
}
#[test]
fn pass_guest_back_as_borrow() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(type $t' (resource (rep i32)))
(export $t "t" (type $t'))
(core func $new (canon resource.new $t))
(core module $m
(import "" "new" (func $new (param i32) (result i32)))
(func (export "mk") (result i32)
(call $new (i32.const 100))
)
(func (export "take") (param i32)
(if (i32.ne (local.get 0) (i32.const 100)) (then (unreachable)))
)
)
(core instance $i (instantiate $m
(with "" (instance
(export "new" (func $new))
))
))
(func (export "mk") (result (own $t))
(canon lift (core func $i "mk")))
(func (export "take") (param "x" (borrow $t))
(canon lift (core func $i "take")))
)
"#,
)?;
let mut store = Store::new(&engine, ());
let i = Linker::new(&engine).instantiate(&mut store, &c)?;
let mk = i.get_typed_func::<(), (ResourceAny,)>(&mut store, "mk")?;
let take = i.get_typed_func::<(&ResourceAny,), ()>(&mut store, "take")?;
let (resource,) = mk.call(&mut store, ())?;
take.call(&mut store, (&resource,))?;
resource.resource_drop(&mut store)?;
let err = take.call(&mut store, (&resource,)).unwrap_err();
assert_eq!(err.to_string(), "unknown handle index 1");
Ok(())
}
#[test]
fn pass_host_borrow_to_guest() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(import "t" (type $t (sub resource)))
(core func $drop (canon resource.drop $t))
(core module $m
(import "" "drop" (func $drop (param i32)))
(func (export "take") (param i32)
(call $drop (local.get 0))
)
)
(core instance $i (instantiate $m
(with "" (instance
(export "drop" (func $drop))
))
))
(func (export "take") (param "x" (borrow $t))
(canon lift (core func $i "take")))
)
"#,
)?;
struct MyType;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t", ResourceType::host::<MyType>(), |_, _| Ok(()))?;
let i = linker.instantiate(&mut store, &c)?;
let take = i.get_typed_func::<(&Resource<MyType>,), ()>(&mut store, "take")?;
let resource = Resource::new_borrow(100);
take.call(&mut store, (&resource,))?;
Ok(())
}
#[tokio::test]
async fn drop_on_owned_resource() -> Result<()> {
let mut config = wasmtime::Config::new();
config.wasm_component_model_async(true);
let engine = &wasmtime::Engine::new(&config)?;
let c = Component::new(
&engine,
r#"
(component
(import "t" (type $t (sub resource)))
(import "[constructor]t" (func $ctor (result (own $t))))
(import "[method]t.foo" (func $foo async (param "self" (borrow $t))))
(core func $ctor (canon lower (func $ctor)))
(core func $drop (canon resource.drop $t))
(core func $foo (canon lower (func $foo) async))
(core module $m
(import "" "ctor" (func $ctor (result i32)))
(import "" "foo" (func $foo (param i32) (result i32)))
(import "" "drop" (func $drop (param i32)))
(func (export "f")
(local $r i32)
(local $status i32)
(local.set $r (call $ctor))
(local.set $status (call $foo (local.get $r)))
(if (i32.ne (i32.const 1 (; STARTED ;)) (i32.and (local.get $status) (i32.const 0xf)))
(then unreachable))
(call $drop (local.get $r))
(unreachable)
)
)
(core instance $i (instantiate $m
(with "" (instance
(export "ctor" (func $ctor))
(export "foo" (func $foo))
(export "drop" (func $drop))
))
))
(func (export "f") async (canon lift (core func $i "f")))
)
"#,
)?;
struct MyType;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t", ResourceType::host::<MyType>(), |_, _| Ok(()))?;
linker.root().func_wrap("[constructor]t", |_, ()| {
Ok((Resource::<MyType>::new_own(300),))
})?;
linker
.root()
.func_wrap_concurrent("[method]t.foo", |_cx, (r,): (Resource<MyType>,)| {
assert!(!r.owned());
Box::pin(core::future::pending::<Result<()>>())
})?;
let i = linker.instantiate_async(&mut store, &c).await?;
let f = i.get_typed_func::<(), ()>(&mut store, "f")?;
let err = f.call_async(&mut store, ()).await.unwrap_err();
assert!(
format!("{err:?}").contains("cannot remove owned resource while borrowed"),
"bad error: {err:?}"
);
Ok(())
}
#[test]
fn guest_different_host_same() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(import "t1" (type $t1 (sub resource)))
(import "t2" (type $t2 (sub resource)))
(import "f" (func $f (param "a" (borrow $t1)) (param "b" (borrow $t2))))
(export $g1 "g1" (type $t1))
(export $g2 "g2" (type $t2))
(core func $f (canon lower (func $f)))
(core func $drop1 (canon resource.drop $t1))
(core func $drop2 (canon resource.drop $t2))
(core module $m
(import "" "f" (func $f (param i32 i32)))
(import "" "drop1" (func $drop1 (param i32)))
(import "" "drop2" (func $drop2 (param i32)))
(func (export "f") (param i32 i32)
;; different types, but everything goes into the same
;; handle index namespace
(if (i32.ne (local.get 0) (i32.const 1)) (then (unreachable)))
(if (i32.ne (local.get 1) (i32.const 2)) (then (unreachable)))
;; host should end up getting the same resource
(call $f (local.get 0) (local.get 1))
;; drop our borrows
(call $drop1 (local.get 0))
(call $drop2 (local.get 1))
)
)
(core instance $i (instantiate $m
(with "" (instance
(export "f" (func $f))
(export "drop1" (func $drop1))
(export "drop2" (func $drop2))
))
))
(func (export "f2") (param "a" (borrow $g1)) (param "b" (borrow $g2))
(canon lift (core func $i "f")))
)
"#,
)?;
struct MyType;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t1", ResourceType::host::<MyType>(), |_, _| Ok(()))?;
linker
.root()
.resource("t2", ResourceType::host::<MyType>(), |_, _| Ok(()))?;
linker.root().func_wrap(
"f",
|_cx, (r1, r2): (Resource<MyType>, Resource<MyType>)| {
assert!(!r1.owned());
assert!(!r2.owned());
assert_eq!(r1.rep(), 100);
assert_eq!(r2.rep(), 100);
Ok(())
},
)?;
let i = linker.instantiate(&mut store, &c)?;
let f = i.get_typed_func::<(&Resource<MyType>, &Resource<MyType>), ()>(&mut store, "f2")?;
let t1 = i.get_resource(&mut store, "g1").unwrap();
let t2 = i.get_resource(&mut store, "g2").unwrap();
assert_eq!(t1, t2);
assert_eq!(t1, ResourceType::host::<MyType>());
let resource = Resource::new_own(100);
f.call(&mut store, (&resource, &resource))?;
Ok(())
}
#[test]
fn resource_any_to_typed_handles_borrow() -> Result<()> {
let engine = super::engine();
let c = Component::new(
&engine,
r#"
(component
(import "t" (type $t (sub resource)))
(import "f" (func $f (param "a" (borrow $t))))
(core func $f (canon lower (func $f)))
(core module $m
(import "" "f" (func $f (param i32)))
(func (export "f") (param i32)
(call $f (local.get 0))
)
)
(core instance $i (instantiate $m
(with "" (instance
(export "f" (func $f))
))
))
(func (export "f") (param "a" (own $t))
(canon lift (core func $i "f")))
)
"#,
)?;
struct MyType;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t", ResourceType::host::<MyType>(), |_, _| Ok(()))?;
linker
.root()
.func_wrap("f", |mut cx, (r,): (ResourceAny,)| {
let r = r.try_into_resource::<MyType>(&mut cx).unwrap();
assert_eq!(r.rep(), 100);
assert!(!r.owned());
Ok(())
})?;
let i = linker.instantiate(&mut store, &c)?;
let f = i.get_typed_func::<(&Resource<MyType>,), ()>(&mut store, "f")?;
let resource = Resource::new_own(100);
f.call(&mut store, (&resource,))?;
Ok(())
}
#[test]
fn resource_dynamic() -> Result<()> {
let r = ResourceDynamic::new_own(1, 2);
assert_eq!(r.rep(), 1);
assert!(r.owned());
assert_eq!(r.ty(), 2);
let engine = super::engine();
let mut store = Store::new(&engine, ());
let r2 = r.try_into_resource_any(&mut store)?;
assert_eq!(r2.ty(), ResourceType::host_dynamic(2));
assert!(r2.owned());
let r3 = r2.try_into_resource_dynamic(&mut store)?;
assert_eq!(r3.rep(), 1);
assert_eq!(r3.ty(), 2);
let c = Component::new(
&engine,
r#"
(component
(import "t" (type $t (sub resource)))
(import "u" (type $u (sub resource)))
(core func $t_drop (canon resource.drop $t))
(core func $u_drop (canon resource.drop $u))
(func (export "drop-t") (param "x" (own $t))
(canon lift (core func $t_drop)))
(func (export "drop-u") (param "x" (own $u))
(canon lift (core func $u_drop)))
)
"#,
)?;
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t", ResourceType::host_dynamic(2), |_, _| Ok(()))?;
linker
.root()
.resource("u", ResourceType::host_dynamic(3), |_, _| Ok(()))?;
let instance = linker.instantiate(&mut store, &c)?;
let drop_t = instance.get_typed_func::<(ResourceDynamic,), ()>(&mut store, "drop-t")?;
let drop_u = instance.get_typed_func::<(ResourceDynamic,), ()>(&mut store, "drop-u")?;
drop_t.call(&mut store, (ResourceDynamic::new_own(1, 2),))?;
drop_u.call(&mut store, (ResourceDynamic::new_own(2, 3),))?;
assert!(
drop_t
.call(&mut store, (ResourceDynamic::new_own(1, 1),))
.is_err()
);
Ok(())
}
#[test]
fn resource_dynamic_not_static() -> Result<()> {
let engine = super::engine();
let mut store = Store::new(&engine, ());
let c = Component::new(
&engine,
r#"
(component
(import "t" (type $t (sub resource)))
(core func $t_drop (canon resource.drop $t))
(func (export "drop-t") (param "x" (own $t))
(canon lift (core func $t_drop)))
)
"#,
)?;
struct T;
{
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t", ResourceType::host_dynamic(2), |_, _| Ok(()))?;
let instance = linker.instantiate(&mut store, &c)?;
instance.get_typed_func::<(ResourceDynamic,), ()>(&mut store, "drop-t")?;
assert!(
instance
.get_typed_func::<(Resource<T>,), ()>(&mut store, "drop-t")
.is_err()
);
}
{
let mut linker = Linker::new(&engine);
linker
.root()
.resource("t", ResourceType::host::<T>(), |_, _| Ok(()))?;
let instance = linker.instantiate(&mut store, &c)?;
assert!(
instance
.get_typed_func::<(ResourceDynamic,), ()>(&mut store, "drop-t")
.is_err()
);
instance.get_typed_func::<(Resource<T>,), ()>(&mut store, "drop-t")?;
}
Ok(())
}
#[test]
fn intrinsic_trampolines() -> Result<()> {
let engine = super::engine();
let mut store = Store::new(&engine, ());
let linker = Linker::new(&engine);
let c = Component::new(
&engine,
r#"
(component
(type $r' (resource (rep i32)))
(export $r "r" (type $r'))
(core func $new (canon resource.new $r))
(core func $rep (canon resource.rep $r))
(func (export "new") (param "x" u32) (result (own $r))
(canon lift (core func $new)))
(func (export "rep") (param "x" (borrow $r)) (result u32)
(canon lift (core func $rep)))
)
"#,
)?;
let i = linker.instantiate(&mut store, &c)?;
let new = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "new")?;
let rep = i.get_typed_func::<(ResourceAny,), (u32,)>(&mut store, "rep")?;
let r = new.call(&mut store, (42,))?.0;
assert!(rep.call(&mut store, (r,)).is_err());
Ok(())
}
#[tokio::test]
async fn drop_after_sync_lowered_async_host_function() -> Result<()> {
let mut config = Config::new();
config.wasm_component_model_async(true);
let engine = Engine::new(&config)?;
let mut store = Store::new(&engine, ());
let mut linker = Linker::new(&engine);
let c = Component::new(
&engine,
r#"
(component
(import "r" (type $r (sub resource)))
;;(import "f" (func $f async (param "x" (borrow $r)) (param "y" (borrow $r))))
;;(import "n" (func $n (result (own $r))))
(import "f" (func $f async))
(component $A
(import "f" (func $f async))
(core module $m
(import "" "f" (func $f))
(func (export "call") (result i32) call $f i32.const 0)
(func (export "cb") (param i32 i32 i32) (result i32) unreachable)
)
(core func $f (canon lower (func $f)))
(core instance $i (instantiate $m
(with "" (instance (export "f" (func $f))))
))
(func (export "call") async
(canon lift (core func $i "call")
async
(callback (func $i "cb"))))
)
(component $B
(import "r" (type $r (sub resource)))
(import "f" (func $f async))
(core module $m
(import "" "f" (func $f (result i32)))
(import "" "r" (func $r))
(import "" "drop" (func $drop (param i32)))
(func (export "call") (param i32) (result i32)
;; Call into component `$A` which is an async-lifted function. This
;; function will call a sync-lowered async function from the host. In
;; effect this means that the task started here by calling `$f` is
;; blocked.
call $f
drop ;; ignore the `SUBTASK_STARTED` status code
;; Now drop our borrow that this function was provided. This is
;; entirely disconnected from `$A` and it should be using the borrow
;; tracking of this task, no other task.
(call $drop (local.get 0))
;; Now finish up this task without actually waiting on `$A`.
call $r
i32.const 0
)
(func (export "cb") (param i32 i32 i32) (result i32) unreachable)
)
(core func $f (canon lower (func $f) async))
(core func $r (canon task.return))
(core func $drop (canon resource.drop $r))
(core instance $i (instantiate $m
(with "" (instance
(export "f" (func $f))
(export "r" (func $r))
(export "drop" (func $drop))
))
))
(func (export "call") async (param "x" (borrow $r))
(canon lift (core func $i "call")
async
(callback (func $i "cb"))))
)
(instance $a (instantiate $A (with "f" (func $f))))
(instance $b (instantiate $B
(with "f" (func $a "call"))
(with "r" (type $r))
))
(export "call" (func $b "call"))
)
"#,
)?;
let mut root = linker.root();
root.resource("r", ResourceType::host::<u32>(), |_, _| Ok(()))?;
root.func_wrap_concurrent("f", |_accessor, ()| {
Box::pin(async move { std::future::pending::<Result<()>>().await })
})?;
let i = linker.instantiate_async(&mut store, &c).await?;
let call = i.get_typed_func::<(Resource<u32>,), ()>(&mut store, "call")?;
let r = Resource::new_borrow(3);
call.call_async(&mut store, (r,)).await?;
Ok(())
}