From 6e0651762292e90458e89960fcb199e7befc416c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 24 Jan 2026 19:06:38 -0800 Subject: [PATCH] Check may-leave flags in trampolines, not Rust This commit moves all may-leave flag handling into compiled trampolines rather than doing this in Rust. This means it can't be forgotten on the Rust side of things and will be slightly more efficient to boot. This then additionally exempts some intrinsics from checking may-leave since Wasmtime erroneously checked when it shouldn't have. Closes #12397 Closes #12403 --- crates/cranelift/src/compiler/component.rs | 106 +++++++++++--- .../src/runtime/component/concurrent.rs | 104 +------------- .../concurrent/futures_and_streams.rs | 20 --- .../src/runtime/component/func/host.rs | 9 -- .../src/runtime/component/instance.rs | 6 - crates/wasmtime/src/runtime/vm/component.rs | 12 -- .../src/runtime/vm/component/libcalls.rs | 130 +++++------------- .../disas/riscv64-component-builtins-asm.wat | 32 +++-- tests/disas/riscv64-component-builtins.wat | 26 ++-- .../threading-builtins.wast | 19 +++ .../threading-trap-in-post-return.wast | 63 +++++++++ .../component-model/async/task-builtins.wast | 44 ++++++ .../error-context-trap-in-post-return.wast | 50 +++++++ .../component-model/resources.wast | 33 +++++ 14 files changed, 371 insertions(+), 283 deletions(-) create mode 100644 tests/misc_testsuite/component-model-threading/threading-trap-in-post-return.wast create mode 100644 tests/misc_testsuite/component-model/error-context-trap-in-post-return.wast diff --git a/crates/cranelift/src/compiler/component.rs b/crates/cranelift/src/compiler/component.rs index 93795cd6da89..7a960de15f98 100644 --- a/crates/cranelift/src/compiler/component.rs +++ b/crates/cranelift/src/compiler/component.rs @@ -119,6 +119,8 @@ impl<'a> TrampolineCompiler<'a> { } fn translate(&mut self, trampoline: &Trampoline) { + self.check_may_leave(trampoline); + match trampoline { Trampoline::Transcoder { op, @@ -1202,23 +1204,7 @@ impl<'a> TrampolineCompiler<'a> { // calling itself. let old_may_block = if let Some(def) = resource_def { if self.types[resource].unwrap_concrete_instance() != def.instance { - let flags = self.builder.ins().load( - ir::types::I32, - trusted, - vmctx, - i32::try_from( - self.offsets - .instance_flags(self.types[resource].unwrap_concrete_instance()), - ) - .unwrap(), - ); - let masked = self - .builder - .ins() - .band_imm(flags, i64::from(FLAG_MAY_LEAVE)); - self.builder - .ins() - .trapz(masked, TRAP_CANNOT_LEAVE_COMPONENT); + self.check_may_leave_instance(self.types[resource].unwrap_concrete_instance()); if self.compiler.tunables.concurrency_support { // Stash the old value of `may_block` and then set it to false. @@ -1471,6 +1457,92 @@ impl<'a> TrampolineCompiler<'a> { self.compiler .call_indirect_host(&mut self.builder, index, host_sig, host_fn, args) } + + fn check_may_leave(&mut self, trampoline: &Trampoline) { + let instance = match trampoline { + // These intrinsics explicitly do not check the may-leave flag. + Trampoline::ResourceRep { .. } + | Trampoline::ThreadIndex + | Trampoline::BackpressureInc { .. } + | Trampoline::BackpressureDec { .. } + | Trampoline::ContextGet { .. } + | Trampoline::ContextSet { .. } => return, + + // Intrinsics used in adapters generated by FACT that aren't called + // directly from guest wasm, so no check is needed. + Trampoline::ResourceTransferOwn + | Trampoline::ResourceTransferBorrow + | Trampoline::ResourceEnterCall + | Trampoline::ResourceExitCall + | Trampoline::PrepareCall { .. } + | Trampoline::SyncStartCall { .. } + | Trampoline::AsyncStartCall { .. } + | Trampoline::FutureTransfer + | Trampoline::StreamTransfer + | Trampoline::ErrorContextTransfer + | Trampoline::Trap + | Trampoline::EnterSyncCall + | Trampoline::ExitSyncCall + | Trampoline::Transcoder { .. } => return, + + Trampoline::LowerImport { options, .. } => self.component.options[*options].instance, + + Trampoline::ResourceNew { instance, .. } + | Trampoline::ResourceDrop { instance, .. } + | Trampoline::TaskReturn { instance, .. } + | Trampoline::TaskCancel { instance } + | Trampoline::WaitableSetNew { instance } + | Trampoline::WaitableSetWait { instance, .. } + | Trampoline::WaitableSetPoll { instance, .. } + | Trampoline::WaitableSetDrop { instance } + | Trampoline::WaitableJoin { instance } + | Trampoline::ThreadYield { instance, .. } + | Trampoline::ThreadSwitchTo { instance, .. } + | Trampoline::ThreadNewIndirect { instance, .. } + | Trampoline::ThreadSuspend { instance, .. } + | Trampoline::ThreadResumeLater { instance } + | Trampoline::ThreadYieldTo { instance, .. } + | Trampoline::SubtaskDrop { instance } + | Trampoline::SubtaskCancel { instance, .. } + | Trampoline::ErrorContextNew { instance, .. } + | Trampoline::ErrorContextDebugMessage { instance, .. } + | Trampoline::ErrorContextDrop { instance, .. } + | Trampoline::StreamNew { instance, .. } + | Trampoline::StreamRead { instance, .. } + | Trampoline::StreamWrite { instance, .. } + | Trampoline::StreamCancelRead { instance, .. } + | Trampoline::StreamCancelWrite { instance, .. } + | Trampoline::StreamDropReadable { instance, .. } + | Trampoline::StreamDropWritable { instance, .. } + | Trampoline::FutureNew { instance, .. } + | Trampoline::FutureRead { instance, .. } + | Trampoline::FutureWrite { instance, .. } + | Trampoline::FutureCancelRead { instance, .. } + | Trampoline::FutureCancelWrite { instance, .. } + | Trampoline::FutureDropReadable { instance, .. } + | Trampoline::FutureDropWritable { instance, .. } => *instance, + }; + + self.check_may_leave_instance(instance) + } + + fn check_may_leave_instance(&mut self, instance: RuntimeComponentInstanceIndex) { + let vmctx = self.builder.func.dfg.block_params(self.block0)[0]; + + let flags = self.builder.ins().load( + ir::types::I32, + ir::MemFlags::trusted(), + vmctx, + i32::try_from(self.offsets.instance_flags(instance)).unwrap(), + ); + let may_leave_bit = self + .builder + .ins() + .band_imm(flags, i64::from(FLAG_MAY_LEAVE)); + self.builder + .ins() + .trapz(may_leave_bit, TRAP_CANNOT_LEAVE_COMPONENT); + } } impl ComponentCompiler for Compiler { diff --git a/crates/wasmtime/src/runtime/component/concurrent.rs b/crates/wasmtime/src/runtime/component/concurrent.rs index 6c4323e4bc8f..bd198d19ba79 100644 --- a/crates/wasmtime/src/runtime/component/concurrent.rs +++ b/crates/wasmtime/src/runtime/component/concurrent.rs @@ -720,8 +720,6 @@ pub(crate) fn poll_and_block( future: impl Future> + Send + 'static, caller_instance: RuntimeInstance, ) -> Result { - store.check_may_leave(caller_instance)?; - let state = store.concurrent_state_mut(); let caller = state.guest_thread.unwrap(); @@ -1388,21 +1386,6 @@ impl StoreContextMut<'_, T> { } impl StoreOpaque { - fn check_may_leave(&mut self, instance: RuntimeInstance) -> Result<()> { - Instance::from_wasmtime(self, instance.instance) - .id() - .get(self) - .check_may_leave(instance.index)?; - - // While we're here, verify that the caller instance matches the most - // recent task pushed onto the task stack: - let state = self.concurrent_state_mut(); - let caller = state.guest_thread.unwrap(); - assert_eq!(state.get_mut(caller.task)?.instance, instance); - - Ok(()) - } - /// Push a `GuestTask` onto the task stack for either a sync-to-sync, /// guest-to-guest call or a sync host-to-guest call. /// @@ -1863,17 +1846,6 @@ impl StoreOpaque { } impl Instance { - fn check_may_leave( - self, - store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, - ) -> Result<()> { - store.check_may_leave(RuntimeInstance { - instance: self.id().instance(), - index: caller, - }) - } - /// Get the next pending event for the specified task and (optional) /// waitable set, along with the waitable handle if applicable. fn get_event( @@ -2355,8 +2327,6 @@ impl Instance { string_encoding: u8, caller_info: CallerInfo, ) -> Result<()> { - self.check_may_leave(store.0, caller_instance)?; - if let (CallerInfo::Sync { .. }, true) = (&caller_info, callee_async) { // A task may only call an async-typed function via a sync lower if // it was created by a call to an async export. Otherwise, we'll @@ -2896,13 +2866,10 @@ impl Instance { pub(crate) fn task_return( self, store: &mut dyn VMStore, - caller: RuntimeComponentInstanceIndex, ty: TypeTupleIndex, options: OptionsIndex, storage: &[ValRaw], ) -> Result<()> { - self.check_may_leave(store, caller)?; - let state = store.concurrent_state_mut(); let guest_thread = state.guest_thread.unwrap(); let lift = state @@ -2948,13 +2915,7 @@ impl Instance { } /// Implements the `task.cancel` intrinsic. - pub(crate) fn task_cancel( - self, - store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, - ) -> Result<()> { - self.check_may_leave(store, caller)?; - + pub(crate) fn task_cancel(self, store: &mut StoreOpaque) -> Result<()> { let state = store.concurrent_state_mut(); let guest_thread = state.guest_thread.unwrap(); let task = state.get_mut(guest_thread.task)?; @@ -3018,8 +2979,6 @@ impl Instance { store: &mut StoreOpaque, caller_instance: RuntimeComponentInstanceIndex, ) -> Result { - self.check_may_leave(store, caller_instance)?; - let set = store.concurrent_state_mut().push(WaitableSet::default())?; let handle = store .handle_table(RuntimeInstance { @@ -3038,8 +2997,6 @@ impl Instance { caller_instance: RuntimeComponentInstanceIndex, set: u32, ) -> Result<()> { - self.check_may_leave(store, caller_instance)?; - let rep = store .handle_table(RuntimeInstance { instance: self.id().instance(), @@ -3068,8 +3025,6 @@ impl Instance { waitable_handle: u32, set_handle: u32, ) -> Result<()> { - self.check_may_leave(store, caller_instance)?; - let mut instance = self.id().get_mut(store); let waitable = Waitable::from_instance(instance.as_mut(), caller_instance, waitable_handle)?; @@ -3098,8 +3053,6 @@ impl Instance { caller_instance: RuntimeComponentInstanceIndex, task_id: u32, ) -> Result<()> { - self.check_may_leave(store, caller_instance)?; - self.waitable_join(store, caller_instance, task_id, 0)?; let (rep, is_host) = store @@ -3162,13 +3115,10 @@ impl Instance { pub(crate) fn waitable_set_wait( self, store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, options: OptionsIndex, set: u32, payload: u32, ) -> Result { - self.check_may_leave(store, caller)?; - if !self.options(store, options).async_ { // The caller may only call `waitable-set.wait` from an async task // (i.e. a task created via a call to an async export). @@ -3204,13 +3154,10 @@ impl Instance { pub(crate) fn waitable_set_poll( self, store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, options: OptionsIndex, set: u32, payload: u32, ) -> Result { - self.check_may_leave(store, caller)?; - let &CanonicalOptions { cancellable, instance: caller_instance, @@ -3256,8 +3203,6 @@ impl Instance { start_func_idx: u32, context: i32, ) -> Result { - self.check_may_leave(store.0, runtime_instance)?; - log::trace!("creating new thread"); let start_func_ty = FuncType::new(store.engine(), [ValType::I32], []); @@ -3392,8 +3337,6 @@ impl Instance { yielding: bool, to_thread: Option, ) -> Result { - self.check_may_leave(store, caller)?; - if to_thread.is_none() { let state = store.concurrent_state_mut(); if yielding { @@ -3543,8 +3486,6 @@ impl Instance { async_: bool, task_id: u32, ) -> Result { - self.check_may_leave(store, caller_instance)?; - if !async_ { // The caller may only sync call `subtask.cancel` from an async task // (i.e. a task created via a call to an async export). Otherwise, @@ -3693,26 +3634,11 @@ impl Instance { } } - pub(crate) fn context_get( - self, - store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, - slot: u32, - ) -> Result { - self.check_may_leave(store, caller)?; - + pub(crate) fn context_get(self, store: &mut StoreOpaque, slot: u32) -> Result { store.concurrent_state_mut().context_get(slot) } - pub(crate) fn context_set( - self, - store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, - slot: u32, - value: u32, - ) -> Result<()> { - self.check_may_leave(store, caller)?; - + pub(crate) fn context_set(self, store: &mut StoreOpaque, slot: u32, value: u32) -> Result<()> { store.concurrent_state_mut().context_set(slot, value) } } @@ -3797,7 +3723,6 @@ pub trait VMComponentAsyncStore { fn future_drop_writable( &mut self, instance: Instance, - caller: RuntimeComponentInstanceIndex, ty: TypeFutureTableIndex, writer: u32, ) -> Result<()>; @@ -3860,7 +3785,6 @@ pub trait VMComponentAsyncStore { fn stream_drop_writable( &mut self, instance: Instance, - caller: RuntimeComponentInstanceIndex, ty: TypeStreamTableIndex, writer: u32, ) -> Result<()>; @@ -3869,7 +3793,6 @@ pub trait VMComponentAsyncStore { fn error_context_debug_message( &mut self, instance: Instance, - caller: RuntimeComponentInstanceIndex, ty: TypeComponentLocalErrorContextTableIndex, options: OptionsIndex, err_ctx_handle: u32, @@ -4000,8 +3923,6 @@ impl VMComponentAsyncStore for StoreInner { future: u32, address: u32, ) -> Result { - instance.check_may_leave(self, caller)?; - instance .guest_write( StoreContextMut(self), @@ -4025,8 +3946,6 @@ impl VMComponentAsyncStore for StoreInner { future: u32, address: u32, ) -> Result { - instance.check_may_leave(self, caller)?; - instance .guest_read( StoreContextMut(self), @@ -4051,8 +3970,6 @@ impl VMComponentAsyncStore for StoreInner { address: u32, count: u32, ) -> Result { - instance.check_may_leave(self, caller)?; - instance .guest_write( StoreContextMut(self), @@ -4077,8 +3994,6 @@ impl VMComponentAsyncStore for StoreInner { address: u32, count: u32, ) -> Result { - instance.check_may_leave(self, caller)?; - instance .guest_read( StoreContextMut(self), @@ -4096,12 +4011,9 @@ impl VMComponentAsyncStore for StoreInner { fn future_drop_writable( &mut self, instance: Instance, - caller: RuntimeComponentInstanceIndex, ty: TypeFutureTableIndex, writer: u32, ) -> Result<()> { - instance.check_may_leave(self, caller)?; - instance.guest_drop_writable(self, TransmitIndex::Future(ty), writer) } @@ -4117,8 +4029,6 @@ impl VMComponentAsyncStore for StoreInner { address: u32, count: u32, ) -> Result { - instance.check_may_leave(self, caller)?; - instance .guest_write( StoreContextMut(self), @@ -4148,8 +4058,6 @@ impl VMComponentAsyncStore for StoreInner { address: u32, count: u32, ) -> Result { - instance.check_may_leave(self, caller)?; - instance .guest_read( StoreContextMut(self), @@ -4170,26 +4078,20 @@ impl VMComponentAsyncStore for StoreInner { fn stream_drop_writable( &mut self, instance: Instance, - caller: RuntimeComponentInstanceIndex, ty: TypeStreamTableIndex, writer: u32, ) -> Result<()> { - instance.check_may_leave(self, caller)?; - instance.guest_drop_writable(self, TransmitIndex::Stream(ty), writer) } fn error_context_debug_message( &mut self, instance: Instance, - caller: RuntimeComponentInstanceIndex, ty: TypeComponentLocalErrorContextTableIndex, options: OptionsIndex, err_ctx_handle: u32, debug_msg_address: u32, ) -> Result<()> { - instance.check_may_leave(self, caller)?; - instance.error_context_debug_message( StoreContextMut(self), ty, diff --git a/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs b/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs index 3bf53db135fa..c4ddc08846d0 100644 --- a/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs +++ b/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs @@ -4002,13 +4002,11 @@ impl Instance { pub(crate) fn error_context_new( self, store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, ty: TypeComponentLocalErrorContextTableIndex, options: OptionsIndex, debug_msg_address: u32, debug_msg_len: u32, ) -> Result { - self.id().get(store).check_may_leave(caller)?; let lift_ctx = &mut LiftContext::new(store, options, self); let debug_msg = String::linear_lift_from_flat( lift_ctx, @@ -4085,12 +4083,10 @@ impl Instance { pub(crate) fn future_cancel_read( self, store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, ty: TypeFutureTableIndex, async_: bool, reader: u32, ) -> Result { - self.id().get(store).check_may_leave(caller)?; self.guest_cancel_read(store, TransmitIndex::Future(ty), async_, reader) .map(|v| v.encode()) } @@ -4099,12 +4095,10 @@ impl Instance { pub(crate) fn future_cancel_write( self, store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, ty: TypeFutureTableIndex, async_: bool, writer: u32, ) -> Result { - self.id().get(store).check_may_leave(caller)?; self.guest_cancel_write(store, TransmitIndex::Future(ty), async_, writer) .map(|v| v.encode()) } @@ -4113,12 +4107,10 @@ impl Instance { pub(crate) fn stream_cancel_read( self, store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, ty: TypeStreamTableIndex, async_: bool, reader: u32, ) -> Result { - self.id().get(store).check_may_leave(caller)?; self.guest_cancel_read(store, TransmitIndex::Stream(ty), async_, reader) .map(|v| v.encode()) } @@ -4127,12 +4119,10 @@ impl Instance { pub(crate) fn stream_cancel_write( self, store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, ty: TypeStreamTableIndex, async_: bool, writer: u32, ) -> Result { - self.id().get(store).check_may_leave(caller)?; self.guest_cancel_write(store, TransmitIndex::Stream(ty), async_, writer) .map(|v| v.encode()) } @@ -4141,11 +4131,9 @@ impl Instance { pub(crate) fn future_drop_readable( self, store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, ty: TypeFutureTableIndex, reader: u32, ) -> Result<()> { - self.id().get(store).check_may_leave(caller)?; self.guest_drop_readable(store, TransmitIndex::Future(ty), reader) } @@ -4153,11 +4141,9 @@ impl Instance { pub(crate) fn stream_drop_readable( self, store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, ty: TypeStreamTableIndex, reader: u32, ) -> Result<()> { - self.id().get(store).check_may_leave(caller)?; self.guest_drop_readable(store, TransmitIndex::Stream(ty), reader) } @@ -4195,12 +4181,10 @@ impl Instance { pub(crate) fn error_context_drop( self, store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, ty: TypeComponentLocalErrorContextTableIndex, error_context: u32, ) -> Result<()> { let instance = self.id().get_mut(store); - instance.check_may_leave(caller)?; let local_handle_table = instance.table_for_error_context(ty); @@ -4265,10 +4249,8 @@ impl Instance { pub(crate) fn future_new( self, store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, ty: TypeFutureTableIndex, ) -> Result { - self.id().get(store).check_may_leave(caller)?; self.guest_new(store, TransmitIndex::Future(ty)) } @@ -4276,10 +4258,8 @@ impl Instance { pub(crate) fn stream_new( self, store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, ty: TypeStreamTableIndex, ) -> Result { - self.id().get(store).check_may_leave(caller)?; self.guest_new(store, TransmitIndex::Stream(ty)) } diff --git a/crates/wasmtime/src/runtime/component/func/host.rs b/crates/wasmtime/src/runtime/component/func/host.rs index 8a05da370579..b746f75c6348 100644 --- a/crates/wasmtime/src/runtime/component/func/host.rs +++ b/crates/wasmtime/src/runtime/component/func/host.rs @@ -371,15 +371,6 @@ where ) -> Result<()> { let vminstance = instance.id().get(store.0); let opts = &vminstance.component().env_component().options[options]; - let caller_instance = opts.instance; - let flags = vminstance.instance_flags(caller_instance); - - // Perform a dynamic check that this instance can indeed be left. - // Exiting the component is disallowed, for example, when the `realloc` - // function calls a canonical import. - if unsafe { !flags.may_leave() } { - return Err(format_err!(crate::Trap::CannotLeaveComponent)); - } if opts.async_ { #[cfg(feature = "component-model-async")] diff --git a/crates/wasmtime/src/runtime/component/instance.rs b/crates/wasmtime/src/runtime/component/instance.rs index 48e2dd0986dd..3c3cdb113138 100644 --- a/crates/wasmtime/src/runtime/component/instance.rs +++ b/crates/wasmtime/src/runtime/component/instance.rs @@ -379,11 +379,9 @@ impl Instance { pub(crate) fn resource_new32( self, store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, ty: TypeResourceTableIndex, rep: u32, ) -> Result { - self.id().get(store).check_may_leave(caller)?; let (calls, _, _, instance) = store.component_resource_state_with_instance(self); resource_tables(calls, instance).resource_new(TypedResource::Component { ty, rep }) } @@ -393,11 +391,9 @@ impl Instance { pub(crate) fn resource_rep32( self, store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, ty: TypeResourceTableIndex, index: u32, ) -> Result { - self.id().get(store).check_may_leave(caller)?; let (calls, _, _, instance) = store.component_resource_state_with_instance(self); resource_tables(calls, instance).resource_rep(TypedResourceIndex::Component { ty, index }) } @@ -406,11 +402,9 @@ impl Instance { pub(crate) fn resource_drop( self, store: &mut StoreOpaque, - caller: RuntimeComponentInstanceIndex, ty: TypeResourceTableIndex, index: u32, ) -> Result> { - self.id().get(store).check_may_leave(caller)?; let (calls, _, _, instance) = store.component_resource_state_with_instance(self); resource_tables(calls, instance).resource_drop(TypedResourceIndex::Component { ty, index }) } diff --git a/crates/wasmtime/src/runtime/vm/component.rs b/crates/wasmtime/src/runtime/vm/component.rs index dfedb1b2fba1..065666e36ce7 100644 --- a/crates/wasmtime/src/runtime/vm/component.rs +++ b/crates/wasmtime/src/runtime/vm/component.rs @@ -947,18 +947,6 @@ impl ComponentInstance { } } - pub(crate) fn check_may_leave( - &self, - instance: RuntimeComponentInstanceIndex, - ) -> crate::Result<()> { - let flags = self.instance_flags(instance); - if unsafe { flags.may_leave() } { - Ok(()) - } else { - Err(crate::format_err!(crate::Trap::CannotLeaveComponent)) - } - } - pub(crate) fn task_may_block(&self) -> NonNull { unsafe { self.vmctx_plus_offset_raw::(self.offsets.task_may_block()) } } diff --git a/crates/wasmtime/src/runtime/vm/component/libcalls.rs b/crates/wasmtime/src/runtime/vm/component/libcalls.rs index e8ee548e192a..fa2808602d7d 100644 --- a/crates/wasmtime/src/runtime/vm/component/libcalls.rs +++ b/crates/wasmtime/src/runtime/vm/component/libcalls.rs @@ -584,42 +584,36 @@ fn inflate_latin1_bytes(dst: &mut [u16], latin1_bytes_so_far: usize) -> &mut [u1 fn resource_new32( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, resource: u32, rep: u32, ) -> Result { - let caller_instance = RuntimeComponentInstanceIndex::from_u32(caller_instance); let resource = TypeResourceTableIndex::from_u32(resource); - instance.resource_new32(store, caller_instance, resource, rep) + instance.resource_new32(store, resource, rep) } fn resource_rep32( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, resource: u32, idx: u32, ) -> Result { - let caller_instance = RuntimeComponentInstanceIndex::from_u32(caller_instance); let resource = TypeResourceTableIndex::from_u32(resource); - instance.resource_rep32(store, caller_instance, resource, idx) + instance.resource_rep32(store, resource, idx) } fn resource_drop( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, resource: u32, idx: u32, ) -> Result { - let caller_instance = RuntimeComponentInstanceIndex::from_u32(caller_instance); let resource = TypeResourceTableIndex::from_u32(resource); - Ok(ResourceDropRet(instance.resource_drop( - store, - caller_instance, - resource, - idx, - )?)) + Ok(ResourceDropRet( + instance.resource_drop(store, resource, idx)?, + )) } struct ResourceDropRet(Option); @@ -725,7 +719,7 @@ fn backpressure_modify( unsafe fn task_return( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, ty: u32, options: u32, storage: *mut u8, @@ -733,7 +727,6 @@ unsafe fn task_return( ) -> Result<()> { instance.task_return( store, - RuntimeComponentInstanceIndex::from_u32(caller_instance), TypeTupleIndex::from_u32(ty), OptionsIndex::from_u32(options), unsafe { core::slice::from_raw_parts(storage.cast(), storage_len) }, @@ -741,11 +734,8 @@ unsafe fn task_return( } #[cfg(feature = "component-model-async")] -fn task_cancel(store: &mut dyn VMStore, instance: Instance, caller_instance: u32) -> Result<()> { - instance.task_cancel( - store, - RuntimeComponentInstanceIndex::from_u32(caller_instance), - ) +fn task_cancel(store: &mut dyn VMStore, instance: Instance, _caller_instance: u32) -> Result<()> { + instance.task_cancel(store) } #[cfg(feature = "component-model-async")] @@ -764,36 +754,24 @@ fn waitable_set_new( fn waitable_set_wait( store: &mut dyn VMStore, instance: Instance, - caller: u32, + _caller: u32, options: u32, set: u32, payload: u32, ) -> Result { - instance.waitable_set_wait( - store, - RuntimeComponentInstanceIndex::from_u32(caller), - OptionsIndex::from_u32(options), - set, - payload, - ) + instance.waitable_set_wait(store, OptionsIndex::from_u32(options), set, payload) } #[cfg(feature = "component-model-async")] fn waitable_set_poll( store: &mut dyn VMStore, instance: Instance, - caller: u32, + _caller: u32, options: u32, set: u32, payload: u32, ) -> Result { - instance.waitable_set_poll( - store, - RuntimeComponentInstanceIndex::from_u32(caller), - OptionsIndex::from_u32(options), - set, - payload, - ) + instance.waitable_set_poll(store, OptionsIndex::from_u32(options), set, payload) } #[cfg(feature = "component-model-async")] @@ -1014,14 +992,10 @@ unsafe impl HostResultHasUnwindSentinel for ResourcePair { fn future_new( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, ty: u32, ) -> Result { - instance.future_new( - store, - RuntimeComponentInstanceIndex::from_u32(caller_instance), - TypeFutureTableIndex::from_u32(ty), - ) + instance.future_new(store, TypeFutureTableIndex::from_u32(ty)) } #[cfg(feature = "component-model-async")] @@ -1068,14 +1042,13 @@ fn future_read( fn future_cancel_write( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, ty: u32, async_: u8, writer: u32, ) -> Result { instance.future_cancel_write( store, - RuntimeComponentInstanceIndex::from_u32(caller_instance), TypeFutureTableIndex::from_u32(ty), async_ != 0, writer, @@ -1086,14 +1059,13 @@ fn future_cancel_write( fn future_cancel_read( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, ty: u32, async_: u8, reader: u32, ) -> Result { instance.future_cancel_read( store, - RuntimeComponentInstanceIndex::from_u32(caller_instance), TypeFutureTableIndex::from_u32(ty), async_ != 0, reader, @@ -1104,13 +1076,12 @@ fn future_cancel_read( fn future_drop_writable( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, ty: u32, writer: u32, ) -> Result<()> { store.component_async_store().future_drop_writable( instance, - RuntimeComponentInstanceIndex::from_u32(caller_instance), TypeFutureTableIndex::from_u32(ty), writer, ) @@ -1120,30 +1091,21 @@ fn future_drop_writable( fn future_drop_readable( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, ty: u32, reader: u32, ) -> Result<()> { - instance.future_drop_readable( - store, - RuntimeComponentInstanceIndex::from_u32(caller_instance), - TypeFutureTableIndex::from_u32(ty), - reader, - ) + instance.future_drop_readable(store, TypeFutureTableIndex::from_u32(ty), reader) } #[cfg(feature = "component-model-async")] fn stream_new( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, ty: u32, ) -> Result { - instance.stream_new( - store, - RuntimeComponentInstanceIndex::from_u32(caller_instance), - TypeStreamTableIndex::from_u32(ty), - ) + instance.stream_new(store, TypeStreamTableIndex::from_u32(ty)) } #[cfg(feature = "component-model-async")] @@ -1194,14 +1156,13 @@ fn stream_read( fn stream_cancel_write( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, ty: u32, async_: u8, writer: u32, ) -> Result { instance.stream_cancel_write( store, - RuntimeComponentInstanceIndex::from_u32(caller_instance), TypeStreamTableIndex::from_u32(ty), async_ != 0, writer, @@ -1212,14 +1173,13 @@ fn stream_cancel_write( fn stream_cancel_read( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, ty: u32, async_: u8, reader: u32, ) -> Result { instance.stream_cancel_read( store, - RuntimeComponentInstanceIndex::from_u32(caller_instance), TypeStreamTableIndex::from_u32(ty), async_ != 0, reader, @@ -1230,13 +1190,12 @@ fn stream_cancel_read( fn stream_drop_writable( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, ty: u32, writer: u32, ) -> Result<()> { store.component_async_store().stream_drop_writable( instance, - RuntimeComponentInstanceIndex::from_u32(caller_instance), TypeStreamTableIndex::from_u32(ty), writer, ) @@ -1246,16 +1205,11 @@ fn stream_drop_writable( fn stream_drop_readable( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, ty: u32, reader: u32, ) -> Result<()> { - instance.stream_drop_readable( - store, - RuntimeComponentInstanceIndex::from_u32(caller_instance), - TypeStreamTableIndex::from_u32(ty), - reader, - ) + instance.stream_drop_readable(store, TypeStreamTableIndex::from_u32(ty), reader) } #[cfg(feature = "component-model-async")] @@ -1314,7 +1268,7 @@ fn flat_stream_read( fn error_context_new( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, ty: u32, options: u32, debug_msg_address: u32, @@ -1322,7 +1276,6 @@ fn error_context_new( ) -> Result { instance.error_context_new( store.store_opaque_mut(), - RuntimeComponentInstanceIndex::from_u32(caller_instance), TypeComponentLocalErrorContextTableIndex::from_u32(ty), OptionsIndex::from_u32(options), debug_msg_address, @@ -1334,7 +1287,7 @@ fn error_context_new( fn error_context_debug_message( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, ty: u32, options: u32, err_ctx_handle: u32, @@ -1342,7 +1295,6 @@ fn error_context_debug_message( ) -> Result<()> { store.component_async_store().error_context_debug_message( instance, - RuntimeComponentInstanceIndex::from_u32(caller_instance), TypeComponentLocalErrorContextTableIndex::from_u32(ty), OptionsIndex::from_u32(options), err_ctx_handle, @@ -1354,13 +1306,12 @@ fn error_context_debug_message( fn error_context_drop( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, ty: u32, err_ctx_handle: u32, ) -> Result<()> { instance.error_context_drop( store, - RuntimeComponentInstanceIndex::from_u32(caller_instance), TypeComponentLocalErrorContextTableIndex::from_u32(ty), err_ctx_handle, ) @@ -1370,30 +1321,21 @@ fn error_context_drop( fn context_get( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, slot: u32, ) -> Result { - instance.context_get( - store, - RuntimeComponentInstanceIndex::from_u32(caller_instance), - slot, - ) + instance.context_get(store, slot) } #[cfg(feature = "component-model-async")] fn context_set( store: &mut dyn VMStore, instance: Instance, - caller_instance: u32, + _caller_instance: u32, slot: u32, val: u32, ) -> Result<()> { - instance.context_set( - store, - RuntimeComponentInstanceIndex::from_u32(caller_instance), - slot, - val, - ) + instance.context_set(store, slot, val) } #[cfg(feature = "component-model-async")] diff --git a/tests/disas/riscv64-component-builtins-asm.wat b/tests/disas/riscv64-component-builtins-asm.wat index dd6a465899e7..9bc5c184b4a7 100644 --- a/tests/disas/riscv64-component-builtins-asm.wat +++ b/tests/disas/riscv64-component-builtins-asm.wat @@ -19,24 +19,30 @@ ;; addi sp, sp, -0x10 ;; sd s1, 8(sp) ;; mv s1, a1 -;; mv a3, a2 -;; ld a4, 0x10(a0) -;; mv a5, s0 -;; sd a5, 0x28(a4) -;; ld a5, 8(s0) -;; sd a5, 0x30(a4) -;; ld a1, 8(a0) -;; ld a5, 0x10(a1) +;; mv a7, a2 +;; ld a1, 0x10(a0) +;; mv a2, s0 +;; sd a2, 0x28(a1) +;; ld a2, 8(s0) +;; sd a2, 0x30(a1) +;; lw a2, 0x20(a0) +;; andi a2, a2, 1 +;; bnez a2, 8 +;; .byte 0x00, 0x00, 0x00, 0x00 +;; ╰─╼ trap: CannotLeaveComponent +;; ld a3, 8(a0) +;; ld a5, 0x10(a3) ;; mv a4, zero ;; slli a1, a4, 0x20 ;; srai a1, a1, 0x20 ;; slli a2, a4, 0x20 ;; srai a2, a2, 0x20 +;; mv a3, a7 ;; slli a3, a3, 0x20 ;; srai a3, a3, 0x20 ;; jalr a5 -;; addi a1, zero, -1 -;; beq a0, a1, 0x1c +;; addi a3, zero, -1 +;; beq a0, a3, 0x1c ;; ld s1, 8(sp) ;; addi sp, sp, 0x10 ;; ld ra, 8(sp) @@ -44,8 +50,8 @@ ;; addi sp, sp, 0x10 ;; ret ;; mv a1, s1 -;; ld a2, 0x10(a1) -;; ld a2, 0x198(a2) +;; ld a4, 0x10(a1) +;; ld a4, 0x198(a4) ;; mv a0, a1 -;; jalr a2 +;; jalr a4 ;; .byte 0x00, 0x00, 0x00, 0x00 diff --git a/tests/disas/riscv64-component-builtins.wat b/tests/disas/riscv64-component-builtins.wat index 4a1102fed04a..00d44db61103 100644 --- a/tests/disas/riscv64-component-builtins.wat +++ b/tests/disas/riscv64-component-builtins.wat @@ -20,22 +20,26 @@ ;; store notrap aligned v4, v3+40 ;; v5 = get_return_address.i64 ;; store notrap aligned v5, v3+48 -;; v8 = load.i64 notrap aligned readonly v0+8 -;; v9 = load.i64 notrap aligned readonly v8+16 -;; v6 = iconst.i32 0 -;; v10 = call_indirect sig0, v9(v0, v6, v6, v2) ; v6 = 0, v6 = 0 -;; v11 = iconst.i64 -1 -;; v12 = icmp ne v10, v11 ; v11 = -1 -;; brif v12, block2, block1 +;; v6 = load.i32 notrap aligned v0+32 +;; v17 = iconst.i32 1 +;; v7 = band v6, v17 ; v17 = 1 +;; trapz v7, user25 +;; v10 = load.i64 notrap aligned readonly v0+8 +;; v11 = load.i64 notrap aligned readonly v10+16 +;; v8 = iconst.i32 0 +;; v12 = call_indirect sig0, v11(v0, v8, v8, v2) ; v8 = 0, v8 = 0 +;; v13 = iconst.i64 -1 +;; v14 = icmp ne v12, v13 ; v13 = -1 +;; brif v14, block2, block1 ;; ;; block1 cold: -;; v13 = load.i64 notrap aligned readonly v1+16 -;; v14 = load.i64 notrap aligned readonly v13+408 -;; call_indirect sig1, v14(v1) +;; v15 = load.i64 notrap aligned readonly v1+16 +;; v16 = load.i64 notrap aligned readonly v15+408 +;; call_indirect sig1, v16(v1) ;; trap user1 ;; ;; block2: -;; brif.i64 v10, block3, block4 +;; brif.i64 v12, block3, block4 ;; ;; block3: ;; jump block4 diff --git a/tests/misc_testsuite/component-model-threading/threading-builtins.wast b/tests/misc_testsuite/component-model-threading/threading-builtins.wast index 5a09d41693bf..bf10f60439c3 100644 --- a/tests/misc_testsuite/component-model-threading/threading-builtins.wast +++ b/tests/misc_testsuite/component-model-threading/threading-builtins.wast @@ -100,3 +100,22 @@ (func (export "run") async (result u32) (canon lift (core func $i "run")))) (assert_return (invoke "run") (u32.const 42)) + +;; Test that `thread.index` is exempt from may-leave checks +(component + (core func $thread.index (canon thread.index)) + + (core module $DM + (import "" "thread.index" (func $thread.index (result i32))) + + (func (export "run")) + (func (export "post-return") call $thread.index drop) + ) + (core instance $dm (instantiate $DM (with "" (instance + (export "thread.index" (func $thread.index)) + )))) + (func (export "run") + (canon lift (core func $dm "run") (post-return (func $dm "post-return")))) +) + +(assert_return (invoke "run")) diff --git a/tests/misc_testsuite/component-model-threading/threading-trap-in-post-return.wast b/tests/misc_testsuite/component-model-threading/threading-trap-in-post-return.wast new file mode 100644 index 000000000000..c129a5cbf324 --- /dev/null +++ b/tests/misc_testsuite/component-model-threading/threading-trap-in-post-return.wast @@ -0,0 +1,63 @@ +;;! component_model_threading = true + +(component definition $Tester + (core module $Memory + (table (export "t") 1 funcref) + ) + (core instance $memory (instantiate $Memory)) + + (core type $ft (func (param i32))) + (core func $thread.new-indirect (canon thread.new-indirect $ft (table $memory "t"))) + (core func $thread.switch-to (canon thread.switch-to)) + (core func $thread.suspend (canon thread.suspend)) + (core func $thread.resume-later (canon thread.resume-later)) + (core func $thread.yield-to (canon thread.yield-to)) + + (core module $DM + (import "" "thread.new-indirect" (func $thread.new-indirect (param i32 i32) (result i32))) + (import "" "thread.switch-to" (func $thread.switch-to (param i32) (result i32))) + (import "" "thread.suspend" (func $thread.suspend (result i32))) + (import "" "thread.resume-later" (func $thread.resume-later (param i32))) + (import "" "thread.yield-to" (func $thread.yield-to (param i32) (result i32))) + + (func (export "noop")) + (func (export "trap-calling-thread-new-indirect") (call $thread.new-indirect (i32.const 0) (i32.const 0)) drop) + (func (export "trap-calling-thread-switch-to") (call $thread.switch-to (i32.const 0)) drop) + (func (export "trap-calling-thread-suspend") (call $thread.suspend) drop) + (func (export "trap-calling-thread-resume-later") (call $thread.resume-later (i32.const 0))) + (func (export "trap-calling-thread-yield-to") (call $thread.yield-to (i32.const 0)) drop) + ) + (core instance $dm (instantiate $DM (with "" (instance + (export "thread.new-indirect" (func $thread.new-indirect)) + (export "thread.switch-to" (func $thread.switch-to)) + (export "thread.suspend" (func $thread.suspend)) + (export "thread.resume-later" (func $thread.resume-later)) + (export "thread.yield-to" (func $thread.yield-to)) + )))) + (func (export "trap-calling-thread-new-indirect") + (canon lift (core func $dm "noop") + (post-return (func $dm "trap-calling-thread-new-indirect")))) + (func (export "trap-calling-thread-switch-to") + (canon lift (core func $dm "noop") + (post-return (func $dm "trap-calling-thread-switch-to")))) + (func (export "trap-calling-thread-suspend") + (canon lift (core func $dm "noop") + (post-return (func $dm "trap-calling-thread-suspend")))) + (func (export "trap-calling-thread-resume-later") + (canon lift (core func $dm "noop") + (post-return (func $dm "trap-calling-thread-resume-later")))) + (func (export "trap-calling-thread-yield-to") + (canon lift (core func $dm "noop") + (post-return (func $dm "trap-calling-thread-yield-to")))) +) + +(component instance $i0 $Tester) +(assert_trap (invoke "trap-calling-thread-new-indirect") "cannot leave component instance") +(component instance $i1 $Tester) +(assert_trap (invoke "trap-calling-thread-switch-to") "cannot leave component instance") +(component instance $i2 $Tester) +(assert_trap (invoke "trap-calling-thread-suspend") "cannot leave component instance") +(component instance $i3 $Tester) +(assert_trap (invoke "trap-calling-thread-resume-later") "cannot leave component instance") +(component instance $i4 $Tester) +(assert_trap (invoke "trap-calling-thread-yield-to") "cannot leave component instance") diff --git a/tests/misc_testsuite/component-model/async/task-builtins.wast b/tests/misc_testsuite/component-model/async/task-builtins.wast index 93a74011d8f6..7dcf6e8cb545 100644 --- a/tests/misc_testsuite/component-model/async/task-builtins.wast +++ b/tests/misc_testsuite/component-model/async/task-builtins.wast @@ -75,3 +75,47 @@ (core func $subtask-cancel (canon subtask.cancel)) (core instance $i (instantiate $m (with "" (instance (export "subtask.cancel" (func $subtask-cancel)))))) ) + +;; Test that some intrinsics are exempt from may-leave checks +(component + (core func $backpressure.inc (canon backpressure.inc)) + (core func $backpressure.dec (canon backpressure.dec)) + (core func $context.get (canon context.get i32 0)) + (core func $context.set (canon context.set i32 0)) + + (core module $DM + (import "" "backpressure.inc" (func $backpressure.inc)) + (import "" "backpressure.dec" (func $backpressure.dec)) + (import "" "context.get" (func $context.get (result i32))) + (import "" "context.set" (func $context.set (param i32))) + + (global $g (mut i32) (i32.const 0)) + + (func (export "run") + (call $context.set (i32.const 100)) + ) + (func (export "post-return") + call $backpressure.inc + call $backpressure.dec + + ;; context.get should be what was set in `run` + call $context.get + i32.const 100 + i32.ne + if unreachable end + + i32.const 32 + call $context.set + ) + ) + (core instance $dm (instantiate $DM (with "" (instance + (export "backpressure.inc" (func $backpressure.inc)) + (export "backpressure.dec" (func $backpressure.dec)) + (export "context.get" (func $context.get)) + (export "context.set" (func $context.set)) + )))) + (func (export "run") + (canon lift (core func $dm "run") (post-return (func $dm "post-return")))) +) + +(assert_return (invoke "run")) diff --git a/tests/misc_testsuite/component-model/error-context-trap-in-post-return.wast b/tests/misc_testsuite/component-model/error-context-trap-in-post-return.wast new file mode 100644 index 000000000000..715238041d9e --- /dev/null +++ b/tests/misc_testsuite/component-model/error-context-trap-in-post-return.wast @@ -0,0 +1,50 @@ +;;! component_model_error_context = true + +(component definition $Tester + (core module $Memory + (memory (export "mem") 1) + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) + ) + (core instance $memory (instantiate $Memory)) + + (core func $error-context.new (canon error-context.new (memory $memory "mem"))) + (core func $error-context.debug-message + (canon error-context.debug-message + (memory $memory "mem") + (realloc (func $memory "realloc")))) + (core func $error-context.drop (canon error-context.drop)) + + (core module $DM + (import "" "mem" (memory 1)) + (import "" "error-context.new" (func $error-context.new (param i32 i32) (result i32))) + (import "" "error-context.debug-message" (func $error-context.debug-message (param i32 i32))) + (import "" "error-context.drop" (func $error-context.drop (param i32))) + + (func (export "noop")) + (func (export "trap-calling-error-context-new") (call $error-context.new (i32.const 0) (i32.const 0)) drop) + (func (export "trap-calling-error-context-debug-message") (call $error-context.debug-message (i32.const 0) (i32.const 0))) + (func (export "trap-calling-error-context-drop") (call $error-context.drop (i32.const 0))) + ) + (core instance $dm (instantiate $DM (with "" (instance + (export "mem" (memory $memory "mem")) + (export "error-context.new" (func $error-context.new)) + (export "error-context.debug-message" (func $error-context.debug-message)) + (export "error-context.drop" (func $error-context.drop)) + )))) + (func (export "trap-calling-error-context-new") + (canon lift (core func $dm "noop") + (post-return (func $dm "trap-calling-error-context-new")))) + (func (export "trap-calling-error-context-debug-message") + (canon lift (core func $dm "noop") + (post-return (func $dm "trap-calling-error-context-debug-message")))) + (func (export "trap-calling-error-context-drop") + (canon lift (core func $dm "noop") + (post-return (func $dm "trap-calling-error-context-drop")))) +) + +(component instance $i0 $Tester) +(assert_trap (invoke "trap-calling-error-context-new") "cannot leave component instance") +(component instance $i1 $Tester) +(assert_trap (invoke "trap-calling-error-context-debug-message") "cannot leave component instance") +(component instance $i2 $Tester) +(assert_trap (invoke "trap-calling-error-context-drop") "cannot leave component instance") diff --git a/tests/misc_testsuite/component-model/resources.wast b/tests/misc_testsuite/component-model/resources.wast index 5302e7eb904a..baa633e8580a 100644 --- a/tests/misc_testsuite/component-model/resources.wast +++ b/tests/misc_testsuite/component-model/resources.wast @@ -1091,3 +1091,36 @@ (assert_trap (invoke "drop-r1-as-r2") "handle index 2 used with the wrong type, expected guest-defined resource but found a different guest-defined resource") (component instance $C1 $C) (assert_trap (invoke "return-r1-as-r2") "handle index 2 used with the wrong type, expected guest-defined resource but found a different guest-defined resource") + +;; Test that `resource.rep` is exempt from may-leave checks +(component + (type $r (resource (rep i32))) + (core func $resource.new (canon resource.new $r)) + (core func $resource.rep (canon resource.rep $r)) + + (core module $DM + (import "" "resource.new" (func $resource.new (param i32) (result i32))) + (import "" "resource.rep" (func $resource.rep (param i32) (result i32))) + + (global $g (mut i32) (i32.const 0)) + + (func (export "run") + (global.set $g (call $resource.new (i32.const 42))) + ) + (func (export "post-return") + (i32.eq + (call $resource.rep (global.get $g)) + (i32.const 42)) + if return end + unreachable + ) + ) + (core instance $dm (instantiate $DM (with "" (instance + (export "resource.new" (func $resource.new)) + (export "resource.rep" (func $resource.rep)) + )))) + (func (export "run") + (canon lift (core func $dm "run") (post-return (func $dm "post-return")))) +) + +(assert_return (invoke "run"))