Repository: bytecodealliance/wasmtime
Pull request #9358 (2024-10-02 18:25:03)

Implement the `array.init_elem` Wasm GC instruction
<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->



Patch:
diff --git a/crates/wasmtime/src/runtime/gc/enabled/arrayref.rs b/crates/wasmtime/src/runtime/gc/enabled/arrayref.rs
index bde909427..0dc4d4cec 100644
--- a/crates/wasmtime/src/runtime/gc/enabled/arrayref.rs
+++ b/crates/wasmtime/src/runtime/gc/enabled/arrayref.rs
@@ -694,7 +694,7 @@ impl ArrayRef {
         store: &mut AutoAssertNoGc<'_>,
         gc_ref: VMGcRef,
     ) -> Rooted<Self> {
-        debug_assert!(!gc_ref.is_i31());
+        debug_assert!(gc_ref.is_arrayref(&*store.unwrap_gc_store().gc_heap));
         Rooted::new(store, gc_ref)
     }
 }
diff --git a/crates/wasmtime/src/runtime/gc/enabled/structref.rs b/crates/wasmtime/src/runtime/gc/enabled/structref.rs
index a1d82e74f..c3171008a 100644
--- a/crates/wasmtime/src/runtime/gc/enabled/structref.rs
+++ b/crates/wasmtime/src/runtime/gc/enabled/structref.rs
@@ -561,7 +561,7 @@ impl StructRef {
         store: &mut AutoAssertNoGc<'_>,
         gc_ref: VMGcRef,
     ) -> Rooted<Self> {
-        debug_assert!(!gc_ref.is_i31());
+        debug_assert!(gc_ref.is_structref(&*store.unwrap_gc_store().gc_heap));
         Rooted::new(store, gc_ref)
     }
 }
diff --git a/crates/wasmtime/src/runtime/vm/libcalls.rs b/crates/wasmtime/src/runtime/vm/libcalls.rs
index b0f964300..01519dd5e 100644
--- a/crates/wasmtime/src/runtime/vm/libcalls.rs
+++ b/crates/wasmtime/src/runtime/vm/libcalls.rs
@@ -727,6 +727,7 @@ unsafe fn array_new_elem(
     };
     use wasmtime_environ::{ModuleInternedTypeIndex, TableSegmentElements};
 
+    // Convert indices to their typed forms.
     let array_type_index = ModuleInternedTypeIndex::from_u32(array_type_index);
     let elem_index = ElemIndex::from_u32(elem_index);
 
@@ -808,15 +809,113 @@ unsafe fn array_new_elem(
 
 #[cfg(feature = "gc")]
 unsafe fn array_init_elem(
-    _instance: &mut Instance,
-    _array_type_index: u32,
-    _array: u32,
-    _dst_index: u32,
-    _elem_index: u32,
-    _src: u32,
-    _len: u32,
+    instance: &mut Instance,
+    array_type_index: u32,
+    array: u32,
+    dst: u32,
+    elem_index: u32,
+    src: u32,
+    len: u32,
 ) -> Result<()> {
-    bail!("the `array.init_elem` instruction is not yet implemented")
+    use crate::{
+        store::AutoAssertNoGc,
+        vm::const_expr::{ConstEvalContext, ConstExprEvaluator},
+        ArrayRef, Func, Val,
+    };
+    use wasmtime_environ::{ModuleInternedTypeIndex, TableSegmentElements};
+
+    // NB: Don't use `OpaqueRootScope` here because we need to borrow the store
+    // through `instance` during const evaluation, which is within the same
+    // region that the `OpaqueRootScope` would otherwise span while borrowing
+    // the same store, resulting in double borrows.
+    instance.with_gc_lifo_scope(|instance| {
+        // Convert the indices into their typed forms.
+        let _array_type_index = ModuleInternedTypeIndex::from_u32(array_type_index);
+        let elem_index = ElemIndex::from_u32(elem_index);
+
+        log::trace!(
+            "array.init_elem(array={array:#x}, dst={dst}, elem_index={elem_index:?}, src={src}, len={len})",
+        );
+
+        // Convert the raw GC ref into a `Rooted<ArrayRef>`.
+        let array =
+            VMGcRef::from_raw_u32(array).ok_or_else(|| Trap::NullReference.into_anyhow())?;
+        let array = {
+            let mut no_gc = AutoAssertNoGc::new((*instance.store()).store_opaque_mut());
+            ArrayRef::from_cloned_gc_ref(&mut no_gc, array)
+        };
+
+        // Bounds check the destination within the array.
+        let array_len = array._len((*instance.store()).store_opaque())?;
+        log::trace!("array_len = {array_len}");
+        if dst
+            .checked_add(len)
+            .ok_or_else(|| Trap::ArrayOutOfBounds.into_anyhow())?
+            > array_len
+        {
+            return Err(Trap::ArrayOutOfBounds.into_anyhow());
+        }
+
+        // Get the passive element segment.
+        let mut storage = None;
+        let elements = instance.passive_element_segment(&mut storage, elem_index);
+
+        // Convert array offsets into `usize`s.
+        let src = usize::try_from(src).map_err(|_| Trap::TableOutOfBounds.into_anyhow())?;
+        let len = usize::try_from(len).map_err(|_| Trap::TableOutOfBounds.into_anyhow())?;
+
+        // Turn the elements into `Val`s.
+        let vals = match elements {
+            TableSegmentElements::Functions(fs) => fs
+                .get(src..)
+                .and_then(|s| s.get(..len))
+                .ok_or_else(|| Trap::TableOutOfBounds.into_anyhow())?
+                .iter()
+                .map(|f| {
+                    let raw_func_ref = instance.get_func_ref(*f).unwrap_or(core::ptr::null_mut());
+                    let func = Func::from_vm_func_ref(
+                        (*instance.store()).store_opaque_mut(),
+                        raw_func_ref,
+                    );
+                    Val::FuncRef(func)
+                })
+                .collect::<Vec<_>>(),
+            TableSegmentElements::Expressions(xs) => {
+                let elem_ty = array
+                    ._ty((*instance.store()).store_opaque())?
+                    .element_type();
+                let elem_ty = elem_ty.unwrap_val_type();
+
+                let mut const_context = ConstEvalContext::new(instance);
+                let mut const_evaluator = ConstExprEvaluator::default();
+
+                xs.get(src..)
+                    .and_then(|s| s.get(..len))
+                    .ok_or_else(|| Trap::TableOutOfBounds.into_anyhow())?
+                    .iter()
+                    .map(|x| unsafe {
+                        let raw = const_evaluator
+                            .eval(&mut const_context, x)
+                            .expect("const expr should be valid");
+                        let mut store = AutoAssertNoGc::new(
+                            (*const_context.instance.store()).store_opaque_mut(),
+                        );
+                        Val::_from_raw(&mut store, raw, elem_ty)
+                    })
+                    .collect::<Vec<_>>()
+            }
+        };
+
+        // Copy the values into the array.
+        let store = (*instance.store()).store_opaque_mut();
+        for (i, val) in vals.into_iter().enumerate() {
+            let i = u32::try_from(i).unwrap();
+            let j = dst.checked_add(i).unwrap();
+            array._set(store, j, val)?;
+        }
+
+        Ok(())
+    })
 }
 
 #[cfg(feature = "gc")]


Test patch:
diff --git a/tests/wast.rs b/tests/wast.rs
index 123fd7634..933dfc33b 100644
--- a/tests/wast.rs
+++ b/tests/wast.rs
@@ -203,7 +203,6 @@ fn should_fail(test: &Path, strategy: Strategy) -> bool {
     }
     let unsupported_gc_tests = [
         "array_copy.wast",
-        "array_init_elem.wast",
         "binary_gc.wast",
         "br_on_cast_fail.wast",
         "br_on_cast.wast",