From 5ec1917fe661b4774d51565bff1141b00a1d431c Mon Sep 17 00:00:00 2001 From: Frank Emrich Date: Tue, 11 Jul 2023 10:59:31 +0100 Subject: [PATCH] Implement cont.bind (#41) This patch implements the `cont.bind` operator. --- cranelift/filetests/src/test_wasm/env.rs | 24 +++- cranelift/wasm/src/code_translator.rs | 29 ++++- cranelift/wasm/src/environ/dummy.rs | 12 +- cranelift/wasm/src/environ/spec.rs | 11 +- crates/cranelift/src/func_environ.rs | 58 +++++++++- crates/environ/src/builtin.rs | 20 +++- crates/runtime/src/continuation.rs | 106 ++++++++++++++---- crates/runtime/src/libcalls.rs | 30 +++++ .../typed-continuations/cont_bind1.wast | 30 +++++ .../typed-continuations/cont_bind2.wast | 31 +++++ .../typed-continuations/cont_bind3.wast | 40 +++++++ .../typed-continuations/cont_bind4.wast | 103 +++++++++++++++++ 12 files changed, 453 insertions(+), 41 deletions(-) create mode 100644 tests/misc_testsuite/typed-continuations/cont_bind1.wast create mode 100644 tests/misc_testsuite/typed-continuations/cont_bind2.wast create mode 100644 tests/misc_testsuite/typed-continuations/cont_bind3.wast create mode 100644 tests/misc_testsuite/typed-continuations/cont_bind4.wast diff --git a/cranelift/filetests/src/test_wasm/env.rs b/cranelift/filetests/src/test_wasm/env.rs index dafbecf0263a..4462782a4909 100644 --- a/cranelift/filetests/src/test_wasm/env.rs +++ b/cranelift/filetests/src/test_wasm/env.rs @@ -698,8 +698,8 @@ impl<'a> FuncEnvironment for FuncEnv<'a> { builder: &mut cranelift_frontend::FunctionBuilder, state: &cranelift_wasm::FuncTranslationState, tag_index: u32, - ) { - self.inner.translate_suspend(builder, state, tag_index) + ) -> ir::Value { + return self.inner.translate_suspend(builder, state, tag_index); } /// TODO @@ -767,10 +767,15 @@ impl<'a> FuncEnvironment for FuncEnv<'a> { &mut self, builder: &mut cranelift_frontend::FunctionBuilder, values: &[ir::Value], + remaining_arg_count: ir::Value, contobj: ir::Value, ) { - self.inner - .typed_continuations_store_resume_args(builder, values, contobj) + self.inner.typed_continuations_store_resume_args( + builder, + values, + remaining_arg_count, + contobj, + ) } /// TODO @@ -792,4 +797,15 @@ impl<'a> FuncEnvironment for FuncEnv<'a> { self.inner .typed_continuations_cont_ref_get_cont_obj(builder, contref) } + + fn typed_continuations_load_tag_return_values( + &mut self, + builder: &mut cranelift_frontend::FunctionBuilder, + contobj: ir::Value, + valtypes: &[wasmtime_types::WasmType], + ) -> Vec { + return self + .inner + .typed_continuations_load_tag_return_values(builder, contobj, valtypes); + } } diff --git a/cranelift/wasm/src/code_translator.rs b/cranelift/wasm/src/code_translator.rs index c96ac4420b27..3a3c67be1603 100644 --- a/cranelift/wasm/src/code_translator.rs +++ b/cranelift/wasm/src/code_translator.rs @@ -2589,18 +2589,37 @@ pub fn translate_operator( environ.typed_continuations_store_payloads(builder, ¶m_types, params); state.popn(param_count); - environ.translate_suspend(builder, state, *tag_index); + let vmctx = environ.translate_suspend(builder, state, *tag_index); + + let contobj = environ.typed_continuations_load_continuation_object(builder, vmctx); let return_types = environ.tag_returns(*tag_index).to_vec(); - let return_values = environ.typed_continuations_load_payloads(builder, &return_types); + let return_values = + environ.typed_continuations_load_tag_return_values(builder, contobj, &return_types); state.pushn(&return_values); } Operator::ContBind { - src_index: _, - dst_index: _, + src_index, + dst_index, + } => { + let src_arity = environ.continuation_arguments(*src_index).len(); + let dst_arity = environ.continuation_arguments(*dst_index).len(); + let arg_count = src_arity - dst_arity; + + let (original_contref, args) = state.peekn(arg_count + 1).split_last().unwrap(); + let contobj = + environ.typed_continuations_cont_ref_get_cont_obj(builder, *original_contref); + + let src_arity_value = builder.ins().iconst(I32, src_arity as i64); + environ.typed_continuations_store_resume_args(builder, args, src_arity_value, contobj); + + let new_contref = environ.typed_continuations_new_cont_ref(builder, contobj); + + state.popn(arg_count + 1); + state.push1(new_contref); } - | Operator::ResumeThrow { + Operator::ResumeThrow { type_index: _, tag_index: _, resumetable: _, diff --git a/cranelift/wasm/src/environ/dummy.rs b/cranelift/wasm/src/environ/dummy.rs index d4210c65c2e4..7612fe9388e2 100644 --- a/cranelift/wasm/src/environ/dummy.rs +++ b/cranelift/wasm/src/environ/dummy.rs @@ -710,7 +710,7 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ _builder: &mut FunctionBuilder, _state: &FuncTranslationState, _tag_index: u32, - ) { + ) -> ir::Value { todo!() } @@ -738,10 +738,20 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ todo!() } + fn typed_continuations_load_tag_return_values( + &mut self, + _builder: &mut FunctionBuilder, + _contobj: ir::Value, + _valtypes: &[WasmType], + ) -> Vec { + todo!() + } + fn typed_continuations_store_resume_args( &mut self, _builder: &mut FunctionBuilder, _values: &[ir::Value], + _remaining_arg_count: ir::Value, _contref: ir::Value, ) { todo!() diff --git a/cranelift/wasm/src/environ/spec.rs b/cranelift/wasm/src/environ/spec.rs index 3d51d2c03c70..0ffe16b32220 100644 --- a/cranelift/wasm/src/environ/spec.rs +++ b/cranelift/wasm/src/environ/spec.rs @@ -602,7 +602,7 @@ pub trait FuncEnvironment: TargetEnvironment { builder: &mut FunctionBuilder, state: &FuncTranslationState, tag_index: u32, - ); + ) -> ir::Value; /// TODO fn continuation_arguments(&self, type_index: u32) -> &[wasmtime_types::WasmType]; @@ -625,6 +625,14 @@ pub trait FuncEnvironment: TargetEnvironment { valtypes: &[wasmtime_types::WasmType], ) -> std::vec::Vec; + /// TODO + fn typed_continuations_load_tag_return_values( + &mut self, + builder: &mut FunctionBuilder, + contobj: ir::Value, + valtypes: &[wasmtime_types::WasmType], + ) -> std::vec::Vec; + /// TODO fn typed_continuations_store_payloads( &mut self, @@ -638,6 +646,7 @@ pub trait FuncEnvironment: TargetEnvironment { &mut self, builder: &mut FunctionBuilder, values: &[ir::Value], + remaining_arg_count: ir::Value, contobj: ir::Value, ); diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index 274b882ddf20..eda1689dde29 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -2292,7 +2292,8 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m // Second: Call the `resume` builtin if call_args.len() > 0 { - self.typed_continuations_store_resume_args(builder, call_args, contobj); + let count = builder.ins().iconst(I32, call_args.len() as i64); + self.typed_continuations_store_resume_args(builder, call_args, count, contobj); } let (vmctx, result) = generate_builtin_call!(self, builder, resume, [contobj]); @@ -2328,10 +2329,11 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m builder: &mut FunctionBuilder, _state: &FuncTranslationState, tag_index: u32, - ) { + ) -> ir::Value { let tag_index = builder.ins().iconst(I32, tag_index as i64); - generate_builtin_call_no_return_val!(self, builder, suspend, [tag_index]); + // Returns the vmctx + return generate_builtin_call_no_return_val!(self, builder, suspend, [tag_index]); } fn continuation_arguments(&self, index: u32) -> &[WasmType] { @@ -2390,6 +2392,47 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m values } + fn typed_continuations_load_tag_return_values( + &mut self, + builder: &mut FunctionBuilder, + contobj: ir::Value, + valtypes: &[WasmType], + ) -> Vec { + let memflags = ir::MemFlags::trusted(); + let mut values = vec![]; + + if valtypes.len() > 0 { + let nargs = builder.ins().iconst(I32, valtypes.len() as i64); + + let (_vmctx, payload_ptr) = generate_builtin_call!( + self, + builder, + cont_obj_get_tag_return_values_buffer, + [contobj, nargs] + ); + + let mut offset = 0; + for valtype in valtypes { + let val = builder.ins().load( + super::value_type(self.isa, *valtype), + memflags, + payload_ptr, + offset, + ); + values.push(val); + offset += self.offsets.ptr.maximum_value_size() as i32; + } + + generate_builtin_call_no_return_val!( + self, + builder, + cont_obj_deallocate_tag_return_values_buffer, + [contobj] + ); + } + values + } + /// TODO fn typed_continuations_cont_ref_get_cont_obj( &mut self, @@ -2406,6 +2449,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m &mut self, builder: &mut FunctionBuilder, values: &[ir::Value], + remaining_arg_count: ir::Value, contobj: ir::Value, ) { let nargs = builder.ins().iconst(I32, values.len() as i64); @@ -2438,8 +2482,12 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m { builder.switch_to_block(use_payloads_block); builder.seal_block(use_payloads_block); - let (_vmctx, ptr) = - generate_builtin_call!(self, builder, alllocate_payload_buffer, [nargs]); + let (_vmctx, ptr) = generate_builtin_call!( + self, + builder, + cont_obj_occupy_next_tag_returns_slots, + [contobj, nargs, remaining_arg_count] + ); builder.ins().jump(store_data_block, &[ptr]); } diff --git a/crates/environ/src/builtin.rs b/crates/environ/src/builtin.rs index 5716d88682fc..ad695266fe1d 100644 --- a/crates/environ/src/builtin.rs +++ b/crates/environ/src/builtin.rs @@ -52,6 +52,7 @@ macro_rules! foreach_builtin_function { /// Invoked when we reach a new epoch. new_epoch(vmctx: vmctx) -> i64; /// Creates a new continuation from a funcref. + cont_new(vmctx: vmctx, r: pointer, param_count: i64, result_count: i64) -> pointer; /// Resumes a continuation. resume(vmctx: vmctx, contobj: pointer) -> i32; @@ -74,20 +75,37 @@ macro_rules! foreach_builtin_function { //cont_obj_drop(vmctx: vmctx, contobj: pointer); /// Crates a new continuation reference. new_cont_ref(vmctx: vmctx, contobj: pointer) -> pointer; + + /// Allocates a buffer large enough for storing `element_count` tag /// payloads and stores it in the `VMContext` in such a way that /// subsequent calls to `get_payload_buffer` will return the same /// buffer. /// Returns a pointer to that buffer. + /// Such a payload buffer is only used to store payloads provided + /// at a suspend site and read in a corresponding handler. alllocate_payload_buffer(vmctx: vmctx, element_count: i32) -> pointer; /// Counterpart to `alllocate_payload_buffer`, deallocating the /// buffer. For debugging purposes, `expected_element_capacity` /// should be the same value passed when allocating. dealllocate_payload_buffer(vmctx: vmctx, expected_element_capacity: i32); - /// Returns pointer to the payload buffer. For debugging purposes, + /// Returns pointer to the payload buffer, whose function was described earlier. /// `expected_element_capacity` should be the same value passed when /// allocating. get_payload_buffer(vmctx: vmctx, expected_element_capacity: i32) -> pointer; + + + /// Returns a pointer to the next empty slot within the tag return value buffer + /// of the given continuation object. + /// Such a buffer is used to store payloads provided by cont.bind and resume + /// and received at a suspend site. + /// The next `arg_count` slots within the buffer are marked as used. + /// If no such buffer currently exists, a new one is allocated. + cont_obj_occupy_next_tag_returns_slots(vmctx: vmctx, contobj: pointer, arg_count : i32, remaining_arg_count : i32) -> pointer; + /// Returns a pointer to the beginning of the tag return value buffer + cont_obj_get_tag_return_values_buffer(vmctx: vmctx, contobj: pointer, expected_count : i32) -> pointer; + /// Deallocated the tag return value buffer within the continuation object. + cont_obj_deallocate_tag_return_values_buffer(vmctx: vmctx, contobj: pointer); } }; } diff --git a/crates/runtime/src/continuation.rs b/crates/runtime/src/continuation.rs index 76e7e4ed9f4a..e9ad9d61d68e 100644 --- a/crates/runtime/src/continuation.rs +++ b/crates/runtime/src/continuation.rs @@ -11,21 +11,36 @@ use wasmtime_fibre::{Fiber, FiberStack, Suspend}; type ContinuationFiber = Fiber<'static, (), u32, ()>; type Yield = Suspend<(), u32, ()>; -struct Args { +struct Payloads { length: usize, capacity: usize, /// This is null if and only if capacity (and thus also `length`) are 0. data: *mut u128, } -impl Args { - fn empty() -> Args { - return Args { +impl Payloads { + fn new(capacity: usize) -> Payloads { + let data = if capacity == 0 { + ptr::null_mut() + } else { + let mut args = Vec::with_capacity(capacity); + let args_ptr = args.as_mut_ptr(); + args.leak(); + args_ptr + }; + return Payloads { length: 0, - capacity: 0, - data: ptr::null_mut(), + capacity, + data, }; } + + fn occupy_next(&mut self, count: usize) -> *mut u128 { + let original_length = self.length; + assert!(self.length + count <= self.capacity); + self.length += count; + return unsafe { self.data.offset(original_length as isize) }; + } } /// Encodes the life cycle of a `ContinuationObject`. @@ -54,7 +69,12 @@ pub struct ContinuationObject { /// 1. The arguments to the function passed to cont.new /// 2. The return values of that function /// Note that this is *not* used for tag payloads. - args: Args, + args: Payloads, + + // Once a continuation is suspended, this buffer is used to hold payloads + // provided by cont.bind and resume and received at the suspend site. + // In particular, this may only be Some when `state` is `Invoked`. + tag_return_values: Option>, state: State, } @@ -102,10 +122,47 @@ pub fn cont_obj_occupy_next_args_slots( arg_count: usize, ) -> *mut u128 { assert!(unsafe { (*obj).state == State::Allocated }); - let args_len = unsafe { (*obj).args.length }; - unsafe { (*obj).args.length += arg_count }; - assert!(unsafe { (*obj).args.length <= (*obj).args.capacity }); - unsafe { (*obj).args.data.offset(args_len as isize) } + let args = &mut unsafe { obj.as_mut() }.unwrap().args; + return args.occupy_next(arg_count); +} + +/// TODO +#[inline(always)] +pub fn cont_obj_occupy_next_tag_returns_slots( + obj: *mut ContinuationObject, + arg_count: usize, + remaining_arg_count: usize, +) -> *mut u128 { + let obj = unsafe { obj.as_mut().unwrap() }; + assert!(obj.state == State::Invoked); + let payloads = obj + .tag_return_values + .get_or_insert_with(|| Box::new(Payloads::new(remaining_arg_count))); + return payloads.occupy_next(arg_count); +} + +/// TODO +pub fn cont_obj_get_tag_return_values_buffer( + obj: *mut ContinuationObject, + expected_value_count: usize, +) -> *mut u128 { + let obj = unsafe { obj.as_mut().unwrap() }; + assert!(obj.state == State::Invoked); + + let payloads = &mut obj.tag_return_values.as_ref().unwrap(); + assert_eq!(payloads.length, expected_value_count); + assert_eq!(payloads.length, payloads.capacity); + assert!(!payloads.data.is_null()); + return payloads.data; +} + +/// TODO +pub fn cont_obj_deallocate_tag_return_values_buffer(obj: *mut ContinuationObject) { + let obj = unsafe { obj.as_mut().unwrap() }; + assert!(obj.state == State::Invoked); + let existing = obj.tag_return_values.take().unwrap(); + mem::drop((*existing).data); + obj.tag_return_values = None; } /// TODO @@ -133,8 +190,12 @@ pub fn drop_cont_obj(contobj: *mut ContinuationObject) { mem::drop(unsafe { (*contobj).fiber }); unsafe { mem::drop((*contobj).args.data); + }; + let tag_return_vals = &mut unsafe { contobj.as_mut().unwrap() }.tag_return_values; + match tag_return_vals { + None => (), + Some(b) => mem::drop((*b).data), } - mem::drop(contobj) } /// TODO @@ -212,18 +273,7 @@ pub fn cont_new( }; let capacity = cmp::max(param_count, result_count); - let payload = if capacity == 0 { - Args::empty() - } else { - let mut args = Vec::with_capacity(capacity); - let args_ptr = args.as_mut_ptr(); - args.leak(); - Args { - length: 0, - capacity, - data: args_ptr, - } - }; + let payload = Payloads::new(capacity); let args_ptr = payload.data; let fiber = Box::new( @@ -239,6 +289,7 @@ pub fn cont_new( let contobj = Box::new(ContinuationObject { fiber: Box::into_raw(fiber), args: payload, + tag_return_values: None, state: State::Allocated, }); let contref = new_cont_ref(Box::into_raw(contobj)); @@ -264,6 +315,13 @@ pub fn resume( .get_mut()) = 0 }; unsafe { (*contobj).state = State::Invoked }; + // This is to make sure that after we resume from a suspend, we can load the continuation object + // to access the tag return values. + unsafe { + let cont_store_ptr = + instance.get_typed_continuations_store_mut() as *mut *mut ContinuationObject; + cont_store_ptr.write(contobj) + }; match unsafe { fiber.as_mut().unwrap().resume(()) } { Ok(()) => { // The result of the continuation was written to the first diff --git a/crates/runtime/src/libcalls.rs b/crates/runtime/src/libcalls.rs index a6260288edca..e730565be5ef 100644 --- a/crates/runtime/src/libcalls.rs +++ b/crates/runtime/src/libcalls.rs @@ -688,6 +688,36 @@ fn cont_obj_has_state_invoked( ) as u32) } +fn cont_obj_occupy_next_tag_returns_slots( + _instance: &mut Instance, + contobj: *mut u8, + arg_count: u32, + remaining_arg_count: u32, +) -> *mut u8 { + crate::continuation::cont_obj_occupy_next_tag_returns_slots( + contobj as *mut crate::continuation::ContinuationObject, + arg_count as usize, + remaining_arg_count as usize, + ) as *mut u8 +} + +fn cont_obj_get_tag_return_values_buffer( + _instance: &mut Instance, + contobj: *mut u8, + expected_value_count: u32, +) -> *mut u8 { + crate::continuation::cont_obj_get_tag_return_values_buffer( + contobj as *mut crate::continuation::ContinuationObject, + expected_value_count as usize, + ) as *mut u8 +} + +fn cont_obj_deallocate_tag_return_values_buffer(_instance: &mut Instance, contobj: *mut u8) { + crate::continuation::cont_obj_deallocate_tag_return_values_buffer( + contobj as *mut crate::continuation::ContinuationObject, + ); +} + fn alllocate_payload_buffer(instance: &mut Instance, element_count: u32) -> *mut u8 { crate::continuation::alllocate_payload_buffer(instance, element_count as usize) as *mut u8 } diff --git a/tests/misc_testsuite/typed-continuations/cont_bind1.wast b/tests/misc_testsuite/typed-continuations/cont_bind1.wast new file mode 100644 index 000000000000..9ebb5622adf0 --- /dev/null +++ b/tests/misc_testsuite/typed-continuations/cont_bind1.wast @@ -0,0 +1,30 @@ +;; Simple test for cont.bind: cont.bind supplies 0 arguments + +(module + (type $unit_to_int (func (result i32))) + (type $int_int_to_int (func (param i32 i32) (result i32))) + (type $int_to_int (func (param i32) (result i32))) + + (type $ct0 (cont $unit_to_int)) + (type $ct1 (cont $int_int_to_int)) + + (tag $e) + + (func $g (param $x i32) (param $y i32) (result i32) + (suspend $e) + (i32.add (local.get $x) (local.get $y))) + (elem declare func $g) + + (func $test (export "test") (result i32) + (block $on_e (result (ref $ct0)) + (i32.const 49) ;; consumed by resume + (i32.const 51) ;; consumed by resume + (cont.new $ct1 (ref.func $g)) + (cont.bind $ct1 $ct1) + (resume $ct1 (tag $e $on_e)) + (unreachable)) + ;; on_e + (resume $ct0)) +) + +(assert_return (invoke "test") (i32.const 100)) diff --git a/tests/misc_testsuite/typed-continuations/cont_bind2.wast b/tests/misc_testsuite/typed-continuations/cont_bind2.wast new file mode 100644 index 000000000000..430a5b4f156b --- /dev/null +++ b/tests/misc_testsuite/typed-continuations/cont_bind2.wast @@ -0,0 +1,31 @@ +;; Simple test for cont.bind: cont.bind turns 2-arg continution into 1-arg one before calling resume + +(module + (type $unit_to_int (func (result i32))) + (type $int_int_to_int (func (param i32 i32) (result i32))) + (type $int_to_int (func (param i32) (result i32))) + + (type $ct0 (cont $unit_to_int)) + (type $ct1 (cont $int_to_int)) + (type $ct2 (cont $int_int_to_int)) + + (tag $e) + + (func $g (param $x i32) (param $y i32) (result i32) + (suspend $e) + (i32.add (local.get $x) (local.get $y))) + (elem declare func $g) + + (func $test (export "test") (result i32) + (block $on_e (result (ref $ct0)) + (i32.const 49) ;; consumed by resume + (i32.const 51) ;; consumed by cont.bind + (cont.new $ct2 (ref.func $g)) + (cont.bind $ct2 $ct1) + (resume $ct1 (tag $e $on_e)) + (unreachable)) + ;; on_e + (resume $ct0)) +) + +(assert_return (invoke "test") (i32.const 100)) diff --git a/tests/misc_testsuite/typed-continuations/cont_bind3.wast b/tests/misc_testsuite/typed-continuations/cont_bind3.wast new file mode 100644 index 000000000000..e1496728b453 --- /dev/null +++ b/tests/misc_testsuite/typed-continuations/cont_bind3.wast @@ -0,0 +1,40 @@ +;; Testing cont.bind on continuations received from suspending rather than cont.new. + +(module + (type $unit_to_int (func (result i32))) + (type $int_to_unit (func (param i32))) + (type $int_to_int (func (param i32) (result i32))) + (type $2int_to_int (func (param i32 i32) (result i32))) + (type $3int_to_int (func (param i32 i32 i32) (result i32))) + + (type $ct0 (cont $unit_to_int)) + (type $ct1 (cont $3int_to_int)) + (type $ct2 (cont $2int_to_int)) + (type $ct3 (cont $int_to_int)) + + (tag $e (param i32 i32) (result i32 i32 i32)) + + (func $g (result i32) + (suspend $e (i32.const 5) (i32.const 15)) + (i32.add) + (i32.add)) + (elem declare func $g) + + (func $test (export "test") (result i32) + (local $k (ref $ct1)) + (i32.const 35) ;; to be consumed by second call to cont.resume + (i32.const 45) ;; to be consumed by second call to cont.bind + (block $on_e (result i32 i32 (ref $ct1)) + (resume $ct0 (tag $e $on_e) (cont.new $ct0 (ref.func $g))) + (unreachable)) + ;; on_e: + (local.set $k) + (i32.add) ;; add two values received from $e, leave on stack to be consumed by first call to cont.bind + (local.get $k) + (cont.bind $ct1 $ct2) ;; consumes the result (= 20) of the addition two lines earlier + (cont.bind $ct2 $ct3) ;; consumes the constant value 45 put on stack earlier + (resume $ct3) ;; consumes the constant value 35 put on stack earlier + ) +) + +(assert_return (invoke "test") (i32.const 100)) diff --git a/tests/misc_testsuite/typed-continuations/cont_bind4.wast b/tests/misc_testsuite/typed-continuations/cont_bind4.wast new file mode 100644 index 000000000000..b5fe2009a22b --- /dev/null +++ b/tests/misc_testsuite/typed-continuations/cont_bind4.wast @@ -0,0 +1,103 @@ +;; Testing that the creation of the necessary payload buffers works as expect, +;; even when the same continuation object is suspended multiple times + +(module + (type $unit_to_int (func (result i32))) + (type $int_to_unit (func (param i32))) + (type $int_to_int (func (param i32) (result i32))) + (type $2int_to_int (func (param i32 i32) (result i32))) + (type $3int_to_int (func (param i32 i32 i32) (result i32))) + + (type $ct0 (cont $int_to_int)) + (type $ct1 (cont $unit_to_int)) + (type $ct2 (cont $2int_to_int)) + + (global $checker (mut i32) (i32.const 0)) + + (func $check_stack (param $expected i32) (param $actual i32) (result i32) + (if (result i32) + (i32.xor (local.get $expected) (local.get $actual)) + (then (unreachable)) + (else (local.get $actual)))) + + (func $check_stack2 + (param $expected1 i32) + (param $expected2 i32) + (param $actual1 i32) + (param $actual2 i32) + (result i32 i32) + (if + (i32.xor (local.get $expected1) (local.get $actual1)) + (then (unreachable)) + (else)) + (if + (i32.xor (local.get $expected2) (local.get $actual2)) + (then (unreachable)) + (else)) + (local.get $actual1) + (local.get $actual2)) + + + (tag $e (param i32) (result i32)) + (tag $f (param i32 i32) (result i32 i32)) + + (func $g (param $x i32) (result i32) + (i32.add (local.get $x) (i32.const 1)) + (call $check_stack (i32.const 10)) + (suspend $e) + (call $check_stack (i32.const 15)) + (i32.add (i32.const 5)) + (call $check_stack (i32.const 20)) + (suspend $e) + (call $check_stack (i32.const 25)) + (i32.const 30) + (suspend $f) + (call $check_stack2 (i32.const 35) (i32.const 40)) + (i32.add)) + (elem declare func $g) + + (func $test (export "test") (result i32) + (local $k1 (ref $ct0)) + (local $k2 (ref $ct1)) + (local $k3 (ref $ct2)) + (local $i i32) + + (block $on_e1 (result i32 (ref $ct0)) + (i32.const 9) + (cont.new $ct0 (ref.func $g)) + (cont.bind $ct0 $ct1) ;; binding 9 here as value of parameter $x of $g + (resume $ct1 (tag $e $on_e1)) + (unreachable)) + (local.set $k1) + (call $check_stack (i32.const 10)) + (i32.add (i32.const 5)) + (call $check_stack (i32.const 15)) + (cont.bind $ct0 $ct1 (local.get $k1)) ;; binding 15 + (local.set $k2) + + + (block $on_e2 (result i32 (ref $ct0)) + (resume $ct1 (tag $e $on_e2) (local.get $k2)) + (unreachable)) + (local.set $k1) + (call $check_stack (i32.const 20)) + (i32.add (i32.const 5)) + (call $check_stack (i32.const 25)) + (cont.bind $ct0 $ct1 (local.get $k1)) ;; binding 25 + (local.set $k2) + (block $on_f (result i32 i32 (ref $ct2)) + (resume $ct1 (tag $f $on_f) (local.get $k2)) + (unreachable)) + (local.set $k3) + (call $check_stack2 (i32.const 25) (i32.const 30)) + (i32.add (i32.const 10)) + (local.set $i) + (i32.add (i32.const 10)) + (local.get $i) + (call $check_stack2 (i32.const 35) (i32.const 40)) + (local.get $k3) + (cont.bind $ct2 $ct1) ;; binding 35, 40 + (resume $ct1)) +) + +(assert_return (invoke "test") (i32.const 75))