From 93f17e3c0faf35d21e7aa30070657edc8386a055 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 21 Feb 2024 12:07:10 -0600 Subject: [PATCH] Reserve handle index 0 in the component model (#7661) This commit updates the allocation scheme for resources in the component model to start at 1 instead of 0 when communicating with components. This is an implementation of WebAssembly/component-model#284. While this broke a number of tests we have this shouldn't actually break any components in practice. The broken tests were all overly-precise in their assertions and error messages and this shouldn't idiomatically come up in any guest language, so this should not be a practically breaking change. This change additionally places an upper limit on the maximum allocatable index at `1 << 30` which is also specified in the above PR. --- crates/runtime/src/component.rs | 6 +- crates/runtime/src/component/libcalls.rs | 4 +- crates/runtime/src/component/resources.rs | 70 ++++++++++++------- .../src/runtime/component/func/options.rs | 20 ++++-- .../src/runtime/component/resources.rs | 48 ++++++++----- tests/all/component_model/resources.rs | 12 ++-- .../component-model/resources.wast | 54 +++++++------- 7 files changed, 128 insertions(+), 86 deletions(-) diff --git a/crates/runtime/src/component.rs b/crates/runtime/src/component.rs index 03111eaace11..00ed71bde40a 100644 --- a/crates/runtime/src/component.rs +++ b/crates/runtime/src/component.rs @@ -531,7 +531,7 @@ impl ComponentInstance { /// Implementation of the `resource.new` intrinsic for `i32` /// representations. - pub fn resource_new32(&mut self, resource: TypeResourceTableIndex, rep: u32) -> u32 { + pub fn resource_new32(&mut self, resource: TypeResourceTableIndex, rep: u32) -> Result { self.resource_tables().resource_new(Some(resource), rep) } @@ -600,7 +600,7 @@ impl ComponentInstance { ) -> Result { let mut tables = self.resource_tables(); let rep = tables.resource_lift_own(Some(src), idx)?; - Ok(tables.resource_lower_own(Some(dst), rep)) + tables.resource_lower_own(Some(dst), rep) } pub(crate) fn resource_transfer_borrow( @@ -623,7 +623,7 @@ impl ComponentInstance { if dst_owns_resource { return Ok(rep); } - Ok(tables.resource_lower_borrow(Some(dst), rep)) + tables.resource_lower_borrow(Some(dst), rep) } pub(crate) fn resource_enter_call(&mut self) { diff --git a/crates/runtime/src/component/libcalls.rs b/crates/runtime/src/component/libcalls.rs index 920a505339a3..cd0323ad3c45 100644 --- a/crates/runtime/src/component/libcalls.rs +++ b/crates/runtime/src/component/libcalls.rs @@ -519,9 +519,7 @@ fn inflate_latin1_bytes(dst: &mut [u16], latin1_bytes_so_far: usize) -> &mut [u1 unsafe fn resource_new32(vmctx: *mut VMComponentContext, resource: u32, rep: u32) -> Result { let resource = TypeResourceTableIndex::from_u32(resource); - Ok(ComponentInstance::from_vmctx(vmctx, |instance| { - instance.resource_new32(resource, rep) - })) + ComponentInstance::from_vmctx(vmctx, |instance| instance.resource_new32(resource, rep)) } unsafe fn resource_rep32(vmctx: *mut VMComponentContext, resource: u32, idx: u32) -> Result { diff --git a/crates/runtime/src/component/resources.rs b/crates/runtime/src/component/resources.rs index 4c22db5569b0..a9442536220c 100644 --- a/crates/runtime/src/component/resources.rs +++ b/crates/runtime/src/component/resources.rs @@ -28,6 +28,11 @@ use std::mem; use wasmtime_environ::component::TypeResourceTableIndex; use wasmtime_environ::PrimaryMap; +/// The maximum handle value is specified in +/// +/// currently and keeps the upper bit free for use in the component. +const MAX_RESOURCE_HANDLE: u32 = 1 << 30; + /// Contextual state necessary to perform resource-related operations. /// /// This state a bit odd since it has a few optional bits, but the idea is that @@ -125,7 +130,7 @@ impl ResourceTables<'_> { /// Implementation of the `resource.new` canonical intrinsic. /// /// Note that this is the same as `resource_lower_own`. - pub fn resource_new(&mut self, ty: Option, rep: u32) -> u32 { + pub fn resource_new(&mut self, ty: Option, rep: u32) -> Result { self.table(ty).insert(Slot::Own { rep, lend_count: 0 }) } @@ -173,7 +178,11 @@ impl ResourceTables<'_> { /// the same as `resource_new` implementation-wise. /// /// This is an implementation of the canonical ABI `lower_own` function. - pub fn resource_lower_own(&mut self, ty: Option, rep: u32) -> u32 { + pub fn resource_lower_own( + &mut self, + ty: Option, + rep: u32, + ) -> Result { self.table(ty).insert(Slot::Own { rep, lend_count: 0 }) } @@ -237,7 +246,11 @@ impl ResourceTables<'_> { /// function. The other half of this implementation is located on /// `VMComponentContext` which handles the special case of avoiding borrow /// tracking entirely. - pub fn resource_lower_borrow(&mut self, ty: Option, rep: u32) -> u32 { + pub fn resource_lower_borrow( + &mut self, + ty: Option, + rep: u32, + ) -> Result { let scope = self.calls.scopes.len() - 1; let borrow_count = &mut self.calls.scopes.last_mut().unwrap().borrow_count; *borrow_count = borrow_count.checked_add(1).unwrap(); @@ -278,12 +291,8 @@ impl ResourceTables<'_> { } impl ResourceTable { - fn next(&self) -> usize { - self.next as usize - } - - fn insert(&mut self, new: Slot) -> u32 { - let next = self.next(); + fn insert(&mut self, new: Slot) -> Result { + let next = self.next as usize; if next == self.slots.len() { self.slots.push(Slot::Free { next: self.next.checked_add(1).unwrap(), @@ -294,36 +303,49 @@ impl ResourceTable { Slot::Free { next } => next, _ => unreachable!(), }; - u32::try_from(ret).unwrap() + + // The component model reserves index 0 as never allocatable so add one + // to the table index to start the numbering at 1 instead. Also note + // that the component model places an upper-limit per-table on the + // maximum allowed index. + let ret = ret + 1; + if ret >= MAX_RESOURCE_HANDLE { + bail!("cannot allocate another handle: index overflow"); + } + Ok(ret) + } + + fn handle_index_to_table_index(&self, idx: u32) -> Option { + // NB: `idx` is decremented by one to account for the `+1` above during + // allocation. + let idx = idx.checked_sub(1)?; + usize::try_from(idx).ok() } fn rep(&self, idx: u32) -> Result { - match usize::try_from(idx).ok().and_then(|i| self.slots.get(i)) { + let slot = self + .handle_index_to_table_index(idx) + .and_then(|i| self.slots.get(i)); + match slot { None | Some(Slot::Free { .. }) => bail!("unknown handle index {idx}"), Some(Slot::Own { rep, .. } | Slot::Borrow { rep, .. }) => Ok(*rep), } } fn get_mut(&mut self, idx: u32) -> Result<&mut Slot> { - match usize::try_from(idx) - .ok() - .and_then(|i| self.slots.get_mut(i)) - { + let slot = self + .handle_index_to_table_index(idx) + .and_then(|i| self.slots.get_mut(i)); + match slot { None | Some(Slot::Free { .. }) => bail!("unknown handle index {idx}"), Some(other) => Ok(other), } } fn remove(&mut self, idx: u32) -> Result { - match usize::try_from(idx).ok().and_then(|i| self.slots.get(i)) { - Some(Slot::Own { .. }) | Some(Slot::Borrow { .. }) => {} - _ => bail!("unknown handle index {idx}"), - }; - let ret = mem::replace( - &mut self.slots[idx as usize], - Slot::Free { next: self.next }, - ); - self.next = idx; + let to_fill = Slot::Free { next: self.next }; + let ret = mem::replace(self.get_mut(idx)?, to_fill); + self.next = idx - 1; Ok(ret) } } diff --git a/crates/wasmtime/src/runtime/component/func/options.rs b/crates/wasmtime/src/runtime/component/func/options.rs index 0dfe313accbb..f08a29223a68 100644 --- a/crates/wasmtime/src/runtime/component/func/options.rs +++ b/crates/wasmtime/src/runtime/component/func/options.rs @@ -289,13 +289,21 @@ impl<'a, T> LowerContext<'a, T> { /// into a guest-local index. /// /// The `ty` provided is which table to put this into. - pub fn guest_resource_lower_own(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 { + pub fn guest_resource_lower_own( + &mut self, + ty: TypeResourceTableIndex, + rep: u32, + ) -> Result { self.resource_tables().guest_resource_lower_own(rep, ty) } /// Lowers a `borrow` resource into the guest, converting the `rep` to a /// guest-local index in the `ty` table specified. - pub fn guest_resource_lower_borrow(&mut self, ty: TypeResourceTableIndex, rep: u32) -> u32 { + pub fn guest_resource_lower_borrow( + &mut self, + ty: TypeResourceTableIndex, + rep: u32, + ) -> Result { // Implement `lower_borrow`'s special case here where if a borrow is // inserted into a table owned by the instance which implemented the // original resource then no borrow tracking is employed and instead the @@ -308,7 +316,7 @@ impl<'a, T> LowerContext<'a, T> { // Note that the unsafety here should be valid given the contract of // `LowerContext::new`. if unsafe { (*self.instance).resource_owned_by_own_instance(ty) } { - return rep; + return Ok(rep); } self.resource_tables().guest_resource_lower_borrow(rep, ty) } @@ -330,7 +338,7 @@ impl<'a, T> LowerContext<'a, T> { /// /// Note that this is a special case for `Resource`. Most of the time a /// host value shouldn't be lowered with a lowering context. - pub fn host_resource_lower_own(&mut self, rep: u32) -> HostResourceIndex { + pub fn host_resource_lower_own(&mut self, rep: u32) -> Result { self.resource_tables().host_resource_lower_own(rep) } @@ -483,13 +491,13 @@ impl<'a> LiftContext<'a> { /// Lowers a resource into the host-owned table, returning the index it was /// inserted at. - pub fn host_resource_lower_own(&mut self, rep: u32) -> HostResourceIndex { + pub fn host_resource_lower_own(&mut self, rep: u32) -> Result { self.resource_tables().host_resource_lower_own(rep) } /// Lowers a resource into the host-owned table, returning the index it was /// inserted at. - pub fn host_resource_lower_borrow(&mut self, rep: u32) -> HostResourceIndex { + pub fn host_resource_lower_borrow(&mut self, rep: u32) -> Result { self.resource_tables().host_resource_lower_borrow(rep) } diff --git a/crates/wasmtime/src/runtime/component/resources.rs b/crates/wasmtime/src/runtime/component/resources.rs index b41b073dc39d..09e164cfd16c 100644 --- a/crates/wasmtime/src/runtime/component/resources.rs +++ b/crates/wasmtime/src/runtime/component/resources.rs @@ -397,15 +397,15 @@ impl<'a> HostResourceTables<'a> { /// will point to the `rep` specified as well as recording that it has the /// `ty` specified. The returned index is suitable for conversion into /// either [`Resource`] or [`ResourceAny`]. - pub fn host_resource_lower_own(&mut self, rep: u32) -> HostResourceIndex { - let idx = self.tables.resource_lower_own(None, rep); - self.new_host_index(idx) + pub fn host_resource_lower_own(&mut self, rep: u32) -> Result { + let idx = self.tables.resource_lower_own(None, rep)?; + Ok(self.new_host_index(idx)) } /// See [`HostResourceTables::host_resource_lower_own`]. - pub fn host_resource_lower_borrow(&mut self, rep: u32) -> HostResourceIndex { - let idx = self.tables.resource_lower_borrow(None, rep); - self.new_host_index(idx) + pub fn host_resource_lower_borrow(&mut self, rep: u32) -> Result { + let idx = self.tables.resource_lower_borrow(None, rep)?; + Ok(self.new_host_index(idx)) } /// Validates that `idx` is still valid for the host tables, notably @@ -453,6 +453,12 @@ impl<'a> HostResourceTables<'a> { match list.get_mut(idx as usize) { Some(slot) => *slot = gen, None => { + // Resource handles start at 1, not zero, so push two elements + // for the first resource handle. + if list.is_empty() { + assert_eq!(idx, 1); + list.push(0); + } assert_eq!(idx as usize, list.len()); list.push(gen); } @@ -482,7 +488,11 @@ impl<'a> HostResourceTables<'a> { /// into a guest-local index. /// /// The `ty` provided is which table to put this into. - pub fn guest_resource_lower_own(&mut self, rep: u32, ty: TypeResourceTableIndex) -> u32 { + pub fn guest_resource_lower_own( + &mut self, + rep: u32, + ty: TypeResourceTableIndex, + ) -> Result { self.tables.resource_lower_own(Some(ty), rep) } @@ -495,7 +505,11 @@ impl<'a> HostResourceTables<'a> { /// into a guest has a special case where `rep` is returned directly if `ty` /// belongs to the component being lowered into. That property must be /// handled by the caller of this function. - pub fn guest_resource_lower_borrow(&mut self, rep: u32, ty: TypeResourceTableIndex) -> u32 { + pub fn guest_resource_lower_borrow( + &mut self, + rep: u32, + ty: TypeResourceTableIndex, + ) -> Result { self.tables.resource_lower_borrow(Some(ty), rep) } @@ -612,7 +626,7 @@ where // can move the rep into the guest table. ResourceState::Index(idx) => cx.host_resource_lift_own(idx)?, }; - Ok(cx.guest_resource_lower_own(t, rep)) + cx.guest_resource_lower_own(t, rep) } InterfaceType::Borrow(t) => { let rep = match self.state.get() { @@ -632,7 +646,7 @@ where // // Afterwards this is the same as the `idx` case below. ResourceState::NotInTable => { - let idx = cx.host_resource_lower_own(self.rep); + let idx = cx.host_resource_lower_own(self.rep)?; let prev = self.state.swap(ResourceState::Index(idx)); assert_eq!(prev, ResourceState::NotInTable); cx.host_resource_lift_borrow(idx)? @@ -642,7 +656,7 @@ where // out of the table with borrow-tracking employed. ResourceState::Index(idx) => cx.host_resource_lift_borrow(idx)?, }; - Ok(cx.guest_resource_lower_borrow(t, rep)) + cx.guest_resource_lower_borrow(t, rep) } _ => bad_type_info(), } @@ -879,9 +893,9 @@ impl ResourceAny { let mut tables = HostResourceTables::new_host(store.0); let (idx, own_state) = match state.get() { - ResourceState::Borrow => (tables.host_resource_lower_borrow(rep), None), + ResourceState::Borrow => (tables.host_resource_lower_borrow(rep)?, None), ResourceState::NotInTable => { - let idx = tables.host_resource_lower_own(rep); + let idx = tables.host_resource_lower_own(rep)?; ( idx, Some(OwnState { @@ -1025,14 +1039,14 @@ impl ResourceAny { bail!("mismatched resource types"); } let rep = cx.host_resource_lift_own(self.idx)?; - Ok(cx.guest_resource_lower_own(t, rep)) + cx.guest_resource_lower_own(t, rep) } InterfaceType::Borrow(t) => { if cx.resource_type(t) != self.ty { bail!("mismatched resource types"); } let rep = cx.host_resource_lift_borrow(self.idx)?; - Ok(cx.guest_resource_lower_borrow(t, rep)) + cx.guest_resource_lower_borrow(t, rep) } _ => bad_type_info(), } @@ -1043,7 +1057,7 @@ impl ResourceAny { InterfaceType::Own(t) => { let ty = cx.resource_type(t); let (rep, dtor, flags) = cx.guest_resource_lift_own(t, index)?; - let idx = cx.host_resource_lower_own(rep); + let idx = cx.host_resource_lower_own(rep)?; Ok(ResourceAny { idx, ty, @@ -1057,7 +1071,7 @@ impl ResourceAny { InterfaceType::Borrow(t) => { let ty = cx.resource_type(t); let rep = cx.guest_resource_lift_borrow(t, index)?; - let idx = cx.host_resource_lower_borrow(rep); + let idx = cx.host_resource_lower_borrow(rep)?; Ok(ResourceAny { idx, ty, diff --git a/tests/all/component_model/resources.rs b/tests/all/component_model/resources.rs index 08059df4c218..bd5fa9d2053e 100644 --- a/tests/all/component_model/resources.rs +++ b/tests/all/component_model/resources.rs @@ -233,7 +233,7 @@ fn mismatch_intrinsics() -> Result<()> { let ctor = i.get_typed_func::<(u32,), (ResourceAny,)>(&mut store, "ctor")?; assert_eq!( ctor.call(&mut store, (100,)).unwrap_err().to_string(), - "unknown handle index 0" + "unknown handle index 1" ); Ok(()) @@ -371,7 +371,7 @@ fn drop_guest_twice() -> Result<()> { assert_eq!( dtor.call(&mut store, (&t,)).unwrap_err().to_string(), - "unknown handle index 0" + "unknown handle index 1" ); Ok(()) @@ -1250,7 +1250,7 @@ fn pass_guest_back_as_borrow() -> Result<()> { // Should not be valid to use `resource` again let err = take.call(&mut store, (&resource,)).unwrap_err(); - assert_eq!(err.to_string(), "unknown handle index 0"); + assert_eq!(err.to_string(), "unknown handle index 1"); Ok(()) } @@ -1412,9 +1412,9 @@ fn guest_different_host_same() -> Result<()> { (import "" "drop2" (func $drop2 (param i32))) (func (export "f") (param i32 i32) - ;; separate tables both have initial index of 0 - (if (i32.ne (local.get 0) (i32.const 0)) (then (unreachable))) - (if (i32.ne (local.get 1) (i32.const 0)) (then (unreachable))) + ;; separate tables both have initial index of 1 + (if (i32.ne (local.get 0) (i32.const 1)) (then (unreachable))) + (if (i32.ne (local.get 1) (i32.const 1)) (then (unreachable))) ;; host should end up getting the same resource (call $f (local.get 0) (local.get 1)) diff --git a/tests/misc_testsuite/component-model/resources.wast b/tests/misc_testsuite/component-model/resources.wast index 89b97536aebc..351ed85b3b6f 100644 --- a/tests/misc_testsuite/component-model/resources.wast +++ b/tests/misc_testsuite/component-model/resources.wast @@ -14,7 +14,7 @@ (local $r i32) (local.set $r (call $new (i32.const 100))) - (if (i32.ne (local.get $r) (i32.const 0)) (then (unreachable))) + (if (i32.ne (local.get $r) (i32.const 1)) (then (unreachable))) (if (i32.ne (call $rep (local.get $r)) (i32.const 100)) (then (unreachable))) (call $drop (local.get $r)) @@ -95,13 +95,13 @@ ;; resources assigned sequentially (local.set $r1 (call $new (i32.const 100))) - (if (i32.ne (local.get $r1) (i32.const 0)) (then (unreachable))) + (if (i32.ne (local.get $r1) (i32.const 1)) (then (unreachable))) (local.set $r2 (call $new (i32.const 200))) - (if (i32.ne (local.get $r2) (i32.const 1)) (then (unreachable))) + (if (i32.ne (local.get $r2) (i32.const 2)) (then (unreachable))) (local.set $r3 (call $new (i32.const 300))) - (if (i32.ne (local.get $r3) (i32.const 2)) (then (unreachable))) + (if (i32.ne (local.get $r3) (i32.const 3)) (then (unreachable))) ;; representations all look good (if (i32.ne (call $rep (local.get $r1)) (i32.const 100)) (then (unreachable))) @@ -113,7 +113,7 @@ (local.set $r2 (call $new (i32.const 400))) ;; should have reused index 1 - (if (i32.ne (local.get $r2) (i32.const 1)) (then (unreachable))) + (if (i32.ne (local.get $r2) (i32.const 2)) (then (unreachable))) ;; representations all look good (if (i32.ne (call $rep (local.get $r1)) (i32.const 100)) (then (unreachable))) @@ -135,13 +135,13 @@ (if (i32.ne (call $rep (local.get $r3)) (i32.const 700)) (then (unreachable))) ;; indices should be lifo - (if (i32.ne (local.get $r1) (i32.const 2)) (then (unreachable))) - (if (i32.ne (local.get $r2) (i32.const 1)) (then (unreachable))) - (if (i32.ne (local.get $r3) (i32.const 0)) (then (unreachable))) + (if (i32.ne (local.get $r1) (i32.const 3)) (then (unreachable))) + (if (i32.ne (local.get $r2) (i32.const 2)) (then (unreachable))) + (if (i32.ne (local.get $r3) (i32.const 1)) (then (unreachable))) ;; bump one more time (local.set $r4 (call $new (i32.const 800))) - (if (i32.ne (local.get $r4) (i32.const 3)) (then (unreachable))) + (if (i32.ne (local.get $r4) (i32.const 4)) (then (unreachable))) ;; deallocate everything (call $drop (local.get $r1)) @@ -241,13 +241,13 @@ (local.set $r2 (call $ctor (i32.const 200))) ;; assert r1/r2 are sequential - (if (i32.ne (local.get $r1) (i32.const 0)) (then (unreachable))) - (if (i32.ne (local.get $r2) (i32.const 1)) (then (unreachable))) + (if (i32.ne (local.get $r1) (i32.const 1)) (then (unreachable))) + (if (i32.ne (local.get $r2) (i32.const 2)) (then (unreachable))) ;; reallocate r1 and it should be reassigned the same index (call $drop (local.get $r1)) (local.set $r1 (call $ctor (i32.const 300))) - (if (i32.ne (local.get $r1) (i32.const 0)) (then (unreachable))) + (if (i32.ne (local.get $r1) (i32.const 1)) (then (unreachable))) ;; internal values should match (call $assert (local.get $r1) (i32.const 300)) @@ -443,7 +443,7 @@ (import "" "ctor" (func $ctor (param i32) (result i32))) (func $start - (if (i32.ne (call $ctor (i32.const 100)) (i32.const 0)) (then (unreachable))) + (if (i32.ne (call $ctor (i32.const 100)) (i32.const 1)) (then (unreachable))) ) (start $start) ) @@ -464,7 +464,7 @@ (import "" "drop" (func $drop (param i32))) (func (export "f") - (call $drop (i32.const 0)) + (call $drop (i32.const 1)) ) ) (core instance $i (instantiate $m @@ -492,10 +492,10 @@ (import "" "drop" (func $drop (param i32))) (func (export "alloc") - (if (i32.ne (call $ctor (i32.const 100)) (i32.const 0)) (then (unreachable))) + (if (i32.ne (call $ctor (i32.const 100)) (i32.const 1)) (then (unreachable))) ) (func (export "dealloc") - (call $drop (i32.const 0)) + (call $drop (i32.const 1)) ) ) (core instance $i (instantiate $m @@ -552,10 +552,10 @@ (import "" "drop" (func $drop (param i32))) (func (export "alloc") - (if (i32.ne (call $ctor (i32.const 100)) (i32.const 0)) (then (unreachable))) + (if (i32.ne (call $ctor (i32.const 100)) (i32.const 1)) (then (unreachable))) ) (func (export "dealloc") - (call $drop (i32.const 0)) + (call $drop (i32.const 1)) ) ) (core instance $i (instantiate $m @@ -617,12 +617,12 @@ (call $drop2 (call $new2 (i32.const 104))) ;; should be referencing the same namespace - (if (i32.ne (call $new1 (i32.const 105)) (i32.const 0)) (then (unreachable))) - (if (i32.ne (call $new2 (i32.const 105)) (i32.const 1)) (then (unreachable))) + (if (i32.ne (call $new1 (i32.const 105)) (i32.const 1)) (then (unreachable))) + (if (i32.ne (call $new2 (i32.const 105)) (i32.const 2)) (then (unreachable))) ;; use different drops out of order - (call $drop2 (i32.const 0)) - (call $drop1 (i32.const 1)) + (call $drop2 (i32.const 1)) + (call $drop1 (i32.const 2)) ) (start $start) @@ -701,8 +701,8 @@ (local.set $r2 (call $new2 (i32.const 200))) ;; both should be index 0 - (if (i32.ne (local.get $r1) (i32.const 0)) (then (unreachable))) - (if (i32.ne (local.get $r2) (i32.const 0)) (then (unreachable))) + (if (i32.ne (local.get $r1) (i32.const 1)) (then (unreachable))) + (if (i32.ne (local.get $r2) (i32.const 1)) (then (unreachable))) ;; nothing should be dropped yet (if (i32.ne (call $drops) (i32.const 0)) (then (unreachable))) @@ -866,7 +866,7 @@ ;; table should be empty at this point, so a fresh allocation should get ;; index 0 - (if (i32.ne (call $ctor (i32.const 600)) (i32.const 0)) (then (unreachable))) + (if (i32.ne (call $ctor (i32.const 600)) (i32.const 1)) (then (unreachable))) ) (start $start) @@ -1024,8 +1024,8 @@ (local.set $r1 (call $ctor (i32.const 100))) (local.set $r2 (call $ctor (i32.const 200))) - (if (i32.ne (local.get $r1) (i32.const 0)) (then (unreachable))) - (if (i32.ne (local.get $r2) (i32.const 1)) (then (unreachable))) + (if (i32.ne (local.get $r1) (i32.const 1)) (then (unreachable))) + (if (i32.ne (local.get $r2) (i32.const 2)) (then (unreachable))) (call $assert-borrow (local.get $r2) (i32.const 200)) (call $assert-borrow (local.get $r1) (i32.const 100))