Repository: bytecodealliance/wasm-tools
Pull request #1649 (2024-06-28 21:47:31)

threads: add `shared` tables
This change spreads `shared` attributes on to tables. As with #1480, this does not yet turn on fuzzing for `shared` things so the only checking is via the in-progress test suite in `tests/local/shared-everything-threads`.

Implementing this raises an issue with the spec: the addition of `shared` in the text format means that inline table expressions (e.g.,
`(table <type> (elem ...))`) are difficult to parse: the parser would potentially have to look past two tokens now, `shared` and `i32|i64` to see if a type appears that indicates we should be parsing an inline format. There are multiple options for what to do here; I opened an issue to figure this out at the spec level first ([https://github.com/bytecodealliance/wasm-tools/pull/71]).

[proposal]: https://github.com/WebAssembly/shared-everything-threads
[https://github.com/bytecodealliance/wasm-tools/pull/71]: https://github.com/WebAssembly/shared-everything-threads/issues/71

abrown: __DO NOT MERGE__: this is based on #1646 and will be rebased once that merges.
abrown: Ok, this is rebased and ready for review.

Patch:
diff --git a/crates/wasm-encoder/src/core/elements.rs b/crates/wasm-encoder/src/core/elements.rs
index 28a1031e..6ed6e03e 100644
--- a/crates/wasm-encoder/src/core/elements.rs
+++ b/crates/wasm-encoder/src/core/elements.rs
@@ -18,6 +18,7 @@ use crate::{encode_section, ConstExpr, Encode, RefType, Section, SectionId};
 ///     minimum: 128,
 ///     maximum: None,
 ///     table64: false,
+///     shared: false,
 /// });
 ///
 /// let mut elements = ElementSection::new();
diff --git a/crates/wasm-encoder/src/core/tables.rs b/crates/wasm-encoder/src/core/tables.rs
index 6b35915b..3bb2d58e 100644
--- a/crates/wasm-encoder/src/core/tables.rs
+++ b/crates/wasm-encoder/src/core/tables.rs
@@ -15,6 +15,7 @@ use crate::{encode_section, ConstExpr, Encode, RefType, Section, SectionId, ValT
 ///     minimum: 128,
 ///     maximum: None,
 ///     table64: false,
+///     shared: false,
 /// });
 ///
 /// let mut module = Module::new();
@@ -87,6 +88,10 @@ pub struct TableType {
     pub minimum: u64,
     /// Maximum size, in elements, of this table
     pub maximum: Option<u64>,
+    /// Whether this table is shared or not.
+    ///
+    /// This is included the shared-everything-threads proposal.
+    pub shared: bool,
 }
 
 impl TableType {
@@ -106,6 +111,9 @@ impl Encode for TableType {
         if self.maximum.is_some() {
             flags |= 0b001;
         }
+        if self.shared {
+            flags |= 0b010;
+        }
         if self.table64 {
             flags |= 0b100;
         }
diff --git a/crates/wasm-encoder/src/reencode.rs b/crates/wasm-encoder/src/reencode.rs
index 5c6a6711..c8fb29aa 100644
--- a/crates/wasm-encoder/src/reencode.rs
+++ b/crates/wasm-encoder/src/reencode.rs
@@ -1154,6 +1154,7 @@ pub mod utils {
             minimum: table_ty.initial,
             maximum: table_ty.maximum,
             table64: table_ty.table64,
+            shared: table_ty.shared,
         })
     }
 
diff --git a/crates/wasm-mutate/src/mutators/translate.rs b/crates/wasm-mutate/src/mutators/translate.rs
index 706e7629..d1ac04c5 100644
--- a/crates/wasm-mutate/src/mutators/translate.rs
+++ b/crates/wasm-mutate/src/mutators/translate.rs
@@ -156,6 +156,7 @@ pub fn table_type(
         minimum: ty.initial,
         maximum: ty.maximum,
         table64: ty.table64,
+        shared: ty.shared,
     })
 }
 
diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs
index 43acc72b..2f8b5529 100644
--- a/crates/wasm-smith/src/core.rs
+++ b/crates/wasm-smith/src/core.rs
@@ -2542,6 +2542,7 @@ pub(crate) fn arbitrary_table_type(
         minimum,
         maximum,
         table64,
+        shared: false, // TODO: handle shared
     })
 }
 
diff --git a/crates/wasmparser/src/readers/core/operators.rs b/crates/wasmparser/src/readers/core/operators.rs
index ff3ef843..fe775476 100644
--- a/crates/wasmparser/src/readers/core/operators.rs
+++ b/crates/wasmparser/src/readers/core/operators.rs
@@ -31,7 +31,7 @@ pub enum BlockType {
 }
 
 /// Represents a memory immediate in a WebAssembly memory instruction.
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
 pub struct MemArg {
     /// Alignment, stored as `n` where the actual alignment is `2^n`
     pub align: u8,
@@ -64,6 +64,16 @@ pub struct BrTable<'a> {
     pub(crate) default: u32,
 }
 
+impl PartialEq<Self> for BrTable<'_> {
+    fn eq(&self, other: &Self) -> bool {
+        self.cnt == other.cnt
+            && self.default == other.default
+            && self.reader.remaining_buffer() == other.reader.remaining_buffer()
+    }
+}
+
+impl Eq for BrTable<'_> {}
+
 /// An IEEE binary32 immediate floating point value, represented as a u32
 /// containing the bit pattern.
 ///
@@ -155,7 +165,7 @@ macro_rules! define_operator {
         /// Instructions as defined [here].
         ///
         /// [here]: https://webassembly.github.io/spec/core/binary/instructions.html
-        #[derive(Debug, Clone)]
+        #[derive(Debug, Clone, Eq, PartialEq)]
         #[allow(missing_docs)]
         pub enum Operator<'a> {
             $(
@@ -397,7 +407,7 @@ impl<'a, V: VisitOperator<'a> + ?Sized> VisitOperator<'a> for Box<V> {
 }
 
 /// A `try_table` entries representation.
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Eq, PartialEq)]
 pub struct TryTable {
     /// The block type describing the try block itself.
     pub ty: BlockType,
@@ -406,7 +416,7 @@ pub struct TryTable {
 }
 
 /// Catch clauses that can be specified in [`TryTable`].
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
 #[allow(missing_docs)]
 pub enum Catch {
     /// Equivalent of `catch`
diff --git a/crates/wasmparser/src/readers/core/tables.rs b/crates/wasmparser/src/readers/core/tables.rs
index 6c144a57..7df2a767 100644
--- a/crates/wasmparser/src/readers/core/tables.rs
+++ b/crates/wasmparser/src/readers/core/tables.rs
@@ -68,10 +68,11 @@ impl<'a> FromReader<'a> for TableType {
         let element_type = reader.read()?;
         let pos = reader.original_position();
         let flags = reader.read_u8()?;
-        if (flags & !0b101) != 0 {
+        if (flags & !0b111) != 0 {
             bail!(pos, "invalid table resizable limits flags");
         }
         let has_max = (flags & 0b001) != 0;
+        let shared = (flags & 0b010) != 0;
         let table64 = (flags & 0b100) != 0;
         Ok(TableType {
             element_type,
@@ -88,6 +89,7 @@ impl<'a> FromReader<'a> for TableType {
             } else {
                 Some(reader.read_var_u32()?.into())
             },
+            shared,
         })
     }
 }
diff --git a/crates/wasmparser/src/readers/core/types.rs b/crates/wasmparser/src/readers/core/types.rs
index f85ca093..7acc6128 100644
--- a/crates/wasmparser/src/readers/core/types.rs
+++ b/crates/wasmparser/src/readers/core/types.rs
@@ -1700,6 +1700,10 @@ pub struct TableType {
     /// For 32-bit tables (when `table64` is `false`) this is guaranteed to
     /// be at most `u32::MAX` for valid types.
     pub maximum: Option<u64>,
+    /// Whether this table is shared or not.
+    ///
+    /// This is included the shared-everything-threads proposal.
+    pub shared: bool,
 }
 
 impl TableType {
diff --git a/crates/wasmparser/src/validator.rs b/crates/wasmparser/src/validator.rs
index 4396cb51..de9e1e42 100644
--- a/crates/wasmparser/src/validator.rs
+++ b/crates/wasmparser/src/validator.rs
@@ -1537,6 +1537,7 @@ mod tests {
                 maximum: None,
                 element_type: RefType::FUNCREF,
                 table64: false,
+                shared: false,
             }
         );
 
diff --git a/crates/wasmparser/src/validator/core.rs b/crates/wasmparser/src/validator/core.rs
index 4c78eaf4..003dbe59 100644
--- a/crates/wasmparser/src/validator/core.rs
+++ b/crates/wasmparser/src/validator/core.rs
@@ -146,7 +146,7 @@ impl ModuleState {
         offset: usize,
     ) -> Result<()> {
         self.module
-            .check_table_type(&mut table.ty, features, offset)?;
+            .check_table_type(&mut table.ty, features, types, offset)?;
 
         match &table.init {
             TableInit::RefNull => {
@@ -870,7 +870,7 @@ impl Module {
                 EntityType::Func(self.types[*type_index as usize])
             }
             TypeRef::Table(t) => {
-                self.check_table_type(t, features, offset)?;
+                self.check_table_type(t, features, types, offset)?;
                 EntityType::Table(*t)
             }
             TypeRef::Memory(t) => {
@@ -892,10 +892,11 @@ impl Module {
         &self,
         ty: &mut TableType,
         features: &WasmFeatures,
+        types: &TypeList,
         offset: usize,
     ) -> Result<()> {
-        // the `funcref` value type is allowed all the way back to the MVP, so
-        // don't check it here
+        // The `funcref` value type is allowed all the way back to the MVP, so
+        // don't check it here.
         if ty.element_type != RefType::FUNCREF {
             self.check_ref_type(&mut ty.element_type, features, offset)?
         }
@@ -914,6 +915,23 @@ impl Module {
                 offset,
             ));
         }
+
+        if ty.shared {
+            if !features.shared_everything_threads() {
+                return Err(BinaryReaderError::new(
+                    "shared tables require the shared-everything-threads proposal",
+                    offset,
+                ));
+            }
+
+            if !types.reftype_is_shared(ty.element_type) {
+                return Err(BinaryReaderError::new(
+                    "shared tables must have a shared element type",
+                    offset,
+                ));
+            }
+        }
+
         Ok(())
     }
 
diff --git a/crates/wasmparser/src/validator/types.rs b/crates/wasmparser/src/validator/types.rs
index 578ba8b2..d713fbee 100644
--- a/crates/wasmparser/src/validator/types.rs
+++ b/crates/wasmparser/src/validator/types.rs
@@ -2834,19 +2834,23 @@ impl TypeList {
     }
 
     /// Is `ty` shared?
-    ///
-    /// This is complicated by reference types, since they may have concrete
-    /// heap types whose shared-ness must be checked by looking at the type they
-    /// point to.
     pub fn valtype_is_shared(&self, ty: ValType) -> bool {
         match ty {
             ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => true,
-            ValType::Ref(rt) => match rt.heap_type() {
-                HeapType::Abstract { shared, .. } => shared,
-                HeapType::Concrete(index) => {
-                    self[index.as_core_type_id().unwrap()].composite_type.shared
-                }
-            },
+            ValType::Ref(rt) => self.reftype_is_shared(rt),
+        }
+    }
+
+    /// Is the reference type `ty` shared?
+    ///
+    /// This is complicated by concrete heap types whose shared-ness must be
+    /// checked by looking at the type they point to.
+    pub fn reftype_is_shared(&self, ty: RefType) -> bool {
+        match ty.heap_type() {
+            HeapType::Abstract { shared, .. } => shared,
+            HeapType::Concrete(index) => {
+                self[index.as_core_type_id().unwrap()].composite_type.shared
+            }
         }
     }
 
diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs
index 6c6d2943..f8e09f62 100644
--- a/crates/wasmprinter/src/lib.rs
+++ b/crates/wasmprinter/src/lib.rs
@@ -1130,6 +1130,9 @@ impl Printer<'_, '_> {
             self.print_name(&state.core.table_names, state.core.tables)?;
             self.result.write_str(" ")?;
         }
+        if ty.shared {
+            self.print_type_keyword("shared ")?;
+        }
         if ty.table64 {
             self.print_type_keyword("i64 ")?;
         }
diff --git a/crates/wast/src/component/binary.rs b/crates/wast/src/component/binary.rs
index c57d6333..19806666 100644
--- a/crates/wast/src/component/binary.rs
+++ b/crates/wast/src/component/binary.rs
@@ -648,6 +648,7 @@ impl From<core::TableType<'_>> for wasm_encoder::TableType {
             minimum: ty.limits.min,
             maximum: ty.limits.max,
             table64: ty.limits.is64,
+            shared: ty.shared,
         }
     }
 }
diff --git a/crates/wast/src/core/binary.rs b/crates/wast/src/core/binary.rs
index 7beaa82e..47fd4b0d 100644
--- a/crates/wast/src/core/binary.rs
+++ b/crates/wast/src/core/binary.rs
@@ -560,6 +560,9 @@ impl<'a> Encode for TableType<'a> {
         if self.limits.max.is_some() {
             flags |= 1 << 0;
         }
+        if self.shared {
+            flags |= 1 << 1;
+        }
         if self.limits.is64 {
             flags |= 1 << 2;
         }
diff --git a/crates/wast/src/core/resolve/deinline_import_export.rs b/crates/wast/src/core/resolve/deinline_import_export.rs
index 8dbb5f6a..06cb870b 100644
--- a/crates/wast/src/core/resolve/deinline_import_export.rs
+++ b/crates/wast/src/core/resolve/deinline_import_export.rs
@@ -103,11 +103,13 @@ pub fn run(fields: &mut Vec<ModuleField>) {
                             },
                         });
                     }
-                    // If data is defined inline insert an explicit `data` module
-                    // field here instead, switching this to a `Normal` memory.
+                    // If data is defined inline insert an explicit `data`
+                    // module field here instead, switching this to a `Normal`
+                    // memory.
                     TableKind::Inline {
                         payload,
                         elem,
+                        shared,
                         is64,
                     } => {
                         let is64 = *is64;
@@ -123,6 +125,7 @@ pub fn run(fields: &mut Vec<ModuleField>) {
                                     is64,
                                 },
                                 elem: *elem,
+                                shared: *shared,
                             },
                             init_expr: None,
                         };
diff --git a/crates/wast/src/core/table.rs b/crates/wast/src/core/table.rs
index 3d489033..80875df6 100644
--- a/crates/wast/src/core/table.rs
+++ b/crates/wast/src/core/table.rs
@@ -29,7 +29,7 @@ pub enum TableKind<'a> {
         ty: TableType<'a>,
     },
 
-    /// A typical memory definition which simply says the limits of the table
+    /// A typical memory definition which simply says the limits of the table.
     Normal {
         /// Table type.
         ty: TableType<'a>,
@@ -37,12 +37,14 @@ pub enum TableKind<'a> {
         init_expr: Option<Expression<'a>>,
     },
 
-    /// The elem segments of this table, starting from 0, explicitly listed
+    /// The elem segments of this table, starting from 0, explicitly listed.
     Inline {
         /// The element type of this table.
         elem: RefType<'a>,
-        /// Whether or not this will be creating a 64-bit table
+        /// Whether or not this will be creating a 64-bit table.
         is64: bool,
+        /// Whether this table is shared or not.
+        shared: bool,
         /// The element table entries to have, and the length of this list is
         /// the limits of the table as well.
         payload: ElemPayload<'a>,
@@ -58,13 +60,19 @@ impl<'a> Parse<'a> for Table<'a> {
 
         // Afterwards figure out which style this is, either:
         //
-        //  * `elemtype (elem ...)`
-        //  * `(import "a" "b") limits`
-        //  * `limits`
+        //  * inline: `elemtype (elem ...)`
+        //  * normal: `tabletype`
+        //  * import: `(import "a" "b") tabletype`
+        //
+        // Where `tabletype := shared? index_type limits reftype`
         let mut l = parser.lookahead1();
 
+        let is_shared = l.peek::<kw::shared>()?;
         let has_index_type = l.peek::<kw::i32>()? | l.peek::<kw::i64>()?;
-        let kind = if l.peek::<RefType>()? || (has_index_type && parser.peek2::<RefType>()?) {
+        let kind = if l.peek::<RefType>()?
+            || ((is_shared || has_index_type) && parser.peek2::<RefType>()?)
+        {
+            let shared = parser.parse::<Option<kw::shared>>()?.is_some();
             let is64 = if parser.parse::<Option<kw::i32>>()?.is_some() {
                 false
             } else {
@@ -82,9 +90,10 @@ impl<'a> Parse<'a> for Table<'a> {
             TableKind::Inline {
                 elem,
                 is64,
+                shared,
                 payload,
             }
-        } else if has_index_type || l.peek::<u64>()? {
+        } else if is_shared || has_index_type || l.peek::<u64>()? {
             TableKind::Normal {
                 ty: parser.parse()?,
                 init_expr: if !parser.is_empty() {
diff --git a/crates/wast/src/core/types.rs b/crates/wast/src/core/types.rs
index 48f128c0..a20d65e4 100644
--- a/crates/wast/src/core/types.rs
+++ b/crates/wast/src/core/types.rs
@@ -587,18 +587,21 @@ impl<'a> Parse<'a> for Limits {
     }
 }
 
-/// Configuration for a table of a wasm mdoule
+/// Configuration for a table of a wasm module.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 pub struct TableType<'a> {
     /// Limits on the element sizes of this table
     pub limits: Limits,
     /// The type of element stored in this table
     pub elem: RefType<'a>,
+    /// Whether or not this is a shared table.
+    pub shared: bool,
 }
 
 impl<'a> Parse<'a> for TableType<'a> {
     fn parse(parser: Parser<'a>) -> Result<Self> {
         Ok(TableType {
+            shared: parser.parse::<Option<kw::shared>>()?.is_some(),
             limits: parser.parse()?,
             elem: parser.parse()?,
         })
diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs
index f045cb83..35599208 100644
--- a/crates/wit-component/src/encoding.rs
+++ b/crates/wit-component/src/encoding.rs
@@ -1269,6 +1269,7 @@ impl<'a> EncodingState<'a> {
             minimum: signatures.len() as u64,
             maximum: Some(signatures.len() as u64),
             table64: false,
+            shared: false,
         };
 
         tables.table(table_type);
diff --git a/crates/wit-component/src/linking.rs b/crates/wit-component/src/linking.rs
index a9d3ca95..0ba89cbe 100644
--- a/crates/wit-component/src/linking.rs
+++ b/crates/wit-component/src/linking.rs
@@ -468,6 +468,7 @@ fn make_env_module<'a>(
             minimum: table_offset.into(),
             maximum: None,
             table64: false,
+            shared: false,
         });
         exports.export("__indirect_function_table", ExportKind::Table, 0);
         module.section(&tables);
@@ -560,6 +561,7 @@ fn make_init_module(
             minimum: 0,
             maximum: None,
             table64: false,
+            shared: false,
         },
     );
 
diff --git a/crates/wit-encoder/src/enum_.rs b/crates/wit-encoder/src/enum_.rs
index 80c0afc8..b84f3725 100644
--- a/crates/wit-encoder/src/enum_.rs
+++ b/crates/wit-encoder/src/enum_.rs
@@ -18,6 +18,14 @@ where
 }
 
 impl Enum {
+    pub fn empty() -> Self {
+        Self::default()
+    }
+
+    pub fn case(&mut self, case: impl Into<EnumCase>) {
+        self.cases.push(case.into());
+    }
+
     pub fn cases(&self) -> &[EnumCase] {
         &self.cases
     }
diff --git a/crates/wit-encoder/src/function.rs b/crates/wit-encoder/src/function.rs
index e0ffe79e..6c86aa26 100644
--- a/crates/wit-encoder/src/function.rs
+++ b/crates/wit-encoder/src/function.rs
@@ -47,6 +47,10 @@ impl Params {
         Self::default()
     }
 
+    pub fn push(&mut self, name: impl Into<Ident>, ty: Type) {
+        self.items.push((name.into(), ty));
+    }
+
     pub fn items(&self) -> &Vec<(Ident, Type)> {
         &self.items
     }
@@ -115,7 +119,7 @@ impl Results {
         Results::Anon(type_)
     }
 
-    pub fn named(types: impl IntoIterator<Item = (impl Into<String>, Type)>) -> Results {
+    pub fn named(types: impl IntoIterator<Item = (impl Into<Ident>, Type)>) -> Results {
         Results::Named(
             types
                 .into_iter()
@@ -154,14 +158,30 @@ impl StandaloneFunc {
         }
     }
 
+    pub fn name(&self) -> &Ident {
+        &self.name
+    }
+
+    pub fn name_mut(&mut self) -> &mut Ident {
+        &mut self.name
+    }
+
     pub fn params(&mut self, params: impl Into<Params>) {
         self.params = params.into();
     }
 
+    pub fn params_mut(&mut self) -> &mut Params {
+        &mut self.params
+    }
+
     pub fn results(&mut self, results: impl Into<Results>) {
         self.results = results.into();
     }
 
+    pub fn results_mut(&mut self) -> &mut Results {
+        &mut self.results
+    }
+
     pub fn docs(&mut self, docs: Option<impl Into<Docs>>) {
         self.docs = docs.map(|d| d.into());
     }
diff --git a/crates/wit-encoder/src/ident.rs b/crates/wit-encoder/src/ident.rs
index 0d9b86a4..4b7116f5 100644
--- a/crates/wit-encoder/src/ident.rs
+++ b/crates/wit-encoder/src/ident.rs
@@ -29,12 +29,18 @@ impl fmt::Display for Ident {
     }
 }
 
+impl AsRef<str> for Ident {
+    fn as_ref(&self) -> &str {
+        self.0.as_ref()
+    }
+}
+
 fn is_keyword(name: &str) -> bool {
     match name {
         "u8" | "u16" | "u32" | "u64" | "s8" | "s16" | "s32" | "s64" | "float32" | "float64"
         | "char" | "bool" | "string" | "tuple" | "list" | "option" | "result" | "use" | "type"
         | "resource" | "func" | "record" | "enum" | "flags" | "variant" | "static"
-        | "interface" | "world" | "import" | "export" | "package" => true,
+        | "interface" | "world" | "import" | "export" | "package" | "own" | "borrow" => true,
         _ => false,
     }
 }
diff --git a/crates/wit-encoder/src/interface.rs b/crates/wit-encoder/src/interface.rs
index 943a36d0..5641426c 100644
--- a/crates/wit-encoder/src/interface.rs
+++ b/crates/wit-encoder/src/interface.rs
@@ -38,7 +38,7 @@ impl Interface {
         &self.items
     }
 
-    pub fn functions_mut(&mut self) -> &mut Vec<InterfaceItem> {
+    pub fn items_mut(&mut self) -> &mut Vec<InterfaceItem> {
         &mut self.items
     }
 
diff --git a/crates/wit-encoder/src/package.rs b/crates/wit-encoder/src/package.rs
index 2c5eca4c..0289e052 100644
--- a/crates/wit-encoder/src/package.rs
+++ b/crates/wit-encoder/src/package.rs
@@ -36,6 +36,14 @@ impl Package {
     pub fn world(&mut self, world: World) {
         self.items.push(PackageItem::World(world))
     }
+
+    pub fn items(&self) -> &[PackageItem] {
+        &self.items
+    }
+
+    pub fn items_mut(&mut self) -> &mut Vec<PackageItem> {
+        &mut self.items
+    }
 }
 
 impl Render for Package {
diff --git a/crates/wit-encoder/src/record.rs b/crates/wit-encoder/src/record.rs
index 4280ca85..9f91a8cd 100644
--- a/crates/wit-encoder/src/record.rs
+++ b/crates/wit-encoder/src/record.rs
@@ -40,6 +40,14 @@ impl Field {
     pub fn docs(&mut self, docs: Option<impl Into<Docs>>) {
         self.docs = docs.map(|d| d.into());
     }
+
+    pub fn ty(&self) -> &Type {
+        &self.ty
+    }
+
+    pub fn ty_mut(&mut self) -> &mut Type {
+        &mut self.ty
+    }
 }
 
 impl<N> Into<Field> for (N, Type)
diff --git a/crates/wit-encoder/src/resource.rs b/crates/wit-encoder/src/resource.rs
index 0d78c4ce..644cca97 100644
--- a/crates/wit-encoder/src/resource.rs
+++ b/crates/wit-encoder/src/resource.rs
@@ -2,7 +2,7 @@ use crate::{ident::Ident, Docs, Params, Results};
 
 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub struct Resource {
-    pub funcs: Vec<ResourceFunc>,
+    pub(crate) funcs: Vec<ResourceFunc>,
 }
 
 impl Resource {
@@ -78,6 +78,10 @@ impl ResourceFunc {
         self.params = params.into();
     }
 
+    pub fn params_mut(&mut self) -> &mut Params {
+        &mut self.params
+    }
+
     pub fn results(&mut self, results: impl Into<Results>) {
         match &self.kind {
             ResourceFuncKind::Method(name, _) => {
diff --git a/crates/wit-encoder/src/tuple.rs b/crates/wit-encoder/src/tuple.rs
index 77554ba5..0bc97bac 100644
--- a/crates/wit-encoder/src/tuple.rs
+++ b/crates/wit-encoder/src/tuple.rs
@@ -2,12 +2,16 @@ use std::fmt::Display;
 
 use crate::Type;
 
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
 pub struct Tuple {
     pub(crate) types: Vec<Type>,
 }
 
 impl Tuple {
+    pub fn empty() -> Self {
+        Default::default()
+    }
+
     pub fn types(&self) -> &[Type] {
         &self.types
     }
diff --git a/crates/wit-encoder/src/ty.rs b/crates/wit-encoder/src/ty.rs
index ec2e0a08..3e45f6d6 100644
--- a/crates/wit-encoder/src/ty.rs
+++ b/crates/wit-encoder/src/ty.rs
@@ -135,6 +135,14 @@ impl VariantCase {
     pub fn docs(&mut self, docs: Option<impl Into<Docs>>) {
         self.docs = docs.map(|d| d.into());
     }
+
+    pub fn ty(&self) -> Option<&Type> {
+        self.ty.as_ref()
+    }
+
+    pub fn ty_mut(&mut self) -> &mut Option<Type> {
+        &mut self.ty
+    }
 }
 
 impl<N> Into<VariantCase> for (N,)
diff --git a/crates/wit-encoder/src/variant.rs b/crates/wit-encoder/src/variant.rs
index dd52ab27..2322f9da 100644
--- a/crates/wit-encoder/src/variant.rs
+++ b/crates/wit-encoder/src/variant.rs
@@ -1,11 +1,19 @@
 use crate::VariantCase;
 
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
 pub struct Variant {
     pub(crate) cases: Vec<VariantCase>,
 }
 
 impl Variant {
+    pub fn empty() -> Self {
+        Self::default()
+    }
+
+    pub fn case(&mut self, case: impl Into<VariantCase>) {
+        self.cases.push(case.into());
+    }
+
     pub fn cases(&self) -> &[VariantCase] {
         &self.cases
     }
diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs
index c5a2f237..be539e9e 100644
--- a/crates/wit-parser/src/resolve.rs
+++ b/crates/wit-parser/src/resolve.rs
@@ -1021,8 +1021,9 @@ impl Resolve {
                         0 => bail!("no worlds found in package `{}`", pkg.name),
                         1 => return Ok(*pkg.worlds.values().next().unwrap()),
                         _ => bail!(
-                            "multiple worlds found in package `{}`: one must be explicitly chosen",
-                            pkg.name
+                            "multiple worlds found in package `{}`, one must be explicitly chosen:{}",
+                            pkg.name,
+                            pkg.worlds.keys().map(|name| format!("\n  {name}")).collect::<String>()
                         ),
                     }
                 }


Test patch:
diff --git a/tests/cli/dump-llvm-object.wat.stdout b/tests/cli/dump-llvm-object.wat.stdout
index a421d723..fb518e22 100644
--- a/tests/cli/dump-llvm-object.wat.stdout
+++ b/tests/cli/dump-llvm-object.wat.stdout
@@ -47,7 +47,7 @@
        | 39 65 32 32
        | 65 30 65 45
        | 00 02      
-  0x98 | 03 65 6e 76 | import [table 0] Import { module: "env", name: "__indirect_function_table", ty: Table(TableType { element_type: funcref, table64: false, initial: 4, maximum: None }) }
+  0x98 | 03 65 6e 76 | import [table 0] Import { module: "env", name: "__indirect_function_table", ty: Table(TableType { element_type: funcref, table64: false, initial: 4, maximum: None, shared: false }) }
        | 19 5f 5f 69
        | 6e 64 69 72
        | 65 63 74 5f
diff --git a/tests/cli/dump/alias2.wat.stdout b/tests/cli/dump/alias2.wat.stdout
index 2710d46e..c88baade 100644
--- a/tests/cli/dump/alias2.wat.stdout
+++ b/tests/cli/dump/alias2.wat.stdout
@@ -133,7 +133,7 @@
    0x161 | 00          | [func 0] type 0
    0x162 | 04 04       | table section
    0x164 | 01          | 1 count
-   0x165 | 70 00 01    | [table 0] Table { ty: TableType { element_type: funcref, table64: false, initial: 1, maximum: None }, init: RefNull }
+   0x165 | 70 00 01    | [table 0] Table { ty: TableType { element_type: funcref, table64: false, initial: 1, maximum: None, shared: false }, init: RefNull }
    0x168 | 05 03       | memory section
    0x16a | 01          | 1 count
    0x16b | 00 01       | [memory 0] MemoryType { memory64: false, shared: false, initial: 1, maximum: None, page_size_log2: None }
@@ -173,7 +173,7 @@
          | 00 01      
    0x1b6 | 00 01 33 03 | import [global 0] Import { module: "", name: "3", ty: Global(GlobalType { content_type: I32, mutable: false, shared: false }) }
          | 7f 00      
-   0x1bc | 00 01 34 01 | import [table 0] Import { module: "", name: "4", ty: Table(TableType { element_type: funcref, table64: false, initial: 1, maximum: None }) }
+   0x1bc | 00 01 34 01 | import [table 0] Import { module: "", name: "4", ty: Table(TableType { element_type: funcref, table64: false, initial: 1, maximum: None, shared: false }) }
          | 70 00 01   
    0x1c3 | 00 0a       | custom section
    0x1c5 | 04 6e 61 6d | name: "name"
diff --git a/tests/cli/dump/module-types.wat.stdout b/tests/cli/dump/module-types.wat.stdout
index ddde806e..1e36b9d1 100644
--- a/tests/cli/dump/module-types.wat.stdout
+++ b/tests/cli/dump/module-types.wat.stdout
@@ -2,7 +2,7 @@
       | 0d 00 01 00
   0x8 | 03 23       | core type section
   0xa | 01          | 1 count
-  0xb | 50 05 01 60 | [core type 0] Module([Type(SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } }), Import(Import { module: "", name: "f", ty: Func(0) }), Import(Import { module: "", name: "g", ty: Global(GlobalType { content_type: I32, mutable: false, shared: false }) }), Import(Import { module: "", name: "t", ty: Table(TableType { element_type: funcref, table64: false, initial: 1, maximum: None }) }), Import(Import { module: "", name: "m", ty: Memory(MemoryType { memory64: false, shared: false, initial: 1, maximum: None, page_size_log2: None }) })])
+  0xb | 50 05 01 60 | [core type 0] Module([Type(SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } }), Import(Import { module: "", name: "f", ty: Func(0) }), Import(Import { module: "", name: "g", ty: Global(GlobalType { content_type: I32, mutable: false, shared: false }) }), Import(Import { module: "", name: "t", ty: Table(TableType { element_type: funcref, table64: false, initial: 1, maximum: None, shared: false }) }), Import(Import { module: "", name: "m", ty: Memory(MemoryType { memory64: false, shared: false, initial: 1, maximum: None, page_size_log2: None }) })])
       | 00 00 00 00
       | 01 66 00 00
       | 00 00 01 67
diff --git a/tests/cli/dump/simple.wat.stdout b/tests/cli/dump/simple.wat.stdout
index 59e8902f..b01a25e8 100644
--- a/tests/cli/dump/simple.wat.stdout
+++ b/tests/cli/dump/simple.wat.stdout
@@ -17,7 +17,7 @@
  0x20 | 01          | [func 3] type 1
  0x21 | 04 04       | table section
  0x23 | 01          | 1 count
- 0x24 | 70 00 01    | [table 0] Table { ty: TableType { element_type: funcref, table64: false, initial: 1, maximum: None }, init: RefNull }
+ 0x24 | 70 00 01    | [table 0] Table { ty: TableType { element_type: funcref, table64: false, initial: 1, maximum: None, shared: false }, init: RefNull }
  0x27 | 05 03       | memory section
  0x29 | 01          | 1 count
  0x2a | 00 01       | [memory 0] MemoryType { memory64: false, shared: false, initial: 1, maximum: None, page_size_log2: None }
diff --git a/tests/local/missing-features/tables.wast b/tests/local/missing-features/tables.wast
new file mode 100644
index 00000000..49e4b6f6
--- /dev/null
+++ b/tests/local/missing-features/tables.wast
@@ -0,0 +1,5 @@
+(assert_invalid
+  (module
+    (table shared 1 funcref)
+  )
+  "shared tables require the shared-everything-threads proposal")
diff --git a/tests/local/shared-everything-threads/global.wast b/tests/local/shared-everything-threads/global.wast
index 9d47be9f..1066fcf4 100644
--- a/tests/local/shared-everything-threads/global.wast
+++ b/tests/local/shared-everything-threads/global.wast
@@ -1,4 +1,4 @@
-;; Check shared attribute.
+;; Check the `shared` attribute on globals.
 
 (module
   ;; Imported.
diff --git a/tests/local/shared-everything-threads/table.wast b/tests/local/shared-everything-threads/table.wast
new file mode 100644
index 00000000..0b4eedc2
--- /dev/null
+++ b/tests/local/shared-everything-threads/table.wast
@@ -0,0 +1,43 @@
+;; Check the `shared` attribute on tables.
+
+(module
+  ;; Imported.
+  (table (import "spectest" "table_ref") shared 1 (ref null (shared func)))
+  (table (import "spectest" "table_ref_with_max") shared 1 1 (ref null (shared func)))
+
+  ;; Normal.
+  (table shared 1 (ref null (shared func)))
+  (table shared 1 1 (ref null (shared func)))
+
+  ;; Inlined.
+  (table shared (ref null (shared func)) (elem (ref.null (shared func))))
+)
+
+;; Note that shared elements can live within an unshared table.
+(module
+  (table (import "spectest" "table_ref") 1 (ref null (shared func)))
+)
+
+(assert_malformed
+  (module quote "(table 1 shared funcref)")
+  "unexpected token")
+
+(assert_malformed
+  (module quote "(table 1 funcref shared)")
+  "unexpected token")
+
+;; The proposal creates too much ambiguity to allow this syntax: the parser
+;; would need to lookahead multiple tokens.
+(assert_malformed
+  (module quote "(table shared i64 (ref null (shared func)) (elem (ref.null (shared func))))")
+  "unexpected token")
+
+(assert_invalid
+  (module (table (import "spectest" "table_ref") shared 0 funcref))
+  "shared tables must have a shared element type")
+
+(assert_invalid
+  (module
+    (type $t (func))
+    (table shared 0 (ref $t)))
+  "shared tables must have a shared element type")
diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs
index 66fdc1e4..6f1b64c7 100644
--- a/tests/roundtrip.rs
+++ b/tests/roundtrip.rs
@@ -717,6 +717,7 @@ fn error_matches(error: &str, message: &str) -> bool {
         // wasmparser implements more features than the default spec
         // interpreter, so these error looks different.
         return error.contains("threads must be enabled for shared memories")
+            || error.contains("shared tables require the shared-everything-threads proposal")
             || error.contains("invalid table resizable limits flags")
             // honestly this feels like the spec interpreter is just weird
             || error.contains("unexpected end-of-file")
diff --git a/tests/snapshots/local/missing-features/tables.wast.json b/tests/snapshots/local/missing-features/tables.wast.json
new file mode 100644
index 00000000..5e06332b
--- /dev/null
+++ b/tests/snapshots/local/missing-features/tables.wast.json
@@ -0,0 +1,12 @@
+{
+  "source_filename": "tests/local/missing-features/tables.wast",
+  "commands": [
+    {
+      "type": "assert_invalid",
+      "line": 2,
+      "filename": "tables.0.wasm",
+      "text": "shared tables require the shared-everything-threads proposal",
+      "module_type": "binary"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/local/shared-everything-threads/table.wast.json b/tests/snapshots/local/shared-everything-threads/table.wast.json
new file mode 100644
index 00000000..8c2bc6b1
--- /dev/null
+++ b/tests/snapshots/local/shared-everything-threads/table.wast.json
@@ -0,0 +1,50 @@
+{
+  "source_filename": "tests/local/shared-everything-threads/table.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 3,
+      "filename": "table.0.wasm"
+    },
+    {
+      "type": "module",
+      "line": 17,
+      "filename": "table.1.wasm"
+    },
+    {
+      "type": "assert_malformed",
+      "line": 22,
+      "filename": "table.2.wat",
+      "text": "unexpected token",
+      "module_type": "text"
+    },
+    {
+      "type": "assert_malformed",
+      "line": 26,
+      "filename": "table.3.wat",
+      "text": "unexpected token",
+      "module_type": "text"
+    },
+    {
+      "type": "assert_malformed",
+      "line": 32,
+      "filename": "table.4.wat",
+      "text": "unexpected token",
+      "module_type": "text"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 36,
+      "filename": "table.5.wasm",
+      "text": "shared tables must have a shared element type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 40,
+      "filename": "table.6.wasm",
+      "text": "shared tables must have a shared element type",
+      "module_type": "binary"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/local/shared-everything-threads/table.wast/0.print b/tests/snapshots/local/shared-everything-threads/table.wast/0.print
new file mode 100644
index 00000000..80ca7763
--- /dev/null
+++ b/tests/snapshots/local/shared-everything-threads/table.wast/0.print
@@ -0,0 +1,8 @@
+(module
+  (import "spectest" "table_ref" (table (;0;) shared 1 (ref null (shared func))))
+  (import "spectest" "table_ref_with_max" (table (;1;) shared 1 1 (ref null (shared func))))
+  (table (;2;) shared 1 (ref null (shared func)))
+  (table (;3;) shared 1 1 (ref null (shared func)))
+  (table (;4;) shared 1 1 (ref null (shared func)))
+  (elem (;0;) (table 4) (i32.const 0) (ref null (shared func)) (ref.null (shared func)))
+)
diff --git a/tests/snapshots/local/shared-everything-threads/table.wast/1.print b/tests/snapshots/local/shared-everything-threads/table.wast/1.print
new file mode 100644
index 00000000..56e5cf89
--- /dev/null
+++ b/tests/snapshots/local/shared-everything-threads/table.wast/1.print
@@ -0,0 +1,3 @@
+(module
+  (import "spectest" "table_ref" (table (;0;) 1 (ref null (shared func))))
+)