Repository: bytecodealliance/wasm-tools
Pull request #2104 (2025-03-18 15:54:20)

Improve support for memory64/shared-everything-threads in components
Previously the validator in wasm-tools was updated to conservatively reject all aliases of exports of core memories/tables that were 64-bit or shared. This is a coarse approximation of the actual rules though which today disallow using 64-bit memories in the canonical ABI. Instead this commit updates with a few minor changes to improve the situation here, namely:

* Aliasing core exports is now allowed for any table and memory type.
* Usage of memories in canonical ABI positions now verifies that the memory is of the expected type, using the standard sub-typing.
* Usage of tables in `canon thread.spawn_indirect` now performs a type-check via a similar method to ensure the table is of the right type.

This adjusts the behavior of a few tests here and there as well to account for some slight differences in behavior with these proposals.


Patch:
diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs
index 2207a109..9c44b0b3 100644
--- a/crates/wasmparser/src/validator/component.rs
+++ b/crates/wasmparser/src/validator/component.rs
@@ -1833,7 +1833,7 @@ impl ComponentState {
             )
         }
 
-        self.memory_at(memory, offset)?;
+        self.cabi_memory_at(memory, offset)?;
 
         self.core_funcs
             .push(types.intern_func_type(FuncType::new([ValType::I32; 2], [ValType::I32]), offset));
@@ -1861,7 +1861,7 @@ impl ComponentState {
             )
         }
 
-        self.memory_at(memory, offset)?;
+        self.cabi_memory_at(memory, offset)?;
 
         self.core_funcs
             .push(types.intern_func_type(FuncType::new([ValType::I32; 2], [ValType::I32]), offset));
@@ -1978,14 +1978,24 @@ impl ComponentState {
         // for more details:
         // https://github.com/WebAssembly/component-model/blob/6e08e283/design/mvp/CanonicalABI.md#-canon-threadspawn_indirect.
         let table = self.table_at(table_index, offset)?;
-        // TODO: check that the table is shared once components allow shared
-        // tables.
-        let expected_ty = RefType::FUNCREF
-            .shared()
-            .expect("a funcref can always be shared");
-        if table.element_type != expected_ty {
-            bail!(offset, "expected a table of shared `funcref`");
-        }
+
+        SubtypeCx::table_type(
+            table,
+            &TableType {
+                initial: 0,
+                maximum: None,
+                table64: false,
+                shared: true,
+                element_type: RefType::FUNCREF
+                    .shared()
+                    .expect("a funcref can always be shared"),
+            },
+            offset,
+        )
+        .map_err(|mut e| {
+            e.add_context("table is not a 32-bit shared table of (ref null (shared func))".into());
+            e
+        })?;
 
         // Insert the core function.
         let func_ty = FuncType::new([ValType::I32, ValType::I32], [ValType::I32]);
@@ -2244,7 +2254,7 @@ impl ComponentState {
                 CanonicalOption::Memory(idx) => {
                     memory = match memory {
                         None => {
-                            self.memory_at(*idx, offset)?;
+                            self.cabi_memory_at(*idx, offset)?;
                             Some(*idx)
                         }
                         Some(_) => {
@@ -3330,20 +3340,6 @@ impl ComponentState {
                     offset,
                 )?;
                 push_module_export!(EntityType::Table, core_tables, "table");
-
-                let ty = self.core_tables.last().unwrap();
-                if ty.table64 {
-                    bail!(
-                        offset,
-                        "64-bit tables are not compatible with components yet"
-                    );
-                }
-                if ty.shared {
-                    bail!(
-                        offset,
-                        "shared tables are not compatible with components yet"
-                    );
-                }
             }
             ExternalKind::Memory => {
                 check_max(
@@ -3354,20 +3350,6 @@ impl ComponentState {
                     offset,
                 )?;
                 push_module_export!(EntityType::Memory, core_memories, "memory");
-
-                let ty = self.core_memories.last().unwrap();
-                if ty.memory64 {
-                    bail!(
-                        offset,
-                        "64-bit linear memories are not compatible with components yet"
-                    );
-                }
-                if ty.shared {
-                    bail!(
-                        offset,
-                        "shared linear memories are not compatible with components yet"
-                    );
-                }
             }
             ExternalKind::Global => {
                 check_max(
@@ -3976,6 +3958,30 @@ impl ComponentState {
         }
     }
 
+    /// Validates that the linear memory at `idx` is valid to use as a canonical
+    /// ABI memory.
+    ///
+    /// At this time this requires that the memory is a plain 32-bit linear
+    /// memory. Notably this disallows shared memory and 64-bit linear memories.
+    fn cabi_memory_at(&self, idx: u32, offset: usize) -> Result<()> {
+        let ty = self.memory_at(idx, offset)?;
+        SubtypeCx::memory_type(
+            ty,
+            &MemoryType {
+                initial: 0,
+                maximum: None,
+                memory64: false,
+                shared: false,
+                page_size_log2: None,
+            },
+            offset,
+        )
+        .map_err(|mut e| {
+            e.add_context("canonical ABI memory is not a 32-bit linear memory".into());
+            e
+        })
+    }
+
     /// Completes the translation of this component, performing final
     /// validation of its structure.
     ///
diff --git a/crates/wasmparser/src/validator/component_types.rs b/crates/wasmparser/src/validator/component_types.rs
index 67e1addb..271b850f 100644
--- a/crates/wasmparser/src/validator/component_types.rs
+++ b/crates/wasmparser/src/validator/component_types.rs
@@ -7,7 +7,10 @@ use crate::validator::types::{
     CoreTypeId, EntityType, SnapshotList, TypeAlloc, TypeData, TypeIdentifier, TypeInfo, TypeList,
     Types, TypesKind, TypesRef, TypesRefKind,
 };
-use crate::{BinaryReaderError, CanonicalOption, FuncType, PrimitiveValType, Result, ValType};
+use crate::{
+    BinaryReaderError, CanonicalOption, FuncType, MemoryType, PrimitiveValType, Result, TableType,
+    ValType,
+};
 use core::fmt;
 use core::ops::Index;
 use core::sync::atomic::{AtomicUsize, Ordering};
@@ -2499,6 +2502,21 @@ pub struct SubtypeCx<'a> {
     pub b: SubtypeArena<'a>,
 }
 
+macro_rules! limits_match {
+    ($a:expr, $b:expr) => {{
+        let a = $a;
+        let b = $b;
+        a.initial >= b.initial
+            && match b.maximum {
+                Some(b_max) => match a.maximum {
+                    Some(a_max) => a_max <= b_max,
+                    None => false,
+                },
+                None => true,
+            }
+    }};
+}
+
 impl<'a> SubtypeCx<'a> {
     /// Create a new instance with the specified type lists
     pub fn new_with_refs(a: TypesRef<'a>, b: TypesRef<'a>) -> SubtypeCx<'a> {
@@ -3019,55 +3037,14 @@ impl<'a> SubtypeCx<'a> {
     }
 
     pub(crate) fn entity_type(&self, a: &EntityType, b: &EntityType, offset: usize) -> Result<()> {
-        macro_rules! limits_match {
-            ($a:expr, $b:expr) => {{
-                let a = $a;
-                let b = $b;
-                a.initial >= b.initial
-                    && match b.maximum {
-                        Some(b_max) => match a.maximum {
-                            Some(a_max) => a_max <= b_max,
-                            None => false,
-                        },
-                        None => true,
-                    }
-            }};
-        }
-
         match (a, b) {
             (EntityType::Func(a), EntityType::Func(b)) => {
                 self.core_func_type(self.a[*a].unwrap_func(), self.b[*b].unwrap_func(), offset)
             }
             (EntityType::Func(_), b) => bail!(offset, "expected {}, found func", b.desc()),
-            (EntityType::Table(a), EntityType::Table(b)) => {
-                if a.element_type != b.element_type {
-                    bail!(
-                        offset,
-                        "expected table element type {}, found {}",
-                        b.element_type,
-                        a.element_type,
-                    )
-                }
-                if limits_match!(a, b) {
-                    Ok(())
-                } else {
-                    bail!(offset, "mismatch in table limits")
-                }
-            }
+            (EntityType::Table(a), EntityType::Table(b)) => Self::table_type(a, b, offset),
             (EntityType::Table(_), b) => bail!(offset, "expected {}, found table", b.desc()),
-            (EntityType::Memory(a), EntityType::Memory(b)) => {
-                if a.shared != b.shared {
-                    bail!(offset, "mismatch in the shared flag for memories")
-                }
-                if a.memory64 != b.memory64 {
-                    bail!(offset, "mismatch in index type used for memories")
-                }
-                if limits_match!(a, b) {
-                    Ok(())
-                } else {
-                    bail!(offset, "mismatch in memory limits")
-                }
-            }
+            (EntityType::Memory(a), EntityType::Memory(b)) => Self::memory_type(a, b, offset),
             (EntityType::Memory(_), b) => bail!(offset, "expected {}, found memory", b.desc()),
             (EntityType::Global(a), EntityType::Global(b)) => {
                 if a.mutable != b.mutable {
@@ -3092,6 +3069,39 @@ impl<'a> SubtypeCx<'a> {
         }
     }
 
+    pub(crate) fn table_type(a: &TableType, b: &TableType, offset: usize) -> Result<()> {
+        if a.element_type != b.element_type {
+            bail!(
+                offset,
+                "expected table element type {}, found {}",
+                b.element_type,
+                a.element_type,
+            )
+        }
+        if a.shared != b.shared {
+            bail!(offset, "mismatch in the shared flag for tables")
+        }
+        if limits_match!(a, b) {
+            Ok(())
+        } else {
+            bail!(offset, "mismatch in table limits")
+        }
+    }
+
+    pub(crate) fn memory_type(a: &MemoryType, b: &MemoryType, offset: usize) -> Result<()> {
+        if a.shared != b.shared {
+            bail!(offset, "mismatch in the shared flag for memories")
+        }
+        if a.memory64 != b.memory64 {
+            bail!(offset, "mismatch in index type used for memories")
+        }
+        if limits_match!(a, b) {
+            Ok(())
+        } else {
+            bail!(offset, "mismatch in memory limits")
+        }
+    }
+
     fn core_func_type(&self, a: &FuncType, b: &FuncType, offset: usize) -> Result<()> {
         if a == b {
             Ok(())


Test patch:
diff --git a/tests/cli/component-model/memory64.wast b/tests/cli/component-model/memory64.wast
index 7da46519..0ec55341 100644
--- a/tests/cli/component-model/memory64.wast
+++ b/tests/cli/component-model/memory64.wast
@@ -22,20 +22,33 @@
   )
   "mismatch in index type used for memories")
 
+(component
+  (core module $A
+    (memory (export "m") i64 1))
+  (core instance $A (instantiate $A))
+  (alias core export $A "m" (core memory $m))
+
+  (core module $B (import "" "" (memory i64 1)))
+  (core instance (instantiate $B (with "" (instance (export "" (memory $m))))))
+)
+
+(component
+  (core module $A
+    (table (export "m") i64 1 funcref))
+  (core instance $A (instantiate $A))
+  (alias core export $A "m" (core table $m))
+
+  (core module $B (import "" "" (table i64 1 funcref)))
+  (core instance (instantiate $B (with "" (instance (export "" (table $m))))))
+)
+
 (assert_invalid
   (component
+    (import "x" (func $x (param "x" string)))
     (core module $A
       (memory (export "m") i64 1))
     (core instance $A (instantiate $A))
     (alias core export $A "m" (core memory $m))
+    (core func (canon lower (func $x) (memory $m)))
   )
-  "64-bit linear memories are not compatible with components yet")
-
-(assert_invalid
-  (component
-    (core module $A
-      (table (export "m") i64 1 funcref))
-    (core instance $A (instantiate $A))
-    (alias core export $A "m" (core table $m))
-  )
-  "64-bit tables are not compatible with components yet")
+  "canonical ABI memory is not a 32-bit linear memory")
diff --git a/tests/cli/component-model/shared-everything-threads/not-accepted.wast b/tests/cli/component-model/shared-everything-threads/not-accepted.wast
index 8c2f87e0..d86d1307 100644
--- a/tests/cli/component-model/shared-everything-threads/not-accepted.wast
+++ b/tests/cli/component-model/shared-everything-threads/not-accepted.wast
@@ -1,20 +1,34 @@
 ;; RUN: wast --assert default --snapshot tests/snapshots % -f shared-everything-threads
 
-(assert_invalid
-  (component
-    (core module $A
-      (memory (export "m") 1 2 shared))
-    (core instance $A (instantiate $A))
-    (alias core export $A "m" (core memory $m))
+(component
+  (core module $A
+    (memory (export "m") 1 2 shared))
+  (core instance $A (instantiate $A))
+  (alias core export $A "m" (core memory $m))
+
+  (core module $B (import "" "" (memory 1 2 shared)))
+  (core instance (instantiate $B (with "" (instance (export "" (memory $m))))))
+)
+
+(component
+  (core module $A
+    (table (export "m") shared 1 2 (ref null (shared func)))
   )
-  "shared linear memories are not compatible with components yet")
+  (core instance $A (instantiate $A))
+  (alias core export $A "m" (core table $m))
+
+  (core module $B (import "" "" (table shared 1 2 (ref null (shared func)))))
+  (core instance (instantiate $B (with "" (instance (export "" (table $m))))))
+)
 
 (assert_invalid
   (component
+    (import "x" (func $x (param "x" string)))
+
     (core module $A
-      (table (export "m") shared 1 2 (ref null (shared func)))
-    )
+      (memory (export "m") 1 2 shared))
     (core instance $A (instantiate $A))
-    (alias core export $A "m" (core table $m))
+    (alias core export $A "m" (core memory $m))
+    (core func (canon lower (func $x) (memory $m)))
   )
-  "shared tables are not compatible with components yet")
+  "canonical ABI memory is not a 32-bit linear memory")
diff --git a/tests/cli/shared-everything-threads/builtins.wast b/tests/cli/shared-everything-threads/builtins.wast
index 80804333..4545f206 100644
--- a/tests/cli/shared-everything-threads/builtins.wast
+++ b/tests/cli/shared-everything-threads/builtins.wast
@@ -6,7 +6,7 @@
   (core type $start (shared (func (param $context i32))))
   (core func $spawn_ref (canon thread.spawn_ref $start))
 
-  (core module $libc (table (export "start-table") 1 (ref null (shared func))))
+  (core module $libc (table (export "start-table") shared 1 (ref null (shared func))))
   (core instance $libc (instantiate $libc))
   (core func $spawn_indirect (canon thread.spawn_indirect $start (table $libc "start-table")))
 
@@ -17,7 +17,7 @@
   (core type $start (shared (func (param $context i32))))
   (core func $spawn_ref (canon thread.spawn_ref $start))
 
-  (core module $libc (table (export "start-table") 1 (ref null (shared func))))
+  (core module $libc (table (export "start-table") shared 1 (ref null (shared func))))
   (core instance $libc (instantiate $libc))
   (core func $spawn_indirect (canon thread.spawn_indirect $start (table $libc "start-table")))
 
@@ -49,12 +49,12 @@
 
 (assert_invalid
   (component
-    (core type $start (func))
+    (core type $start (shared (func (param i32))))
     ;; Refer to a non-existent table type (i.e., 0); validation
     ;; for `thread.spawn_indirect` happens first.
     (core func $spawn_indirect (canon thread.spawn_indirect $start (table 0)))
   )
-  "spawn type must be shared"
+  "unknown table 0: table index out of bounds"
 )
 
 (assert_invalid
@@ -68,7 +68,9 @@
 (assert_invalid
   (component
     (core type $start (shared (func)))
-    (core func $spawn_indirect (canon thread.spawn_indirect $start (table 0)))
+    (core module $libc (table (export "start-table") shared 1 (ref null (shared func))))
+    (core instance $libc (instantiate $libc))
+    (core func $spawn_indirect (canon thread.spawn_indirect $start (table $libc "start-table")))
   )
   "spawn function must take a single `i32` argument (currently)"
 )
@@ -76,20 +78,9 @@
 (assert_invalid
   (component
     (core type $start (shared (func (param i32))))
-    (core func $spawn_indirect (canon thread.spawn_indirect $start (table 0)))
-  )
-  "unknown table"
-)
-
-(assert_invalid
-  (component
-    (core type $start (shared (func (param i32))))
-    (core module $libc (table (export "start-table") 1 funcref))
+    (core module $libc (table (export "start-table") 1 (ref null (shared func))))
     (core instance $libc (instantiate $libc))
     (core func $spawn_indirect (canon thread.spawn_indirect $start (table $libc "start-table")))
   )
-  "expected a table of shared `funcref`"
+  "mismatch in the shared flag for tables"
 )
-
-;; TODO: add a test confirming that an unshared table is invalid once components
-;; allow shared tables; see `not-accepted.wast`.
diff --git a/tests/snapshots/cli/component-model/memory64.wast.json b/tests/snapshots/cli/component-model/memory64.wast.json
index 91e9264b..ae6e7b21 100644
--- a/tests/snapshots/cli/component-model/memory64.wast.json
+++ b/tests/snapshots/cli/component-model/memory64.wast.json
@@ -16,18 +16,23 @@
       "text": "mismatch in index type used for memories"
     },
     {
-      "type": "assert_invalid",
-      "line": 26,
+      "type": "module",
+      "line": 25,
       "filename": "memory64.2.wasm",
-      "module_type": "binary",
-      "text": "64-bit linear memories are not compatible with components yet"
+      "module_type": "binary"
     },
     {
-      "type": "assert_invalid",
+      "type": "module",
       "line": 35,
       "filename": "memory64.3.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 46,
+      "filename": "memory64.4.wasm",
       "module_type": "binary",
-      "text": "64-bit tables are not compatible with components yet"
+      "text": "canonical ABI memory is not a 32-bit linear memory"
     }
   ]
 }
\ No newline at end of file
diff --git a/tests/snapshots/cli/component-model/memory64.wast/2.print b/tests/snapshots/cli/component-model/memory64.wast/2.print
new file mode 100644
index 00000000..ceb62aa2
--- /dev/null
+++ b/tests/snapshots/cli/component-model/memory64.wast/2.print
@@ -0,0 +1,18 @@
+(component
+  (core module $A (;0;)
+    (memory (;0;) i64 1)
+    (export "m" (memory 0))
+  )
+  (core instance $A (;0;) (instantiate $A))
+  (alias core export $A "m" (core memory $m (;0;)))
+  (core module $B (;1;)
+    (import "" "" (memory (;0;) i64 1))
+  )
+  (core instance (;1;)
+    (export "" (memory $m))
+  )
+  (core instance (;2;) (instantiate $B
+      (with "" (instance 1))
+    )
+  )
+)
diff --git a/tests/snapshots/cli/component-model/memory64.wast/3.print b/tests/snapshots/cli/component-model/memory64.wast/3.print
new file mode 100644
index 00000000..7ef1625b
--- /dev/null
+++ b/tests/snapshots/cli/component-model/memory64.wast/3.print
@@ -0,0 +1,18 @@
+(component
+  (core module $A (;0;)
+    (table (;0;) i64 1 funcref)
+    (export "m" (table 0))
+  )
+  (core instance $A (;0;) (instantiate $A))
+  (alias core export $A "m" (core table $m (;0;)))
+  (core module $B (;1;)
+    (import "" "" (table (;0;) i64 1 funcref))
+  )
+  (core instance (;1;)
+    (export "" (table $m))
+  )
+  (core instance (;2;) (instantiate $B
+      (with "" (instance 1))
+    )
+  )
+)
diff --git a/tests/snapshots/cli/component-model/shared-everything-threads/not-accepted.wast.json b/tests/snapshots/cli/component-model/shared-everything-threads/not-accepted.wast.json
index 80da898a..d5e65d45 100644
--- a/tests/snapshots/cli/component-model/shared-everything-threads/not-accepted.wast.json
+++ b/tests/snapshots/cli/component-model/shared-everything-threads/not-accepted.wast.json
@@ -2,18 +2,23 @@
   "source_filename": "tests/cli/component-model/shared-everything-threads/not-accepted.wast",
   "commands": [
     {
-      "type": "assert_invalid",
-      "line": 4,
+      "type": "module",
+      "line": 3,
       "filename": "not-accepted.0.wasm",
-      "module_type": "binary",
-      "text": "shared linear memories are not compatible with components yet"
+      "module_type": "binary"
     },
     {
-      "type": "assert_invalid",
+      "type": "module",
       "line": 13,
       "filename": "not-accepted.1.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 25,
+      "filename": "not-accepted.2.wasm",
       "module_type": "binary",
-      "text": "shared tables are not compatible with components yet"
+      "text": "canonical ABI memory is not a 32-bit linear memory"
     }
   ]
 }
\ No newline at end of file
diff --git a/tests/snapshots/cli/component-model/shared-everything-threads/not-accepted.wast/0.print b/tests/snapshots/cli/component-model/shared-everything-threads/not-accepted.wast/0.print
new file mode 100644
index 00000000..bf8e8b4d
--- /dev/null
+++ b/tests/snapshots/cli/component-model/shared-everything-threads/not-accepted.wast/0.print
@@ -0,0 +1,18 @@
+(component
+  (core module $A (;0;)
+    (memory (;0;) 1 2 shared)
+    (export "m" (memory 0))
+  )
+  (core instance $A (;0;) (instantiate $A))
+  (alias core export $A "m" (core memory $m (;0;)))
+  (core module $B (;1;)
+    (import "" "" (memory (;0;) 1 2 shared))
+  )
+  (core instance (;1;)
+    (export "" (memory $m))
+  )
+  (core instance (;2;) (instantiate $B
+      (with "" (instance 1))
+    )
+  )
+)
diff --git a/tests/snapshots/cli/component-model/shared-everything-threads/not-accepted.wast/1.print b/tests/snapshots/cli/component-model/shared-everything-threads/not-accepted.wast/1.print
new file mode 100644
index 00000000..c07f1c3d
--- /dev/null
+++ b/tests/snapshots/cli/component-model/shared-everything-threads/not-accepted.wast/1.print
@@ -0,0 +1,18 @@
+(component
+  (core module $A (;0;)
+    (table (;0;) shared 1 2 (ref null (shared func)))
+    (export "m" (table 0))
+  )
+  (core instance $A (;0;) (instantiate $A))
+  (alias core export $A "m" (core table $m (;0;)))
+  (core module $B (;1;)
+    (import "" "" (table (;0;) shared 1 2 (ref null (shared func))))
+  )
+  (core instance (;1;)
+    (export "" (table $m))
+  )
+  (core instance (;2;) (instantiate $B
+      (with "" (instance 1))
+    )
+  )
+)
diff --git a/tests/snapshots/cli/shared-everything-threads/builtins.wast.json b/tests/snapshots/cli/shared-everything-threads/builtins.wast.json
index 42e3085c..a522f753 100644
--- a/tests/snapshots/cli/shared-everything-threads/builtins.wast.json
+++ b/tests/snapshots/cli/shared-everything-threads/builtins.wast.json
@@ -25,7 +25,7 @@
       "line": 51,
       "filename": "builtins.3.wasm",
       "module_type": "binary",
-      "text": "spawn type must be shared"
+      "text": "unknown table 0: table index out of bounds"
     },
     {
       "type": "assert_invalid",
@@ -43,17 +43,10 @@
     },
     {
       "type": "assert_invalid",
-      "line": 77,
+      "line": 79,
       "filename": "builtins.6.wasm",
       "module_type": "binary",
-      "text": "unknown table"
-    },
-    {
-      "type": "assert_invalid",
-      "line": 85,
-      "filename": "builtins.7.wasm",
-      "module_type": "binary",
-      "text": "expected a table of shared `funcref`"
+      "text": "mismatch in the shared flag for tables"
     }
   ]
 }
\ No newline at end of file
diff --git a/tests/snapshots/cli/shared-everything-threads/builtins.wast/0.print b/tests/snapshots/cli/shared-everything-threads/builtins.wast/0.print
index 0c111952..b242fb26 100644
--- a/tests/snapshots/cli/shared-everything-threads/builtins.wast/0.print
+++ b/tests/snapshots/cli/shared-everything-threads/builtins.wast/0.print
@@ -2,7 +2,7 @@
   (core type $start (;0;) (shared (func (param i32))))
   (core func $spawn_ref (;0;) (canon thread.spawn_ref $start))
   (core module $libc (;0;)
-    (table (;0;) 1 (ref null (shared func)))
+    (table (;0;) shared 1 (ref null (shared func)))
     (export "start-table" (table 0))
   )
   (core instance $libc (;0;) (instantiate $libc))
diff --git a/tests/snapshots/cli/shared-everything-threads/builtins.wast/1.print b/tests/snapshots/cli/shared-everything-threads/builtins.wast/1.print
index 6beff1ca..1174f32a 100644
--- a/tests/snapshots/cli/shared-everything-threads/builtins.wast/1.print
+++ b/tests/snapshots/cli/shared-everything-threads/builtins.wast/1.print
@@ -2,7 +2,7 @@
   (core type $start (;0;) (shared (func (param i32))))
   (core func $spawn_ref (;0;) (canon thread.spawn_ref $start))
   (core module $libc (;0;)
-    (table (;0;) 1 (ref null (shared func)))
+    (table (;0;) shared 1 (ref null (shared func)))
     (export "start-table" (table 0))
   )
   (core instance $libc (;0;) (instantiate $libc))