diff --git a/cranelift/codegen/src/ir/memflags.rs b/cranelift/codegen/src/ir/memflags.rs index ec4ba7330231..61f9858831f8 100644 --- a/cranelift/codegen/src/ir/memflags.rs +++ b/cranelift/codegen/src/ir/memflags.rs @@ -339,9 +339,8 @@ impl MemFlags { 0b1010 => Some(TrapCode::Interrupt), 0b1011 => Some(TrapCode::NullReference), 0b1100 => Some(TrapCode::NullI31Ref), - 0b1101 => Some(TrapCode::UnhandledTag), - // 0b1101 => {} not allocated - // 0b1110 => {} not allocated + 0b1101 => Some(TrapCode::ContinuationAlreadyConsumed), + 0b1110 => Some(TrapCode::UnhandledTag), 0b1111 => None, _ => unreachable!(), } @@ -369,7 +368,8 @@ impl MemFlags { Some(TrapCode::Interrupt) => 0b1010, Some(TrapCode::NullReference) => 0b1011, Some(TrapCode::NullI31Ref) => 0b1100, - Some(TrapCode::UnhandledTag) => 0b1101, + Some(TrapCode::ContinuationAlreadyConsumed) => 0b1101, + Some(TrapCode::UnhandledTag) => 0b1110, None => 0b1111, Some(TrapCode::User(_)) => panic!("cannot set user trap code in mem flags"), diff --git a/cranelift/codegen/src/ir/trapcode.rs b/cranelift/codegen/src/ir/trapcode.rs index 63d8d761d2cf..bf270ab7899f 100644 --- a/cranelift/codegen/src/ir/trapcode.rs +++ b/cranelift/codegen/src/ir/trapcode.rs @@ -58,6 +58,10 @@ pub enum TrapCode { /// We are suspending to a tag for which there is no active handler. UnhandledTag, + /// Attempt to resume a continuation object whose revision is out + /// of sync with the underlying continuation reference. + ContinuationAlreadyConsumed, + /// A null `i31ref` was encountered which was required to be non-null. NullI31Ref, } @@ -79,6 +83,7 @@ impl TrapCode { TrapCode::Interrupt, TrapCode::NullReference, TrapCode::UnhandledTag, + TrapCode::ContinuationAlreadyConsumed, ] } } @@ -102,6 +107,7 @@ impl Display for TrapCode { NullReference => "null_reference", UnhandledTag => "unhandled_tag", NullI31Ref => "null_i31ref", + ContinuationAlreadyConsumed => "continuation_already_consumed", }; f.write_str(identifier) } @@ -127,6 +133,7 @@ impl FromStr for TrapCode { "null_reference" => Ok(NullReference), "unhandled_tag" => Ok(UnhandledTag), "null_i31ref" => Ok(NullI31Ref), + "continuation_already_consumed" => Ok(ContinuationAlreadyConsumed), _ if s.starts_with("user") => s[4..].parse().map(User).map_err(|_| ()), _ => Err(()), } diff --git a/crates/continuations/src/lib.rs b/crates/continuations/src/lib.rs index 7f5032b8a817..988919ab5ff6 100644 --- a/crates/continuations/src/lib.rs +++ b/crates/continuations/src/lib.rs @@ -302,6 +302,8 @@ pub mod offsets { pub const TAG_RETURN_VALUES: usize = ARGS + core::mem::size_of::(); /// Offset of `state` field pub const STATE: usize = TAG_RETURN_VALUES + core::mem::size_of::(); + /// Offset of `revision` field + pub const REVISION: usize = STATE + core::mem::size_of::(); } pub mod stack_limits { diff --git a/crates/cranelift-shared/src/lib.rs b/crates/cranelift-shared/src/lib.rs index 408c266e0d5c..91ff71e64da0 100644 --- a/crates/cranelift-shared/src/lib.rs +++ b/crates/cranelift-shared/src/lib.rs @@ -87,6 +87,8 @@ pub fn mach_trap_to_trap(trap: &MachTrap) -> Option { ir::TrapCode::User(CANNOT_ENTER_CODE) => Trap::CannotEnterComponent, ir::TrapCode::NullReference => Trap::NullReference, ir::TrapCode::NullI31Ref => Trap::NullI31Ref, + ir::TrapCode::UnhandledTag => Trap::UnhandledTag, + ir::TrapCode::ContinuationAlreadyConsumed => Trap::ContinuationAlreadyConsumed, // These do not get converted to wasmtime traps, since they // shouldn't ever be hit in theory. Instead of catching and handling diff --git a/crates/cranelift/src/lib.rs b/crates/cranelift/src/lib.rs index 42deee0ddda7..5f7fa2aef7f0 100644 --- a/crates/cranelift/src/lib.rs +++ b/crates/cranelift/src/lib.rs @@ -306,6 +306,7 @@ pub fn mach_trap_to_trap(trap: &MachTrap) -> Option { ir::TrapCode::NullReference => Trap::NullReference, ir::TrapCode::UnhandledTag => Trap::UnhandledTag, ir::TrapCode::NullI31Ref => Trap::NullI31Ref, + ir::TrapCode::ContinuationAlreadyConsumed => Trap::ContinuationAlreadyConsumed, // These do not get converted to wasmtime traps, since they // shouldn't ever be hit in theory. Instead of catching and handling diff --git a/crates/cranelift/src/wasmfx/baseline.rs b/crates/cranelift/src/wasmfx/baseline.rs index ce3af36ea78c..5c5d3527fac7 100644 --- a/crates/cranelift/src/wasmfx/baseline.rs +++ b/crates/cranelift/src/wasmfx/baseline.rs @@ -8,10 +8,49 @@ use cranelift_codegen::ir::InstBuilder; use cranelift_frontend::{FunctionBuilder, Switch}; use cranelift_wasm::FuncEnvironment; use cranelift_wasm::{FuncTranslationState, WasmResult, WasmValType}; -use shared::typed_continuations_cont_obj_get_cont_ref; -use shared::typed_continuations_new_cont_obj; +use shared::{assemble_contobj, disassemble_contobj}; use wasmtime_environ::PtrSize; +fn get_revision<'a>( + _env: &mut crate::func_environ::FuncEnvironment<'a>, + builder: &mut FunctionBuilder, + contref: ir::Value, +) -> ir::Value { + if cfg!(feature = "unsafe_disable_continuation_linearity_check") { + builder.ins().iconst(I64, 0) + } else { + let mem_flags = ir::MemFlags::trusted(); + builder.ins().load(I64, mem_flags, contref, 0) + } +} + +fn compare_revision_and_increment<'a>( + env: &mut crate::func_environ::FuncEnvironment<'a>, + builder: &mut FunctionBuilder, + contref: ir::Value, + witness: ir::Value, +) -> ir::Value { + if cfg!(feature = "unsafe_disable_continuation_linearity_check") { + builder.ins().iconst(I64, 0) + } else { + let mem_flags = ir::MemFlags::trusted(); + let revision = get_revision(env, builder, contref); + + let evidence = builder.ins().icmp(IntCC::Equal, witness, revision); + builder + .ins() + .trapz(evidence, ir::TrapCode::ContinuationAlreadyConsumed); + + let revision_plus1 = builder.ins().iadd_imm(revision, 1); + let overflow = builder + .ins() + .icmp_imm(IntCC::UnsignedLessThan, revision_plus1, 1 << 16); + builder.ins().trapz(overflow, ir::TrapCode::IntegerOverflow); // TODO(dhil): Consider introducing a designated trap code. + builder.ins().store(mem_flags, revision_plus1, contref, 0); + revision_plus1 + } +} + fn typed_continuations_load_payloads<'a>( env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, @@ -194,9 +233,9 @@ pub(crate) fn translate_resume<'a>( let forwarding_block = builder.create_block(); // Prelude: Push the continuation arguments. - { - let resumee_fiber = - shared::typed_continuations_cont_obj_get_cont_ref(env, builder, resumee_obj); + let next_revision = { + let (witness, resumee_fiber) = disassemble_contobj(env, builder, resumee_obj); + let next_revision = compare_revision_and_increment(env, builder, resumee_fiber, witness); if resume_args.len() > 0 { let nargs = builder.ins().iconst(I64, resume_args.len() as i64); @@ -221,7 +260,8 @@ pub(crate) fn translate_resume<'a>( } builder.ins().jump(resume_block, &[resumee_fiber]); - } + next_revision + }; // Resume block: here we continue the (suspended) resumee. let (tag, resumee_fiber) = { @@ -298,7 +338,7 @@ pub(crate) fn translate_resume<'a>( let mut args = typed_continuations_load_payloads(env, builder, ¶m_types); // Create and push the continuation object. - let resumee_obj = shared::typed_continuations_new_cont_obj(env, builder, resumee_fiber); + let resumee_obj = assemble_contobj(env, builder, next_revision, resumee_fiber); args.push(resumee_obj); // Finally, emit the jump to `label`. @@ -378,11 +418,11 @@ pub(crate) fn translate_cont_bind<'a>( args: &[ir::Value], remaining_arg_count: usize, ) -> ir::Value { - let contref = typed_continuations_cont_obj_get_cont_ref(env, builder, contobj); + let (witness, contref) = disassemble_contobj(env, builder, contobj); + let revision = compare_revision_and_increment(env, builder, contref, witness); let remaining_arg_count = builder.ins().iconst(I32, remaining_arg_count as i64); typed_continuations_store_resume_args(env, builder, args, remaining_arg_count, contref); - - typed_continuations_new_cont_obj(env, builder, contref) + assemble_contobj(env, builder, revision, contref) } pub(crate) fn translate_cont_new<'a>( @@ -397,7 +437,8 @@ pub(crate) fn translate_cont_new<'a>( let nargs = builder.ins().iconst(I64, arg_types.len() as i64); let nreturns = builder.ins().iconst(I64, return_types.len() as i64); call_builtin!(builder, env, let contref = tc_baseline_cont_new(func, nargs, nreturns)); - let contobj = typed_continuations_new_cont_obj(env, builder, contref); + let revision = get_revision(env, builder, contref); + let contobj = assemble_contobj(env, builder, revision, contref); Ok(contobj) } diff --git a/crates/cranelift/src/wasmfx/optimized.rs b/crates/cranelift/src/wasmfx/optimized.rs index d6cc6cf153ad..1c52a7ea3b9f 100644 --- a/crates/cranelift/src/wasmfx/optimized.rs +++ b/crates/cranelift/src/wasmfx/optimized.rs @@ -8,8 +8,6 @@ use cranelift_codegen::ir::InstBuilder; use cranelift_frontend::{FunctionBuilder, Switch}; use cranelift_wasm::FuncEnvironment; use cranelift_wasm::{FuncTranslationState, WasmResult, WasmValType}; -use shared::typed_continuations_cont_obj_get_cont_ref; -use shared::typed_continuations_new_cont_obj; use wasmtime_environ::PtrSize; #[macro_use] @@ -76,7 +74,7 @@ pub(crate) mod typed_continuation_helpers { val: ir::Value| { let ty = builder.func.dfg.value_type(val); let val = match ty { - I32 => builder.ins().uextend(I64, val), + I8 | I32 => builder.ins().uextend(I64, val), I64 => val, _ => panic!("Cannot print type {}", ty), }; @@ -419,6 +417,57 @@ pub(crate) mod typed_continuation_helpers { let offset = wasmtime_continuations::offsets::vm_cont_ref::PARENT_CHAIN as i32; new_stack_chain.store(env, builder, self.address, offset) } + + /// Gets the revision counter the a given continuation + /// reference. + pub fn get_revision<'a>( + &mut self, + _env: &mut crate::func_environ::FuncEnvironment<'a>, + builder: &mut FunctionBuilder, + ) -> ir::Value { + if cfg!(feature = "unsafe_disable_continuation_linearity_check") { + builder.ins().iconst(I64, 0) + } else { + let mem_flags = ir::MemFlags::trusted(); + let offset = wasmtime_continuations::offsets::vm_cont_ref::REVISION as i32; + let revision = builder.ins().load(I64, mem_flags, self.address, offset); + revision + } + } + + /// Sets the revision counter on the given continuation + /// reference to `revision + 1`. + pub fn incr_revision<'a>( + &mut self, + env: &mut crate::func_environ::FuncEnvironment<'a>, + builder: &mut FunctionBuilder, + revision: ir::Value, + ) -> ir::Value { + if cfg!(feature = "unsafe_disable_continuation_linearity_check") { + builder.ins().iconst(I64, 0) + } else { + if cfg!(debug_assertions) { + let actual_revision = self.get_revision(env, builder); + emit_debug_assert_eq!(env, builder, revision, actual_revision); + } + let mem_flags = ir::MemFlags::trusted(); + let offset = wasmtime_continuations::offsets::vm_cont_ref::REVISION as i32; + let revision_plus1 = builder.ins().iadd_imm(revision, 1); + builder + .ins() + .store(mem_flags, revision_plus1, self.address, offset); + if cfg!(debug_assertions) { + let new_revision = self.get_revision(env, builder); + emit_debug_assert_eq!(env, builder, revision_plus1, new_revision); + } + let overflow = + builder + .ins() + .icmp_imm(IntCC::UnsignedLessThan, revision_plus1, 1 << 16); + builder.ins().trapz(overflow, ir::TrapCode::IntegerOverflow); // TODO(dhil): Consider introducing a designated trap code. + revision_plus1 + } + } } impl Payloads { @@ -1268,11 +1317,31 @@ pub(crate) fn translate_cont_bind<'a>( args: &[ir::Value], remaining_arg_count: usize, ) -> ir::Value { - let contref = typed_continuations_cont_obj_get_cont_ref(env, builder, contobj); + //let contref = typed_continuations_cont_obj_get_cont_ref(env, builder, contobj); + let (witness, contref) = shared::disassemble_contobj(env, builder, contobj); + let mut vmcontref = tc::VMContRef::new(contref, env.pointer_type()); + let revision = vmcontref.get_revision(env, builder); + let evidence = builder.ins().icmp(IntCC::Equal, witness, revision); + emit_debug_println!( + env, + builder, + "[cont_bind] witness = {}, revision = {}, evidence = {}", + witness, + revision, + evidence + ); + builder + .ins() + .trapz(evidence, ir::TrapCode::ContinuationAlreadyConsumed); + let remaining_arg_count = builder.ins().iconst(I32, remaining_arg_count as i64); typed_continuations_store_resume_args(env, builder, args, remaining_arg_count, contref); - typed_continuations_new_cont_obj(env, builder, contref) + let revision = vmcontref.incr_revision(env, builder, revision); + emit_debug_println!(env, builder, "new revision = {}", revision); + let contobj = shared::assemble_contobj(env, builder, revision, contref); + emit_debug_println!(env, builder, "[cont_bind] contobj = {:p}", contobj); + contobj } pub(crate) fn translate_cont_new<'a>( @@ -1286,7 +1355,9 @@ pub(crate) fn translate_cont_new<'a>( let nargs = builder.ins().iconst(I32, arg_types.len() as i64); let nreturns = builder.ins().iconst(I32, return_types.len() as i64); call_builtin!(builder, env, let contref = tc_cont_new(func, nargs, nreturns)); - let contobj = typed_continuations_new_cont_obj(env, builder, contref); + let tag = tc::VMContRef::new(contref, env.pointer_type()).get_revision(env, builder); + let contobj = shared::assemble_contobj(env, builder, tag, contref); + emit_debug_println!(env, builder, "[cont_new] contobj = {:p}", contobj); Ok(contobj) } @@ -1308,9 +1379,20 @@ pub(crate) fn translate_resume<'a>( // Preamble: Part of previously active block - let (resume_contref, parent_stack_chain) = { - let resume_contref = - shared::typed_continuations_cont_obj_get_cont_ref(env, builder, contobj); + let (next_revision, resume_contref, parent_stack_chain) = { + let (witness, resume_contref) = shared::disassemble_contobj(env, builder, contobj); + + let mut vmcontref = tc::VMContRef::new(resume_contref, env.pointer_type()); + + let revision = vmcontref.get_revision(env, builder); + let evidence = builder.ins().icmp(IntCC::Equal, revision, witness); + emit_debug_println!(env, builder, "[resume] contobj = {:p}, resume_contref = {:p} witness = {}, revision = {}, evidence = {}", contobj, resume_contref, witness, revision, evidence); + + builder + .ins() + .trapz(evidence, ir::TrapCode::ContinuationAlreadyConsumed); + let next_revision = vmcontref.incr_revision(env, builder, revision); + emit_debug_println!(env, builder, "[resume] new revision = {}", next_revision); if resume_args.len() > 0 { // We store the arguments in the `VMContRef` to be resumed. @@ -1322,14 +1404,10 @@ pub(crate) fn translate_resume<'a>( let original_stack_chain = tc::VMContext::new(vmctx, env.pointer_type()).load_stack_chain(env, builder); original_stack_chain.assert_not_absent(env, builder); - tc::VMContRef::new(resume_contref, env.pointer_type()).set_parent_stack_chain( - env, - builder, - &original_stack_chain, - ); + vmcontref.set_parent_stack_chain(env, builder, &original_stack_chain); builder.ins().jump(resume_block, &[]); - (resume_contref, original_stack_chain) + (next_revision, resume_contref, original_stack_chain) }; // Resume block: actually resume the fiber corresponding to the @@ -1499,12 +1577,19 @@ pub(crate) fn translate_resume<'a>( // link to `StackChain::Absent`. let pointer_type = env.pointer_type(); let chain = tc::StackChain::absent(builder, pointer_type); - tc::VMContRef::new(resume_contref, pointer_type) - .set_parent_stack_chain(env, builder, &chain); + let mut vmcontref = tc::VMContRef::new(resume_contref, pointer_type); + vmcontref.set_parent_stack_chain(env, builder, &chain); // Create and push the continuation object. We only create // them here because we don't need them when forwarding. - let contobj = typed_continuations_new_cont_obj(env, builder, resume_contref); + let contobj = shared::assemble_contobj(env, builder, next_revision, resume_contref); + emit_debug_println!( + env, + builder, + "[resume] revision = {}, contobj = {:p}", + next_revision, + contobj + ); args.push(contobj); diff --git a/crates/cranelift/src/wasmfx/shared.rs b/crates/cranelift/src/wasmfx/shared.rs index e34133e82dd8..64d0d486151e 100644 --- a/crates/cranelift/src/wasmfx/shared.rs +++ b/crates/cranelift/src/wasmfx/shared.rs @@ -25,31 +25,60 @@ macro_rules! call_builtin { #[allow(unused_imports)] pub(crate) use call_builtin; -/// TODO -pub(crate) fn typed_continuations_cont_obj_get_cont_ref<'a>( +struct TaggedPointer; + +type Uintptr = u64; +const UINTPTR_MAX: u64 = 18_446_744_073_709_551_615u64; + +impl<'a> TaggedPointer { + const HB_TAG_BITS: u64 = 16; + const HB_POINTER_BITS: u64 = 48; + const HB_POINTER_MASK: Uintptr = (UINTPTR_MAX >> Self::HB_TAG_BITS); + + pub fn untag( + _env: &mut crate::func_environ::FuncEnvironment<'a>, + builder: &mut FunctionBuilder, + ptr: ir::Value, + ) -> (ir::Value, ir::Value) { + let tag = builder.ins().ushr_imm(ptr, Self::HB_POINTER_BITS as i64); + let unmasked = builder.ins().band_imm(ptr, Self::HB_POINTER_MASK as i64); + (tag, unmasked) + } + + pub fn with_tag( + _env: &mut crate::func_environ::FuncEnvironment<'a>, + builder: &mut FunctionBuilder, + tag: ir::Value, + ptr: ir::Value, + ) -> ir::Value { + let tag = builder.ins().ishl_imm(tag, Self::HB_POINTER_BITS as i64); + builder.ins().bor(ptr, tag) + } +} + +pub(crate) fn disassemble_contobj<'a>( env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, contobj: ir::Value, -) -> ir::Value { +) -> (ir::Value, ir::Value) { if cfg!(feature = "unsafe_disable_continuation_linearity_check") { - // The "contobj" is a contref already - return contobj; + let zero = builder.ins().iconst(cranelift_codegen::ir::types::I64, 0); + (zero, contobj) } else { - call_builtin!(builder, env, let result = tc_cont_obj_get_cont_ref(contobj)); - return result; + TaggedPointer::untag(env, builder, contobj) } } -pub(crate) fn typed_continuations_new_cont_obj<'a>( +pub(crate) fn assemble_contobj<'a>( env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, + count: ir::Value, contref_addr: ir::Value, ) -> ir::Value { if cfg!(feature = "unsafe_disable_continuation_linearity_check") { - return contref_addr; + contref_addr } else { - call_builtin!(builder, env, let result = tc_new_cont_obj(contref_addr)); - return result; + TaggedPointer::with_tag(env, builder, count, contref_addr) } } diff --git a/crates/environ/src/builtin.rs b/crates/environ/src/builtin.rs index 2e50477bf6e1..9dd365478a3d 100644 --- a/crates/environ/src/builtin.rs +++ b/crates/environ/src/builtin.rs @@ -105,13 +105,6 @@ macro_rules! foreach_builtin_function { tc_resume(vmctx: vmctx, contref: pointer, parent_stack_limits: pointer) -> i64; // Suspends a continuation. tc_suspend(vmctx: vmctx, tag: i32); - // Returns the continuation reference corresponding to the given continuation object. - tc_cont_obj_get_cont_ref(vmctx: vmctx, contobj: pointer) -> pointer; - // Drops the given continuation reference. Currently unused. - //cont_ref_drop(vmctx: vmctx, contref: pointer); - // Creates a new continuation object. - tc_new_cont_obj(vmctx: vmctx, contref: pointer) -> pointer; - // Sets the tag return values of `child_contref` to those of `parent_contref`. // This is implemented by exchanging the pointers to the underlying buffers. diff --git a/crates/environ/src/trap_encoding.rs b/crates/environ/src/trap_encoding.rs index 3cb9b1a243c4..1665128c021f 100644 --- a/crates/environ/src/trap_encoding.rs +++ b/crates/environ/src/trap_encoding.rs @@ -85,6 +85,9 @@ pub enum Trap { /// We are suspending to a tag for which there is no active handler. UnhandledTag, + + /// Attempt to resume a continuation twice. + ContinuationAlreadyConsumed, // if adding a variant here be sure to update the `check!` macro below } @@ -120,6 +123,7 @@ impl Trap { NullI31Ref CannotEnterComponent UnhandledTag + ContinuationAlreadyConsumed } None @@ -149,6 +153,7 @@ impl fmt::Display for Trap { NullI31Ref => "null i31 reference", CannotEnterComponent => "cannot enter component instance", UnhandledTag => "unhandled tag", + ContinuationAlreadyConsumed => "continuation already consumed", }; write!(f, "wasm trap: {desc}") } diff --git a/crates/wasmtime/src/runtime/vm/continuation.rs b/crates/wasmtime/src/runtime/vm/continuation.rs index 58fb50521f22..6fde7bb5339e 100644 --- a/crates/wasmtime/src/runtime/vm/continuation.rs +++ b/crates/wasmtime/src/runtime/vm/continuation.rs @@ -8,13 +8,6 @@ cfg_if::cfg_if! { } } -/// M:1 Many-to-one mapping. A single VMContRef may be -/// referenced by multiple VMContObj, though, only one -/// VMContObj may hold a non-null reference to the object -/// at a given time. -#[repr(C)] -pub struct VMContObj(pub Option<*mut imp::VMContRef>); - #[cfg(not(feature = "wasmfx_baseline"))] pub mod optimized { use super::stack_chain::StackChain; @@ -59,6 +52,9 @@ pub mod optimized { /// Indicates the state of this continuation. pub state: State, + + /// Revision counter. + pub revision: u64, } /// TODO @@ -163,6 +159,7 @@ pub mod optimized { let tsp = fiber.stack().top().unwrap(); let stack_limit = unsafe { tsp.sub(stack_size - red_zone_size) } as usize; let contref = Box::new(VMContRef { + revision: 0, limits: StackLimits::with_stack_limit(stack_limit), fiber, parent_chain: StackChain::Absent, @@ -306,7 +303,12 @@ pub mod optimized { std::mem::size_of::(), CONTINUATION_FIBER_SIZE ); - assert_eq!(std::mem::size_of::(), STACK_CHAIN_SIZE); + assert_eq!(core::mem::size_of::(), STACK_CHAIN_SIZE); + + assert_eq!( + memoffset::offset_of!(VMContRef, revision), + vm_cont_ref::REVISION + ); } } @@ -329,6 +331,8 @@ pub mod baseline { /// wasmtime_fiber::Fiber, a suspend object, a parent pointer, an /// arguments buffer, and a return buffer. pub struct VMContRef { + /// Revision counter. + pub revision: u64, pub fiber: Box, pub suspend: *mut Yield, pub limits: StackLimits, @@ -405,6 +409,7 @@ pub mod baseline { }; let contref = Box::new(VMContRef { + revision: 0, limits: StackLimits::with_stack_limit(0), parent_chain: StackChain::Absent, parent: core::ptr::null_mut(), diff --git a/crates/wasmtime/src/runtime/vm/libcalls.rs b/crates/wasmtime/src/runtime/vm/libcalls.rs index a5d0c359c54b..f77f492d289d 100644 --- a/crates/wasmtime/src/runtime/vm/libcalls.rs +++ b/crates/wasmtime/src/runtime/vm/libcalls.rs @@ -863,52 +863,6 @@ fn tc_suspend(instance: &mut Instance, tag_index: u32) -> Result<(), TrapReason> crate::vm::continuation::optimized::suspend(instance, tag_index) } -fn tc_new_cont_obj(_instance: &mut Instance, contref: *mut u8) -> *mut u8 { - // If this is enabled, we should never call this function. - assert!(!cfg!( - feature = "unsafe_disable_continuation_linearity_check" - )); - - let contobj = alloc::boxed::Box::new(crate::vm::continuation::VMContObj(Some( - contref.cast::(), - ))); - alloc::boxed::Box::into_raw(contobj).cast::() -} - -fn tc_cont_obj_get_cont_ref( - _instance: &mut Instance, - contobj: *mut u8, -) -> Result<*mut u8, TrapReason> { - // If this is enabled, we should never call this function. - assert!(!cfg!( - feature = "unsafe_disable_continuation_linearity_check" - )); - - let contobj = contobj.cast::(); - let contopt = unsafe { - contobj - .as_mut() - .ok_or_else(|| { - TrapReason::user_without_backtrace(anyhow::anyhow!( - "Attempt to dereference null VMContObj!" - )) - })? - .0 - }; - match contopt { - None => Err(TrapReason::user_without_backtrace(anyhow::Error::msg( - "continuation already consumed", - ))), // TODO(dhil): presumably we can set things up such that - // we always read from a non-null reference. - Some(contref) => { - unsafe { - *contobj = crate::vm::continuation::VMContObj(None); - } - Ok(contref.cast::()) - } - } -} - fn tc_cont_ref_forward_tag_return_values_buffer( _instance: &mut Instance, parent_contref: *mut u8, diff --git a/tests/all/typed_continuations.rs b/tests/all/typed_continuations.rs index e4c5f4a52a87..50b0b827463d 100644 --- a/tests/all/typed_continuations.rs +++ b/tests/all/typed_continuations.rs @@ -1128,3 +1128,58 @@ mod traps { Ok(()) } } + +mod misc { + use super::test_utils::*; + use wasmtime::*; + + #[test] + pub fn continuation_revision_counter_wraparound() -> Result<()> { + let wat = r#" +(module + (type $ft (func)) + (type $ct (cont $ft)) + + (tag $yield) + + (func $loop + (loop $loop + (suspend $yield) + (br $loop) + ) + ) + (elem declare func $loop) + + ;; Loops 65536 times to overflow the 16 bit revision counter on the continuation reference. + (func (export "entry") + (local $k (ref $ct)) + (local $i i32) + (local.set $k (cont.new $ct (ref.func $loop))) + (loop $go-again + (block $on-yield (result (ref $ct)) + (resume $ct (tag $yield $on-yield) (local.get $k)) + (unreachable) + ) + (local.set $k) + (local.set $i (i32.add (i32.const 1) (local.get $i))) + (br_if $go-again (i32.lt_u (local.get $i) (i32.const 65536))) + ) + ) +) +"#; + + let runner = Runner::new(); + cfg_if::cfg_if! { + if #[cfg(feature = "unsafe_disable_continuation_linearity_check")] { + let result = runner.run_test::<()>(wat, &[])?; + assert_eq!(result, ()) + } else { + let error = runner.run_test::<()>(wat, &[]) + .expect_err("expected an overflow"); + assert!(error.root_cause().is::()); + assert_eq!(*error.downcast_ref::().unwrap(), Trap::IntegerOverflow); + } + } + Ok(()) + } +}