Skip to content

Commit f542f63

Browse files
committed
WIP.
1 parent 668849e commit f542f63

File tree

7 files changed

+110
-28
lines changed

7 files changed

+110
-28
lines changed

crates/environ/src/trap_encoding.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,6 @@ pub enum Trap {
107107
/// that all host tasks have completed and any/all host-owned stream/future
108108
/// handles have been dropped.
109109
AsyncDeadlock,
110-
111-
/// An exception was thrown out of Wasm into the host and this is
112-
/// not supported in the current configuration.
113-
ExceptionToHost,
114110
// if adding a variant here be sure to update the `check!` macro below
115111
}
116112

@@ -152,7 +148,6 @@ impl Trap {
152148
ContinuationAlreadyConsumed
153149
DisabledOpcode
154150
AsyncDeadlock
155-
ExceptionToHost
156151
}
157152

158153
None
@@ -188,7 +183,6 @@ impl fmt::Display for Trap {
188183
ContinuationAlreadyConsumed => "continuation already consumed",
189184
DisabledOpcode => "pulley opcode disabled at compile time was executed",
190185
AsyncDeadlock => "deadlock detected: event loop cannot make further progress",
191-
ExceptionToHost => "Wasm exception thrown across host boundary",
192186
};
193187
write!(f, "wasm trap: {desc}")
194188
}

crates/unwinder/src/throw.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ pub unsafe fn compute_throw_action<F: Fn(&Frame) -> Option<usize>>(
5858
// tiself.
5959
let result = unsafe {
6060
crate::stackwalk::visit_frames(unwind, exit_pc, exit_frame, entry_frame, |frame| {
61+
log::trace!("visit_frame: frame {frame:?}");
6162
let Some(sp) = frame.sp() else {
6263
// Cannot possibly unwind to this frame if SP is not
6364
// known. This is only the case for the first

crates/wasmtime/src/runtime/func.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
use crate::prelude::*;
21
use crate::runtime::Uninhabited;
32
use crate::runtime::vm::{
43
InterpreterRef, SendSyncPtr, StoreBox, VMArrayCallHostFuncContext, VMCommonStackInformation,
54
VMContext, VMFuncRef, VMFunctionImport, VMOpaqueContext, VMStoreContext,
65
};
76
use crate::store::{AutoAssertNoGc, StoreId, StoreOpaque};
87
use crate::type_registry::RegisteredType;
8+
use crate::vm::{ExceptionTombstone, VMGcRef};
99
use crate::{
1010
AsContext, AsContextMut, CallHook, Engine, Extern, FuncType, Instance, ModuleExport, Ref,
1111
StoreContext, StoreContextMut, Val, ValRaw, ValType,
1212
};
13+
use crate::{ExnRef, Rooted, prelude::*};
1314
use alloc::sync::Arc;
1415
use core::ffi::c_void;
1516
#[cfg(feature = "async")]
@@ -1265,7 +1266,20 @@ impl Func {
12651266

12661267
val_vec.extend((0..ty.results().len()).map(|_| Val::null_func_ref()));
12671268
let (params, results) = val_vec.split_at_mut(nparams);
1268-
func(caller.sub_caller(), params, results)?;
1269+
match func(caller.sub_caller(), params, results) {
1270+
Ok(()) => {}
1271+
Err(e) if e.is::<Rooted<ExnRef>>() => {
1272+
let exnref = e.downcast::<Rooted<ExnRef>>().unwrap();
1273+
let exnref = VMGcRef::from_raw_u32(exnref.to_raw(&mut caller.store.0).unwrap())
1274+
.unwrap()
1275+
.into_exnref_unchecked();
1276+
caller.store.0.set_pending_exception(exnref);
1277+
return Err(ExceptionTombstone.into());
1278+
}
1279+
Err(e) => {
1280+
return Err(e);
1281+
}
1282+
}
12691283

12701284
// Unlike our arguments we need to dynamically check that the return
12711285
// values produced are correct. There could be a bug in `func` that

crates/wasmtime/src/runtime/trap.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use super::ExnRef;
22
#[cfg(feature = "coredump")]
33
use super::coredump::WasmCoreDump;
44
use super::store::AutoAssertNoGc;
5+
use super::vm::ExceptionTombstone;
56
use crate::prelude::*;
67
use crate::store::StoreOpaque;
78
use crate::{AsContext, Module};
@@ -81,6 +82,14 @@ pub(crate) fn from_runtime_box(
8182
coredumpstack,
8283
} = *runtime_trap;
8384
let (mut error, pc) = match reason {
85+
crate::runtime::vm::TrapReason::User(error) if error.is::<ExceptionTombstone>() => {
86+
println!("from_runtime_box: ExceptionTombstone");
87+
let mut nogc = AutoAssertNoGc::new(store);
88+
let exnref = nogc.take_pending_exception();
89+
let exnref = ExnRef::_from_raw(&mut nogc, exnref.as_gc_ref().as_raw_u32())
90+
.expect("Exception should be non-null");
91+
(exnref.into(), None)
92+
}
8493
// For user-defined errors they're already an `anyhow::Error` so no
8594
// conversion is really necessary here, but a `backtrace` may have
8695
// been captured so it's attempted to get inserted here.
@@ -112,13 +121,6 @@ pub(crate) fn from_runtime_box(
112121
(err, Some(pc))
113122
}
114123
crate::runtime::vm::TrapReason::Wasm(trap_code) => (trap_code.into(), None),
115-
crate::runtime::vm::TrapReason::Exception => {
116-
let mut nogc = AutoAssertNoGc::new(store);
117-
let exnref = nogc.take_pending_exception();
118-
let exnref = ExnRef::_from_raw(&mut nogc, exnref.as_gc_ref().as_raw_u32())
119-
.expect("Exception should be non-null");
120-
(exnref.into(), None)
121-
}
122124
};
123125

124126
if let Some(bt) = backtrace {

crates/wasmtime/src/runtime/vm/libcalls.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,13 +1574,13 @@ unsafe fn throw_ref(
15741574
_instance: Pin<&mut Instance>,
15751575
exnref: u32,
15761576
) -> Result<(), TrapReason> {
1577-
use crate::AsStoreOpaqueMut;
1577+
use crate::{AsStoreOpaqueMut, vm::ExceptionTombstone};
15781578

15791579
let exnref = VMGcRef::from_raw_u32(exnref).ok_or_else(|| Trap::NullReference)?;
15801580
let exnref = store.unwrap_gc_store_mut().clone_gc_ref(&exnref);
15811581
let exnref = exnref
15821582
.into_exnref(&*store.unwrap_gc_store().gc_heap)
15831583
.expect("gc ref should be an exception object");
15841584
store.as_store_opaque_mut().set_pending_exception(exnref);
1585-
Err(TrapReason::Exception)
1585+
Err(TrapReason::User(ExceptionTombstone.into()))
15861586
}

crates/wasmtime/src/runtime/vm/traphandlers.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -367,9 +367,29 @@ pub enum TrapReason {
367367

368368
/// A trap raised from a wasm libcall
369369
Wasm(wasmtime_environ::Trap),
370+
}
370371

371-
/// A pending exception (stored in and rooted by the `Store`).
372-
Exception,
372+
/// Special tombstone Error object that we use to indicate a thrown
373+
/// exception. We use this tombstone directly in the libcall
374+
/// implementing `throw_ref` (throws from Wasm), and we create the
375+
/// tombstone when intercepting a `Rooted<ExnRef>` when wrapping a
376+
/// host function into a `Func`.
377+
///
378+
/// This will be captured in a `TrapReason::User` as a boxed
379+
/// error. When provided as a `TrapReason`, the rooted
380+
/// pending-exception slot on the `Store` must have already been
381+
/// set. Both of the above creation sites ensure this invariant.
382+
pub(crate) struct ExceptionTombstone;
383+
impl core::error::Error for ExceptionTombstone {}
384+
impl core::fmt::Debug for ExceptionTombstone {
385+
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
386+
write!(f, "Wasm exception")
387+
}
388+
}
389+
impl core::fmt::Display for ExceptionTombstone {
390+
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
391+
write!(f, "Wasm exception")
392+
}
373393
}
374394

375395
impl From<Error> for TrapReason {
@@ -758,7 +778,10 @@ impl CallThreadState {
758778
// An unwind due to an already-set pending exception sets
759779
// a special UnwindState that triggers the handler-search
760780
// stack-walk on unwind().
761-
UnwindReason::Trap(TrapReason::Exception) => {
781+
UnwindReason::Trap(TrapReason::User(err))
782+
if err.downcast_ref::<ExceptionTombstone>().is_some() =>
783+
{
784+
println!("record_unwind: ExceptionTombstone");
762785
self.unwind.set(UnwindState::ThrowException);
763786
}
764787
// And if we are just propagating an existing trap that already has
@@ -799,7 +822,9 @@ impl CallThreadState {
799822
use wasmtime_unwinder::ThrowAction;
800823

801824
let mut unwind = self.unwind.replace(UnwindState::None);
825+
println!("unwind!");
802826
if let UnwindState::ThrowException = &unwind {
827+
println!("throw exception");
803828
// Take the pending exception from the store and resolve its throw action.
804829
let exnref = nogc.take_pending_exception();
805830
let action = unsafe { compute_throw(nogc, &exnref) };
@@ -816,10 +841,11 @@ impl CallThreadState {
816841
);
817842
},
818843
ThrowAction::None => {
844+
println!("no handler");
819845
// Throw all the way to entry from host, and put the exnref back on the store.
820846
nogc.set_pending_exception(exnref);
821847
unwind = UnwindState::UnwindToHost {
822-
reason: UnwindReason::Trap(TrapReason::Exception),
848+
reason: UnwindReason::Trap(TrapReason::User(ExceptionTombstone.into())),
823849
backtrace: None,
824850
coredump_stack: None,
825851
}

tests/all/exceptions.rs

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,22 +104,67 @@ fn exception_escape_to_host() -> Result<()> {
104104
&engine,
105105
r#"
106106
(module
107-
(tag $e0)
107+
(import "test" "e0" (tag $e0 (param i32)))
108108
109109
(func $throw (export "throw")
110-
(throw $e0)))
110+
(throw $e0 (i32.const 42))))
111111
"#,
112112
)?;
113113

114-
let instance = Instance::new(&mut store, &module, &[])?;
114+
let functy = FuncType::new(&engine, [ValType::I32], []);
115+
let tagty = TagType::new(functy);
116+
let tag = Tag::new(&mut store, &tagty)?;
117+
let instance = Instance::new(&mut store, &module, &[Extern::Tag(tag)])?;
115118
let func = instance.get_func(&mut store, "throw").unwrap();
116119
let mut results = [];
117120
let result = func.call(&mut store, &[], &mut results[..]);
118121
assert!(result.is_err());
119-
assert_eq!(
120-
result.unwrap_err().downcast::<Trap>().unwrap(),
121-
Trap::ExceptionToHost
122-
);
122+
let exn = result.unwrap_err().downcast::<Rooted<ExnRef>>().unwrap();
123+
let exntag = exn.tag(&mut store)?;
124+
assert!(Tag::eq(&exntag, &tag, &store));
125+
126+
Ok(())
127+
}
128+
129+
#[test]
130+
fn exception_from_host() -> Result<()> {
131+
let mut store = gc_and_exceptions_store()?;
132+
let engine = store.engine();
133+
134+
let module = Module::new(
135+
&engine,
136+
r#"
137+
(module
138+
(import "test" "e0" (tag $e0 (param i32)))
139+
(import "test" "f" (func $f (param i32)))
140+
141+
(func $catch (export "catch") (result i32)
142+
(block $b (result i32)
143+
(try_table (result i32) (catch $e0 $b)
144+
i32.const 42
145+
call $f
146+
i32.const 0))))
147+
"#,
148+
)?;
149+
150+
let functy = FuncType::new(&engine, [ValType::I32], []);
151+
let tagty = TagType::new(functy.clone());
152+
let exnty = ExnType::from_tag_type(&tagty).unwrap();
153+
let exnpre = ExnRefPre::new(&mut store, exnty);
154+
let tag = Tag::new(&mut store, &tagty)?;
155+
let extfunc = Func::new(&mut store, functy, move |caller, args, _rets| {
156+
let exn = ExnRef::new(caller, &exnpre, &tag, &[Val::I32(args[0].unwrap_i32())]).unwrap();
157+
Err(exn.into())
158+
});
159+
let instance = Instance::new(
160+
&mut store,
161+
&module,
162+
&[Extern::Tag(tag), Extern::Func(extfunc)],
163+
)?;
164+
let func = instance.get_func(&mut store, "catch").unwrap();
165+
let mut results = [Val::null_any_ref()];
166+
func.call(&mut store, &[], &mut results[..])?;
167+
assert_eq!(results[0].unwrap_i32(), 42);
123168

124169
Ok(())
125170
}

0 commit comments

Comments
 (0)