Repository: bytecodealliance/wasm-tools
Pull request #1600 (2024-06-10 15:55:09)

threads: add `shared` heap types
This changes heap types everywhere to allow them to be `shared` as described in the shared-everything-threads [proposal]. The key refactoring is to push down all the abstract heap types (e.g., `func`, `any`, etc.) into an `AbstractHeapType`, leaving the `HeapType` enum with two variants: `Abstract` and `Concrete`. This allows the `Abstract` side to be marked `shared`; the `Concrete` side's `shared`-ness must be checked by looking at the pointed-to type, something which isn't yet addressed by this PR.

There are several parts of the PR that bump up against the boundary of what can be marked `shared` currently (e.g., `FuncType` and related types can't yet be marked `shared`). I've commented these locations with `TODO: handle shared` to be dealt with in future PRs that continue expanding `shared` everywhere.

[proposal]: https://github.com/WebAssembly/shared-everything-threads


Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 8c0cc802..24fb01f0 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -168,6 +168,7 @@ jobs:
     - uses: actions/checkout@v4
     - uses: bytecodealliance/wasmtime/.github/actions/install-rust@v20.0.0
     - run: rustup component add rustfmt
+    - run: printf "\n" > playground/component/src/bindings.rs
     # Note that this doesn't use `cargo fmt` because that doesn't format
     # modules-defined-in-macros which is in use in `wast` for example. This is
     # the best alternative I can come up with at this time
diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml
new file mode 100644
index 00000000..b13631fe
--- /dev/null
+++ b/.github/workflows/playground.yml
@@ -0,0 +1,59 @@
+name: Build playground
+on:
+  push:
+    branches: [main]
+  pull_request:
+  merge_group:
+
+# Cancel any in-flight jobs for the same PR/branch so there's only one active
+# at a time
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+defaults:
+  run:
+    shell: bash
+
+jobs:
+  build:
+    name: Build playground deployment
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          submodules: true
+      - uses: actions/setup-node@v4
+        with:
+          node-version: 20
+      - uses: bytecodealliance/wasmtime/.github/actions/install-rust@v20.0.0
+      - uses: cargo-bins/cargo-binstall@v1.6.9
+      - run: cargo binstall cargo-component -y
+      - run: rustup component add rustfmt # needed for cargo-component, apparently?
+      - run: npm ci
+        working-directory: playground
+      - run: npm run build
+        working-directory: playground
+
+      # also prepare to deploy GH pages on main
+      - if: github.ref == 'refs/heads/main'
+        uses: actions/configure-pages@v5
+      - if: github.ref == 'refs/heads/main'
+        uses: actions/upload-pages-artifact@v3
+        with:
+          path: "./playground/dist"
+
+  deploy:
+    name: Deploy playground
+    if: github.ref == 'refs/heads/main'
+    needs: build
+    permissions:
+      pages: write
+      id-token: write
+    runs-on: ubuntu-latest
+    environment:
+      name: github-pages
+      url: ${{ steps.deployment.outputs.page_url }}
+    steps:
+      - id: deployment
+        uses: actions/deploy-pages@v4
diff --git a/Cargo.lock b/Cargo.lock
index 7bf57c07..6d25cdff 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -285,6 +285,15 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
 
+[[package]]
+name = "component"
+version = "0.0.0"
+dependencies = [
+ "wasmprinter 0.210.0",
+ "wat",
+ "wit-bindgen-rt",
+]
+
 [[package]]
 name = "constant_time_eq"
 version = "0.3.0"
@@ -1480,7 +1489,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-compose"
-version = "0.209.1"
+version = "0.210.0"
 dependencies = [
  "anyhow",
  "glob",
@@ -1494,9 +1503,9 @@ dependencies = [
  "serde_derive",
  "serde_yaml",
  "smallvec",
- "wasm-encoder 0.209.1",
- "wasmparser 0.209.1",
- "wasmprinter 0.209.1",
+ "wasm-encoder 0.210.0",
+ "wasmparser 0.210.0",
+ "wasmprinter 0.210.0",
  "wat",
  "wit-component",
 ]
@@ -1512,17 +1521,17 @@ dependencies = [
 
 [[package]]
 name = "wasm-encoder"
-version = "0.209.1"
+version = "0.210.0"
 dependencies = [
  "anyhow",
  "leb128",
  "tempfile",
- "wasmparser 0.209.1",
+ "wasmparser 0.210.0",
 ]
 
 [[package]]
 name = "wasm-metadata"
-version = "0.209.1"
+version = "0.210.0"
 dependencies = [
  "anyhow",
  "clap",
@@ -1531,14 +1540,14 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "spdx",
- "wasm-encoder 0.209.1",
- "wasmparser 0.209.1",
+ "wasm-encoder 0.210.0",
+ "wasmparser 0.210.0",
  "wat",
 ]
 
 [[package]]
 name = "wasm-mutate"
-version = "0.209.1"
+version = "0.210.0"
 dependencies = [
  "anyhow",
  "clap",
@@ -1547,9 +1556,9 @@ dependencies = [
  "log",
  "rand",
  "thiserror",
- "wasm-encoder 0.209.1",
- "wasmparser 0.209.1",
- "wasmprinter 0.209.1",
+ "wasm-encoder 0.210.0",
+ "wasmparser 0.210.0",
+ "wasmprinter 0.210.0",
  "wat",
 ]
 
@@ -1566,14 +1575,14 @@ dependencies = [
  "num_cpus",
  "rand",
  "wasm-mutate",
- "wasmparser 0.209.1",
- "wasmprinter 0.209.1",
+ "wasmparser 0.210.0",
+ "wasmprinter 0.210.0",
  "wasmtime",
 ]
 
 [[package]]
 name = "wasm-shrink"
-version = "0.209.1"
+version = "0.210.0"
 dependencies = [
  "anyhow",
  "blake3",
@@ -1582,14 +1591,14 @@ dependencies = [
  "log",
  "rand",
  "wasm-mutate",
- "wasmparser 0.209.1",
- "wasmprinter 0.209.1",
+ "wasmparser 0.210.0",
+ "wasmprinter 0.210.0",
  "wat",
 ]
 
 [[package]]
 name = "wasm-smith"
-version = "0.209.1"
+version = "0.210.0"
 dependencies = [
  "anyhow",
  "arbitrary",
@@ -1602,15 +1611,15 @@ dependencies = [
  "rand",
  "serde",
  "serde_derive",
- "wasm-encoder 0.209.1",
- "wasmparser 0.209.1",
- "wasmprinter 0.209.1",
+ "wasm-encoder 0.210.0",
+ "wasmparser 0.210.0",
+ "wasmprinter 0.210.0",
  "wat",
 ]
 
 [[package]]
 name = "wasm-tools"
-version = "1.209.1"
+version = "1.210.0"
 dependencies = [
  "addr2line",
  "anyhow",
@@ -1634,17 +1643,17 @@ dependencies = [
  "tempfile",
  "termcolor",
  "wasm-compose",
- "wasm-encoder 0.209.1",
+ "wasm-encoder 0.210.0",
  "wasm-metadata",
  "wasm-mutate",
  "wasm-shrink",
  "wasm-smith",
- "wasmparser 0.209.1",
- "wasmprinter 0.209.1",
+ "wasmparser 0.210.0",
+ "wasmprinter 0.210.0",
  "wast",
  "wat",
  "wit-component",
- "wit-parser 0.209.1",
+ "wit-parser 0.210.0",
  "wit-smith",
 ]
 
@@ -1656,8 +1665,8 @@ dependencies = [
  "wasm-mutate",
  "wasm-shrink",
  "wasm-smith",
- "wasmparser 0.209.1",
- "wasmprinter 0.209.1",
+ "wasmparser 0.210.0",
+ "wasmprinter 0.210.0",
  "wast",
  "wat",
 ]
@@ -1672,16 +1681,16 @@ dependencies = [
  "libfuzzer-sys",
  "log",
  "tempfile",
- "wasm-encoder 0.209.1",
+ "wasm-encoder 0.210.0",
  "wasm-mutate",
  "wasm-smith",
- "wasmparser 0.209.1",
- "wasmprinter 0.209.1",
+ "wasmparser 0.210.0",
+ "wasmprinter 0.210.0",
  "wasmtime",
  "wast",
  "wat",
  "wit-component",
- "wit-parser 0.209.1",
+ "wit-parser 0.210.0",
  "wit-smith",
 ]
 
@@ -1698,7 +1707,7 @@ dependencies = [
 
 [[package]]
 name = "wasmparser"
-version = "0.209.1"
+version = "0.210.0"
 dependencies = [
  "ahash",
  "anyhow",
@@ -1712,7 +1721,7 @@ dependencies = [
  "rayon",
  "semver",
  "serde",
- "wasm-encoder 0.209.1",
+ "wasm-encoder 0.210.0",
  "wast",
  "wat",
 ]
@@ -1729,14 +1738,14 @@ dependencies = [
 
 [[package]]
 name = "wasmprinter"
-version = "0.209.1"
+version = "0.210.0"
 dependencies = [
  "anyhow",
  "diff",
  "rayon",
  "tempfile",
  "termcolor",
- "wasmparser 0.209.1",
+ "wasmparser 0.210.0",
  "wast",
  "wat",
 ]
@@ -1954,7 +1963,7 @@ dependencies = [
 
 [[package]]
 name = "wast"
-version = "209.0.1"
+version = "210.0.0"
 dependencies = [
  "anyhow",
  "bumpalo",
@@ -1962,14 +1971,14 @@ dependencies = [
  "libtest-mimic",
  "memchr",
  "unicode-width",
- "wasm-encoder 0.209.1",
- "wasmparser 0.209.1",
+ "wasm-encoder 0.210.0",
+ "wasmparser 0.210.0",
  "wat",
 ]
 
 [[package]]
 name = "wat"
-version = "1.209.1"
+version = "1.210.0"
 dependencies = [
  "wast",
 ]
@@ -2095,9 +2104,18 @@ version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
 
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29c7526379ace8709ee9ab9f2bb50f112d95581063a59ef3097d9c10153886c9"
+dependencies = [
+ "bitflags",
+]
+
 [[package]]
 name = "wit-component"
-version = "0.209.1"
+version = "0.210.0"
 dependencies = [
  "anyhow",
  "bitflags",
@@ -2110,14 +2128,14 @@ dependencies = [
  "serde",
  "serde_derive",
  "serde_json",
- "wasm-encoder 0.209.1",
+ "wasm-encoder 0.210.0",
  "wasm-metadata",
- "wasmparser 0.209.1",
- "wasmprinter 0.209.1",
+ "wasmparser 0.210.0",
+ "wasmprinter 0.210.0",
  "wasmtime",
  "wast",
  "wat",
- "wit-parser 0.209.1",
+ "wit-parser 0.210.0",
 ]
 
 [[package]]
@@ -2140,7 +2158,7 @@ dependencies = [
 
 [[package]]
 name = "wit-parser"
-version = "0.209.1"
+version = "0.210.0"
 dependencies = [
  "anyhow",
  "env_logger",
@@ -2154,9 +2172,9 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "unicode-xid",
- "wasmparser 0.209.1",
+ "wasmparser 0.210.0",
  "wat",
- "wit-parser 0.209.1",
+ "wit-parser 0.210.0",
 ]
 
 [[package]]
@@ -2167,13 +2185,13 @@ dependencies = [
  "env_logger",
  "libfuzzer-sys",
  "log",
- "wasmprinter 0.209.1",
- "wit-parser 0.209.1",
+ "wasmprinter 0.210.0",
+ "wit-parser 0.210.0",
 ]
 
 [[package]]
 name = "wit-smith"
-version = "0.209.1"
+version = "0.210.0"
 dependencies = [
  "arbitrary",
  "clap",
@@ -2181,7 +2199,7 @@ dependencies = [
  "log",
  "semver",
  "wit-component",
- "wit-parser 0.209.1",
+ "wit-parser 0.210.0",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 4bf41c45..ed667498 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasm-tools"
-version = "1.209.1"
+version = "1.210.0"
 authors = ["The Wasmtime Project Developers"]
 edition.workspace = true
 description = "CLI tools for interoperating with WebAssembly files"
@@ -39,6 +39,7 @@ members = [
   'fuzz',
   'crates/wit-parser/fuzz',
   'crates/wit-component/dl',
+  'playground/component',
 ]
 
 [workspace.lints.rust]
@@ -49,7 +50,7 @@ all = "allow"
 
 [workspace.package]
 edition = '2021'
-version = "0.209.1"
+version = "0.210.0"
 # Current policy for wasm-tools is the same as Wasmtime which is that this
 # number can be no larger than the current stable release of Rust minus 2.
 rust-version = "1.76.0"
@@ -82,19 +83,19 @@ hashbrown = { version = "0.14.3", default-features = false, features = ['ahash']
 ahash = { version = "0.8.11", default-features = false }
 termcolor = "1.2.0"
 
-wasm-compose = { version = "0.209.1", path = "crates/wasm-compose" }
-wasm-encoder = { version = "0.209.1", path = "crates/wasm-encoder" }
-wasm-metadata = { version = "0.209.1", path = "crates/wasm-metadata" }
-wasm-mutate = { version = "0.209.1", path = "crates/wasm-mutate" }
-wasm-shrink = { version = "0.209.1", path = "crates/wasm-shrink" }
-wasm-smith = { version = "0.209.1", path = "crates/wasm-smith" }
-wasmparser = { version = "0.209.1", path = "crates/wasmparser", default-features = false, features = ['std'] }
-wasmprinter = { version = "0.209.1", path = "crates/wasmprinter" }
-wast = { version = "209.0.1", path = "crates/wast" }
-wat = { version = "1.209.1", path = "crates/wat" }
-wit-component = { version = "0.209.1", path = "crates/wit-component" }
-wit-parser = { version = "0.209.1", path = "crates/wit-parser" }
-wit-smith = { version = "0.209.1", path = "crates/wit-smith" }
+wasm-compose = { version = "0.210.0", path = "crates/wasm-compose" }
+wasm-encoder = { version = "0.210.0", path = "crates/wasm-encoder" }
+wasm-metadata = { version = "0.210.0", path = "crates/wasm-metadata" }
+wasm-mutate = { version = "0.210.0", path = "crates/wasm-mutate" }
+wasm-shrink = { version = "0.210.0", path = "crates/wasm-shrink" }
+wasm-smith = { version = "0.210.0", path = "crates/wasm-smith" }
+wasmparser = { version = "0.210.0", path = "crates/wasmparser", default-features = false, features = ['std'] }
+wasmprinter = { version = "0.210.0", path = "crates/wasmprinter" }
+wast = { version = "210.0.0", path = "crates/wast" }
+wat = { version = "1.210.0", path = "crates/wat" }
+wit-component = { version = "0.210.0", path = "crates/wit-component" }
+wit-parser = { version = "0.210.0", path = "crates/wit-parser" }
+wit-smith = { version = "0.210.0", path = "crates/wit-smith" }
 
 [dependencies]
 anyhow = { workspace = true }
diff --git a/crates/wasm-encoder/src/core/types.rs b/crates/wasm-encoder/src/core/types.rs
index 8b5f340b..0fa12f68 100644
--- a/crates/wasm-encoder/src/core/types.rs
+++ b/crates/wasm-encoder/src/core/types.rs
@@ -344,33 +344,91 @@ pub struct RefType {
 }
 
 impl RefType {
-    /// Alias for the `funcref` type in WebAssembly
+    /// Alias for the `anyref` type in WebAssembly.
+    pub const ANYREF: RefType = RefType {
+        nullable: true,
+        heap_type: HeapType::Abstract {
+            shared: false,
+            ty: AbstractHeapType::Any,
+        },
+    };
+
+    /// Alias for the `anyref` type in WebAssembly.
+    pub const EQREF: RefType = RefType {
+        nullable: true,
+        heap_type: HeapType::Abstract {
+            shared: false,
+            ty: AbstractHeapType::Eq,
+        },
+    };
+
+    /// Alias for the `funcref` type in WebAssembly.
     pub const FUNCREF: RefType = RefType {
         nullable: true,
-        heap_type: HeapType::Func,
+        heap_type: HeapType::Abstract {
+            shared: false,
+            ty: AbstractHeapType::Func,
+        },
     };
 
-    /// Alias for the `externref` type in WebAssembly
+    /// Alias for the `externref` type in WebAssembly.
     pub const EXTERNREF: RefType = RefType {
         nullable: true,
-        heap_type: HeapType::Extern,
+        heap_type: HeapType::Abstract {
+            shared: false,
+            ty: AbstractHeapType::Extern,
+        },
     };
 
-    /// Alias for the `exnref` type in WebAssembly
+    /// Alias for the `i31ref` type in WebAssembly.
+    pub const I31REF: RefType = RefType {
+        nullable: true,
+        heap_type: HeapType::Abstract {
+            shared: false,
+            ty: AbstractHeapType::I31,
+        },
+    };
+
+    /// Alias for the `arrayref` type in WebAssembly.
+    pub const ARRAYREF: RefType = RefType {
+        nullable: true,
+        heap_type: HeapType::Abstract {
+            shared: false,
+            ty: AbstractHeapType::Array,
+        },
+    };
+
+    /// Alias for the `exnref` type in WebAssembly.
     pub const EXNREF: RefType = RefType {
         nullable: true,
-        heap_type: HeapType::Exn,
+        heap_type: HeapType::Abstract {
+            shared: false,
+            ty: AbstractHeapType::Exn,
+        },
     };
+
+    /// Set the nullability of this reference type.
+    pub fn nullable(mut self, nullable: bool) -> Self {
+        self.nullable = nullable;
+        self
+    }
 }
 
 impl Encode for RefType {
     fn encode(&self, sink: &mut Vec<u8>) {
         if self.nullable {
             // Favor the original encodings of `funcref` and `externref` where
-            // possible
+            // possible.
+            use AbstractHeapType::*;
             match self.heap_type {
-                HeapType::Func => return sink.push(0x70),
-                HeapType::Extern => return sink.push(0x6f),
+                HeapType::Abstract {
+                    shared: false,
+                    ty: Func,
+                } => return sink.push(0x70),
+                HeapType::Abstract {
+                    shared: false,
+                    ty: Extern,
+                } => return sink.push(0x6f),
                 _ => {}
             }
         }
@@ -405,6 +463,78 @@ impl From<RefType> for ValType {
 /// Part of the function references proposal.
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
 pub enum HeapType {
+    /// An abstract heap type; e.g., `anyref`.
+    Abstract {
+        /// Whether the type is shared.
+        shared: bool,
+        /// The actual heap type.
+        ty: AbstractHeapType,
+    },
+
+    /// A concrete Wasm-defined type at the given index.
+    Concrete(u32),
+}
+
+impl HeapType {
+    /// Alias for the unshared `any` heap type.
+    pub const ANY: Self = Self::Abstract {
+        shared: false,
+        ty: AbstractHeapType::Any,
+    };
+
+    /// Alias for the unshared `func` heap type.
+    pub const FUNC: Self = Self::Abstract {
+        shared: false,
+        ty: AbstractHeapType::Func,
+    };
+
+    /// Alias for the unshared `extern` heap type.
+    pub const EXTERN: Self = Self::Abstract {
+        shared: false,
+        ty: AbstractHeapType::Extern,
+    };
+
+    /// Alias for the unshared `i31` heap type.
+    pub const I31: Self = Self::Abstract {
+        shared: false,
+        ty: AbstractHeapType::I31,
+    };
+}
+
+impl Encode for HeapType {
+    fn encode(&self, sink: &mut Vec<u8>) {
+        match self {
+            HeapType::Abstract { shared, ty } => {
+                if *shared {
+                    sink.push(0x65);
+                }
+                ty.encode(sink);
+            }
+            // Note that this is encoded as a signed type rather than unsigned
+            // as it's decoded as an s33
+            HeapType::Concrete(i) => i64::from(*i).encode(sink),
+        }
+    }
+}
+
+#[cfg(feature = "wasmparser")]
+impl TryFrom<wasmparser::HeapType> for HeapType {
+    type Error = ();
+
+    fn try_from(heap_type: wasmparser::HeapType) -> Result<Self, Self::Error> {
+        Ok(match heap_type {
+            wasmparser::HeapType::Concrete(i) => HeapType::Concrete(i.as_module_index().ok_or(())?),
+            wasmparser::HeapType::Abstract { shared, ty } => HeapType::Abstract {
+                shared,
+                ty: ty.into(),
+            },
+        })
+    }
+}
+
+/// An abstract heap type.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
+pub enum AbstractHeapType {
     /// Untyped (any) function.
     Func,
 
@@ -455,53 +585,46 @@ pub enum HeapType {
 
     /// The abstract `noexn` heap type.
     NoExn,
-
-    /// A concrete Wasm-defined type at the given index.
-    Concrete(u32),
 }
 
-impl Encode for HeapType {
+impl Encode for AbstractHeapType {
     fn encode(&self, sink: &mut Vec<u8>) {
+        use AbstractHeapType::*;
         match self {
-            HeapType::Func => sink.push(0x70),
-            HeapType::Extern => sink.push(0x6F),
-            HeapType::Any => sink.push(0x6E),
-            HeapType::None => sink.push(0x71),
-            HeapType::NoExtern => sink.push(0x72),
-            HeapType::NoFunc => sink.push(0x73),
-            HeapType::Eq => sink.push(0x6D),
-            HeapType::Struct => sink.push(0x6B),
-            HeapType::Array => sink.push(0x6A),
-            HeapType::I31 => sink.push(0x6C),
-            HeapType::Exn => sink.push(0x69),
-            HeapType::NoExn => sink.push(0x74),
-            // Note that this is encoded as a signed type rather than unsigned
-            // as it's decoded as an s33
-            HeapType::Concrete(i) => i64::from(*i).encode(sink),
+            Func => sink.push(0x70),
+            Extern => sink.push(0x6F),
+            Any => sink.push(0x6E),
+            None => sink.push(0x71),
+            NoExtern => sink.push(0x72),
+            NoFunc => sink.push(0x73),
+            Eq => sink.push(0x6D),
+            Struct => sink.push(0x6B),
+            Array => sink.push(0x6A),
+            I31 => sink.push(0x6C),
+            Exn => sink.push(0x69),
+            NoExn => sink.push(0x74),
         }
     }
 }
 
 #[cfg(feature = "wasmparser")]
-impl TryFrom<wasmparser::HeapType> for HeapType {
-    type Error = ();
-
-    fn try_from(heap_type: wasmparser::HeapType) -> Result<Self, Self::Error> {
-        Ok(match heap_type {
-            wasmparser::HeapType::Concrete(i) => HeapType::Concrete(i.as_module_index().ok_or(())?),
-            wasmparser::HeapType::Func => HeapType::Func,
-            wasmparser::HeapType::Extern => HeapType::Extern,
-            wasmparser::HeapType::Any => HeapType::Any,
-            wasmparser::HeapType::None => HeapType::None,
-            wasmparser::HeapType::NoExtern => HeapType::NoExtern,
-            wasmparser::HeapType::NoFunc => HeapType::NoFunc,
-            wasmparser::HeapType::Eq => HeapType::Eq,
-            wasmparser::HeapType::Struct => HeapType::Struct,
-            wasmparser::HeapType::Array => HeapType::Array,
-            wasmparser::HeapType::I31 => HeapType::I31,
-            wasmparser::HeapType::Exn => HeapType::Exn,
-            wasmparser::HeapType::NoExn => HeapType::NoExn,
-        })
+impl From<wasmparser::AbstractHeapType> for AbstractHeapType {
+    fn from(value: wasmparser::AbstractHeapType) -> Self {
+        use wasmparser::AbstractHeapType::*;
+        match value {
+            Func => AbstractHeapType::Func,
+            Extern => AbstractHeapType::Extern,
+            Any => AbstractHeapType::Any,
+            None => AbstractHeapType::None,
+            NoExtern => AbstractHeapType::NoExtern,
+            NoFunc => AbstractHeapType::NoFunc,
+            Eq => AbstractHeapType::Eq,
+            Struct => AbstractHeapType::Struct,
+            Array => AbstractHeapType::Array,
+            I31 => AbstractHeapType::I31,
+            Exn => AbstractHeapType::Exn,
+            NoExn => AbstractHeapType::NoExn,
+        }
     }
 }
 
diff --git a/crates/wasm-mutate/Cargo.toml b/crates/wasm-mutate/Cargo.toml
index 6a96c9ed..3a9385cd 100644
--- a/crates/wasm-mutate/Cargo.toml
+++ b/crates/wasm-mutate/Cargo.toml
@@ -14,7 +14,7 @@ workspace = true
 clap = { workspace = true, optional = true }
 thiserror = "1.0.28"
 wasmparser = { workspace = true }
-wasm-encoder = { workspace = true }
+wasm-encoder = { workspace = true, features = ["wasmparser"] }
 rand = { workspace = true }
 log = { workspace = true }
 egg = "0.6.0"
diff --git a/crates/wasm-mutate/src/module.rs b/crates/wasm-mutate/src/module.rs
index 87653dd7..d6ef3846 100644
--- a/crates/wasm-mutate/src/module.rs
+++ b/crates/wasm-mutate/src/module.rs
@@ -80,19 +80,11 @@ pub fn map_ref_type(ref_ty: wasmparser::RefType) -> Result<RefType> {
     Ok(RefType {
         nullable: ref_ty.is_nullable(),
         heap_type: match ref_ty.heap_type() {
-            wasmparser::HeapType::Func => HeapType::Func,
-            wasmparser::HeapType::Extern => HeapType::Extern,
-            wasmparser::HeapType::Any => HeapType::Any,
-            wasmparser::HeapType::None => HeapType::None,
-            wasmparser::HeapType::NoExtern => HeapType::NoExtern,
-            wasmparser::HeapType::NoFunc => HeapType::NoFunc,
-            wasmparser::HeapType::Eq => HeapType::Eq,
-            wasmparser::HeapType::Struct => HeapType::Struct,
-            wasmparser::HeapType::Array => HeapType::Array,
-            wasmparser::HeapType::I31 => HeapType::I31,
-            wasmparser::HeapType::Exn => HeapType::Exn,
-            wasmparser::HeapType::NoExn => HeapType::NoExn,
             wasmparser::HeapType::Concrete(i) => HeapType::Concrete(i.as_module_index().unwrap()),
+            wasmparser::HeapType::Abstract { shared, ty } => {
+                let ty = ty.into();
+                HeapType::Abstract { shared, ty }
+            }
         },
     })
 }
diff --git a/crates/wasm-mutate/src/mutators/add_function.rs b/crates/wasm-mutate/src/mutators/add_function.rs
index 2de031fd..7165f367 100644
--- a/crates/wasm-mutate/src/mutators/add_function.rs
+++ b/crates/wasm-mutate/src/mutators/add_function.rs
@@ -4,7 +4,7 @@ use super::Mutator;
 use crate::module::{PrimitiveTypeInfo, TypeInfo};
 use crate::{Result, WasmMutate};
 use rand::Rng;
-use wasm_encoder::{HeapType, Instruction, Module};
+use wasm_encoder::{AbstractHeapType, HeapType, Instruction, Module};
 
 /// Mutator that adds new, empty functions to a Wasm module.
 #[derive(Clone, Copy)]
@@ -62,10 +62,16 @@ impl Mutator for AddFunctionMutator {
                     func.instruction(&Instruction::V128Const(0));
                 }
                 PrimitiveTypeInfo::FuncRef => {
-                    func.instruction(&Instruction::RefNull(HeapType::Func));
+                    func.instruction(&Instruction::RefNull(HeapType::Abstract {
+                        shared: false,
+                        ty: AbstractHeapType::Func,
+                    }));
                 }
                 PrimitiveTypeInfo::ExternRef => {
-                    func.instruction(&Instruction::RefNull(HeapType::Extern));
+                    func.instruction(&Instruction::RefNull(HeapType::Abstract {
+                        shared: false,
+                        ty: AbstractHeapType::Extern,
+                    }));
                 }
                 PrimitiveTypeInfo::Empty => unreachable!(),
             }
diff --git a/crates/wasm-mutate/src/mutators/modify_const_exprs.rs b/crates/wasm-mutate/src/mutators/modify_const_exprs.rs
index 7b7fb905..173f53cf 100644
--- a/crates/wasm-mutate/src/mutators/modify_const_exprs.rs
+++ b/crates/wasm-mutate/src/mutators/modify_const_exprs.rs
@@ -124,8 +124,14 @@ impl<'cfg, 'wasm> Translator for InitTranslator<'cfg, 'wasm> {
                 } else {
                     f64::from_bits(self.config.rng().gen())
                 }),
-                T::FUNCREF => CE::ref_null(wasm_encoder::HeapType::Func),
-                T::EXTERNREF => CE::ref_null(wasm_encoder::HeapType::Extern),
+                T::FUNCREF => CE::ref_null(wasm_encoder::HeapType::Abstract {
+                    shared: false,
+                    ty: wasm_encoder::AbstractHeapType::Func,
+                }),
+                T::EXTERNREF => CE::ref_null(wasm_encoder::HeapType::Abstract {
+                    shared: false,
+                    ty: wasm_encoder::AbstractHeapType::Func,
+                }),
                 T::Ref(_) => unimplemented!(),
             }
         } else {
diff --git a/crates/wasm-mutate/src/mutators/peephole/dfg.rs b/crates/wasm-mutate/src/mutators/peephole/dfg.rs
index c69391b3..a5e1ca2a 100644
--- a/crates/wasm-mutate/src/mutators/peephole/dfg.rs
+++ b/crates/wasm-mutate/src/mutators/peephole/dfg.rs
@@ -758,12 +758,12 @@ impl<'a> DFGBuilder {
                 }
 
                 Operator::RefNull {
-                    hty: wasmparser::HeapType::Extern,
+                    hty: wasmparser::HeapType::EXTERN,
                 } => {
                     self.push_node(Lang::RefNull(RefType::Extern), idx);
                 }
                 Operator::RefNull {
-                    hty: wasmparser::HeapType::Func,
+                    hty: wasmparser::HeapType::FUNC,
                 } => {
                     self.push_node(Lang::RefNull(RefType::Func), idx);
                 }
diff --git a/crates/wasm-mutate/src/mutators/peephole/eggsy/lang.rs b/crates/wasm-mutate/src/mutators/peephole/eggsy/lang.rs
index e5bb831b..db4d9210 100644
--- a/crates/wasm-mutate/src/mutators/peephole/eggsy/lang.rs
+++ b/crates/wasm-mutate/src/mutators/peephole/eggsy/lang.rs
@@ -3,7 +3,7 @@
 use egg::Id;
 use std::fmt::{self, Display};
 use std::str::FromStr;
-use wasm_encoder::HeapType;
+use wasm_encoder::{AbstractHeapType, HeapType};
 
 /// This is a macro used to define the `Lang` enum.
 ///
@@ -1072,8 +1072,14 @@ pub enum RefType {
 impl From<RefType> for HeapType {
     fn from(rt: RefType) -> Self {
         match rt {
-            RefType::Func => HeapType::Func,
-            RefType::Extern => HeapType::Extern,
+            RefType::Func => HeapType::Abstract {
+                shared: false,
+                ty: AbstractHeapType::Func,
+            },
+            RefType::Extern => HeapType::Abstract {
+                shared: false,
+                ty: AbstractHeapType::Extern,
+            },
         }
     }
 }
diff --git a/crates/wasm-mutate/src/mutators/snip_function.rs b/crates/wasm-mutate/src/mutators/snip_function.rs
index 1ac67890..00f86e92 100644
--- a/crates/wasm-mutate/src/mutators/snip_function.rs
+++ b/crates/wasm-mutate/src/mutators/snip_function.rs
@@ -61,10 +61,10 @@ impl Mutator for SnipMutator {
                                 f.instruction(&Instruction::V128Const(0));
                             }
                             PrimitiveTypeInfo::FuncRef => {
-                                f.instruction(&Instruction::RefNull(HeapType::Func));
+                                f.instruction(&Instruction::RefNull(HeapType::FUNC));
                             }
                             PrimitiveTypeInfo::ExternRef => {
-                                f.instruction(&Instruction::RefNull(HeapType::Extern));
+                                f.instruction(&Instruction::RefNull(HeapType::EXTERN));
                             }
                             PrimitiveTypeInfo::Empty => {
                                 unreachable!()
diff --git a/crates/wasm-mutate/src/mutators/translate.rs b/crates/wasm-mutate/src/mutators/translate.rs
index 5c5fbbb2..839879bc 100644
--- a/crates/wasm-mutate/src/mutators/translate.rs
+++ b/crates/wasm-mutate/src/mutators/translate.rs
@@ -210,21 +210,16 @@ pub fn refty(t: &mut dyn Translator, ty: &wasmparser::RefType) -> Result<RefType
 
 pub fn heapty(t: &mut dyn Translator, ty: &wasmparser::HeapType) -> Result<HeapType> {
     match ty {
-        wasmparser::HeapType::Func => Ok(HeapType::Func),
-        wasmparser::HeapType::Extern => Ok(HeapType::Extern),
-        wasmparser::HeapType::Any => Ok(HeapType::Any),
-        wasmparser::HeapType::None => Ok(HeapType::None),
-        wasmparser::HeapType::NoExtern => Ok(HeapType::NoExtern),
-        wasmparser::HeapType::NoFunc => Ok(HeapType::NoFunc),
-        wasmparser::HeapType::Eq => Ok(HeapType::Eq),
-        wasmparser::HeapType::Struct => Ok(HeapType::Struct),
-        wasmparser::HeapType::Array => Ok(HeapType::Array),
-        wasmparser::HeapType::I31 => Ok(HeapType::I31),
-        wasmparser::HeapType::Exn => Ok(HeapType::Exn),
-        wasmparser::HeapType::NoExn => Ok(HeapType::NoExn),
         wasmparser::HeapType::Concrete(i) => Ok(HeapType::Concrete(
             t.remap(Item::Type, i.as_module_index().unwrap())?,
         )),
+        wasmparser::HeapType::Abstract { shared, ty } => {
+            let ty = (*ty).into();
+            Ok(HeapType::Abstract {
+                shared: *shared,
+                ty,
+            })
+        }
     }
 }
 
@@ -251,7 +246,11 @@ pub fn const_expr(
         match op {
             Operator::RefFunc { .. }
             | Operator::RefNull {
-                hty: wasmparser::HeapType::Func,
+                hty:
+                    wasmparser::HeapType::Abstract {
+                        ty: wasmparser::AbstractHeapType::Func,
+                        ..
+                    },
                 ..
             }
             | Operator::GlobalGet { .. } => {}
diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs
index 263c294e..ede06314 100644
--- a/crates/wasm-smith/src/core.rs
+++ b/crates/wasm-smith/src/core.rs
@@ -15,8 +15,8 @@ use std::ops::Range;
 use std::rc::Rc;
 use std::str::{self, FromStr};
 use wasm_encoder::{
-    ArrayType, BlockType, ConstExpr, ExportKind, FieldType, HeapType, RefType, StorageType,
-    StructType, ValType,
+    AbstractHeapType, ArrayType, BlockType, ConstExpr, ExportKind, FieldType, HeapType, RefType,
+    StorageType, StructType, ValType,
 };
 pub(crate) use wasm_encoder::{GlobalType, MemoryType, TableType};
 
@@ -449,31 +449,69 @@ impl Module {
     }
 
     fn heap_type_is_sub_type(&self, a: HeapType, b: HeapType) -> bool {
+        use AbstractHeapType::*;
         use HeapType as HT;
         match (a, b) {
             (a, b) if a == b => true,
 
-            (HT::Eq | HT::I31 | HT::Struct | HT::Array | HT::None, HT::Any) => true,
-            (HT::I31 | HT::Struct | HT::Array | HT::None, HT::Eq) => true,
-            (HT::NoExtern, HT::Extern) => true,
-            (HT::NoFunc, HT::Func) => true,
-            (HT::None, HT::I31 | HT::Array | HT::Struct) => true,
-
-            (HT::Concrete(a), HT::Eq | HT::Any) => matches!(
-                self.ty(a).composite_type,
-                CompositeType::Array(_) | CompositeType::Struct(_)
-            ),
-
-            (HT::Concrete(a), HT::Struct) => {
-                matches!(self.ty(a).composite_type, CompositeType::Struct(_))
+            (
+                HT::Abstract {
+                    shared: a_shared,
+                    ty: a_ty,
+                },
+                HT::Abstract {
+                    shared: b_shared,
+                    ty: b_ty,
+                },
+            ) => {
+                a_shared == b_shared
+                    && match (a_ty, b_ty) {
+                        (Eq | I31 | Struct | Array | None, Any) => true,
+                        (I31 | Struct | Array | None, Eq) => true,
+                        (NoExtern, Extern) => true,
+                        (NoFunc, Func) => true,
+                        (None, I31 | Array | Struct) => true,
+                        (NoExn, Exn) => true,
+                        _ => false,
+                    }
             }
 
-            (HT::Concrete(a), HT::Array) => {
-                matches!(self.ty(a).composite_type, CompositeType::Array(_))
+            (HT::Concrete(a), HT::Abstract { shared, ty }) => {
+                if shared {
+                    // TODO: handle shared
+                    todo!("check shared-ness of concrete type");
+                }
+                match ty {
+                    Eq | Any => matches!(
+                        self.ty(a).composite_type,
+                        CompositeType::Array(_) | CompositeType::Struct(_)
+                    ),
+                    Struct => {
+                        matches!(self.ty(a).composite_type, CompositeType::Struct(_))
+                    }
+                    Array => {
+                        matches!(self.ty(a).composite_type, CompositeType::Array(_))
+                    }
+                    Func => {
+                        matches!(self.ty(a).composite_type, CompositeType::Func(_))
+                    }
+                    _ => false,
+                }
             }
 
-            (HT::Concrete(a), HT::Func) => {
-                matches!(self.ty(a).composite_type, CompositeType::Func(_))
+            (HT::Abstract { shared, ty }, HT::Concrete(b)) => {
+                if shared {
+                    // TODO: handle shared
+                    todo!("check shared-ness of concrete type");
+                }
+                match ty {
+                    None => matches!(
+                        self.ty(b).composite_type,
+                        CompositeType::Array(_) | CompositeType::Struct(_)
+                    ),
+                    NoFunc => matches!(self.ty(b).composite_type, CompositeType::Func(_)),
+                    _ => false,
+                }
             }
 
             (HT::Concrete(mut a), HT::Concrete(b)) => loop {
@@ -486,33 +524,6 @@ impl Module {
                     return false;
                 }
             },
-
-            (HT::None, HT::Concrete(b)) => matches!(
-                self.ty(b).composite_type,
-                CompositeType::Array(_) | CompositeType::Struct(_)
-            ),
-
-            (HT::NoFunc, HT::Concrete(b)) => {
-                matches!(self.ty(b).composite_type, CompositeType::Func(_))
-            }
-
-            (HT::NoExn, HT::Exn) => true,
-
-            // Nothing else matches. (Avoid full wildcard matches so that
-            // adding/modifying variants is easier in the future.)
-            (HT::Concrete(_), _)
-            | (HT::Func, _)
-            | (HT::Extern, _)
-            | (HT::Any, _)
-            | (HT::None, _)
-            | (HT::NoExtern, _)
-            | (HT::NoFunc, _)
-            | (HT::Eq, _)
-            | (HT::Struct, _)
-            | (HT::Array, _)
-            | (HT::I31, _)
-            | (HT::Exn, _)
-            | (HT::NoExn, _) => false,
         }
     }
 
@@ -731,26 +742,40 @@ impl Module {
         use HeapType as HT;
         let mut choices = vec![ty];
         match ty {
-            HT::Any => {
-                choices.extend([HT::Eq, HT::Struct, HT::Array, HT::I31, HT::None]);
-                choices.extend(self.array_types.iter().copied().map(HT::Concrete));
-                choices.extend(self.struct_types.iter().copied().map(HT::Concrete));
-            }
-            HT::Eq => {
-                choices.extend([HT::Struct, HT::Array, HT::I31, HT::None]);
-                choices.extend(self.array_types.iter().copied().map(HT::Concrete));
-                choices.extend(self.struct_types.iter().copied().map(HT::Concrete));
-            }
-            HT::Struct => {
-                choices.extend([HT::Struct, HT::None]);
-                choices.extend(self.struct_types.iter().copied().map(HT::Concrete));
-            }
-            HT::Array => {
-                choices.extend([HT::Array, HT::None]);
-                choices.extend(self.array_types.iter().copied().map(HT::Concrete));
-            }
-            HT::I31 => {
-                choices.push(HT::None);
+            HT::Abstract { shared, ty } => {
+                use AbstractHeapType::*;
+                let ht = |ty| HT::Abstract { shared, ty };
+                match ty {
+                    Any => {
+                        choices.extend([ht(Eq), ht(Struct), ht(Array), ht(I31), ht(None)]);
+                        choices.extend(self.array_types.iter().copied().map(HT::Concrete));
+                        choices.extend(self.struct_types.iter().copied().map(HT::Concrete));
+                    }
+                    Eq => {
+                        choices.extend([ht(Struct), ht(Array), ht(I31), ht(None)]);
+                        choices.extend(self.array_types.iter().copied().map(HT::Concrete));
+                        choices.extend(self.struct_types.iter().copied().map(HT::Concrete));
+                    }
+                    Struct => {
+                        choices.extend([ht(Struct), ht(None)]);
+                        choices.extend(self.struct_types.iter().copied().map(HT::Concrete));
+                    }
+                    Array => {
+                        choices.extend([ht(Array), ht(None)]);
+                        choices.extend(self.array_types.iter().copied().map(HT::Concrete));
+                    }
+                    I31 => {
+                        choices.push(ht(None));
+                    }
+                    Func => {
+                        choices.extend(self.func_types.iter().copied().map(HT::Concrete));
+                        choices.push(ht(NoFunc));
+                    }
+                    Extern => {
+                        choices.push(ht(NoExtern));
+                    }
+                    Exn | NoExn | None | NoExtern | NoFunc => {}
+                }
             }
             HT::Concrete(idx) => {
                 if let Some(subs) = self.super_to_sub_types.get(&idx) {
@@ -762,9 +787,15 @@ impl Module {
                     .map(|ty| &ty.composite_type)
                 {
                     Some(CompositeType::Array(_)) | Some(CompositeType::Struct(_)) => {
-                        choices.push(HT::None)
+                        choices.push(HT::Abstract {
+                            shared: false, // TODO: handle shared
+                            ty: AbstractHeapType::None,
+                        })
                     }
-                    Some(CompositeType::Func(_)) => choices.push(HT::NoFunc),
+                    Some(CompositeType::Func(_)) => choices.push(HT::Abstract {
+                        shared: false, // TODO: handle shared
+                        ty: AbstractHeapType::NoFunc,
+                    }),
                     None => {
                         // The referenced type might be part of this same rec
                         // group we are currently generating, but not generated
@@ -774,14 +805,6 @@ impl Module {
                     }
                 }
             }
-            HT::Func => {
-                choices.extend(self.func_types.iter().copied().map(HT::Concrete));
-                choices.push(HT::NoFunc);
-            }
-            HT::Extern => {
-                choices.push(HT::NoExtern);
-            }
-            HT::Exn | HT::NoExn | HT::None | HT::NoExtern | HT::NoFunc => {}
         }
         Ok(*u.choose(&choices)?)
     }
@@ -848,35 +871,58 @@ impl Module {
         use HeapType as HT;
         let mut choices = vec![ty];
         match ty {
-            HT::None => {
-                choices.extend([HT::Any, HT::Eq, HT::Struct, HT::Array, HT::I31]);
-                choices.extend(self.array_types.iter().copied().map(HT::Concrete));
-                choices.extend(self.struct_types.iter().copied().map(HT::Concrete));
-            }
-            HT::NoExtern => {
-                choices.push(HT::Extern);
-            }
-            HT::NoFunc => {
-                choices.extend(self.func_types.iter().copied().map(HT::Concrete));
-                choices.push(HT::Func);
-            }
-            HT::NoExn => {
-                choices.push(HT::Exn);
+            HT::Abstract { shared, ty } => {
+                use AbstractHeapType::*;
+                let ht = |ty| HT::Abstract { shared, ty };
+                match ty {
+                    None => {
+                        choices.extend([ht(Any), ht(Eq), ht(Struct), ht(Array), ht(I31)]);
+                        choices.extend(self.array_types.iter().copied().map(HT::Concrete));
+                        choices.extend(self.struct_types.iter().copied().map(HT::Concrete));
+                    }
+                    NoExtern => {
+                        choices.push(ht(Extern));
+                    }
+                    NoFunc => {
+                        choices.extend(self.func_types.iter().copied().map(HT::Concrete));
+                        choices.push(ht(Func));
+                    }
+                    NoExn => {
+                        choices.push(ht(Exn));
+                    }
+                    Struct | Array | I31 => {
+                        choices.extend([ht(Any), ht(Eq)]);
+                    }
+                    Eq => {
+                        choices.push(ht(Any));
+                    }
+                    Exn | Any | Func | Extern => {}
+                }
             }
             HT::Concrete(mut idx) => {
+                // TODO: handle shared
+                let ht = |ty| HT::Abstract { shared: false, ty };
                 match &self
                     .types
                     .get(usize::try_from(idx).unwrap())
                     .map(|ty| &ty.composite_type)
                 {
                     Some(CompositeType::Array(_)) => {
-                        choices.extend([HT::Any, HT::Eq, HT::Array]);
+                        choices.extend([
+                            ht(AbstractHeapType::Any),
+                            ht(AbstractHeapType::Eq),
+                            ht(AbstractHeapType::Array),
+                        ]);
                     }
                     Some(CompositeType::Func(_)) => {
-                        choices.push(HT::Func);
+                        choices.push(ht(AbstractHeapType::Func));
                     }
                     Some(CompositeType::Struct(_)) => {
-                        choices.extend([HT::Any, HT::Eq, HT::Struct]);
+                        choices.extend([
+                            ht(AbstractHeapType::Any),
+                            ht(AbstractHeapType::Eq),
+                            ht(AbstractHeapType::Struct),
+                        ]);
                     }
                     None => {
                         // Same as in `arbitrary_matching_heap_type`: this was a
@@ -896,13 +942,6 @@ impl Module {
                     idx = supertype;
                 }
             }
-            HT::Struct | HT::Array | HT::I31 => {
-                choices.extend([HT::Any, HT::Eq]);
-            }
-            HT::Eq => {
-                choices.push(HT::Any);
-            }
-            HT::Exn | HT::Any | HT::Func | HT::Extern => {}
         }
         Ok(*u.choose(&choices)?)
     }
@@ -972,27 +1011,23 @@ impl Module {
             return Ok(HeapType::Concrete(idx));
         }
 
-        let mut choices = vec![HeapType::Func, HeapType::Extern];
+        use AbstractHeapType::*;
+        let mut choices = vec![Func, Extern];
         if self.config.exceptions_enabled {
-            choices.push(HeapType::Exn);
+            choices.push(Exn);
         }
         if self.config.gc_enabled {
             choices.extend(
-                [
-                    HeapType::Any,
-                    HeapType::None,
-                    HeapType::NoExtern,
-                    HeapType::NoFunc,
-                    HeapType::Eq,
-                    HeapType::Struct,
-                    HeapType::Array,
-                    HeapType::I31,
-                ]
-                .iter()
-                .copied(),
+                [Any, None, NoExtern, NoFunc, Eq, Struct, Array, I31]
+                    .iter()
+                    .copied(),
             );
         }
-        u.choose(&choices).copied()
+
+        Ok(HeapType::Abstract {
+            shared: false, // TODO: turn on shared attribute with shared-everything-threads.
+            ty: *u.choose(&choices)?,
+        })
     }
 
     fn arbitrary_func_type(&mut self, u: &mut Unstructured) -> Result<Rc<FuncType>> {
@@ -1572,7 +1607,10 @@ impl Module {
                 }
 
                 match ty.heap_type {
-                    HeapType::Func if num_funcs > 0 => {
+                    HeapType::Abstract {
+                        ty: AbstractHeapType::Func,
+                        ..
+                    } if num_funcs > 0 => {
                         choices.push(Box::new(move |u, _| {
                             let func = u.int_in_range(0..=num_funcs - 1)?;
                             Ok(ConstExpr::ref_func(func))
@@ -1636,22 +1674,31 @@ impl Module {
     #[cfg(feature = "wasmparser")]
     fn _required_exports(&mut self, u: &mut Unstructured, example_module: &[u8]) -> Result<()> {
         fn convert_heap_type(ty: &wasmparser::HeapType) -> HeapType {
+            use wasmparser::AbstractHeapType::*;
             match ty {
                 wasmparser::HeapType::Concrete(_) => {
                     panic!("Unable to handle concrete types in exports")
                 }
-                wasmparser::HeapType::Func => HeapType::Func,
-                wasmparser::HeapType::Extern => HeapType::Extern,
-                wasmparser::HeapType::Any => HeapType::Any,
-                wasmparser::HeapType::None => HeapType::None,
-                wasmparser::HeapType::NoExtern => HeapType::NoExtern,
-                wasmparser::HeapType::NoFunc => HeapType::NoFunc,
-                wasmparser::HeapType::Eq => HeapType::Eq,
-                wasmparser::HeapType::Struct => HeapType::Struct,
-                wasmparser::HeapType::Array => HeapType::Array,
-                wasmparser::HeapType::I31 => HeapType::I31,
-                wasmparser::HeapType::Exn => HeapType::Exn,
-                wasmparser::HeapType::NoExn => HeapType::NoExn,
+                wasmparser::HeapType::Abstract { shared, ty } => {
+                    let ty = match ty {
+                        Func => AbstractHeapType::Func,
+                        Extern => AbstractHeapType::Extern,
+                        Any => AbstractHeapType::Any,
+                        None => AbstractHeapType::None,
+                        NoExtern => AbstractHeapType::NoExtern,
+                        NoFunc => AbstractHeapType::NoFunc,
+                        Eq => AbstractHeapType::Eq,
+                        Struct => AbstractHeapType::Struct,
+                        Array => AbstractHeapType::Array,
+                        I31 => AbstractHeapType::I31,
+                        Exn => AbstractHeapType::Exn,
+                        NoExn => AbstractHeapType::NoExn,
+                    };
+                    HeapType::Abstract {
+                        shared: *shared,
+                        ty,
+                    }
+                }
             }
         }
 
@@ -2016,7 +2063,10 @@ impl Module {
                 let mut func_candidates = Vec::new();
                 if can_use_function_list {
                     match ty.heap_type {
-                        HeapType::Func => {
+                        HeapType::Abstract {
+                            ty: AbstractHeapType::Func,
+                            ..
+                        } => {
                             func_candidates.extend(0..self.funcs.len() as u32);
                         }
                         HeapType::Concrete(ty) => {
@@ -2450,21 +2500,14 @@ pub(crate) fn configured_valtypes(config: &Config) -> Vec<ValType> {
             // contain a non-null self-reference are also impossible to create).
             true,
         ] {
-            for heap_type in [
-                HeapType::Any,
-                HeapType::Eq,
-                HeapType::I31,
-                HeapType::Array,
-                HeapType::Struct,
-                HeapType::None,
-                HeapType::Func,
-                HeapType::NoFunc,
-                HeapType::Extern,
-                HeapType::NoExtern,
+            use AbstractHeapType::*;
+            for ty in [
+                Any, Eq, I31, Array, Struct, None, Func, NoFunc, Extern, NoExtern,
             ] {
                 valtypes.push(ValType::Ref(RefType {
                     nullable,
-                    heap_type,
+                    // TODO: handle shared
+                    heap_type: HeapType::Abstract { shared: false, ty },
                 }));
             }
         }
diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs
index 0c67b6a9..aef756b4 100644
--- a/crates/wasm-smith/src/core/code_builder.rs
+++ b/crates/wasm-smith/src/core/code_builder.rs
@@ -7,8 +7,8 @@ use arbitrary::{Result, Unstructured};
 use std::collections::{BTreeMap, BTreeSet};
 use std::rc::Rc;
 use wasm_encoder::{
-    ArrayType, BlockType, Catch, ConstExpr, ExportKind, FieldType, GlobalType, HeapType, MemArg,
-    RefType, StorageType, StructType,
+    AbstractHeapType, ArrayType, BlockType, Catch, ConstExpr, ExportKind, FieldType, GlobalType,
+    HeapType, MemArg, RefType, StorageType, StructType,
 };
 mod no_traps;
 
@@ -2301,18 +2301,12 @@ fn br_on_null(
             if !module.types.is_empty() && u.arbitrary()? {
                 HeapType::Concrete(u.int_in_range(0..=u32::try_from(module.types.len()).unwrap())?)
             } else {
-                *u.choose(&[
-                    HeapType::Func,
-                    HeapType::Extern,
-                    HeapType::Any,
-                    HeapType::None,
-                    HeapType::NoExtern,
-                    HeapType::NoFunc,
-                    HeapType::Eq,
-                    HeapType::Struct,
-                    HeapType::Array,
-                    HeapType::I31,
-                ])?
+                use AbstractHeapType::*;
+                let ty = *u.choose(&[
+                    Func, Extern, Any, None, NoExtern, NoFunc, Eq, Struct, Array, I31,
+                ])?;
+                // TODO: handle shared
+                HeapType::Abstract { shared: false, ty }
             }
         }
     };
@@ -5384,18 +5378,23 @@ fn ref_null(
         choices.push(RefType::EXNREF);
     }
     if module.config.gc_enabled {
+        use AbstractHeapType::*;
         let r = |heap_type| RefType {
             nullable: true,
             heap_type,
         };
-        choices.push(r(HeapType::Any));
-        choices.push(r(HeapType::Eq));
-        choices.push(r(HeapType::Array));
-        choices.push(r(HeapType::Struct));
-        choices.push(r(HeapType::I31));
-        choices.push(r(HeapType::None));
-        choices.push(r(HeapType::NoFunc));
-        choices.push(r(HeapType::NoExtern));
+        let a = |abstract_heap_type| HeapType::Abstract {
+            shared: false, // TODO: handle shared
+            ty: abstract_heap_type,
+        };
+        choices.push(r(a(Any)));
+        choices.push(r(a(Eq)));
+        choices.push(r(a(Array)));
+        choices.push(r(a(Struct)));
+        choices.push(r(a(I31)));
+        choices.push(r(a(None)));
+        choices.push(r(a(NoFunc)));
+        choices.push(r(a(NoExtern)));
         for i in 0..module.types.len() {
             let i = u32::try_from(i).unwrap();
             choices.push(r(HeapType::Concrete(i)));
@@ -5457,10 +5456,7 @@ fn ref_as_non_null(
 
 #[inline]
 fn ref_eq_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    let eq_ref = ValType::Ref(RefType {
-        nullable: true,
-        heap_type: HeapType::Eq,
-    });
+    let eq_ref = ValType::Ref(RefType::EQREF);
     module.config.gc_enabled && builder.types_on_stack(module, &[eq_ref, eq_ref])
 }
 
@@ -6359,14 +6355,7 @@ fn array_set(
 
 #[inline]
 fn array_len_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    module.config.gc_enabled
-        && builder.type_on_stack(
-            module,
-            ValType::Ref(RefType {
-                nullable: true,
-                heap_type: HeapType::Array,
-            }),
-        )
+    module.config.gc_enabled && builder.type_on_stack(module, ValType::Ref(RefType::ARRAYREF))
 }
 
 fn array_len(
@@ -6574,10 +6563,7 @@ fn ref_i31(
     instructions: &mut Vec<Instruction>,
 ) -> Result<()> {
     builder.pop_operand();
-    builder.push_operand(Some(ValType::Ref(RefType {
-        nullable: false,
-        heap_type: HeapType::I31,
-    })));
+    builder.push_operand(Some(ValType::Ref(RefType::I31REF)));
     instructions.push(Instruction::RefI31);
     Ok(())
 }
@@ -6617,7 +6603,7 @@ fn any_convert_extern_valid(module: &Module, builder: &mut CodeBuilder) -> bool
             module,
             ValType::Ref(RefType {
                 nullable: true,
-                heap_type: HeapType::Extern,
+                heap_type: HeapType::EXTERN,
             }),
         )
 }
@@ -6634,7 +6620,7 @@ fn any_convert_extern(
     };
     builder.push_operand(Some(ValType::Ref(RefType {
         nullable,
-        heap_type: HeapType::Any,
+        heap_type: HeapType::ANY,
     })));
     instructions.push(Instruction::AnyConvertExtern);
     Ok(())
@@ -6642,14 +6628,7 @@ fn any_convert_extern(
 
 #[inline]
 fn extern_convert_any_valid(module: &Module, builder: &mut CodeBuilder) -> bool {
-    module.config.gc_enabled
-        && builder.type_on_stack(
-            module,
-            ValType::Ref(RefType {
-                nullable: true,
-                heap_type: HeapType::Any,
-            }),
-        )
+    module.config.gc_enabled && builder.type_on_stack(module, ValType::Ref(RefType::ANYREF))
 }
 
 fn extern_convert_any(
@@ -6662,10 +6641,12 @@ fn extern_convert_any(
         None => u.arbitrary()?,
         Some(r) => r.nullable,
     };
-    builder.push_operand(Some(ValType::Ref(RefType {
-        nullable,
-        heap_type: HeapType::Extern,
-    })));
+    let ty = if nullable {
+        RefType::EXTERNREF.nullable(true)
+    } else {
+        RefType::EXTERNREF
+    };
+    builder.push_operand(Some(ValType::Ref(ty)));
     instructions.push(Instruction::ExternConvertAny);
     Ok(())
 }
diff --git a/crates/wasmparser/src/readers/core/types.rs b/crates/wasmparser/src/readers/core/types.rs
index 8df5c03d..eb1b253d 100644
--- a/crates/wasmparser/src/readers/core/types.rs
+++ b/crates/wasmparser/src/readers/core/types.rs
@@ -570,6 +570,11 @@ impl CompositeType {
             _ => panic!("not a struct"),
         }
     }
+
+    /// Is the composite type `shared`?
+    pub fn is_shared(&self) -> bool {
+        todo!("shared composite types are not yet implemented")
+    }
 }
 
 /// Represents a type of a function in a WebAssembly module.
@@ -818,15 +823,6 @@ impl ValType {
         }
     }
 
-    /// Whether the type is `shared`.
-    pub fn is_shared(&self) -> bool {
-        match *self {
-            Self::I32 | Self::I64 | Self::F32 | Self::F64 | Self::V128 => true,
-            // TODO: parsing of `shared` refs is not yet implemented.
-            Self::Ref(_) => false,
-        }
-    }
-
     /// Maps any `UnpackedIndex` via the specified closure.
     #[cfg(feature = "validate")]
     pub(crate) fn remap_indices(
@@ -857,27 +853,29 @@ impl ValType {
 /// The GC proposal introduces heap types: any, eq, i31, struct, array,
 /// nofunc, noextern, none.
 //
-// RefType is a bit-packed enum that fits in a `u24` aka `[u8; 3]`.
-// Note that its content is opaque (and subject to change), but its API
-// is stable.
+// RefType is a bit-packed enum that fits in a `u24` aka `[u8; 3]`. Note that
+// its content is opaque (and subject to change), but its API is stable.
 //
 // It has the following internal structure:
 //
 // ```
 // [nullable:u1 concrete==1:u1 index:u22]
-// [nullable:u1 concrete==0:u1 abstype:u4 (unused):u18]
+// [nullable:u1 concrete==0:u1 shared:u1 abstype:u4 (unused):u17]
 // ```
 //
 // Where
 //
 // - `nullable` determines nullability of the ref,
 //
-// - `concrete` determines if the ref is of a dynamically defined type
-//   with an index (encoded in a following bit-packing section) or of a
-//   known fixed type,
+// - `concrete` determines if the ref is of a dynamically defined type with an
+//   index (encoded in a following bit-packing section) or of a known fixed
+//   type,
 //
 // - `index` is the type index,
 //
+// - `shared` determines if the ref is shared, but only if it is not concrete in
+//   which case we would need to examine the type at the concrete index,
+//
 // - `abstype` is an enumeration of abstract types:
 //
 //   ```
@@ -903,33 +901,31 @@ pub struct RefType([u8; 3]);
 
 impl fmt::Debug for RefType {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match (self.is_nullable(), self.heap_type()) {
-            (true, HeapType::Any) => write!(f, "anyref"),
-            (false, HeapType::Any) => write!(f, "(ref any)"),
-            (true, HeapType::None) => write!(f, "nullref"),
-            (false, HeapType::None) => write!(f, "(ref none)"),
-            (true, HeapType::NoExtern) => write!(f, "nullexternref"),
-            (false, HeapType::NoExtern) => write!(f, "(ref noextern)"),
-            (true, HeapType::NoFunc) => write!(f, "nullfuncref"),
-            (false, HeapType::NoFunc) => write!(f, "(ref nofunc)"),
-            (true, HeapType::Eq) => write!(f, "eqref"),
-            (false, HeapType::Eq) => write!(f, "(ref eq)"),
-            (true, HeapType::Struct) => write!(f, "structref"),
-            (false, HeapType::Struct) => write!(f, "(ref struct)"),
-            (true, HeapType::Array) => write!(f, "arrayref"),
-            (false, HeapType::Array) => write!(f, "(ref array)"),
-            (true, HeapType::I31) => write!(f, "i31ref"),
-            (false, HeapType::I31) => write!(f, "(ref i31)"),
-            (true, HeapType::Extern) => write!(f, "externref"),
-            (false, HeapType::Extern) => write!(f, "(ref extern)"),
-            (true, HeapType::Func) => write!(f, "funcref"),
-            (false, HeapType::Func) => write!(f, "(ref func)"),
-            (true, HeapType::Exn) => write!(f, "exnref"),
-            (false, HeapType::Exn) => write!(f, "(ref exn)"),
-            (true, HeapType::NoExn) => write!(f, "nullexnref"),
-            (false, HeapType::NoExn) => write!(f, "(ref noexn)"),
-            (true, HeapType::Concrete(idx)) => write!(f, "(ref null {idx})"),
-            (false, HeapType::Concrete(idx)) => write!(f, "(ref {idx})"),
+        let heap_type = self.heap_type();
+        match heap_type {
+            // All abstract types follow the same general pattern.
+            HeapType::Abstract { shared, ty } => {
+                let nullable = self.is_nullable();
+
+                let name = ty.as_str(nullable);
+                match (nullable, shared) {
+                    // Print the shortened form if nullable; i.e., `*ref`.
+                    (true, true) => write!(f, "(shared {}ref)", name),
+                    (true, false) => write!(f, "{}ref", name),
+                    // Print the long form otherwise; i.e., `(ref *)`.
+                    (false, true) => write!(f, "(ref (shared {}))", name),
+                    (false, false) => write!(f, "(ref {})", name),
+                }
+            }
+            // Handle concrete types separately; they always use the long form
+            // and don't show `shared`-ness.
+            HeapType::Concrete(index) => {
+                if self.is_nullable() {
+                    write!(f, "(ref null {})", index)
+                } else {
+                    write!(f, "(ref {})", index)
+                }
+            }
         }
     }
 }
@@ -967,19 +963,20 @@ impl RefType {
     const CONCRETE_BIT: u32 = 1 << 22;
 
     // The `abstype` field is valid only when `concrete == 0`.
-    const ABSTYPE_MASK: u32 = 0b1111 << 18;
-    const ANY_ABSTYPE: u32 = 0b1111 << 18;
-    const EQ_ABSTYPE: u32 = 0b1101 << 18;
-    const I31_ABSTYPE: u32 = 0b1000 << 18;
-    const STRUCT_ABSTYPE: u32 = 0b1001 << 18;
-    const ARRAY_ABSTYPE: u32 = 0b1100 << 18;
-    const FUNC_ABSTYPE: u32 = 0b0101 << 18;
-    const NOFUNC_ABSTYPE: u32 = 0b0100 << 18;
-    const EXTERN_ABSTYPE: u32 = 0b0011 << 18;
-    const NOEXTERN_ABSTYPE: u32 = 0b0010 << 18;
-    const EXN_ABSTYPE: u32 = 0b0001 << 18;
-    const NOEXN_ABSTYPE: u32 = 0b1110 << 18;
-    const NONE_ABSTYPE: u32 = 0b0000 << 18;
+    const SHARED_BIT: u32 = 1 << 21;
+    const ABSTYPE_MASK: u32 = 0b1111 << 17;
+    const ANY_ABSTYPE: u32 = 0b1111 << 17;
+    const EQ_ABSTYPE: u32 = 0b1101 << 17;
+    const I31_ABSTYPE: u32 = 0b1000 << 17;
+    const STRUCT_ABSTYPE: u32 = 0b1001 << 17;
+    const ARRAY_ABSTYPE: u32 = 0b1100 << 17;
+    const FUNC_ABSTYPE: u32 = 0b0101 << 17;
+    const NOFUNC_ABSTYPE: u32 = 0b0100 << 17;
+    const EXTERN_ABSTYPE: u32 = 0b0011 << 17;
+    const NOEXTERN_ABSTYPE: u32 = 0b0010 << 17;
+    const EXN_ABSTYPE: u32 = 0b0001 << 17;
+    const NOEXN_ABSTYPE: u32 = 0b1110 << 17;
+    const NONE_ABSTYPE: u32 = 0b0000 << 17;
 
     // The `index` is valid only when `concrete == 1`.
     const INDEX_MASK: u32 = (1 << 22) - 1;
@@ -1127,21 +1124,27 @@ impl RefType {
     /// Returns `None` when the heap type's type index (if any) is beyond this
     /// crate's implementation limits and therefore is not representable.
     pub fn new(nullable: bool, heap_type: HeapType) -> Option<Self> {
-        let nullable32 = Self::NULLABLE_BIT * (nullable as u32);
+        let base32 = Self::NULLABLE_BIT * (nullable as u32);
         match heap_type {
             HeapType::Concrete(index) => Some(RefType::concrete(nullable, index.pack()?)),
-            HeapType::Func => Some(Self::from_u32(nullable32 | Self::FUNC_ABSTYPE)),
-            HeapType::Extern => Some(Self::from_u32(nullable32 | Self::EXTERN_ABSTYPE)),
-            HeapType::Any => Some(Self::from_u32(nullable32 | Self::ANY_ABSTYPE)),
-            HeapType::None => Some(Self::from_u32(nullable32 | Self::NONE_ABSTYPE)),
-            HeapType::NoExtern => Some(Self::from_u32(nullable32 | Self::NOEXTERN_ABSTYPE)),
-            HeapType::NoFunc => Some(Self::from_u32(nullable32 | Self::NOFUNC_ABSTYPE)),
-            HeapType::Eq => Some(Self::from_u32(nullable32 | Self::EQ_ABSTYPE)),
-            HeapType::Struct => Some(Self::from_u32(nullable32 | Self::STRUCT_ABSTYPE)),
-            HeapType::Array => Some(Self::from_u32(nullable32 | Self::ARRAY_ABSTYPE)),
-            HeapType::I31 => Some(Self::from_u32(nullable32 | Self::I31_ABSTYPE)),
-            HeapType::Exn => Some(Self::from_u32(nullable32 | Self::EXN_ABSTYPE)),
-            HeapType::NoExn => Some(Self::from_u32(nullable32 | Self::NOEXN_ABSTYPE)),
+            HeapType::Abstract { shared, ty } => {
+                use AbstractHeapType::*;
+                let base32 = base32 | (Self::SHARED_BIT * (shared as u32));
+                match ty {
+                    Func => Some(Self::from_u32(base32 | Self::FUNC_ABSTYPE)),
+                    Extern => Some(Self::from_u32(base32 | Self::EXTERN_ABSTYPE)),
+                    Any => Some(Self::from_u32(base32 | Self::ANY_ABSTYPE)),
+                    None => Some(Self::from_u32(base32 | Self::NONE_ABSTYPE)),
+                    NoExtern => Some(Self::from_u32(base32 | Self::NOEXTERN_ABSTYPE)),
+                    NoFunc => Some(Self::from_u32(base32 | Self::NOFUNC_ABSTYPE)),
+                    Eq => Some(Self::from_u32(base32 | Self::EQ_ABSTYPE)),
+                    Struct => Some(Self::from_u32(base32 | Self::STRUCT_ABSTYPE)),
+                    Array => Some(Self::from_u32(base32 | Self::ARRAY_ABSTYPE)),
+                    I31 => Some(Self::from_u32(base32 | Self::I31_ABSTYPE)),
+                    Exn => Some(Self::from_u32(base32 | Self::EXN_ABSTYPE)),
+                    NoExn => Some(Self::from_u32(base32 | Self::NOEXN_ABSTYPE)),
+                }
+            }
         }
     }
 
@@ -1193,7 +1196,7 @@ impl RefType {
         !self.is_concrete_type_ref() && self.abstype() == Self::EXTERN_ABSTYPE
     }
 
-    /// Is this the abstract untyped array refrence type aka `(ref null
+    /// Is this the abstract untyped array reference type aka `(ref null
     /// array)` aka `arrayref`?
     pub const fn is_array_ref(&self) -> bool {
         !self.is_concrete_type_ref() && self.abstype() == Self::ARRAY_ABSTYPE
@@ -1220,27 +1223,39 @@ impl RefType {
         Self::from_u32(self.as_u32() | Self::NULLABLE_BIT)
     }
 
+    /// Get the shared version of this ref type as long as it is abstract.
+    pub const fn shared(&self) -> Option<Self> {
+        if self.is_concrete_type_ref() {
+            None
+        } else {
+            Some(Self::from_u32(self.as_u32() | Self::SHARED_BIT))
+        }
+    }
+
     /// Get the heap type that this is a reference to.
     pub fn heap_type(&self) -> HeapType {
         let s = self.as_u32();
         if self.is_concrete_type_ref() {
             HeapType::Concrete(self.type_index().unwrap().unpack())
         } else {
-            match s & Self::ABSTYPE_MASK {
-                Self::FUNC_ABSTYPE => HeapType::Func,
-                Self::EXTERN_ABSTYPE => HeapType::Extern,
-                Self::ANY_ABSTYPE => HeapType::Any,
-                Self::NONE_ABSTYPE => HeapType::None,
-                Self::NOEXTERN_ABSTYPE => HeapType::NoExtern,
-                Self::NOFUNC_ABSTYPE => HeapType::NoFunc,
-                Self::EQ_ABSTYPE => HeapType::Eq,
-                Self::STRUCT_ABSTYPE => HeapType::Struct,
-                Self::ARRAY_ABSTYPE => HeapType::Array,
-                Self::I31_ABSTYPE => HeapType::I31,
-                Self::EXN_ABSTYPE => HeapType::Exn,
-                Self::NOEXN_ABSTYPE => HeapType::NoExn,
+            use AbstractHeapType::*;
+            let shared = s & Self::SHARED_BIT != 0;
+            let ty = match s & Self::ABSTYPE_MASK {
+                Self::FUNC_ABSTYPE => Func,
+                Self::EXTERN_ABSTYPE => Extern,
+                Self::ANY_ABSTYPE => Any,
+                Self::NONE_ABSTYPE => None,
+                Self::NOEXTERN_ABSTYPE => NoExtern,
+                Self::NOFUNC_ABSTYPE => NoFunc,
+                Self::EQ_ABSTYPE => Eq,
+                Self::STRUCT_ABSTYPE => Struct,
+                Self::ARRAY_ABSTYPE => Array,
+                Self::I31_ABSTYPE => I31,
+                Self::EXN_ABSTYPE => Exn,
+                Self::NOEXN_ABSTYPE => NoExn,
                 _ => unreachable!(),
-            }
+            };
+            HeapType::Abstract { shared, ty }
         }
     }
 
@@ -1248,33 +1263,72 @@ impl RefType {
     // the indexes stubbed out.
     #[cfg(feature = "validate")]
     pub(crate) fn wat(&self) -> &'static str {
-        match (self.is_nullable(), self.heap_type()) {
-            (true, HeapType::Func) => "funcref",
-            (true, HeapType::Extern) => "externref",
-            (true, HeapType::Concrete(_)) => "(ref null $type)",
-            (true, HeapType::Any) => "anyref",
-            (true, HeapType::None) => "nullref",
-            (true, HeapType::NoExtern) => "nullexternref",
-            (true, HeapType::NoFunc) => "nullfuncref",
-            (true, HeapType::Eq) => "eqref",
-            (true, HeapType::Struct) => "structref",
-            (true, HeapType::Array) => "arrayref",
-            (true, HeapType::I31) => "i31ref",
-            (true, HeapType::Exn) => "exnref",
-            (true, HeapType::NoExn) => "nullexnref",
-            (false, HeapType::Func) => "(ref func)",
-            (false, HeapType::Extern) => "(ref extern)",
-            (false, HeapType::Concrete(_)) => "(ref $type)",
-            (false, HeapType::Any) => "(ref any)",
-            (false, HeapType::None) => "(ref none)",
-            (false, HeapType::NoExtern) => "(ref noextern)",
-            (false, HeapType::NoFunc) => "(ref nofunc)",
-            (false, HeapType::Eq) => "(ref eq)",
-            (false, HeapType::Struct) => "(ref struct)",
-            (false, HeapType::Array) => "(ref array)",
-            (false, HeapType::I31) => "(ref i31)",
-            (false, HeapType::Exn) => "(ref exn)",
-            (false, HeapType::NoExn) => "(ref noexn)",
+        let nullable = self.is_nullable();
+        match self.heap_type() {
+            HeapType::Abstract { shared, ty } => {
+                use AbstractHeapType::*;
+                match (shared, nullable, ty) {
+                    // Shared and nullable.
+                    (true, true, Func) => "(shared funcref)",
+                    (true, true, Extern) => "(shared externref)",
+                    (true, true, Any) => "(shared anyref)",
+                    (true, true, None) => "(shared nullref)",
+                    (true, true, NoExtern) => "(shared nullexternref)",
+                    (true, true, NoFunc) => "(shared nullfuncref)",
+                    (true, true, Eq) => "(shared eqref)",
+                    (true, true, Struct) => "(shared structref)",
+                    (true, true, Array) => "(shared arrayref)",
+                    (true, true, I31) => "(shared i31ref)",
+                    (true, true, Exn) => "(shared exnref)",
+                    (true, true, NoExn) => "(shared nullexnref)",
+                    // Unshared but nullable.
+                    (false, true, Func) => "funcref",
+                    (false, true, Extern) => "externref",
+                    (false, true, Any) => "anyref",
+                    (false, true, None) => "nullref",
+                    (false, true, NoExtern) => "nullexternref",
+                    (false, true, NoFunc) => "nullfuncref",
+                    (false, true, Eq) => "eqref",
+                    (false, true, Struct) => "structref",
+                    (false, true, Array) => "arrayref",
+                    (false, true, I31) => "i31ref",
+                    (false, true, Exn) => "exnref",
+                    (false, true, NoExn) => "nullexnref",
+                    // Shared but not nullable.
+                    (true, false, Func) => "(ref (shared func))",
+                    (true, false, Extern) => "(ref (shared extern))",
+                    (true, false, Any) => "(ref (shared any))",
+                    (true, false, None) => "(ref (shared none))",
+                    (true, false, NoExtern) => "(ref (shared noextern))",
+                    (true, false, NoFunc) => "(ref (shared nofunc))",
+                    (true, false, Eq) => "(ref (shared eq))",
+                    (true, false, Struct) => "(ref (shared struct))",
+                    (true, false, Array) => "(ref (shared array))",
+                    (true, false, I31) => "(ref (shared i31))",
+                    (true, false, Exn) => "(ref (shared exn))",
+                    (true, false, NoExn) => "(ref (shared noexn))",
+                    // Neither shared nor nullable.
+                    (false, false, Func) => "(ref func)",
+                    (false, false, Extern) => "(ref extern)",
+                    (false, false, Any) => "(ref any)",
+                    (false, false, None) => "(ref none)",
+                    (false, false, NoExtern) => "(ref noextern)",
+                    (false, false, NoFunc) => "(ref nofunc)",
+                    (false, false, Eq) => "(ref eq)",
+                    (false, false, Struct) => "(ref struct)",
+                    (false, false, Array) => "(ref array)",
+                    (false, false, I31) => "(ref i31)",
+                    (false, false, Exn) => "(ref exn)",
+                    (false, false, NoExn) => "(ref noexn)",
+                }
+            }
+            HeapType::Concrete(_) => {
+                if nullable {
+                    "(ref null $type)"
+                } else {
+                    "(ref $type)"
+                }
+            }
         }
     }
 }
@@ -1282,11 +1336,38 @@ impl RefType {
 /// A heap type.
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub enum HeapType {
+    /// An abstract heap type; e.g., `anyref`.
+    Abstract {
+        /// Whether the type is shared.
+        ///
+        /// Introduced in the shared-everything-threads proposal.
+        shared: bool,
+        /// The actual heap type.
+        ty: AbstractHeapType,
+    },
     /// A concrete, user-defined type.
     ///
     /// Introduced in the function-references proposal.
     Concrete(UnpackedIndex),
+}
 
+impl HeapType {
+    /// Alias for an unshared `func` heap type.
+    pub const FUNC: Self = Self::Abstract {
+        shared: false,
+        ty: AbstractHeapType::Func,
+    };
+
+    /// Alias for an unshared `extern` heap type.
+    pub const EXTERN: Self = Self::Abstract {
+        shared: false,
+        ty: AbstractHeapType::Extern,
+    };
+}
+
+/// An abstract heap type.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum AbstractHeapType {
     /// The abstract, untyped (any) function.
     ///
     /// Introduced in the references-types proposal.
@@ -1369,6 +1450,30 @@ pub enum HeapType {
     NoExn,
 }
 
+impl AbstractHeapType {
+    const fn as_str(&self, nullable: bool) -> &str {
+        use AbstractHeapType::*;
+        match (nullable, self) {
+            (_, Any) => "any",
+            (true, None) => "null",
+            (false, None) => "none",
+            (true, NoExtern) => "nullextern",
+            (false, NoExtern) => "noextern",
+            (true, NoFunc) => "nullfunc",
+            (false, NoFunc) => "nofunc",
+            (_, Eq) => "eq",
+            (_, Struct) => "struct",
+            (_, Array) => "array",
+            (_, I31) => "i31",
+            (_, Extern) => "extern",
+            (_, Func) => "func",
+            (_, Exn) => "exn",
+            (true, NoExn) => "nullexn",
+            (false, NoExn) => "noexn",
+        }
+    }
+}
+
 impl<'a> FromReader<'a> for StorageType {
     fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
         // NB: See `FromReader<'a> for ValType` for a table of how this
@@ -1421,6 +1526,7 @@ impl<'a> FromReader<'a> for ValType {
         // | 0x6B    | -21     | struct       | gc proposal                  |
         // | 0x6A    | -22     | array        | gc proposal                  |
         // | 0x69    | -23     | exnref       | gc + exceptions proposal     |
+        // | 0x65    | -27     | shared $t    | shared-everything proposal   |
         // | 0x64    | -28     | ref $t       | gc proposal, prefix byte     |
         // | 0x63    | -29     | ref null $t  | gc proposal, prefix byte     |
         // | 0x60    | -32     | func $t      | prefix byte                  |
@@ -1457,8 +1563,8 @@ impl<'a> FromReader<'a> for ValType {
                 reader.read_u8()?;
                 Ok(ValType::V128)
             }
-            0x70 | 0x6F | 0x64 | 0x63 | 0x6E | 0x71 | 0x72 | 0x73 | 0x74 | 0x6D | 0x6B | 0x6A
-            | 0x6C | 0x69 => Ok(ValType::Ref(reader.read()?)),
+            0x70 | 0x6F | 0x65 | 0x64 | 0x63 | 0x6E | 0x71 | 0x72 | 0x73 | 0x74 | 0x6D | 0x6B
+            | 0x6A | 0x6C | 0x69 => Ok(ValType::Ref(reader.read()?)),
             _ => bail!(reader.original_position(), "invalid value type"),
         }
     }
@@ -1466,27 +1572,41 @@ impl<'a> FromReader<'a> for ValType {
 
 impl<'a> FromReader<'a> for RefType {
     fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
+        let absheapty = |byte, pos| match byte {
+            0x70 => Ok(RefType::FUNC.nullable()),
+            0x6F => Ok(RefType::EXTERN.nullable()),
+            0x6E => Ok(RefType::ANY.nullable()),
+            0x71 => Ok(RefType::NONE.nullable()),
+            0x72 => Ok(RefType::NOEXTERN.nullable()),
+            0x73 => Ok(RefType::NOFUNC.nullable()),
+            0x6D => Ok(RefType::EQ.nullable()),
+            0x6B => Ok(RefType::STRUCT.nullable()),
+            0x6A => Ok(RefType::ARRAY.nullable()),
+            0x6C => Ok(RefType::I31.nullable()),
+            0x69 => Ok(RefType::EXN.nullable()),
+            0x74 => Ok(RefType::NOEXN.nullable()),
+            _ => bail!(pos, "invalid abstract heap type"),
+        };
+
         // NB: See `FromReader<'a> for ValType` for a table of how this
         // interacts with other value encodings.
         match reader.read()? {
+            byte @ (0x70 | 0x6F | 0x6E | 0x71 | 0x72 | 0x73 | 0x6D | 0x6B | 0x6A | 0x6C | 0x69
+            | 0x74) => {
+                let pos = reader.original_position();
+                absheapty(byte, pos)
+            }
+            0x65 => {
+                let byte = reader.read()?;
+                let pos = reader.original_position();
+                Ok(absheapty(byte, pos)?.shared().expect("must be abstract"))
+            }
             byte @ (0x63 | 0x64) => {
                 let nullable = byte == 0x63;
                 let pos = reader.original_position();
                 RefType::new(nullable, reader.read()?)
                     .ok_or_else(|| crate::BinaryReaderError::new("type index too large", pos))
             }
-            0x69 => Ok(RefType::EXN.nullable()),
-            0x6A => Ok(RefType::ARRAY.nullable()),
-            0x6B => Ok(RefType::STRUCT.nullable()),
-            0x6C => Ok(RefType::I31.nullable()),
-            0x6D => Ok(RefType::EQ.nullable()),
-            0x6E => Ok(RefType::ANY.nullable()),
-            0x6F => Ok(RefType::EXTERN.nullable()),
-            0x70 => Ok(RefType::FUNC.nullable()),
-            0x71 => Ok(RefType::NONE.nullable()),
-            0x72 => Ok(RefType::NOEXTERN.nullable()),
-            0x73 => Ok(RefType::NOFUNC.nullable()),
-            0x74 => Ok(RefType::NOEXN.nullable()),
             _ => bail!(reader.original_position(), "malformed reference type"),
         }
     }
@@ -1497,53 +1617,14 @@ impl<'a> FromReader<'a> for HeapType {
         // NB: See `FromReader<'a> for ValType` for a table of how this
         // interacts with other value encodings.
         match reader.peek()? {
-            0x70 => {
-                reader.read_u8()?;
-                Ok(HeapType::Func)
-            }
-            0x6F => {
+            0x65 => {
                 reader.read_u8()?;
-                Ok(HeapType::Extern)
+                let ty = reader.read()?;
+                Ok(HeapType::Abstract { shared: true, ty })
             }
-            0x6E => {
-                reader.read_u8()?;
-                Ok(HeapType::Any)
-            }
-            0x71 => {
-                reader.read_u8()?;
-                Ok(HeapType::None)
-            }
-            0x72 => {
-                reader.read_u8()?;
-                Ok(HeapType::NoExtern)
-            }
-            0x73 => {
-                reader.read_u8()?;
-                Ok(HeapType::NoFunc)
-            }
-            0x6D => {
-                reader.read_u8()?;
-                Ok(HeapType::Eq)
-            }
-            0x6B => {
-                reader.read_u8()?;
-                Ok(HeapType::Struct)
-            }
-            0x6A => {
-                reader.read_u8()?;
-                Ok(HeapType::Array)
-            }
-            0x6C => {
-                reader.read_u8()?;
-                Ok(HeapType::I31)
-            }
-            0x69 => {
-                reader.read_u8()?;
-                Ok(HeapType::Exn)
-            }
-            0x74 => {
-                reader.read_u8()?;
-                Ok(HeapType::NoExn)
+            0x70 | 0x6F | 0x6E | 0x71 | 0x72 | 0x73 | 0x6D | 0x6B | 0x6A | 0x6C | 0x69 | 0x74 => {
+                let ty = reader.read()?;
+                Ok(HeapType::Abstract { shared: false, ty })
             }
             _ => {
                 let idx = match u32::try_from(reader.read_var_s33()?) {
@@ -1564,6 +1645,29 @@ impl<'a> FromReader<'a> for HeapType {
     }
 }
 
+impl<'a> FromReader<'a> for AbstractHeapType {
+    fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
+        use AbstractHeapType::*;
+        match reader.read_u8()? {
+            0x70 => Ok(Func),
+            0x6F => Ok(Extern),
+            0x6E => Ok(Any),
+            0x71 => Ok(None),
+            0x72 => Ok(NoExtern),
+            0x73 => Ok(NoFunc),
+            0x6D => Ok(Eq),
+            0x6B => Ok(Struct),
+            0x6A => Ok(Array),
+            0x6C => Ok(I31),
+            0x69 => Ok(Exn),
+            0x74 => Ok(NoExn),
+            _ => {
+                bail!(reader.original_position(), "invalid abstract heap type");
+            }
+        }
+    }
+}
+
 /// Represents a table's type.
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct TableType {
diff --git a/crates/wasmparser/src/resources.rs b/crates/wasmparser/src/resources.rs
index 20ce7435..729fc285 100644
--- a/crates/wasmparser/src/resources.rs
+++ b/crates/wasmparser/src/resources.rs
@@ -161,7 +161,6 @@ where
     fn is_subtype(&self, a: ValType, b: ValType) -> bool {
         T::is_subtype(self, a, b)
     }
-
     fn element_count(&self) -> u32 {
         T::element_count(self)
     }
diff --git a/crates/wasmparser/src/validator.rs b/crates/wasmparser/src/validator.rs
index 5902b925..4396cb51 100644
--- a/crates/wasmparser/src/validator.rs
+++ b/crates/wasmparser/src/validator.rs
@@ -15,9 +15,9 @@
 
 use crate::prelude::*;
 use crate::{
-    limits::*, BinaryReaderError, Encoding, FromReader, FunctionBody, HeapType, Parser, Payload,
-    RefType, Result, SectionLimited, ValType, WasmFeatures, WASM_COMPONENT_VERSION,
-    WASM_MODULE_VERSION,
+    limits::*, AbstractHeapType, BinaryReaderError, Encoding, FromReader, FunctionBody, HeapType,
+    Parser, Payload, RefType, Result, SectionLimited, ValType, WasmFeatures,
+    WASM_COMPONENT_VERSION, WASM_MODULE_VERSION,
 };
 use ::core::mem;
 use ::core::ops::Range;
@@ -249,55 +249,54 @@ impl WasmFeatures {
         if !self.reference_types() {
             return Err("reference types support is not enabled");
         }
-        match (r.heap_type(), r.is_nullable()) {
-            // funcref/externref only require `reference-types`.
-            (HeapType::Func, true) | (HeapType::Extern, true) => Ok(()),
-
-            // Non-nullable func/extern references requires the
-            // `function-references` proposal.
-            (HeapType::Func | HeapType::Extern, false) => {
-                if self.function_references() {
-                    Ok(())
-                } else {
-                    Err("function references required for non-nullable types")
-                }
-            }
-
-            // Indexed types require either the function-references or gc
-            // proposal as gc implies function references here.
-            (HeapType::Concrete(_), _) => {
+        match r.heap_type() {
+            HeapType::Concrete(_) => {
+                // Indexed types require either the function-references or gc
+                // proposal as gc implies function references here.
                 if self.function_references() || self.gc() {
                     Ok(())
                 } else {
                     Err("function references required for index reference types")
                 }
             }
-
-            // These types were added in the gc proposal.
-            (
-                HeapType::Any
-                | HeapType::None
-                | HeapType::Eq
-                | HeapType::Struct
-                | HeapType::Array
-                | HeapType::I31
-                | HeapType::NoExtern
-                | HeapType::NoFunc,
-                _,
-            ) => {
-                if self.gc() {
-                    Ok(())
-                } else {
-                    Err("heap types not supported without the gc feature")
+            HeapType::Abstract { shared, ty } => {
+                use AbstractHeapType::*;
+                if shared && !self.shared_everything_threads() {
+                    return Err(
+                        "shared reference types require the shared-everything-threads proposal",
+                    );
                 }
-            }
+                match (ty, r.is_nullable()) {
+                    // funcref/externref only require `reference-types`.
+                    (Func, true) | (Extern, true) => Ok(()),
+
+                    // Non-nullable func/extern references requires the
+                    // `function-references` proposal.
+                    (Func | Extern, false) => {
+                        if self.function_references() {
+                            Ok(())
+                        } else {
+                            Err("function references required for non-nullable types")
+                        }
+                    }
 
-            // These types were added in the exception-handling proposal.
-            (HeapType::Exn | HeapType::NoExn, _) => {
-                if self.exceptions() {
-                    Ok(())
-                } else {
-                    Err("exception refs not supported without the exception handling feature")
+                    // These types were added in the gc proposal.
+                    (Any | None | Eq | Struct | Array | I31 | NoExtern | NoFunc, _) => {
+                        if self.gc() {
+                            Ok(())
+                        } else {
+                            Err("heap types not supported without the gc feature")
+                        }
+                    }
+
+                    // These types were added in the exception-handling proposal.
+                    (Exn | NoExn, _) => {
+                        if self.exceptions() {
+                            Ok(())
+                        } else {
+                            Err("exception refs not supported without the exception handling feature")
+                        }
+                    }
                 }
             }
         }
diff --git a/crates/wasmparser/src/validator/core.rs b/crates/wasmparser/src/validator/core.rs
index b189c419..173a74d8 100644
--- a/crates/wasmparser/src/validator/core.rs
+++ b/crates/wasmparser/src/validator/core.rs
@@ -132,7 +132,7 @@ impl ModuleState {
         offset: usize,
     ) -> Result<()> {
         self.module
-            .check_global_type(&mut global.ty, features, offset)?;
+            .check_global_type(&mut global.ty, features, types, offset)?;
         self.check_const_expr(&global.init_expr, global.ty.content_type, features, types)?;
         self.module.assert_mut().globals.push(global.ty);
         Ok(())
@@ -855,7 +855,7 @@ impl Module {
                 EntityType::Tag(self.types[t.func_type_idx as usize])
             }
             TypeRef::Global(t) => {
-                self.check_global_type(t, features, offset)?;
+                self.check_global_type(t, features, types, offset)?;
                 EntityType::Global(*t)
             }
         })
@@ -1017,20 +1017,9 @@ impl Module {
     }
 
     fn check_heap_type(&self, ty: &mut HeapType, offset: usize) -> Result<()> {
-        // Check that the heap type is valid
+        // Check that the heap type is valid.
         let type_index = match ty {
-            HeapType::Func
-            | HeapType::Extern
-            | HeapType::Any
-            | HeapType::None
-            | HeapType::NoExtern
-            | HeapType::NoFunc
-            | HeapType::Eq
-            | HeapType::Struct
-            | HeapType::Array
-            | HeapType::I31
-            | HeapType::Exn
-            | HeapType::NoExn => return Ok(()),
+            HeapType::Abstract { .. } => return Ok(()),
             HeapType::Concrete(type_index) => type_index,
         };
         match type_index {
@@ -1073,6 +1062,7 @@ impl Module {
         &self,
         ty: &mut GlobalType,
         features: &WasmFeatures,
+        types: &TypeList,
         offset: usize,
     ) -> Result<()> {
         self.check_value_type(&mut ty.content_type, features, offset)?;
@@ -1083,7 +1073,7 @@ impl Module {
                     offset,
                 ));
             }
-            if !ty.content_type.is_shared() {
+            if !types.valtype_is_shared(ty.content_type) {
                 return Err(BinaryReaderError::new(
                     "shared globals must have a shared value type",
                     offset,
diff --git a/crates/wasmparser/src/validator/operators.rs b/crates/wasmparser/src/validator/operators.rs
index 775964ce..e13994d3 100644
--- a/crates/wasmparser/src/validator/operators.rs
+++ b/crates/wasmparser/src/validator/operators.rs
@@ -22,13 +22,13 @@
 // confusing it's recommended to read over that section to see how it maps to
 // the various methods here.
 
+use crate::prelude::*;
 use crate::{
-    limits::MAX_WASM_FUNCTION_LOCALS, ArrayType, BinaryReaderError, BlockType, BrTable, Catch,
-    CompositeType, FieldType, FuncType, HeapType, Ieee32, Ieee64, MemArg, RefType, Result,
-    StorageType, StructType, SubType, TableType, TryTable, UnpackedIndex, ValType, VisitOperator,
-    WasmFeatures, WasmModuleResources, V128,
+    limits::MAX_WASM_FUNCTION_LOCALS, AbstractHeapType, ArrayType, BinaryReaderError, BlockType,
+    BrTable, Catch, CompositeType, FieldType, FuncType, GlobalType, HeapType, Ieee32, Ieee64,
+    MemArg, RefType, Result, StorageType, StructType, SubType, TableType, TryTable, UnpackedIndex,
+    ValType, VisitOperator, WasmFeatures, WasmModuleResources, V128,
 };
-use crate::{prelude::*, GlobalType};
 use core::ops::{Deref, DerefMut};
 
 pub(crate) struct OperatorValidator {
@@ -3991,7 +3991,11 @@ where
         let is_nullable = extern_ref
             .as_type()
             .map_or(false, |ty| ty.as_reference_type().unwrap().is_nullable());
-        let any_ref = RefType::new(is_nullable, HeapType::Any).unwrap();
+        let heap_type = HeapType::Abstract {
+            shared: false, // TODO: handle shared--see https://github.com/WebAssembly/shared-everything-threads/issues/65.
+            ty: AbstractHeapType::Any,
+        };
+        let any_ref = RefType::new(is_nullable, heap_type).unwrap();
         self.push_operand(any_ref)
     }
     fn visit_extern_convert_any(&mut self) -> Self::Output {
@@ -3999,7 +4003,11 @@ where
         let is_nullable = any_ref
             .as_type()
             .map_or(false, |ty| ty.as_reference_type().unwrap().is_nullable());
-        let extern_ref = RefType::new(is_nullable, HeapType::Extern).unwrap();
+        let heap_type = HeapType::Abstract {
+            shared: false, // TODO: handle shared--see https://github.com/WebAssembly/shared-everything-threads/issues/65.
+            ty: AbstractHeapType::Extern,
+        };
+        let extern_ref = RefType::new(is_nullable, heap_type).unwrap();
         self.push_operand(extern_ref)
     }
     fn visit_ref_test_non_null(&mut self, heap_type: HeapType) -> Self::Output {
diff --git a/crates/wasmparser/src/validator/types.rs b/crates/wasmparser/src/validator/types.rs
index 8da034d7..6e4f452e 100644
--- a/crates/wasmparser/src/validator/types.rs
+++ b/crates/wasmparser/src/validator/types.rs
@@ -4,8 +4,8 @@ use super::{
     component::{ComponentState, ExternKind},
     core::Module,
 };
-use crate::collections::map::Entry;
 use crate::prelude::*;
+use crate::{collections::map::Entry, AbstractHeapType};
 use crate::{validator::names::KebabString, HeapType, ValidatorId};
 use crate::{
     BinaryReaderError, CompositeType, Export, ExternalKind, FuncType, GlobalType, Import, Matches,
@@ -2743,63 +2743,87 @@ impl TypeList {
             &self[id]
         };
 
+        use AbstractHeapType::*;
         use HeapType as HT;
         match (a.heap_type(), b.heap_type()) {
             (a, b) if a == b => true,
 
-            (HT::Eq | HT::I31 | HT::Struct | HT::Array | HT::None, HT::Any) => true,
-            (HT::I31 | HT::Struct | HT::Array | HT::None, HT::Eq) => true,
-            (HT::NoExtern, HT::Extern) => true,
-            (HT::NoFunc, HT::Func) => true,
-            (HT::None, HT::I31 | HT::Array | HT::Struct) => true,
-
-            (HT::Concrete(a), HT::Eq | HT::Any) => matches!(
-                subtype(a_group, a).composite_type,
-                CompositeType::Array(_) | CompositeType::Struct(_)
-            ),
-
-            (HT::Concrete(a), HT::Struct) => {
-                matches!(subtype(a_group, a).composite_type, CompositeType::Struct(_))
+            (
+                HT::Abstract {
+                    shared: a_shared,
+                    ty: a_ty,
+                },
+                HT::Abstract {
+                    shared: b_shared,
+                    ty: b_ty,
+                },
+            ) => {
+                a_shared == b_shared
+                    && match (a_ty, b_ty) {
+                        (Eq | I31 | Struct | Array | None, Any) => true,
+                        (I31 | Struct | Array | None, Eq) => true,
+                        (NoExtern, Extern) => true,
+                        (NoFunc, Func) => true,
+                        (None, I31 | Array | Struct) => true,
+                        (NoExn, Exn) => true,
+                        // Nothing else matches. (Avoid full wildcard matches so
+                        // that adding/modifying variants is easier in the
+                        // future.)
+                        (
+                            Func | Extern | Exn | Any | Eq | Array | I31 | Struct | None | NoFunc
+                            | NoExtern | NoExn,
+                            _,
+                        ) => false,
+                    }
             }
 
-            (HT::Concrete(a), HT::Array) => {
-                matches!(subtype(a_group, a).composite_type, CompositeType::Array(_))
+            (HT::Concrete(a), HT::Abstract { shared, ty }) => {
+                if shared {
+                    // TODO: handle shared
+                    todo!("check shared-ness of concrete type");
+                }
+                match ty {
+                    Any | Eq => matches!(
+                        subtype(a_group, a).composite_type,
+                        CompositeType::Array(_) | CompositeType::Struct(_)
+                    ),
+                    Struct => {
+                        matches!(subtype(a_group, a).composite_type, CompositeType::Struct(_))
+                    }
+                    Array => {
+                        matches!(subtype(a_group, a).composite_type, CompositeType::Array(_))
+                    }
+                    Func => {
+                        matches!(subtype(a_group, a).composite_type, CompositeType::Func(_))
+                    }
+                    // Nothing else matches. (Avoid full wildcard matches so
+                    // that adding/modifying variants is easier in the future.)
+                    Extern | Exn | I31 | None | NoFunc | NoExtern | NoExn => false,
+                }
             }
 
-            (HT::Concrete(a), HT::Func) => {
-                matches!(subtype(a_group, a).composite_type, CompositeType::Func(_))
+            (HT::Abstract { shared, ty }, HT::Concrete(b)) => {
+                if shared {
+                    // TODO: handle shared
+                    todo!("check shared-ness of concrete type");
+                }
+                match ty {
+                    None => matches!(
+                        subtype(b_group, b).composite_type,
+                        CompositeType::Array(_) | CompositeType::Struct(_)
+                    ),
+                    NoFunc => matches!(subtype(b_group, b).composite_type, CompositeType::Func(_)),
+                    // Nothing else matches. (Avoid full wildcard matches so
+                    // that adding/modifying variants is easier in the future.)
+                    Func | Extern | Exn | Any | Eq | Array | I31 | Struct | NoExtern | NoExn => {
+                        false
+                    }
+                }
             }
 
             (HT::Concrete(a), HT::Concrete(b)) => {
                 self.id_is_subtype(core_type_id(a_group, a), core_type_id(b_group, b))
             }
-
-            (HT::None, HT::Concrete(b)) => matches!(
-                subtype(b_group, b).composite_type,
-                CompositeType::Array(_) | CompositeType::Struct(_)
-            ),
-
-            (HT::NoFunc, HT::Concrete(b)) => {
-                matches!(subtype(b_group, b).composite_type, CompositeType::Func(_))
-            }
-
-            (HT::NoExn, HT::Exn) => true,
-
-            // Nothing else matches. (Avoid full wildcard matches so that
-            // adding/modifying variants is easier in the future.)
-            (HT::Concrete(_), _)
-            | (HT::Func, _)
-            | (HT::Extern, _)
-            | (HT::Any, _)
-            | (HT::None, _)
-            | (HT::NoExtern, _)
-            | (HT::NoFunc, _)
-            | (HT::Eq, _)
-            | (HT::Struct, _)
-            | (HT::Array, _)
-            | (HT::I31, _)
-            | (HT::Exn, _)
-            | (HT::NoExn, _) => false,
         }
     }
 
@@ -2819,25 +2843,49 @@ 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
+                    .is_shared(),
+            },
+        }
+    }
+
     /// Get the top type of the given heap type.
     ///
     /// Concrete types must have had their indices canonicalized to core type
     /// ids, otherwise this method will panic.
     pub fn top_type(&self, heap_type: &HeapType) -> HeapType {
+        use AbstractHeapType::*;
         match *heap_type {
             HeapType::Concrete(idx) => match self[idx.as_core_type_id().unwrap()].composite_type {
-                CompositeType::Func(_) => HeapType::Func,
-                CompositeType::Array(_) | CompositeType::Struct(_) => HeapType::Any,
+                CompositeType::Func(_) => HeapType::Abstract {
+                    shared: false, // TODO: handle shared--retrieve from `func` type.
+                    ty: Func,
+                },
+                CompositeType::Array(_) | CompositeType::Struct(_) => HeapType::Abstract {
+                    shared: false, // TODO: handle shared--retrieve from `array` or `struct` type.
+                    ty: Any,
+                },
             },
-            HeapType::Func | HeapType::NoFunc => HeapType::Func,
-            HeapType::Extern | HeapType::NoExtern => HeapType::Extern,
-            HeapType::Any
-            | HeapType::Eq
-            | HeapType::Struct
-            | HeapType::Array
-            | HeapType::I31
-            | HeapType::None => HeapType::Any,
-            HeapType::Exn | HeapType::NoExn => HeapType::Exn,
+            HeapType::Abstract { shared, ty } => {
+                let ty = match ty {
+                    Func | NoFunc => Func,
+                    Extern | NoExtern => Extern,
+                    Any | Eq | Struct | Array | I31 | None => Any,
+                    Exn | NoExn => Exn,
+                };
+                HeapType::Abstract { shared, ty }
+            }
         }
     }
 
diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs
index bf342e7a..5b86607d 100644
--- a/crates/wasmprinter/src/lib.rs
+++ b/crates/wasmprinter/src/lib.rs
@@ -1018,21 +1018,32 @@ impl Printer<'_, '_> {
 
     fn print_heaptype(&mut self, state: &State, ty: HeapType) -> Result<()> {
         match ty {
-            HeapType::Func => self.print_type_keyword("func")?,
-            HeapType::Extern => self.print_type_keyword("extern")?,
-            HeapType::Any => self.print_type_keyword("any")?,
-            HeapType::None => self.print_type_keyword("none")?,
-            HeapType::NoExtern => self.print_type_keyword("noextern")?,
-            HeapType::NoFunc => self.print_type_keyword("nofunc")?,
-            HeapType::Eq => self.print_type_keyword("eq")?,
-            HeapType::Struct => self.print_type_keyword("struct")?,
-            HeapType::Array => self.print_type_keyword("array")?,
-            HeapType::I31 => self.print_type_keyword("i31")?,
-            HeapType::Exn => self.print_type_keyword("exn")?,
-            HeapType::NoExn => self.print_type_keyword("noexn")?,
             HeapType::Concrete(i) => {
                 self.print_idx(&state.core.type_names, i.as_module_index().unwrap())?;
             }
+            HeapType::Abstract { shared, ty } => {
+                use AbstractHeapType::*;
+                if shared {
+                    self.start_group("shared ")?;
+                }
+                match ty {
+                    Func => self.print_type_keyword("func")?,
+                    Extern => self.print_type_keyword("extern")?,
+                    Any => self.print_type_keyword("any")?,
+                    None => self.print_type_keyword("none")?,
+                    NoExtern => self.print_type_keyword("noextern")?,
+                    NoFunc => self.print_type_keyword("nofunc")?,
+                    Eq => self.print_type_keyword("eq")?,
+                    Struct => self.print_type_keyword("struct")?,
+                    Array => self.print_type_keyword("array")?,
+                    I31 => self.print_type_keyword("i31")?,
+                    Exn => self.print_type_keyword("exn")?,
+                    NoExn => self.print_type_keyword("noexn")?,
+                }
+                if shared {
+                    self.end_group()?;
+                }
+            }
         }
         Ok(())
     }
diff --git a/crates/wast/Cargo.toml b/crates/wast/Cargo.toml
index 485c20b8..296e353f 100644
--- a/crates/wast/Cargo.toml
+++ b/crates/wast/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wast"
-version = "209.0.1"
+version = "210.0.0"
 authors = ["Alex Crichton <alex@alexcrichton.com>"]
 edition.workspace = true
 license = "Apache-2.0 WITH LLVM-exception"
diff --git a/crates/wast/src/component/binary.rs b/crates/wast/src/component/binary.rs
index 68864b0c..a55626e7 100644
--- a/crates/wast/src/component/binary.rs
+++ b/crates/wast/src/component/binary.rs
@@ -594,24 +594,27 @@ impl From<core::RefType<'_>> for wasm_encoder::RefType {
 
 impl From<core::HeapType<'_>> for wasm_encoder::HeapType {
     fn from(r: core::HeapType<'_>) -> Self {
+        use wasm_encoder::AbstractHeapType::*;
         match r {
-            core::HeapType::Func => Self::Func,
-            core::HeapType::Extern => Self::Extern,
-            core::HeapType::Exn | core::HeapType::NoExn => {
-                todo!("encoding of exceptions proposal types not yet implemented")
-            }
+            core::HeapType::Abstract { shared, ty } => match ty {
+                core::AbstractHeapType::Func => Self::Abstract { shared, ty: Func },
+                core::AbstractHeapType::Extern => Self::Abstract { shared, ty: Extern },
+                core::AbstractHeapType::Exn | core::AbstractHeapType::NoExn => {
+                    todo!("encoding of exceptions proposal types not yet implemented")
+                }
+                core::AbstractHeapType::Any
+                | core::AbstractHeapType::Eq
+                | core::AbstractHeapType::Struct
+                | core::AbstractHeapType::Array
+                | core::AbstractHeapType::NoFunc
+                | core::AbstractHeapType::NoExtern
+                | core::AbstractHeapType::None
+                | core::AbstractHeapType::I31 => {
+                    todo!("encoding of GC proposal types not yet implemented")
+                }
+            },
             core::HeapType::Concrete(Index::Num(i, _)) => Self::Concrete(i),
             core::HeapType::Concrete(_) => panic!("unresolved index"),
-            core::HeapType::Any
-            | core::HeapType::Eq
-            | core::HeapType::Struct
-            | core::HeapType::Array
-            | core::HeapType::NoFunc
-            | core::HeapType::NoExtern
-            | core::HeapType::None
-            | core::HeapType::I31 => {
-                todo!("encoding of GC proposal types not yet implemented")
-            }
         }
     }
 }
diff --git a/crates/wast/src/component/resolve.rs b/crates/wast/src/component/resolve.rs
index cd7fb7e3..ac072c5b 100644
--- a/crates/wast/src/component/resolve.rs
+++ b/crates/wast/src/component/resolve.rs
@@ -513,18 +513,7 @@ impl<'a> Resolver<'a> {
                 match &mut r.rep {
                     ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => {}
                     ValType::Ref(r) => match &mut r.heap {
-                        core::HeapType::Func
-                        | core::HeapType::Extern
-                        | core::HeapType::Exn
-                        | core::HeapType::Any
-                        | core::HeapType::Eq
-                        | core::HeapType::Array
-                        | core::HeapType::I31
-                        | core::HeapType::Struct
-                        | core::HeapType::None
-                        | core::HeapType::NoFunc
-                        | core::HeapType::NoExtern
-                        | core::HeapType::NoExn => {}
+                        core::HeapType::Abstract { .. } => {}
                         core::HeapType::Concrete(id) => {
                             self.resolve_ns(id, Ns::Type)?;
                         }
diff --git a/crates/wast/src/core/binary.rs b/crates/wast/src/core/binary.rs
index 84d128fa..8899c81d 100644
--- a/crates/wast/src/core/binary.rs
+++ b/crates/wast/src/core/binary.rs
@@ -285,18 +285,12 @@ impl<'a> Encode for ValType<'a> {
 impl<'a> Encode for HeapType<'a> {
     fn encode(&self, e: &mut Vec<u8>) {
         match self {
-            HeapType::Func => e.push(0x70),
-            HeapType::Extern => e.push(0x6f),
-            HeapType::Exn => e.push(0x69),
-            HeapType::Any => e.push(0x6e),
-            HeapType::Eq => e.push(0x6d),
-            HeapType::Struct => e.push(0x6b),
-            HeapType::Array => e.push(0x6a),
-            HeapType::I31 => e.push(0x6c),
-            HeapType::NoFunc => e.push(0x73),
-            HeapType::NoExtern => e.push(0x72),
-            HeapType::NoExn => e.push(0x74),
-            HeapType::None => e.push(0x71),
+            HeapType::Abstract { shared, ty } => {
+                if *shared {
+                    e.push(0x65);
+                }
+                ty.encode(e)
+            }
             // Note that this is encoded as a signed leb128 so be sure to cast
             // to an i64 first
             HeapType::Concrete(Index::Num(n, _)) => i64::from(*n).encode(e),
@@ -307,61 +301,42 @@ impl<'a> Encode for HeapType<'a> {
     }
 }
 
+impl<'a> Encode for AbstractHeapType {
+    fn encode(&self, e: &mut Vec<u8>) {
+        use AbstractHeapType::*;
+        match self {
+            Func => e.push(0x70),
+            Extern => e.push(0x6f),
+            Exn => e.push(0x69),
+            Any => e.push(0x6e),
+            Eq => e.push(0x6d),
+            Struct => e.push(0x6b),
+            Array => e.push(0x6a),
+            I31 => e.push(0x6c),
+            NoFunc => e.push(0x73),
+            NoExtern => e.push(0x72),
+            NoExn => e.push(0x74),
+            None => e.push(0x71),
+        }
+    }
+}
+
 impl<'a> Encode for RefType<'a> {
     fn encode(&self, e: &mut Vec<u8>) {
         match self {
-            // The 'funcref' binary abbreviation
-            RefType {
-                nullable: true,
-                heap: HeapType::Func,
-            } => e.push(0x70),
-            // The 'externref' binary abbreviation
-            RefType {
-                nullable: true,
-                heap: HeapType::Extern,
-            } => e.push(0x6f),
-            // The 'exnref' binary abbreviation
-            RefType {
-                nullable: true,
-                heap: HeapType::Exn,
-            } => e.push(0x69),
-            // The 'eqref' binary abbreviation
-            RefType {
-                nullable: true,
-                heap: HeapType::Eq,
-            } => e.push(0x6d),
-            // The 'structref' binary abbreviation
+            // Binary abbreviations (i.e., short form), for when the ref is
+            // nullable.
             RefType {
                 nullable: true,
-                heap: HeapType::Struct,
-            } => e.push(0x6b),
-            // The 'i31ref' binary abbreviation
-            RefType {
-                nullable: true,
-                heap: HeapType::I31,
-            } => e.push(0x6c),
-            // The 'nullfuncref' binary abbreviation
-            RefType {
-                nullable: true,
-                heap: HeapType::NoFunc,
-            } => e.push(0x73),
-            // The 'nullexternref' binary abbreviation
-            RefType {
-                nullable: true,
-                heap: HeapType::NoExtern,
-            } => e.push(0x72),
-            // The 'nullexnref' binary abbreviation
-            RefType {
-                nullable: true,
-                heap: HeapType::NoExn,
-            } => e.push(0x74),
-            // The 'nullref' binary abbreviation
-            RefType {
-                nullable: true,
-                heap: HeapType::None,
-            } => e.push(0x71),
+                heap: HeapType::Abstract { shared, ty },
+            } => {
+                if *shared {
+                    e.push(0x65);
+                }
+                ty.encode(e);
+            }
 
-            // Generic 'ref null <heaptype>' encoding
+            // Generic 'ref null <heaptype>' encoding (i.e., long form).
             RefType {
                 nullable: true,
                 heap,
@@ -369,7 +344,8 @@ impl<'a> Encode for RefType<'a> {
                 e.push(0x63);
                 heap.encode(e);
             }
-            // Generic 'ref <heaptype>' encoding
+
+            // Generic 'ref <heaptype>' encoding.
             RefType {
                 nullable: false,
                 heap,
@@ -606,7 +582,11 @@ impl Encode for Elem<'_> {
                     ty:
                         RefType {
                             nullable: true,
-                            heap: HeapType::Func,
+                            heap:
+                                HeapType::Abstract {
+                                    shared: false,
+                                    ty: AbstractHeapType::Func,
+                                },
                         },
                     ..
                 },
diff --git a/crates/wast/src/core/types.rs b/crates/wast/src/core/types.rs
index 266bf1d1..b149f33d 100644
--- a/crates/wast/src/core/types.rs
+++ b/crates/wast/src/core/types.rs
@@ -1,5 +1,6 @@
 use crate::core::*;
 use crate::kw;
+use crate::parser::Lookahead1;
 use crate::parser::{Cursor, Parse, Parser, Peek, Result};
 use crate::token::{Id, Index, LParen, NameAnnotation, Span};
 use crate::Error;
@@ -57,10 +58,56 @@ impl<'a> Peek for ValType<'a> {
     }
 }
 
-/// A heap type for a reference type
+/// A heap type for a reference type.
 #[allow(missing_docs)]
 #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
 pub enum HeapType<'a> {
+    /// An abstract reference. With the shared-everything-threads proposal,
+    /// these types can also be marked `shared`.
+    Abstract { shared: bool, ty: AbstractHeapType },
+    /// A reference to a concrete function, struct, or array type defined by
+    /// Wasm: `ref T`. This is part of the function references and GC proposals.
+    Concrete(Index<'a>),
+}
+
+impl<'a> Parse<'a> for HeapType<'a> {
+    fn parse(parser: Parser<'a>) -> Result<Self> {
+        let mut l = parser.lookahead1();
+        if l.peek::<Index>()? {
+            Ok(HeapType::Concrete(parser.parse()?))
+        } else if l.peek::<LParen>()? {
+            parser.parens(|p| {
+                p.parse::<kw::shared>()?;
+                Ok(HeapType::Abstract {
+                    shared: true,
+                    ty: p.parse()?,
+                })
+            })
+        } else if l.peek::<AbstractHeapType>()? {
+            Ok(HeapType::Abstract {
+                shared: false,
+                ty: parser.parse()?,
+            })
+        } else {
+            Err(l.error())
+        }
+    }
+}
+
+impl<'a> Peek for HeapType<'a> {
+    fn peek(cursor: Cursor<'_>) -> Result<bool> {
+        Ok(AbstractHeapType::peek(cursor)?
+            || (LParen::peek(cursor)? && kw::shared::peek2(cursor)?)
+            || (LParen::peek(cursor)? && kw::r#type::peek2(cursor)?))
+    }
+    fn display() -> &'static str {
+        "heaptype"
+    }
+}
+
+/// An abstract heap type.
+#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
+pub enum AbstractHeapType {
     /// An untyped function reference: funcref. This is part of the reference
     /// types proposal.
     Func,
@@ -89,59 +136,54 @@ pub enum HeapType<'a> {
     None,
     /// The bottom type of the exnref hierarchy. Part of the exceptions proposal.
     NoExn,
-    /// A reference to a concrete function, struct, or array type defined by
-    /// Wasm: `ref T`. This is part of the function references and GC proposals.
-    Concrete(Index<'a>),
 }
 
-impl<'a> Parse<'a> for HeapType<'a> {
+impl<'a> Parse<'a> for AbstractHeapType {
     fn parse(parser: Parser<'a>) -> Result<Self> {
         let mut l = parser.lookahead1();
         if l.peek::<kw::func>()? {
             parser.parse::<kw::func>()?;
-            Ok(HeapType::Func)
+            Ok(AbstractHeapType::Func)
         } else if l.peek::<kw::r#extern>()? {
             parser.parse::<kw::r#extern>()?;
-            Ok(HeapType::Extern)
+            Ok(AbstractHeapType::Extern)
         } else if l.peek::<kw::exn>()? {
             parser.parse::<kw::exn>()?;
-            Ok(HeapType::Exn)
+            Ok(AbstractHeapType::Exn)
         } else if l.peek::<kw::r#any>()? {
             parser.parse::<kw::r#any>()?;
-            Ok(HeapType::Any)
+            Ok(AbstractHeapType::Any)
         } else if l.peek::<kw::eq>()? {
             parser.parse::<kw::eq>()?;
-            Ok(HeapType::Eq)
+            Ok(AbstractHeapType::Eq)
         } else if l.peek::<kw::r#struct>()? {
             parser.parse::<kw::r#struct>()?;
-            Ok(HeapType::Struct)
+            Ok(AbstractHeapType::Struct)
         } else if l.peek::<kw::array>()? {
             parser.parse::<kw::array>()?;
-            Ok(HeapType::Array)
+            Ok(AbstractHeapType::Array)
         } else if l.peek::<kw::i31>()? {
             parser.parse::<kw::i31>()?;
-            Ok(HeapType::I31)
+            Ok(AbstractHeapType::I31)
         } else if l.peek::<kw::nofunc>()? {
             parser.parse::<kw::nofunc>()?;
-            Ok(HeapType::NoFunc)
+            Ok(AbstractHeapType::NoFunc)
         } else if l.peek::<kw::noextern>()? {
             parser.parse::<kw::noextern>()?;
-            Ok(HeapType::NoExtern)
+            Ok(AbstractHeapType::NoExtern)
         } else if l.peek::<kw::noexn>()? {
             parser.parse::<kw::noexn>()?;
-            Ok(HeapType::NoExn)
+            Ok(AbstractHeapType::NoExn)
         } else if l.peek::<kw::none>()? {
             parser.parse::<kw::none>()?;
-            Ok(HeapType::None)
-        } else if l.peek::<Index>()? {
-            Ok(HeapType::Concrete(parser.parse()?))
+            Ok(AbstractHeapType::None)
         } else {
             Err(l.error())
         }
     }
 }
 
-impl<'a> Peek for HeapType<'a> {
+impl<'a> Peek for AbstractHeapType {
     fn peek(cursor: Cursor<'_>) -> Result<bool> {
         Ok(kw::func::peek(cursor)?
             || kw::r#extern::peek(cursor)?
@@ -154,11 +196,10 @@ impl<'a> Peek for HeapType<'a> {
             || kw::nofunc::peek(cursor)?
             || kw::noextern::peek(cursor)?
             || kw::noexn::peek(cursor)?
-            || kw::none::peek(cursor)?
-            || (LParen::peek(cursor)? && kw::r#type::peek2(cursor)?))
+            || kw::none::peek(cursor)?)
     }
     fn display() -> &'static str {
-        "heaptype"
+        "absheaptype"
     }
 }
 
@@ -175,7 +216,10 @@ impl<'a> RefType<'a> {
     pub fn func() -> Self {
         RefType {
             nullable: true,
-            heap: HeapType::Func,
+            heap: HeapType::Abstract {
+                shared: false,
+                ty: AbstractHeapType::Func,
+            },
         }
     }
 
@@ -183,7 +227,10 @@ impl<'a> RefType<'a> {
     pub fn r#extern() -> Self {
         RefType {
             nullable: true,
-            heap: HeapType::Extern,
+            heap: HeapType::Abstract {
+                shared: false,
+                ty: AbstractHeapType::Extern,
+            },
         }
     }
 
@@ -191,7 +238,10 @@ impl<'a> RefType<'a> {
     pub fn exn() -> Self {
         RefType {
             nullable: true,
-            heap: HeapType::Exn,
+            heap: HeapType::Abstract {
+                shared: false,
+                ty: AbstractHeapType::Exn,
+            },
         }
     }
 
@@ -199,7 +249,10 @@ impl<'a> RefType<'a> {
     pub fn any() -> Self {
         RefType {
             nullable: true,
-            heap: HeapType::Any,
+            heap: HeapType::Abstract {
+                shared: false,
+                ty: AbstractHeapType::Any,
+            },
         }
     }
 
@@ -207,7 +260,10 @@ impl<'a> RefType<'a> {
     pub fn eq() -> Self {
         RefType {
             nullable: true,
-            heap: HeapType::Eq,
+            heap: HeapType::Abstract {
+                shared: false,
+                ty: AbstractHeapType::Eq,
+            },
         }
     }
 
@@ -215,7 +271,10 @@ impl<'a> RefType<'a> {
     pub fn r#struct() -> Self {
         RefType {
             nullable: true,
-            heap: HeapType::Struct,
+            heap: HeapType::Abstract {
+                shared: false,
+                ty: AbstractHeapType::Struct,
+            },
         }
     }
 
@@ -223,7 +282,10 @@ impl<'a> RefType<'a> {
     pub fn array() -> Self {
         RefType {
             nullable: true,
-            heap: HeapType::Array,
+            heap: HeapType::Abstract {
+                shared: false,
+                ty: AbstractHeapType::Array,
+            },
         }
     }
 
@@ -231,7 +293,10 @@ impl<'a> RefType<'a> {
     pub fn i31() -> Self {
         RefType {
             nullable: true,
-            heap: HeapType::I31,
+            heap: HeapType::Abstract {
+                shared: false,
+                ty: AbstractHeapType::I31,
+            },
         }
     }
 
@@ -239,7 +304,10 @@ impl<'a> RefType<'a> {
     pub fn nullfuncref() -> Self {
         RefType {
             nullable: true,
-            heap: HeapType::NoFunc,
+            heap: HeapType::Abstract {
+                shared: false,
+                ty: AbstractHeapType::NoFunc,
+            },
         }
     }
 
@@ -247,7 +315,10 @@ impl<'a> RefType<'a> {
     pub fn nullexternref() -> Self {
         RefType {
             nullable: true,
-            heap: HeapType::NoExtern,
+            heap: HeapType::Abstract {
+                shared: false,
+                ty: AbstractHeapType::NoExtern,
+            },
         }
     }
 
@@ -255,7 +326,10 @@ impl<'a> RefType<'a> {
     pub fn nullref() -> Self {
         RefType {
             nullable: true,
-            heap: HeapType::None,
+            heap: HeapType::Abstract {
+                shared: false,
+                ty: AbstractHeapType::None,
+            },
         }
     }
 
@@ -263,14 +337,46 @@ impl<'a> RefType<'a> {
     pub fn nullexnref() -> Self {
         RefType {
             nullable: true,
-            heap: HeapType::NoExn,
+            heap: HeapType::Abstract {
+                shared: false,
+                ty: AbstractHeapType::NoExn,
+            },
         }
     }
-}
 
-impl<'a> Parse<'a> for RefType<'a> {
-    fn parse(parser: Parser<'a>) -> Result<Self> {
-        let mut l = parser.lookahead1();
+    /// Make the reference type a `shared` one.
+    ///
+    /// Note that this is not possible for concrete references (e.g., `(ref
+    /// $t)`) so `None` is returned in that case.
+    pub fn shared(self) -> Option<Self> {
+        match self.heap {
+            HeapType::Abstract { ty, .. } => Some(RefType {
+                nullable: self.nullable,
+                heap: HeapType::Abstract { shared: true, ty },
+            }),
+            _ => None,
+        }
+    }
+
+    /// Helper for checking if shorthand forms of reference types can be parsed
+    /// next; e.g., `funcref`.
+    fn peek_shorthand(l: &mut Lookahead1) -> Result<bool> {
+        Ok(l.peek::<kw::funcref>()?
+            || l.peek::<kw::externref>()?
+            || l.peek::<kw::exnref>()?
+            || l.peek::<kw::anyref>()?
+            || l.peek::<kw::eqref>()?
+            || l.peek::<kw::structref>()?
+            || l.peek::<kw::arrayref>()?
+            || l.peek::<kw::i31ref>()?
+            || l.peek::<kw::nullfuncref>()?
+            || l.peek::<kw::nullexternref>()?
+            || l.peek::<kw::nullexnref>()?
+            || l.peek::<kw::nullref>()?)
+    }
+
+    /// Helper for parsing shorthand forms of reference types; e.g., `funcref`.
+    fn parse_shorthand(mut l: Lookahead1, parser: Parser<'a>) -> Result<Self> {
         if l.peek::<kw::funcref>()? {
             parser.parse::<kw::funcref>()?;
             Ok(RefType::func())
@@ -307,10 +413,23 @@ impl<'a> Parse<'a> for RefType<'a> {
         } else if l.peek::<kw::nullref>()? {
             parser.parse::<kw::nullref>()?;
             Ok(RefType::nullref())
+        } else {
+            Err(l.error())
+        }
+    }
+}
+
+impl<'a> Parse<'a> for RefType<'a> {
+    fn parse(parser: Parser<'a>) -> Result<Self> {
+        let mut l = parser.lookahead1();
+        if RefType::peek_shorthand(&mut l)? {
+            // I.e., `*ref`.
+            RefType::parse_shorthand(l, parser)
         } else if l.peek::<LParen>()? {
             parser.parens(|p| {
                 let mut l = parser.lookahead1();
                 if l.peek::<kw::r#ref>()? {
+                    // I.e., `(ref null? ...)`.
                     p.parse::<kw::r#ref>()?;
 
                     let mut nullable = false;
@@ -323,6 +442,11 @@ impl<'a> Parse<'a> for RefType<'a> {
                         nullable,
                         heap: parser.parse()?,
                     })
+                } else if l.peek::<kw::shared>()? {
+                    // I.e., `(shared *ref)`.
+                    p.parse::<kw::shared>()?;
+                    let reftype = RefType::parse_shorthand(l, parser)?;
+                    Ok(reftype.shared().expect("only abstract heap types are used"))
                 } else {
                     Err(l.error())
                 }
@@ -347,6 +471,7 @@ impl<'a> Peek for RefType<'a> {
             || kw::nullexternref::peek(cursor)?
             || kw::nullexnref::peek(cursor)?
             || kw::nullref::peek(cursor)?
+            || (LParen::peek(cursor)? && kw::shared::peek2(cursor)?)
             || (LParen::peek(cursor)? && kw::r#ref::peek2(cursor)?))
     }
     fn display() -> &'static str {
diff --git a/crates/wat/Cargo.toml b/crates/wat/Cargo.toml
index ade0e261..078a8ec3 100644
--- a/crates/wat/Cargo.toml
+++ b/crates/wat/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wat"
-version = "1.209.1"
+version = "1.210.0"
 authors = ["Alex Crichton <alex@alexcrichton.com>"]
 edition.workspace = true
 license = "Apache-2.0 WITH LLVM-exception"
diff --git a/crates/wit-component/Cargo.toml b/crates/wit-component/Cargo.toml
index 8bd969c4..c05f13e6 100644
--- a/crates/wit-component/Cargo.toml
+++ b/crates/wit-component/Cargo.toml
@@ -18,7 +18,7 @@ workspace = true
 
 [dependencies]
 wasmparser = { workspace = true }
-wasm-encoder = { workspace = true }
+wasm-encoder = { workspace = true, features = ["wasmparser"] }
 wasm-metadata = { workspace = true }
 wit-parser = { workspace = true, features = ['decoding', 'serde'] }
 anyhow = { workspace = true }
diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs
index 057e755c..56eecf06 100644
--- a/crates/wit-component/src/encoding.rs
+++ b/crates/wit-component/src/encoding.rs
@@ -2129,39 +2129,32 @@ impl ComponentEncoder {
 
 #[cfg(all(test, feature = "dummy-module"))]
 mod test {
-    use crate::{dummy_module, embed_component_metadata};
-
     use super::*;
-    use std::path::Path;
-    use wit_parser::UnresolvedPackageGroup;
+    use crate::{dummy_module, embed_component_metadata};
 
     #[test]
     fn it_renames_imports() {
         let mut resolve = Resolve::new();
-        let UnresolvedPackageGroup {
-            mut packages,
-            source_map,
-        } = UnresolvedPackageGroup::parse(
-            Path::new("test.wit"),
-            r#"
+        let pkgs = resolve
+            .push_str(
+                "test.wit",
+                r#"
 package test:wit;
 
 interface i {
-f: func();
+    f: func();
 }
 
 world test {
-import i;
-import foo: interface {
-f: func();
-}
+    import i;
+    import foo: interface {
+        f: func();
+    }
 }
 "#,
-        )
-        .unwrap();
-        let pkg = resolve.push(packages.remove(0), &source_map).unwrap();
-
-        let world = resolve.select_world(pkg, None).unwrap();
+            )
+            .unwrap();
+        let world = resolve.select_world(&pkgs, None).unwrap();
 
         let mut module = dummy_module(&resolve, world);
 
diff --git a/crates/wit-component/src/gc.rs b/crates/wit-component/src/gc.rs
index 6f0a5b61..bb857f94 100644
--- a/crates/wit-component/src/gc.rs
+++ b/crates/wit-component/src/gc.rs
@@ -486,18 +486,7 @@ impl<'a> Module<'a> {
 
     fn heapty(&mut self, ty: HeapType) {
         match ty {
-            HeapType::Func
-            | HeapType::Extern
-            | HeapType::Any
-            | HeapType::None
-            | HeapType::NoExtern
-            | HeapType::NoFunc
-            | HeapType::Eq
-            | HeapType::Struct
-            | HeapType::Array
-            | HeapType::I31
-            | HeapType::Exn
-            | HeapType::NoExn => {}
+            HeapType::Abstract { .. } => {}
             HeapType::Concrete(i) => self.ty(i.as_module_index().unwrap()),
         }
     }
@@ -1132,21 +1121,13 @@ impl Encoder {
 
     fn heapty(&self, ht: wasmparser::HeapType) -> wasm_encoder::HeapType {
         match ht {
-            HeapType::Func => wasm_encoder::HeapType::Func,
-            HeapType::Extern => wasm_encoder::HeapType::Extern,
-            HeapType::Any => wasm_encoder::HeapType::Any,
-            HeapType::None => wasm_encoder::HeapType::None,
-            HeapType::NoExtern => wasm_encoder::HeapType::NoExtern,
-            HeapType::NoFunc => wasm_encoder::HeapType::NoFunc,
-            HeapType::Eq => wasm_encoder::HeapType::Eq,
-            HeapType::Struct => wasm_encoder::HeapType::Struct,
-            HeapType::Array => wasm_encoder::HeapType::Array,
-            HeapType::I31 => wasm_encoder::HeapType::I31,
-            HeapType::Exn => wasm_encoder::HeapType::Exn,
-            HeapType::NoExn => wasm_encoder::HeapType::NoExn,
             HeapType::Concrete(idx) => {
                 wasm_encoder::HeapType::Concrete(self.types.remap(idx.as_module_index().unwrap()))
             }
+            HeapType::Abstract { shared, ty } => {
+                let ty = ty.into();
+                wasm_encoder::HeapType::Abstract { shared, ty }
+            }
         }
     }
 }
diff --git a/crates/wit-component/src/lib.rs b/crates/wit-component/src/lib.rs
index 7d81335e..ba175aa8 100644
--- a/crates/wit-component/src/lib.rs
+++ b/crates/wit-component/src/lib.rs
@@ -5,9 +5,9 @@
 use std::str::FromStr;
 use std::{borrow::Cow, fmt::Display};
 
-use anyhow::{bail, Context, Result};
+use anyhow::{bail, Result};
 use wasm_encoder::{CanonicalOption, Encode, Section};
-use wit_parser::{parse_use_path, PackageId, ParsedUsePath, Resolve, WorldId};
+use wit_parser::{Resolve, WorldId};
 
 mod encoding;
 mod gc;
@@ -79,43 +79,6 @@ impl From<StringEncoding> for wasm_encoder::CanonicalOption {
     }
 }
 
-/// Handles world name resolution for cases when multiple packages may have been resolved. If this
-/// is the case, and we're dealing with input that contains a user-supplied world name (like via a
-/// CLI command, for instance), we want to ensure that the world name follows the following rules:
-///
-///   * If there is a single resolved package with a single world, the world name name MAY be
-///     omitted.
-///   * If there is a single resolved package with multiple worlds, the world name MUST be supplied,
-///     but MAY or MAY NOT be fully-qualified.
-///   * If there are multiple resolved packages, the world name MUST be fully-qualified.
-pub fn resolve_world_from_name(
-    resolve: &Resolve,
-    resolved_packages: Vec<PackageId>,
-    world_name: Option<&str>,
-) -> Result<WorldId> {
-    match resolved_packages.len() {
-        0 => bail!("all of the supplied WIT source files were empty"),
-        1 => resolve.select_world(resolved_packages[0], world_name.as_deref()),
-        _ => match world_name.as_deref() {
-            Some(name) => {
-                let world_path = parse_use_path(name).with_context(|| {
-                    format!("failed to parse world specifier `{name}`")
-                })?;
-                match world_path {
-                        ParsedUsePath::Name(name) => bail!("the world specifier must be of the fully-qualified, id-based form (ex: \"wasi:http/proxy\" rather than \"proxy\"); you used {name}"),
-                        ParsedUsePath::Package(pkg_name, _) => {
-                            match resolve.package_names.get(&pkg_name) {
-                                Some(pkg_id) => resolve.select_world(pkg_id.clone(), world_name.as_deref()),
-                                None => bail!("the world specifier you provided named {pkg_name}, but no package with that name was found"),
-                            }
-                        }
-                    }
-            }
-            None => bail!("the supplied WIT source files describe multiple packages; please provide a fully-qualified world-specifier to the `embed` command"),
-        },
-    }
-}
-
 /// A producer section to be added to all modules and components synthesized by
 /// this crate
 pub(crate) fn base_producers() -> wasm_metadata::Producers {
@@ -145,11 +108,9 @@ pub fn embed_component_metadata(
 
 #[cfg(test)]
 mod tests {
-    use std::path::Path;
-
     use anyhow::Result;
     use wasmparser::Payload;
-    use wit_parser::{Resolve, UnresolvedPackageGroup};
+    use wit_parser::Resolve;
 
     use super::{embed_component_metadata, StringEncoding};
 
@@ -184,12 +145,8 @@ world test-world {}
 
         // Parse pre-canned WIT to build resolver
         let mut resolver = Resolve::default();
-        let UnresolvedPackageGroup {
-            mut packages,
-            source_map,
-        } = UnresolvedPackageGroup::parse(&Path::new("in-code.wit"), COMPONENT_WIT)?;
-        let pkg_id = resolver.push(packages.remove(0), &source_map)?;
-        let world = resolver.select_world(pkg_id, Some("test-world").into())?;
+        let pkgs = resolver.push_str("in-code.wit", COMPONENT_WIT)?;
+        let world = resolver.select_world(&pkgs, Some("test-world"))?;
 
         // Embed component metadata
         embed_component_metadata(&mut bytes, &resolver, world, StringEncoding::UTF8)?;
diff --git a/crates/wit-component/src/linking.rs b/crates/wit-component/src/linking.rs
index d47aca56..e3923c1e 100644
--- a/crates/wit-component/src/linking.rs
+++ b/crates/wit-component/src/linking.rs
@@ -35,9 +35,9 @@ use {
     },
     wasm_encoder::{
         CodeSection, ConstExpr, DataSection, ElementSection, Elements, EntityType, ExportKind,
-        ExportSection, Function, FunctionSection, GlobalSection, HeapType, ImportSection,
-        Instruction as Ins, MemArg, MemorySection, MemoryType, Module, RawCustomSection, RefType,
-        StartSection, TableSection, TableType, TypeSection, ValType,
+        ExportSection, Function, FunctionSection, GlobalSection, ImportSection, Instruction as Ins,
+        MemArg, MemorySection, MemoryType, Module, RawCustomSection, RefType, StartSection,
+        TableSection, TableType, TypeSection, ValType,
     },
     wasmparser::SymbolFlags,
 };
@@ -464,10 +464,7 @@ fn make_env_module<'a>(
     {
         let mut tables = TableSection::new();
         tables.table(TableType {
-            element_type: RefType {
-                nullable: true,
-                heap_type: HeapType::Func,
-            },
+            element_type: RefType::FUNCREF,
             minimum: table_offset.into(),
             maximum: None,
             table64: false,
@@ -559,10 +556,7 @@ fn make_init_module(
         "env",
         "__indirect_function_table",
         TableType {
-            element_type: RefType {
-                nullable: true,
-                heap_type: HeapType::Func,
-            },
+            element_type: RefType::FUNCREF,
             minimum: 0,
             maximum: None,
             table64: false,
diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs
index 59cb7353..f3e43614 100644
--- a/crates/wit-component/src/metadata.rs
+++ b/crates/wit-component/src/metadata.rs
@@ -42,7 +42,7 @@
 //! the three arguments originally passed to `encode`.
 
 use crate::validation::BARE_FUNC_MODULE_NAME;
-use crate::{resolve_world_from_name, DecodedWasm, StringEncoding};
+use crate::{DecodedWasm, StringEncoding};
 use anyhow::{bail, Context, Result};
 use indexmap::IndexMap;
 use std::borrow::Cow;
@@ -264,7 +264,7 @@ impl Bindgen {
                     DecodedWasm::Component(..) => bail!("expected encoded wit package(s)"),
                 };
                 resolve = r;
-                world = resolve_world_from_name(&resolve, pkgs, Some(world_name.into()))?;
+                world = resolve.select_world(&pkgs, Some(world_name.into()))?;
             }
 
             // Current format where `data` is a wasm component itself.
diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs
index 72e0857b..2ab630cc 100644
--- a/crates/wit-parser/src/lib.rs
+++ b/crates/wit-parser/src/lib.rs
@@ -228,10 +228,11 @@ impl UnresolvedPackageGroup {
     /// Parses the given string as a wit document.
     ///
     /// The `path` argument is used for error reporting. The `contents` provided
-    /// will not be able to use `pkg` use paths to other documents.
-    pub fn parse(path: &Path, contents: &str) -> Result<UnresolvedPackageGroup> {
+    /// are considered to be the contents of `path`. This function does not read
+    /// the filesystem.
+    pub fn parse(path: impl AsRef<Path>, contents: &str) -> Result<UnresolvedPackageGroup> {
         let mut map = SourceMap::default();
-        map.push(path, contents);
+        map.push(path.as_ref(), contents);
         map.parse()
     }
 
@@ -240,7 +241,8 @@ impl UnresolvedPackageGroup {
     /// The path provided is inferred whether it's a file or a directory. A file
     /// is parsed with [`UnresolvedPackageGroup::parse_file`] and a directory is
     /// parsed with [`UnresolvedPackageGroup::parse_dir`].
-    pub fn parse_path(path: &Path) -> Result<UnresolvedPackageGroup> {
+    pub fn parse_path(path: impl AsRef<Path>) -> Result<UnresolvedPackageGroup> {
+        let path = path.as_ref();
         if path.is_dir() {
             UnresolvedPackageGroup::parse_dir(path)
         } else {
@@ -250,9 +252,10 @@ impl UnresolvedPackageGroup {
 
     /// Parses a WIT package from the file provided.
     ///
-    /// The WIT package returned will be a single-document package and will not
-    /// be able to use `pkg` paths to other documents.
-    pub fn parse_file(path: &Path) -> Result<UnresolvedPackageGroup> {
+    /// The return value represents all packages found in the WIT file which
+    /// might be either one or multiple depending on the syntax used.
+    pub fn parse_file(path: impl AsRef<Path>) -> Result<UnresolvedPackageGroup> {
+        let path = path.as_ref();
         let contents = std::fs::read_to_string(path)
             .with_context(|| format!("failed to read file {path:?}"))?;
         Self::parse(path, &contents)
@@ -260,9 +263,12 @@ impl UnresolvedPackageGroup {
 
     /// Parses a WIT package from the directory provided.
     ///
-    /// All files with the extension `*.wit` or `*.wit.md` will be loaded from
-    /// `path` into the returned package.
-    pub fn parse_dir(path: &Path) -> Result<UnresolvedPackageGroup> {
+    /// This method will look at all files under the `path` specified. All
+    /// `*.wit` files are parsed and assumed to be part of the same package
+    /// grouping. This is useful when a WIT package is split across multiple
+    /// files.
+    pub fn parse_dir(path: impl AsRef<Path>) -> Result<UnresolvedPackageGroup> {
+        let path = path.as_ref();
         let mut map = SourceMap::default();
         let cx = || format!("failed to read directory {path:?}");
         for entry in path.read_dir().with_context(&cx)? {
@@ -281,7 +287,7 @@ impl UnresolvedPackageGroup {
                 Some(name) => name,
                 None => continue,
             };
-            if !filename.ends_with(".wit") && !filename.ends_with(".wit.md") {
+            if !filename.ends_with(".wit") {
                 continue;
             }
             map.push_file(&path)?;
diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs
index a41290a6..c5a2f237 100644
--- a/crates/wit-parser/src/resolve.rs
+++ b/crates/wit-parser/src/resolve.rs
@@ -154,23 +154,35 @@ impl Resolve {
         Resolve::default()
     }
 
-    /// Parse a WIT package from the input `path`.
+    /// Parse WIT packages from the input `path`.
     ///
     /// The input `path` can be one of:
     ///
     /// * A directory containing a WIT package with an optional `deps` directory
     ///   for any dependent WIT packages it references.
-    /// * A single standalone WIT file depending on what's already in `Resolve`.
+    /// * A single standalone WIT file.
     /// * A wasm-encoded WIT package as a single file in the wasm binary format.
     /// * A wasm-encoded WIT package as a single file in the wasm text format.
     ///
-    /// The `PackageId` of the parsed package is returned. For more information
-    /// see [`Resolve::push_dir`] and [`Resolve::push_file`]. This method will
-    /// automatically call the appropriate method based on what kind of
-    /// filesystem entry `path` is.
+    /// In all of these cases packages are allowed to depend on previously
+    /// inserted packages into this `Resolve`. Resolution for packages is based
+    /// on the name of each package and reference.
     ///
-    /// Returns the top-level [`PackageId`] as well as a list of all files read
-    /// during this parse.
+    /// This method returns a list of `PackageId` elements and additionally a
+    /// list of `PathBuf` elements. The `PackageId` elements represent the "main
+    /// package" that was parsed. For example if a single WIT file was specified
+    /// this will be all the packages found in the file. For a directory this
+    /// will be all the packages in the directory itself, but not in the `deps`
+    /// directory. The list of `PackageId` values is useful to pass to
+    /// [`Resolve::select_world`] to take a user-specified world in a
+    /// conventional fashion and select which to use for bindings generation.
+    ///
+    /// The returned list of `PathBuf` elements represents all files parsed
+    /// during this operation. This can be useful for systems that want to
+    /// rebuild or regenerate bindings based on files modified.
+    ///
+    /// More information can also be found at [`Resolve::push_dir`] and
+    /// [`Resolve::push_file`].
     pub fn push_path(&mut self, path: impl AsRef<Path>) -> Result<(Vec<PackageId>, Vec<PathBuf>)> {
         self._push_path(path.as_ref())
     }
@@ -247,25 +259,42 @@ impl Resolve {
     }
 
     /// Parses the filesystem directory at `path` as a WIT package and returns
-    /// the fully resolved [`PackageId`] as a result.
+    /// a fully resolved [`PackageId`] list as a result.
     ///
     /// The directory itself is parsed with [`UnresolvedPackageGroup::parse_dir`]
-    /// which has more information on the layout of the directory. This method,
-    /// however, additionally supports an optional `deps` dir where dependencies
-    /// can be located.
-    ///
-    /// All entries in the `deps` directory are inspected and parsed as follows:
-    ///
-    /// * Any directories inside of `deps` are assumed to be another WIT package
-    ///   and are parsed with [`UnresolvedPackageGroup::parse_dir`].
-    /// * WIT files (`*.wit`) are parsed with [`UnresolvedPackageGroup::parse_file`].
-    /// * WebAssembly files (`*.wasm` or `*.wat`) are assumed to be WIT packages
-    ///   encoded to wasm and are parsed and inserted into `self`.
-    ///
-    /// This function returns the [`PackageId`]s of the root parsed packages at
-    /// `path`, along with a list of all paths that were consumed during parsing
-    /// for the root package and all dependency packages, for each package encountered.
-    pub fn push_dir(&mut self, path: &Path) -> Result<(Vec<PackageId>, Vec<PathBuf>)> {
+    /// and then all packages found are inserted into this `Resolve`. The `path`
+    /// specified may have a `deps` subdirectory which is probed automatically
+    /// for any other WIT dependencies.
+    ///
+    /// The `deps` folder may contain:
+    ///
+    /// * `$path/deps/my-package/*.wit` - a directory that may contain multiple
+    ///   WIT files. This is parsed with [`UnresolvedPackageGroup::parse_dir`]
+    ///   and then inserted into this [`Resolve`]. Note that cannot recursively
+    ///   contain a `deps` directory.
+    /// * `$path/deps/my-package.wit` - a single-file WIT package. This is
+    ///   parsed with [`Resolve::push_file`] and then added to `self` for
+    ///   name reoslution.
+    /// * `$path/deps/my-package.{wasm,wat}` - a wasm-encoded WIT package either
+    ///   in the text for binary format.
+    ///
+    /// In all cases entries in the `deps` folder are added to `self` first
+    /// before adding files found in `path` itself. All WIT packages found are
+    /// candidates for name-based resolution that other packages may used.
+    ///
+    /// This function returns a tuple of two values. The first value is a list
+    /// of [`PackageId`] values which represents the WIT packages found within
+    /// `path`, but not those within `deps`. The `path` provided may contain
+    /// only a single WIT package but might also use the multi-package form of
+    /// WIT, and the returned list will indicate which was used. This argument
+    /// is useful for passing to [`Resolve::select_world`] for choosing
+    /// something to bindgen with.
+    ///
+    /// The second value returned here is the list of paths that were parsed
+    /// when generating the return value. This can be useful for build systems
+    /// that want to rebuild bindings whenever one of the files change.
+    pub fn push_dir(&mut self, path: impl AsRef<Path>) -> Result<(Vec<PackageId>, Vec<PathBuf>)> {
+        let path = path.as_ref();
         let deps_path = path.join("deps");
         let unresolved_deps = self.parse_deps_dir(&deps_path).with_context(|| {
             format!(
@@ -342,13 +371,14 @@ impl Resolve {
     ///   In this the package and all of its dependencies are automatically
     ///   inserted into `self`.
     ///
-    /// In both situations the `PackageId` of the resulting resolved package is
-    /// returned from this method.
+    /// In both situations the `PackageId`s of the resulting resolved packages
+    /// are returned from this method. The return value is mostly useful in
+    /// conjunction with [`Resolve::select_world`].
     pub fn push_file(&mut self, path: impl AsRef<Path>) -> Result<Vec<PackageId>> {
         match self._push_file(path.as_ref())? {
             #[cfg(feature = "decoding")]
             ParsedFile::Package(id) => Ok(vec![id]),
-            ParsedFile::Unresolved(pkgs) => self.append(pkgs),
+            ParsedFile::Unresolved(pkgs) => self.push_group(pkgs),
         }
     }
 
@@ -413,22 +443,32 @@ impl Resolve {
         source_map.rewrite_error(|| Remap::default().append(self, unresolved))
     }
 
-    /// Appends new [`UnresolvedPackageSet`] to this [`Resolve`], creating a
+    /// Appends new [`UnresolvedPackageGroup`] to this [`Resolve`], creating a
     /// fully resolved package with no dangling references.
     ///
-    /// The `deps` argument indicates that the named dependencies in
-    /// `unresolved` to packages are resolved by the mapping specified.
-    ///
     /// Any dependency resolution error or otherwise world-elaboration error
     /// will be returned here, if successful a package identifier is returned
     /// which corresponds to the package that was just inserted.
     ///
-    /// The returned [PackageId]s are listed in topologically sorted order.
-    pub fn append(&mut self, unresolved_groups: UnresolvedPackageGroup) -> Result<Vec<PackageId>> {
+    /// The returned [`PackageId`]s are listed in topologically sorted order.
+    pub fn push_group(
+        &mut self,
+        unresolved_groups: UnresolvedPackageGroup,
+    ) -> Result<Vec<PackageId>> {
         let (pkg_ids, _) = self.sort_unresolved_packages(vec![unresolved_groups])?;
         Ok(pkg_ids)
     }
 
+    /// Convenience method for combining [`UnresolvedPackageGroup::parse`] and
+    /// [`Resolve::push_group`].
+    ///
+    /// The `path` provided is used for error messages but otherwise is not
+    /// read. This method does not touch the filesystem. The `contents` provided
+    /// are the contents of a WIT package.
+    pub fn push_str(&mut self, path: impl AsRef<Path>, contents: &str) -> Result<Vec<PackageId>> {
+        self.push_group(UnresolvedPackageGroup::parse(path.as_ref(), contents)?)
+    }
+
     pub fn all_bits_valid(&self, ty: &Type) -> bool {
         match ty {
             Type::U8
@@ -798,46 +838,154 @@ impl Resolve {
         base
     }
 
-    /// Attempts to locate a world given the "default" package `pkg` and the
+    /// Attempts to locate a world given the "default" set of `packages` and the
     /// optional string specifier `world`.
     ///
     /// This method is intended to be used by bindings generation tools to
-    /// select a world from either `pkg` or a package in this `Resolve`.
+    /// select a world from either `packages` or a package in this `Resolve`.
+    /// The `packages` list is a return value from methods such as
+    /// [`push_path`](Resolve::push_path), [`push_dir`](Resolve::push_dir),
+    /// [`push_file`](Resolve::push_file), [`push_group`](Resolve::push_group),
+    /// or [`push_str`](Resolve::push_str). The return values of those methods
+    /// are the "main package list" which is specified by the user and is used
+    /// as a heuristic for world selection.
     ///
-    /// If `world` is `None` then `pkg` must have precisely one world which will
-    /// be returned.
+    /// If `world` is `None` then `packages` must have one entry and that
+    /// package must have exactly one world. If this is the case then that world
+    /// will be returned, otherwise an error will be returned.
     ///
     /// If `world` is `Some` then it can either be:
     ///
-    /// * A kebab-name of a world contained within `pkg` which is being
-    ///   selected, such as `"the-world"`.
+    /// * A kebab-name of a world such as `"the-world"`. In this situation
+    ///   the `packages` list must have only a single entry. If `packages` has
+    ///   no entries or more than one, or if the kebab-name does not exist in
+    ///   the one package specified, then an error will be returned.
     ///
     /// * An ID-based form of a world which is selected within this `Resolve`,
-    ///   ignoring `pkg`. For example `"wasi:http/proxy"`.
+    ///   for example `"wasi:http/proxy"`. In this situation the `packages`
+    ///   array is ignored and the ID specified is use to lookup a package. Note
+    ///   that a version does not need to be specified in this string if there's
+    ///   only one package of the same name and it has a version. In this
+    ///   situation the version can be omitted.
     ///
     /// If successful the corresponding `WorldId` is returned, otherwise an
     /// error is returned.
-    pub fn select_world(&self, pkg: PackageId, world: Option<&str>) -> Result<WorldId> {
-        let world = match world {
-            Some(world) => world,
-            None => {
-                let pkg = &self.packages[pkg];
-                match pkg.worlds.len() {
-                    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
-                    ),
-                }
-            }
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use anyhow::Result;
+    /// use wit_parser::Resolve;
+    ///
+    /// fn main() -> Result<()> {
+    ///     let mut resolve = Resolve::default();
+    ///
+    ///     // For inputs which have a single package and only one world `None`
+    ///     // can be specified.
+    ///     let ids = resolve.push_str(
+    ///         "./my-test.wit",
+    ///         r#"
+    ///             package example:wit1;
+    ///
+    ///             world foo {
+    ///                 // ...
+    ///             }
+    ///         "#,
+    ///     )?;
+    ///     assert!(resolve.select_world(&ids, None).is_ok());
+    ///
+    ///     // For inputs which have a single package and multiple worlds then
+    ///     // a world must be specified.
+    ///     let ids = resolve.push_str(
+    ///         "./my-test.wit",
+    ///         r#"
+    ///             package example:wit2;
+    ///
+    ///             world foo { /* ... */ }
+    ///
+    ///             world bar { /* ... */ }
+    ///         "#,
+    ///     )?;
+    ///     assert!(resolve.select_world(&ids, None).is_err());
+    ///     assert!(resolve.select_world(&ids, Some("foo")).is_ok());
+    ///     assert!(resolve.select_world(&ids, Some("bar")).is_ok());
+    ///
+    ///     // For inputs which have more than one package then a fully
+    ///     // qualified name must be specified.
+    ///     let ids = resolve.push_str(
+    ///         "./my-test.wit",
+    ///         r#"
+    ///             package example:wit3 {
+    ///                 world foo { /* ... */ }
+    ///             }
+    ///
+    ///             package example:wit4 {
+    ///                 world foo { /* ... */ }
+    ///             }
+    ///         "#,
+    ///     )?;
+    ///     assert!(resolve.select_world(&ids, None).is_err());
+    ///     assert!(resolve.select_world(&ids, Some("foo")).is_err());
+    ///     assert!(resolve.select_world(&ids, Some("example:wit3/foo")).is_ok());
+    ///     assert!(resolve.select_world(&ids, Some("example:wit4/foo")).is_ok());
+    ///
+    ///     // Note that the `ids` or `packages` argument is ignored if a fully
+    ///     // qualified world specified is provided meaning previous worlds
+    ///     // can be selected.
+    ///     assert!(resolve.select_world(&[], Some("example:wit1/foo")).is_ok());
+    ///     assert!(resolve.select_world(&[], Some("example:wit2/foo")).is_ok());
+    ///
+    ///     // When selecting with a version it's ok to drop the version when
+    ///     // there's only a single copy of that package in `Resolve`.
+    ///     resolve.push_str(
+    ///         "./my-test.wit",
+    ///         r#"
+    ///             package example:wit5@1.0.0;
+    ///
+    ///             world foo { /* ... */ }
+    ///         "#,
+    ///     )?;
+    ///     assert!(resolve.select_world(&[], Some("example:wit5/foo")).is_ok());
+    ///
+    ///     // However when a single package has multiple versions in a resolve
+    ///     // it's required to specify the version to select which one.
+    ///     resolve.push_str(
+    ///         "./my-test.wit",
+    ///         r#"
+    ///             package example:wit5@2.0.0;
+    ///
+    ///             world foo { /* ... */ }
+    ///         "#,
+    ///     )?;
+    ///     assert!(resolve.select_world(&[], Some("example:wit5/foo")).is_err());
+    ///     assert!(resolve.select_world(&[], Some("example:wit5/foo@1.0.0")).is_ok());
+    ///     assert!(resolve.select_world(&[], Some("example:wit5/foo@2.0.0")).is_ok());
+    ///
+    ///     Ok(())
+    /// }
+    /// ```
+    pub fn select_world(&self, packages: &[PackageId], world: Option<&str>) -> Result<WorldId> {
+        let world_path = match world {
+            Some(world) => Some(
+                parse_use_path(world)
+                    .with_context(|| format!("failed to parse world specifier `{world}`"))?,
+            ),
+            None => None,
         };
 
-        let path = parse_use_path(world)
-            .with_context(|| format!("failed to parse world specifier `{world}`"))?;
-        let (pkg, world) = match path {
-            ParsedUsePath::Name(name) => (pkg, name),
-            ParsedUsePath::Package(pkg, interface) => {
+        let (pkg, world_name) = match world_path {
+            Some(ParsedUsePath::Name(name)) => match packages {
+                [] => bail!("no packages were found to locate the world `{name}` within"),
+                [one] => (*one, name),
+                [..] => {
+                    bail!(
+                        "the supplied WIT source files describe multiple packages; \
+                         please provide a fully-qualified world-specifier select \
+                         a world amongst these packages"
+                    )
+                }
+            },
+            Some(ParsedUsePath::Package(pkg, interface)) => {
                 let pkg = match self.package_names.get(&pkg) {
                     Some(pkg) => *pkg,
                     None => {
@@ -863,14 +1011,35 @@ impl Resolve {
                         }
                     }
                 };
-                (pkg, interface)
-            }
+                (pkg, interface.to_string())
+            }
+            None => match packages {
+                [] => bail!("no packages were specified nor is a world specified"),
+                [one] => {
+                    let pkg = &self.packages[*one];
+                    match pkg.worlds.len() {
+                        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
+                        ),
+                    }
+                }
+                [..] => {
+                    bail!(
+                        "the supplied WIT source files describe multiple packages; \
+                         please provide a fully-qualified world-specifier select \
+                         a world amongst these packages"
+                    )
+                }
+            },
         };
         let pkg = &self.packages[pkg];
         pkg.worlds
-            .get(&world)
+            .get(&world_name)
             .copied()
-            .ok_or_else(|| anyhow!("no world named `{world}` in package"))
+            .ok_or_else(|| anyhow!("no world named `{world_name}` in package"))
     }
 
     /// Assigns a human readable name to the `WorldKey` specified.
@@ -2512,63 +2681,60 @@ impl<'a> MergeMap<'a> {
 
 #[cfg(test)]
 mod tests {
-    use crate::{PackageId, Resolve};
+    use crate::Resolve;
+    use anyhow::Result;
 
     #[test]
-    fn select_world() {
+    fn select_world() -> Result<()> {
         let mut resolve = Resolve::default();
-        parse_into(
-            &mut resolve,
+        resolve.push_str(
+            "test.wit",
             r#"
                 package foo:bar@0.1.0;
 
                 world foo {}
             "#,
-        );
-        parse_into(
-            &mut resolve,
+        )?;
+        resolve.push_str(
+            "test.wit",
             r#"
                 package foo:baz@0.1.0;
 
                 world foo {}
             "#,
-        );
-        parse_into(
-            &mut resolve,
+        )?;
+        resolve.push_str(
+            "test.wit",
             r#"
                 package foo:baz@0.2.0;
 
                 world foo {}
             "#,
-        );
+        )?;
 
-        let dummy = parse_into(
-            &mut resolve,
+        let dummy = resolve.push_str(
+            "test.wit",
             r#"
                 package foo:dummy;
 
                 world foo {}
             "#,
-        );
+        )?;
 
-        assert!(resolve.select_world(dummy, None).is_ok());
-        assert!(resolve.select_world(dummy, Some("xx")).is_err());
-        assert!(resolve.select_world(dummy, Some("")).is_err());
-        assert!(resolve.select_world(dummy, Some("foo:bar/foo")).is_ok());
+        assert!(resolve.select_world(&dummy, None).is_ok());
+        assert!(resolve.select_world(&dummy, Some("xx")).is_err());
+        assert!(resolve.select_world(&dummy, Some("")).is_err());
+        assert!(resolve.select_world(&dummy, Some("foo:bar/foo")).is_ok());
         assert!(resolve
-            .select_world(dummy, Some("foo:bar/foo@0.1.0"))
+            .select_world(&dummy, Some("foo:bar/foo@0.1.0"))
             .is_ok());
-        assert!(resolve.select_world(dummy, Some("foo:baz/foo")).is_err());
+        assert!(resolve.select_world(&dummy, Some("foo:baz/foo")).is_err());
         assert!(resolve
-            .select_world(dummy, Some("foo:baz/foo@0.1.0"))
+            .select_world(&dummy, Some("foo:baz/foo@0.1.0"))
             .is_ok());
         assert!(resolve
-            .select_world(dummy, Some("foo:baz/foo@0.2.0"))
+            .select_world(&dummy, Some("foo:baz/foo@0.2.0"))
             .is_ok());
-    }
-
-    fn parse_into(resolve: &mut Resolve, wit: &str) -> PackageId {
-        let pkgs = crate::UnresolvedPackageGroup::parse("input.wit".as_ref(), wit).unwrap();
-        resolve.append(pkgs).unwrap()[0]
+        Ok(())
     }
 }
diff --git a/fuzz/src/roundtrip_wit.rs b/fuzz/src/roundtrip_wit.rs
index 19967992..b15cd8c4 100644
--- a/fuzz/src/roundtrip_wit.rs
+++ b/fuzz/src/roundtrip_wit.rs
@@ -90,7 +90,7 @@ fn roundtrip_through_printing(file: &str, resolve: &Resolve, wasm: &[u8]) {
         write_file(&format!("{file}-{pkg_name}.wit"), &doc);
         map.push(format!("{pkg_name}.wit").as_ref(), doc);
         let unresolved = map.parse().unwrap();
-        let id = new_resolve.append(unresolved).unwrap();
+        let id = new_resolve.push_group(unresolved).unwrap();
         last = Some(id.last().unwrap().to_owned());
     }
 
diff --git a/playground/.gitignore b/playground/.gitignore
new file mode 100644
index 00000000..79f14c47
--- /dev/null
+++ b/playground/.gitignore
@@ -0,0 +1,4 @@
+node_modules/
+src/bindings.rs
+component-built/
+dist/
diff --git a/playground/README.md b/playground/README.md
new file mode 100644
index 00000000..13988405
--- /dev/null
+++ b/playground/README.md
@@ -0,0 +1,30 @@
+# Playground
+
+This is a simple online playground for `wasm-tools parse`, available at https://bytecodealliance.github.io/wasm-tools/.
+
+## Building
+
+In addition to `cargo`, you'll need to have `node` and [`cargo-component`](https://github.com/bytecodealliance/cargo-Component) installed and on your path.
+
+Then
+
+```sh
+npm ci
+```
+to install dependencies and
+```sh
+npm run build
+```
+to build.
+
+Output will be placed under `dist`.
+
+## Making changes
+
+The [./component/wit/world.wit](./component/wit/world.wit) file defines the exported functions for use by the playground. After changing it you'll need to `npm run build` to regenerate the bindings file (the command will fail but still generate the bindings) and then implement the functions in [./component/src/lib.rs](./component/src/lib.rs).
+
+After `npm run build`ing again you can make use of those functions from [./src/worker.ts](./src/worker.ts), with full type safety (thanks to [`jco`](https://github.com/bytecodealliance/jco)).
+
+It is also possible to use them outside of a worker, but since they run synchronously and block their thread the playground is designed to do all calls off the main thread.
+
+To preview your changes after building you'll need to run a web server; the worker will not load from a `file:` URL. The simplest way to do this is by running `npx http-server dist`, but you can use whatever means you'd like.
diff --git a/playground/component/.gitignore b/playground/component/.gitignore
new file mode 100644
index 00000000..2f7896d1
--- /dev/null
+++ b/playground/component/.gitignore
@@ -0,0 +1,1 @@
+target/
diff --git a/playground/component/Cargo.toml b/playground/component/Cargo.toml
new file mode 100644
index 00000000..6d3993ff
--- /dev/null
+++ b/playground/component/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "component"
+publish = false
+edition.workspace = true
+
+[dependencies]
+wit-bindgen-rt = { version = "0.26.0", features = ["bitflags"] }
+wat = { workspace = true }
+wasmprinter = { workspace = true }
+
+[lib]
+crate-type = ["cdylib"]
+test = false
+doctest = false
+doc = false
+
+[package.metadata.component]
+package = "component:component"
+
+[package.metadata.component.dependencies]
diff --git a/playground/component/src/lib.rs b/playground/component/src/lib.rs
new file mode 100644
index 00000000..4f493e1e
--- /dev/null
+++ b/playground/component/src/lib.rs
@@ -0,0 +1,63 @@
+#[allow(warnings)]
+mod bindings;
+
+use bindings::{Guest, PrintPart};
+
+struct Component;
+
+struct StringWriter(pub Vec<PrintPart>);
+
+impl wasmprinter::Print for StringWriter {
+    fn write_str(&mut self, s: &str) -> std::io::Result<()> {
+        self.0.push(PrintPart::Str(s.to_string()));
+        Ok(())
+    }
+
+    fn start_name(&mut self) -> std::io::Result<()> {
+        self.0.push(PrintPart::Name);
+        Ok(())
+    }
+
+    fn start_literal(&mut self) -> std::io::Result<()> {
+        self.0.push(PrintPart::Literal);
+        Ok(())
+    }
+
+    fn start_keyword(&mut self) -> std::io::Result<()> {
+        self.0.push(PrintPart::Keyword);
+        Ok(())
+    }
+
+    fn start_type(&mut self) -> std::io::Result<()> {
+        self.0.push(PrintPart::Type);
+        Ok(())
+    }
+
+    fn start_comment(&mut self) -> std::io::Result<()> {
+        self.0.push(PrintPart::Comment);
+        Ok(())
+    }
+
+    fn reset_color(&mut self) -> std::io::Result<()> {
+        self.0.push(PrintPart::Reset);
+        Ok(())
+    }
+}
+
+impl Guest for Component {
+    fn parse(contents: String) -> Result<Vec<u8>, String> {
+        wat::parse_str(contents).map_err(|e| e.to_string())
+    }
+
+    fn print(contents: Vec<u8>, skeleton: bool) -> Result<Vec<PrintPart>, String> {
+        let mut config = wasmprinter::Config::new();
+        config.print_skeleton(skeleton);
+
+        let mut writer = StringWriter(Vec::new());
+        let result = config.print(&contents, &mut writer);
+
+        result.map(|_| writer.0).map_err(|e| e.to_string())
+    }
+}
+
+bindings::export!(Component with_types_in bindings);
diff --git a/playground/component/wit/world.wit b/playground/component/wit/world.wit
new file mode 100644
index 00000000..9d70466a
--- /dev/null
+++ b/playground/component/wit/world.wit
@@ -0,0 +1,16 @@
+package component:component;
+
+world wasm-tools {
+    export parse: func(contents: string) -> result<list<u8>, string>;
+
+    variant print-part {
+        str(string),
+        name,
+        literal,
+        keyword,
+        %type,
+        comment,
+        reset,
+    }
+    export print: func(contents: list<u8>, skeleton: bool) -> result<list<print-part>, string>;
+}
diff --git a/playground/package-lock.json b/playground/package-lock.json
new file mode 100644
index 00000000..5072d599
--- /dev/null
+++ b/playground/package-lock.json
@@ -0,0 +1,798 @@
+{
+  "name": "playground",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "dependencies": {
+        "@bytecodealliance/jco": "^1.2.4",
+        "esbuild": "^0.21.4",
+        "typescript": "^5.4.5"
+      }
+    },
+    "node_modules/@bytecodealliance/jco": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/@bytecodealliance/jco/-/jco-1.2.4.tgz",
+      "integrity": "sha512-uuOm9UkYqWp5uElYDNzlhjbdrAmczEvETgQdI1hFTk79h+mrmHPRV32pgRP5o1eHVwMIpuk4XQkDIhFbksurUw==",
+      "dependencies": {
+        "@bytecodealliance/preview2-shim": "^0.16.2",
+        "binaryen": "^116.0.0",
+        "chalk-template": "^1",
+        "commander": "^12",
+        "mkdirp": "^3",
+        "ora": "^8",
+        "terser": "^5"
+      },
+      "bin": {
+        "jco": "src/jco.js"
+      }
+    },
+    "node_modules/@bytecodealliance/preview2-shim": {
+      "version": "0.16.2",
+      "resolved": "https://registry.npmjs.org/@bytecodealliance/preview2-shim/-/preview2-shim-0.16.2.tgz",
+      "integrity": "sha512-36MwesmbLSf3Y5/OHcS85iBaF0N92CQ4gpjtDVKSbrjxmrBKCWlWVfoQ03F/cqDg8k5K7pzVaVBH0XBIbTCfTQ=="
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.4.tgz",
+      "integrity": "sha512-Zrm+B33R4LWPLjDEVnEqt2+SLTATlru1q/xYKVn8oVTbiRBGmK2VIMoIYGJDGyftnGaC788IuzGFAlb7IQ0Y8A==",
+      "cpu": [
+        "ppc64"
+      ],
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.4.tgz",
+      "integrity": "sha512-E7H/yTd8kGQfY4z9t3nRPk/hrhaCajfA3YSQSBrst8B+3uTcgsi8N+ZWYCaeIDsiVs6m65JPCaQN/DxBRclF3A==",
+      "cpu": [
+        "arm"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.4.tgz",
+      "integrity": "sha512-fYFnz+ObClJ3dNiITySBUx+oNalYUT18/AryMxfovLkYWbutXsct3Wz2ZWAcGGppp+RVVX5FiXeLYGi97umisA==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.4.tgz",
+      "integrity": "sha512-mDqmlge3hFbEPbCWxp4fM6hqq7aZfLEHZAKGP9viq9wMUBVQx202aDIfc3l+d2cKhUJM741VrCXEzRFhPDKH3Q==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.4.tgz",
+      "integrity": "sha512-72eaIrDZDSiWqpmCzVaBD58c8ea8cw/U0fq/PPOTqE3c53D0xVMRt2ooIABZ6/wj99Y+h4ksT/+I+srCDLU9TA==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.4.tgz",
+      "integrity": "sha512-uBsuwRMehGmw1JC7Vecu/upOjTsMhgahmDkWhGLWxIgUn2x/Y4tIwUZngsmVb6XyPSTXJYS4YiASKPcm9Zitag==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.4.tgz",
+      "integrity": "sha512-8JfuSC6YMSAEIZIWNL3GtdUT5NhUA/CMUCpZdDRolUXNAXEE/Vbpe6qlGLpfThtY5NwXq8Hi4nJy4YfPh+TwAg==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.4.tgz",
+      "integrity": "sha512-8d9y9eQhxv4ef7JmXny7591P/PYsDFc4+STaxC1GBv0tMyCdyWfXu2jBuqRsyhY8uL2HU8uPyscgE2KxCY9imQ==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.4.tgz",
+      "integrity": "sha512-2rqFFefpYmpMs+FWjkzSgXg5vViocqpq5a1PSRgT0AvSgxoXmGF17qfGAzKedg6wAwyM7UltrKVo9kxaJLMF/g==",
+      "cpu": [
+        "arm"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.4.tgz",
+      "integrity": "sha512-/GLD2orjNU50v9PcxNpYZi+y8dJ7e7/LhQukN3S4jNDXCKkyyiyAz9zDw3siZ7Eh1tRcnCHAo/WcqKMzmi4eMQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.4.tgz",
+      "integrity": "sha512-pNftBl7m/tFG3t2m/tSjuYeWIffzwAZT9m08+9DPLizxVOsUl8DdFzn9HvJrTQwe3wvJnwTdl92AonY36w/25g==",
+      "cpu": [
+        "ia32"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.4.tgz",
+      "integrity": "sha512-cSD2gzCK5LuVX+hszzXQzlWya6c7hilO71L9h4KHwqI4qeqZ57bAtkgcC2YioXjsbfAv4lPn3qe3b00Zt+jIfQ==",
+      "cpu": [
+        "loong64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.4.tgz",
+      "integrity": "sha512-qtzAd3BJh7UdbiXCrg6npWLYU0YpufsV9XlufKhMhYMJGJCdfX/G6+PNd0+v877X1JG5VmjBLUiFB0o8EUSicA==",
+      "cpu": [
+        "mips64el"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.4.tgz",
+      "integrity": "sha512-yB8AYzOTaL0D5+2a4xEy7OVvbcypvDR05MsB/VVPVA7nL4hc5w5Dyd/ddnayStDgJE59fAgNEOdLhBxjfx5+dg==",
+      "cpu": [
+        "ppc64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.4.tgz",
+      "integrity": "sha512-Y5AgOuVzPjQdgU59ramLoqSSiXddu7F3F+LI5hYy/d1UHN7K5oLzYBDZe23QmQJ9PIVUXwOdKJ/jZahPdxzm9w==",
+      "cpu": [
+        "riscv64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.4.tgz",
+      "integrity": "sha512-Iqc/l/FFwtt8FoTK9riYv9zQNms7B8u+vAI/rxKuN10HgQIXaPzKZc479lZ0x6+vKVQbu55GdpYpeNWzjOhgbA==",
+      "cpu": [
+        "s390x"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.4.tgz",
+      "integrity": "sha512-Td9jv782UMAFsuLZINfUpoF5mZIbAj+jv1YVtE58rFtfvoKRiKSkRGQfHTgKamLVT/fO7203bHa3wU122V/Bdg==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.4.tgz",
+      "integrity": "sha512-Awn38oSXxsPMQxaV0Ipb7W/gxZtk5Tx3+W+rAPdZkyEhQ6968r9NvtkjhnhbEgWXYbgV+JEONJ6PcdBS+nlcpA==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.4.tgz",
+      "integrity": "sha512-IsUmQeCY0aU374R82fxIPu6vkOybWIMc3hVGZ3ChRwL9hA1TwY+tS0lgFWV5+F1+1ssuvvXt3HFqe8roCip8Hg==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.4.tgz",
+      "integrity": "sha512-hsKhgZ4teLUaDA6FG/QIu2q0rI6I36tZVfM4DBZv3BG0mkMIdEnMbhc4xwLvLJSS22uWmaVkFkqWgIS0gPIm+A==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.4.tgz",
+      "integrity": "sha512-UUfMgMoXPoA/bvGUNfUBFLCh0gt9dxZYIx9W4rfJr7+hKe5jxxHmfOK8YSH4qsHLLN4Ck8JZ+v7Q5fIm1huErg==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.4.tgz",
+      "integrity": "sha512-yIxbspZb5kGCAHWm8dexALQ9en1IYDfErzjSEq1KzXFniHv019VT3mNtTK7t8qdy4TwT6QYHI9sEZabONHg+aw==",
+      "cpu": [
+        "ia32"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.4.tgz",
+      "integrity": "sha512-sywLRD3UK/qRJt0oBwdpYLBibk7KiRfbswmWRDabuncQYSlf8aLEEUor/oP6KRz8KEG+HoiVLBhPRD5JWjS8Sg==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@jridgewell/gen-mapping": {
+      "version": "0.3.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+      "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+      "dependencies": {
+        "@jridgewell/set-array": "^1.2.1",
+        "@jridgewell/sourcemap-codec": "^1.4.10",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/set-array": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+      "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/source-map": {
+      "version": "0.3.6",
+      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
+      "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.25"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.4.15",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+      "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
+    },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.25",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+      "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
+      }
+    },
+    "node_modules/acorn": {
+      "version": "8.11.3",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
+      "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/ansi-regex": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+      "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+      }
+    },
+    "node_modules/binaryen": {
+      "version": "116.0.0",
+      "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-116.0.0.tgz",
+      "integrity": "sha512-Hp0dXC6Cb/rTwWEoUS2BRghObE7g/S9umKtxuTDt3f61G6fNTE/YVew/ezyy3IdHcLx3f17qfh6LwETgCfvWkQ==",
+      "bin": {
+        "wasm-opt": "bin/wasm-opt",
+        "wasm2js": "bin/wasm2js"
+      }
+    },
+    "node_modules/buffer-from": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
+    },
+    "node_modules/chalk": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+      "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
+      "engines": {
+        "node": "^12.17.0 || ^14.13 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/chalk-template": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.0.tgz",
+      "integrity": "sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==",
+      "dependencies": {
+        "chalk": "^5.2.0"
+      },
+      "engines": {
+        "node": ">=14.16"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk-template?sponsor=1"
+      }
+    },
+    "node_modules/cli-cursor": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
+      "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
+      "dependencies": {
+        "restore-cursor": "^4.0.0"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/cli-spinners": {
+      "version": "2.9.2",
+      "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
+      "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/commander": {
+      "version": "12.1.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+      "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/emoji-regex": {
+      "version": "10.3.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
+      "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw=="
+    },
+    "node_modules/esbuild": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.4.tgz",
+      "integrity": "sha512-sFMcNNrj+Q0ZDolrp5pDhH0nRPN9hLIM3fRPwgbLYJeSHHgnXSnbV3xYgSVuOeLWH9c73VwmEverVzupIv5xuA==",
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.21.4",
+        "@esbuild/android-arm": "0.21.4",
+        "@esbuild/android-arm64": "0.21.4",
+        "@esbuild/android-x64": "0.21.4",
+        "@esbuild/darwin-arm64": "0.21.4",
+        "@esbuild/darwin-x64": "0.21.4",
+        "@esbuild/freebsd-arm64": "0.21.4",
+        "@esbuild/freebsd-x64": "0.21.4",
+        "@esbuild/linux-arm": "0.21.4",
+        "@esbuild/linux-arm64": "0.21.4",
+        "@esbuild/linux-ia32": "0.21.4",
+        "@esbuild/linux-loong64": "0.21.4",
+        "@esbuild/linux-mips64el": "0.21.4",
+        "@esbuild/linux-ppc64": "0.21.4",
+        "@esbuild/linux-riscv64": "0.21.4",
+        "@esbuild/linux-s390x": "0.21.4",
+        "@esbuild/linux-x64": "0.21.4",
+        "@esbuild/netbsd-x64": "0.21.4",
+        "@esbuild/openbsd-x64": "0.21.4",
+        "@esbuild/sunos-x64": "0.21.4",
+        "@esbuild/win32-arm64": "0.21.4",
+        "@esbuild/win32-ia32": "0.21.4",
+        "@esbuild/win32-x64": "0.21.4"
+      }
+    },
+    "node_modules/get-east-asian-width": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz",
+      "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/is-interactive": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
+      "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/is-unicode-supported": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz",
+      "integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/log-symbols": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz",
+      "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==",
+      "dependencies": {
+        "chalk": "^5.3.0",
+        "is-unicode-supported": "^1.3.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/log-symbols/node_modules/is-unicode-supported": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
+      "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/mimic-fn": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/mkdirp": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
+      "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
+      "bin": {
+        "mkdirp": "dist/cjs/src/bin.js"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/onetime": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+      "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+      "dependencies": {
+        "mimic-fn": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/ora": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/ora/-/ora-8.0.1.tgz",
+      "integrity": "sha512-ANIvzobt1rls2BDny5fWZ3ZVKyD6nscLvfFRpQgfWsythlcsVUC9kL0zq6j2Z5z9wwp1kd7wpsD/T9qNPVLCaQ==",
+      "dependencies": {
+        "chalk": "^5.3.0",
+        "cli-cursor": "^4.0.0",
+        "cli-spinners": "^2.9.2",
+        "is-interactive": "^2.0.0",
+        "is-unicode-supported": "^2.0.0",
+        "log-symbols": "^6.0.0",
+        "stdin-discarder": "^0.2.1",
+        "string-width": "^7.0.0",
+        "strip-ansi": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/restore-cursor": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
+      "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
+      "dependencies": {
+        "onetime": "^5.1.0",
+        "signal-exit": "^3.0.2"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/signal-exit": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+      "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
+    },
+    "node_modules/source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/source-map-support": {
+      "version": "0.5.21",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+      "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+      "dependencies": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
+      }
+    },
+    "node_modules/stdin-discarder": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz",
+      "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/string-width": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz",
+      "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==",
+      "dependencies": {
+        "emoji-regex": "^10.3.0",
+        "get-east-asian-width": "^1.0.0",
+        "strip-ansi": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/strip-ansi": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+      "dependencies": {
+        "ansi-regex": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+      }
+    },
+    "node_modules/terser": {
+      "version": "5.31.1",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.1.tgz",
+      "integrity": "sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==",
+      "dependencies": {
+        "@jridgewell/source-map": "^0.3.3",
+        "acorn": "^8.8.2",
+        "commander": "^2.20.0",
+        "source-map-support": "~0.5.20"
+      },
+      "bin": {
+        "terser": "bin/terser"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/terser/node_modules/commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+    },
+    "node_modules/typescript": {
+      "version": "5.4.5",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
+      "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    }
+  }
+}
diff --git a/playground/package.json b/playground/package.json
new file mode 100644
index 00000000..165eede1
--- /dev/null
+++ b/playground/package.json
@@ -0,0 +1,17 @@
+{
+  "type": "module",
+  "scripts": {
+    "build": "npm run build-component && npm run transpile && npm run typecheck && npm run bundle && npm run copy-files",
+    "build-component": "cd component && cargo component build --release --target wasm32-wasi",
+    "bundle": "for source in worker.ts parse.ts print.ts; do esbuild --bundle --format=esm src/$source --outdir=dist; done",
+    "copy-files": "cp pages/* component-built/*.wasm dist",
+    "clean": "rm -rf component/src/bindings.rs component-built dist && echo '## you will need to run `cargo clean` seperately to delete cargo artifacts'",
+    "transpile": "jco transpile --no-nodejs-compat ../target/wasm32-wasi/release/component.wasm --out-dir component-built",
+    "typecheck": "tsc"
+  },
+  "dependencies": {
+    "@bytecodealliance/jco": "^1.2.4",
+    "esbuild": "^0.21.4",
+    "typescript": "^5.4.5"
+  }
+}
diff --git a/playground/pages/index.html b/playground/pages/index.html
new file mode 100644
index 00000000..38c39b75
--- /dev/null
+++ b/playground/pages/index.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<meta http-equiv="refresh" content="0; url=https://github.com/bytecodealliance/wasm-tools">
diff --git a/playground/pages/parse.html b/playground/pages/parse.html
new file mode 100644
index 00000000..5b05209a
--- /dev/null
+++ b/playground/pages/parse.html
@@ -0,0 +1,86 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <title>wasm-tools parse</title>
+  <script type="module" src="parse.js"></script>
+  <link rel="stylesheet" href="style.css">
+  <style>
+    .textarea-container {
+      flex-grow: 1;
+      padding: .4rem;
+      display: grid;
+      grid-column-gap: calc(.8rem + 8px);;
+      grid-template-columns: 1fr 1fr;
+      grid-template-rows: min-content auto 1fr;
+      grid-template-areas:
+        "h h"
+        "a b"
+        "c d"
+    }
+
+    textarea {
+      resize: none;
+      box-sizing: border-box;
+      padding: .4rem;
+      height: calc(100% - .4rem);
+      width: 100%;
+    }
+
+    button {
+      font-size: inherit;
+      /* color: #505050; */
+    }
+
+    .description {
+      grid-area: h;
+    }
+
+    .input-header {
+      grid-area: a;
+      padding-bottom: .4rem;
+    }
+    .output-header {
+      grid-area: b;
+    }
+
+    .input {
+      grid-area: c;
+    }
+    .output {
+      grid-area: d
+    }
+  </style>
+</head>
+
+<header>
+  <h1>wasm-tools parse</h1>
+</header>
+
+<div class="textarea-container">
+  <div class="description">
+    <p>Translate from the WebAssembly text format to binary using <a href="https://github.com/bytecodealliance/wasm-tools"><code>wasm-tools parse</code></a>.
+    You can go the other direction using <a href="print"><code>print</code></a>.</p>
+  </div>
+  <div class="input-header">wat</div>
+  <div class="input">
+    <textarea id="input">(module
+  (import "foo" "bar" (func (param f32)))
+  (memory (data "hi"))
+  (type (func (param i32) (result i32)))
+  (start 1)
+  (table 0 1 funcref)
+  (func)
+  (func (type 1)
+    i32.const 42
+    drop)
+  (export "e" (func 1)))</textarea>
+  </div>
+
+  <div class="output-header">
+    wasm <button id="download" disabled>Download</button>
+  </div>
+  <div class="output">
+    <textarea id="output"></textarea>
+  </div>
+</div>
diff --git a/playground/pages/print.html b/playground/pages/print.html
new file mode 100644
index 00000000..395f114a
--- /dev/null
+++ b/playground/pages/print.html
@@ -0,0 +1,115 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <title>wasm-tools print</title>
+  <script type="module" src="print.js"></script>
+  <link rel="stylesheet" href="style.css">
+  <style>
+    .textarea-container {
+      flex-grow: 1;
+      padding: .4rem;
+      display: grid;
+      grid-column-gap: calc(.8rem + 8px);;
+      grid-template-rows: min-content auto 1fr;
+      grid-template-areas:
+        "h"
+        "a"
+        "o"
+    }
+
+    button {
+      font-size: inherit;
+    }
+
+    .config {
+      float: right;
+    }
+
+    .description {
+      grid-area: h;
+    }
+
+    .output-header {
+      grid-area: a;
+      padding-bottom: .4rem;
+    }
+
+    .output-container {
+      grid-area: o;
+      overflow-x: scroll;
+    }
+
+    #output {
+      box-sizing: border-box;
+      padding: .4rem;
+      min-height: calc(100% - .4rem);
+      width: 100%;
+      margin: 0;
+      border: 1px solid black;
+      border-radius: 5px;
+      overflow-x: scroll;
+    }
+
+    #overlay {
+      position: fixed;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      background-color: rgba(0, 0, 0, .2);
+      display: none;
+      justify-content: center;
+      align-items: center;
+      text-shadow: 0 0 2px white;
+      font-size: 24px;
+      pointer-events: none;
+    }
+
+    .print-name {
+      color: #9F00BD;
+    }
+
+    .print-literal {
+      color: #FB0416;
+    }
+
+    .print-keyword {
+      font-weight: bold;
+    }
+
+    .print-type {
+      color: #328A5D;
+    }
+
+    .print-comment {
+      color: #B3B3B3;
+    }
+  </style>
+</head>
+
+<header>
+  <h1>wasm-tools print</h1>
+</header>
+
+<div id="overlay">Drop .wasm file here</div>
+
+<div class="textarea-container">
+  <div class="description">
+    <p>Translate WebAssembly binaries to text format using <a href="https://github.com/bytecodealliance/wasm-tools"><code>wasm-tools print</code></a>.
+    You can go the other direction using <a href="parse"><code>parse</code></a>.</p>
+  </div>
+
+  <div class="output-header">
+    Drop a file on this page, or <button id="upload">manually select a file</button>.
+    <input type="file" id="fileInput" accept=".wasm" style="display:none;">
+    <span class="config">
+      <input type="checkbox" id="skeleton">
+      <label for="skeleton">Skip function bodies (recommended for large files)</label>
+    </span>
+  </div>
+  <div class="output-container">
+    <!-- adding contenteditable is the easiest way to let the user copy-paste the whole contents -->
+    <pre id="output" class="iobox" contenteditable="plaintext-only" spellcheck="false"></pre>
+  </div>
+</div>
diff --git a/playground/pages/style.css b/playground/pages/style.css
new file mode 100644
index 00000000..223c1219
--- /dev/null
+++ b/playground/pages/style.css
@@ -0,0 +1,31 @@
+:root {
+  --text-color: #404040;
+}
+
+html {
+  height: 100%;
+}
+
+body {
+  font-family: system,-apple-system,system-ui,BlinkMacSystemFont,sans-serif;
+  color: var(--text-color);
+  font-size: 1.2rem;
+  line-height: 1.3;
+  height: calc(100% - 16px - .4rem);
+  display: flex;
+  flex-direction: column;
+  padding: .4rem
+}
+
+header {
+  text-align: center;
+}
+
+h1 {
+  margin: 0px;
+}
+
+a {
+  color: var(--text-color);
+}
+
diff --git a/playground/src/parse.ts b/playground/src/parse.ts
new file mode 100644
index 00000000..2d0618e4
--- /dev/null
+++ b/playground/src/parse.ts
@@ -0,0 +1,66 @@
+import { type MessageToWorker, debounce } from './utilities.js';
+
+// TODO replace this function once https://github.com/tc39/proposal-arraybuffer-base64 ships
+function base64(bytes: Uint8Array) {
+  // @ts-expect-error it's fine to pass a Uint8Array here
+  return btoa(String.fromCharCode.apply(null, bytes));
+}
+
+let input: HTMLTextAreaElement = document.querySelector('#input')!;
+let output: HTMLTextAreaElement = document.querySelector('#output')!;
+let downloadButton: HTMLButtonElement = document.querySelector('#download')!;
+
+let worker = new Worker('worker.js', { type: 'module' });
+
+let lastBytes: Uint8Array;
+let sent = '';
+let statusMessageTimeout = 0;
+
+// we can't just postmessage to the worker right away because it has a top-level await
+// so we need to wait for it to tell us it's ready
+worker.addEventListener('message', () => {
+  worker.addEventListener('message', gotMessage);
+  update();
+}, { once: true });
+
+function gotMessage({ data }: { data: { success: true, bytes: Uint8Array } | { success: false, error: string }}) {
+  clearTimeout(statusMessageTimeout);
+  if (input.value === sent) {
+    if (data.success) {
+      lastBytes = data.bytes;
+      output.value = base64(lastBytes);
+      downloadButton.disabled = false;
+      output.style.color = '';
+    } else {
+      output.value = data.error;
+      downloadButton.disabled = true;
+      output.style.color = '#a00';
+    }
+  } else {
+    // the user has already moved on, ignore this result and re-run
+    update();
+  }
+}
+
+function update() {
+  sent = input.value;
+  worker.postMessage({ kind: 'parse', source: input.value } satisfies MessageToWorker);
+
+  // wait a bit before updating the output to avoid rapid changes when the output can be parsed quickly
+  statusMessageTimeout = setTimeout(() => {
+    output.value = 'working...';
+    downloadButton.disabled = true;
+  }, 200);
+}
+
+input.addEventListener('input', debounce(update, 100));
+
+downloadButton.addEventListener('click', () => {
+  let blob = new Blob([lastBytes], { type: 'application/wasm' });
+  let link = document.createElement('a');
+  let url = URL.createObjectURL(blob);
+  link.href = url;
+  link.download = 'wasm-tools-parse-output.wasm';
+  link.click();
+  URL.revokeObjectURL(url);
+});
diff --git a/playground/src/print.ts b/playground/src/print.ts
new file mode 100644
index 00000000..7b22e07a
--- /dev/null
+++ b/playground/src/print.ts
@@ -0,0 +1,118 @@
+import type { MessageToWorker } from './utilities.js';
+import type { PrintPart } from '../component-built/component.js';
+
+function unbase64(chars: string) {
+  return new Uint8Array(Array.from(atob(chars), c => c.charCodeAt(0)))
+}
+
+let overlay: HTMLDivElement = document.querySelector('#overlay')!;
+let upload: HTMLButtonElement = document.querySelector('#upload')!;
+let fileInput: HTMLInputElement = document.querySelector('#fileInput')!;
+let output: HTMLPreElement = document.querySelector('#output')!;
+let skeletonCheckbox: HTMLInputElement = document.querySelector('#skeleton')!;
+
+let lastTarget: EventTarget | null;
+let hideOverlayTimer: number;
+addEventListener('dragover', e => {
+  e.preventDefault(); // necessary to make the drop event work, for some reason
+  lastTarget = e.target;
+  overlay.style.display = 'flex';
+  clearTimeout(hideOverlayTimer);
+});
+
+addEventListener('dragleave', e => {
+  e.preventDefault();
+  if (e.target === document || e.target === lastTarget) {
+    // we need a timer here because otherwise the overlay flashes while moving the mouse around the page
+    hideOverlayTimer = setTimeout(() => {
+      overlay.style.display = 'none';
+    }, 10);
+  }
+});
+
+addEventListener('drop', (e: DragEvent) => {
+  e.preventDefault();
+  overlay.style.display = 'none';
+  let file = e.dataTransfer?.files[0];
+  if (!file) return;
+  print(file.arrayBuffer());
+}, { capture: true });
+
+let messageId = 0;
+async function print(f: Promise<ArrayBuffer>) {
+  output.innerHTML = 'working...';
+  let skeleton = skeletonCheckbox.checked;
+  let bytes = new Uint8Array(await f);
+  worker.postMessage({ kind: 'print', messageId, bytes, skeleton } satisfies MessageToWorker, [bytes.buffer]);
+  ++messageId;
+}
+
+let worker = new Worker('worker.js', { type: 'module' });
+
+// we can't just postmessage to the worker right away because it has a top-level await
+// so we need to wait for it to tell us it's ready
+worker.addEventListener('message', () => {
+  worker.addEventListener('message', gotMessage);
+  print(Promise.resolve(unbase64('AGFzbQEAAAABDQNgAX8Bf2ABfQBgAAACCwEDZm9vA2JhcgABAwMCAgEEBQFwAQABBQQBAQEBBwUBAWUAAQgBAQoKAgIACwUAQSoaCwsIAQBBAAsCaGk=').buffer));
+}, { once: true },);
+
+upload.addEventListener('click', () => {
+  fileInput.click();
+  fileInput.onchange = () => {
+    let file = fileInput.files?.[0];
+    if (!file) return;
+    print(file.arrayBuffer());
+  }
+});
+
+function gotMessage({ data }: {
+  data: { messageId: number; success: true; source: PrintPart[] } | { messageId: number; success: false; error: string };
+}) {
+  if (data.messageId !== messageId - 1) {
+    // user has already asked for a new file
+    return;
+  }
+  output.innerHTML = '';
+  if (data.success) {
+    output.append(toDOM(data.source));
+  } else {
+    let span = document.createElement('span');
+    span.style.color = '#a00';
+    span.append(data.error);
+    output.append(span);
+  }
+}
+
+function toDOM(parts: PrintPart[]) {
+  let root = document.createElement('span');
+  let stack = [root];
+  for (let part of parts) {
+    switch (part.tag) {
+      case 'str': {
+        stack[0].append(part.val);
+        break;
+      }
+      case 'name':
+      case 'literal':
+      case 'keyword':
+      case 'type':
+      case 'comment': {
+        let ele = document.createElement('span');
+        ele.className = 'print-' + part.tag;
+        stack[0].append(ele);
+        stack.unshift(ele);
+        break;
+      }
+      case 'reset': {
+        stack.shift();
+        break;
+      }
+      default: {
+        // @ts-expect-error the above should be exhaustive
+        type remaining = typeof part.tag;
+        console.error('got unknown print part', part);
+      }
+    }
+  }
+  return root;
+}
diff --git a/playground/src/utilities.ts b/playground/src/utilities.ts
new file mode 100644
index 00000000..074c4a18
--- /dev/null
+++ b/playground/src/utilities.ts
@@ -0,0 +1,16 @@
+export type MessageToWorker =
+  | { kind: 'parse'; source: string }
+  | { kind: 'print'; messageId: number; bytes: Uint8Array, skeleton: boolean };
+
+export function debounce(f: (...args: unknown[]) => void, ms: number) {
+  let timeout: number | null;
+  return function (this: unknown, ...args: unknown[]) {
+    let fresh = timeout == null;
+    if (!fresh) clearTimeout(timeout!);
+    timeout = setTimeout(() => {
+      timeout = null;
+      if (!fresh) f.apply(this, args);
+    }, ms);
+    if (fresh) f.apply(this, args);
+  };
+}
diff --git a/playground/src/worker.ts b/playground/src/worker.ts
new file mode 100644
index 00000000..32336bce
--- /dev/null
+++ b/playground/src/worker.ts
@@ -0,0 +1,35 @@
+import { parse, print } from '../component-built/component.js';
+import type { MessageToWorker } from './utilities.js'
+
+// workaround for https://github.com/Microsoft/TypeScript/issues/20595
+declare function postMessage(message: any, transfer?: Transferable[]): void;
+
+postMessage('loaded');
+
+addEventListener('message', ({ data }: { data: MessageToWorker }) => {
+  switch (data.kind) {
+    case 'parse': {
+      try {
+        let bytes = parse(data.source);
+        postMessage({ success: true, bytes }, [bytes.buffer]);
+      } catch (e) {
+        postMessage({ success: false, error: (e as Error).message });
+      }
+      return;
+    }
+    case 'print': {
+      try {
+        let source = print(data.bytes, data.skeleton);
+        postMessage({ success: true, messageId: data.messageId, source });
+      } catch (e) {
+        postMessage({ success: false, messageId: data.messageId, error: (e as Error).message });
+      }
+      return;
+    }
+    default: {
+      // @ts-expect-error the above should be exhaustive
+      type remaining = typeof data.kind;
+      console.error('got unknown message', data);
+    }
+  }
+});
diff --git a/playground/tsconfig.json b/playground/tsconfig.json
new file mode 100644
index 00000000..c93177b1
--- /dev/null
+++ b/playground/tsconfig.json
@@ -0,0 +1,12 @@
+{
+  "include": ["src/**/*"],
+  "compilerOptions": {
+      "target": "es2022",
+      "module": "Node16",
+      "moduleResolution": "Node16",
+      "strict": true,
+      "verbatimModuleSyntax": true,
+      "noEmit": true,
+      "lib": ["es2022", "dom", "dom.iterable"],
+  }
+}
\ No newline at end of file
diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs
index 5d759df4..d546f828 100644
--- a/src/bin/wasm-tools/component.rs
+++ b/src/bin/wasm-tools/component.rs
@@ -11,10 +11,9 @@ use wasm_tools::Output;
 use wasmparser::WasmFeatures;
 use wat::Detect;
 use wit_component::{
-    embed_component_metadata, resolve_world_from_name, ComponentEncoder, DecodedWasm, Linker,
-    StringEncoding, WitPrinter,
+    embed_component_metadata, ComponentEncoder, DecodedWasm, Linker, StringEncoding, WitPrinter,
 };
-use wit_parser::{PackageId, Resolve, UnresolvedPackageGroup};
+use wit_parser::{PackageId, Resolve};
 
 /// WebAssembly wit-based component tooling.
 #[derive(Parser)]
@@ -285,7 +284,7 @@ impl EmbedOpts {
             Some(self.io.parse_input_wasm()?)
         };
         let (resolve, pkg_ids) = self.resolve.load()?;
-        let world = resolve_world_from_name(&resolve, pkg_ids, self.world.as_deref())?;
+        let world = resolve.select_world(&pkg_ids, self.world.as_deref())?;
         let mut wasm = wasm.unwrap_or_else(|| wit_component::dummy_module(&resolve, world));
 
         embed_component_metadata(
@@ -590,8 +589,7 @@ impl WitOpts {
                 };
                 let mut resolve =
                     WitResolve::resolve_with_features(&self.features, self.all_features);
-                let pkgs = UnresolvedPackageGroup::parse(path, input)?;
-                let ids = resolve.append(pkgs)?;
+                let ids = resolve.push_str(path, input)?;
                 Ok(DecodedWasm::WitPackages(resolve, ids))
             }
         }
@@ -723,7 +721,7 @@ impl TargetsOpts {
     /// Executes the application.
     fn run(self) -> Result<()> {
         let (resolve, pkg_ids) = self.resolve.load()?;
-        let world = resolve_world_from_name(&resolve, pkg_ids, self.world.as_deref())?;
+        let world = resolve.select_world(&pkg_ids, self.world.as_deref())?;
         let component_to_test = self.input.parse_wasm()?;
 
         wit_component::targets(&resolve, world, &component_to_test)?;
@@ -763,8 +761,8 @@ impl SemverCheckOpts {
 
     fn run(self) -> Result<()> {
         let (resolve, pkg_ids) = self.resolve.load()?;
-        let prev = resolve_world_from_name(&resolve, pkg_ids.clone(), Some(self.prev).as_deref())?;
-        let new = resolve_world_from_name(&resolve, pkg_ids, Some(self.new).as_deref())?;
+        let prev = resolve.select_world(&pkg_ids, Some(self.prev.as_str()))?;
+        let new = resolve.select_world(&pkg_ids, Some(self.new.as_str()))?;
         wit_component::semver_check(resolve, prev, new)?;
         Ok(())
     }
diff --git a/src/bin/wasm-tools/json_from_wast.rs b/src/bin/wasm-tools/json_from_wast.rs
index c85cb946..afb99243 100644
--- a/src/bin/wasm-tools/json_from_wast.rs
+++ b/src/bin/wasm-tools/json_from_wast.rs
@@ -1,7 +1,7 @@
 use anyhow::{bail, Context, Result};
 use clap::Parser;
 use std::path::{Path, PathBuf};
-use wast::core::{HeapType, NanPattern, V128Const, V128Pattern, WastRetCore};
+use wast::core::{AbstractHeapType, HeapType, NanPattern, V128Const, V128Pattern, WastRetCore};
 use wast::lexer::Lexer;
 use wast::parser::{self, ParseBuffer};
 use wast::token::{Span, F32, F64};
@@ -506,21 +506,30 @@ fn f64_to_string(f: F64) -> String {
 
 fn null_heap_ty(ty: HeapType<'_>) -> Result<json::Const> {
     Ok(match ty {
-        HeapType::Func => json::Const::FuncRef {
-            value: Some("null".to_string()),
-        },
-        HeapType::Extern => json::Const::ExternRef {
-            value: "null".to_string(),
-        },
-        HeapType::Any => json::Const::AnyRef {
-            value: Some("null".to_string()),
-        },
-        HeapType::None => json::Const::NullRef,
-        HeapType::NoFunc => json::Const::NullFuncRef,
-        HeapType::NoExtern => json::Const::NullExternRef,
-        HeapType::Exn => json::Const::ExnRef {
-            value: Some("null".to_string()),
-        },
+        HeapType::Abstract { shared, ty } => {
+            use AbstractHeapType::*;
+            if shared {
+                bail!("shared abstract types are not supported in `ref.null`")
+            }
+            match ty {
+                Func => json::Const::FuncRef {
+                    value: Some("null".to_string()),
+                },
+                Extern => json::Const::ExternRef {
+                    value: "null".to_string(),
+                },
+                Any => json::Const::AnyRef {
+                    value: Some("null".to_string()),
+                },
+                None => json::Const::NullRef,
+                NoFunc => json::Const::NullFuncRef,
+                NoExtern => json::Const::NullExternRef,
+                Exn => json::Const::ExnRef {
+                    value: Some("null".to_string()),
+                },
+                _ => bail!("unsupported abstract type found in `ref.null`"),
+            }
+        }
         _ => bail!("unsupported heap type found in `ref.null`"),
     })
 }


Test patch:
diff --git a/crates/wit-component/tests/components.rs b/crates/wit-component/tests/components.rs
index 87917cce..de2ff79c 100644
--- a/crates/wit-component/tests/components.rs
+++ b/crates/wit-component/tests/components.rs
@@ -245,7 +245,7 @@ fn read_core_module(path: &Path, resolve: &Resolve, pkg: PackageId) -> Result<Ve
     let mut wasm = wat::parse_file(path)?;
     let name = path.file_stem().and_then(|s| s.to_str()).unwrap();
     let world = resolve
-        .select_world(pkg, Some(name))
+        .select_world(&[pkg], Some(name))
         .context("failed to select a world")?;
 
     // Add this producer data to the wit-component metadata so we can make sure it gets through the
diff --git a/crates/wit-component/tests/interfaces.rs b/crates/wit-component/tests/interfaces.rs
index 926499db..87c7ac2d 100644
--- a/crates/wit-component/tests/interfaces.rs
+++ b/crates/wit-component/tests/interfaces.rs
@@ -51,7 +51,7 @@ fn run_test(path: &Path, is_dir: bool) -> Result<()> {
     let packages = if is_dir {
         resolve.push_dir(path)?.0
     } else {
-        resolve.append(UnresolvedPackageGroup::parse_file(path)?)?
+        resolve.push_file(path)?
     };
 
     for package in packages {
@@ -107,8 +107,7 @@ fn assert_print(resolve: &Resolve, pkg_ids: &[PackageId], path: &Path, is_dir: b
         assert_output(&expected, &output)?;
     }
 
-    UnresolvedPackageGroup::parse("foo.wit".as_ref(), &output)
-        .context("failed to parse printed output")?;
+    UnresolvedPackageGroup::parse("foo.wit", &output).context("failed to parse printed output")?;
     Ok(())
 }
 
diff --git a/crates/wit-component/tests/linking.rs b/crates/wit-component/tests/linking.rs
index 56757f97..f1d9ac35 100644
--- a/crates/wit-component/tests/linking.rs
+++ b/crates/wit-component/tests/linking.rs
@@ -1,8 +1,7 @@
 use {
     anyhow::{Context, Result},
-    std::path::Path,
     wit_component::StringEncoding,
-    wit_parser::{Resolve, UnresolvedPackageGroup},
+    wit_parser::Resolve,
 };
 
 const FOO: &str = r#"
@@ -141,12 +140,8 @@ fn encode(wat: &str, wit: Option<&str>) -> Result<Vec<u8>> {
 
     if let Some(wit) = wit {
         let mut resolve = Resolve::default();
-        let UnresolvedPackageGroup {
-            mut packages,
-            source_map,
-        } = UnresolvedPackageGroup::parse(Path::new("wit"), wit)?;
-        let pkg = resolve.push(packages.remove(0), &source_map)?;
-        let world = resolve.select_world(pkg, None)?;
+        let pkgs = resolve.push_str("test.wit", wit)?;
+        let world = resolve.select_world(&pkgs, None)?;
 
         wit_component::embed_component_metadata(
             &mut module,
diff --git a/crates/wit-component/tests/targets.rs b/crates/wit-component/tests/targets.rs
index 8dac12d3..7c9581a5 100644
--- a/crates/wit-component/tests/targets.rs
+++ b/crates/wit-component/tests/targets.rs
@@ -1,6 +1,6 @@
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result};
 use std::{fs, path::Path};
-use wit_parser::{Resolve, UnresolvedPackageGroup, WorldId};
+use wit_parser::{Resolve, WorldId};
 
 /// Tests whether a component targets a world.
 ///
@@ -22,7 +22,7 @@ use wit_parser::{Resolve, UnresolvedPackageGroup, WorldId};
 /// Run the test with the environment variable `BLESS` set to update `error.txt`.
 ///
 /// Each test is effectively executing as:
-/// ```wasm-tools component targets -w foobar test.wit test.wat```
+/// `wasm-tools component targets -w foobar test.wit test.wat`
 #[test]
 fn targets() -> Result<()> {
     drop(env_logger::try_init());
@@ -79,23 +79,10 @@ fn load_test_wit(path: &Path) -> Result<(Resolve, WorldId)> {
     const TEST_TARGET_WORLD_ID: &str = "foobar";
 
     let test_wit_path = path.join("test.wit");
-    let UnresolvedPackageGroup {
-        mut packages,
-        source_map,
-    } = UnresolvedPackageGroup::parse_file(&test_wit_path)
-        .context("failed to parse WIT package")?;
-    if packages.is_empty() {
-        bail!("Files were completely empty - are you sure these are the files you're looking for?")
-    }
-    if packages.len() > 1 {
-        bail!("Multi-package targeting tests are not yet supported.")
-    }
-
     let mut resolve = Resolve::default();
-    let package_id = resolve.push(packages.remove(0), &source_map)?;
-
+    let pkgs = resolve.push_file(&test_wit_path)?;
     let world_id = resolve
-        .select_world(package_id, Some(TEST_TARGET_WORLD_ID))
+        .select_world(&pkgs, Some(TEST_TARGET_WORLD_ID))
         .with_context(|| "failed to select world from package".to_string())?;
 
     Ok((resolve, world_id))
diff --git a/crates/wit-component/tests/wit.rs b/crates/wit-component/tests/wit.rs
index 860fd108..1930eabf 100644
--- a/crates/wit-component/tests/wit.rs
+++ b/crates/wit-component/tests/wit.rs
@@ -9,9 +9,9 @@ fn parse_wit_dir() -> Result<()> {
     drop(env_logger::try_init());
 
     let mut resolver = Resolve::default();
-    let package_id = resolver.push_path("tests/wit/parse-dir/wit")?.0[0];
+    let (package_ids, _) = resolver.push_path("tests/wit/parse-dir/wit")?;
     assert!(resolver
-        .select_world(package_id, "foo-world".into())
+        .select_world(&package_ids, "foo-world".into())
         .is_ok());
 
     Ok(())
@@ -23,10 +23,8 @@ fn parse_wit_file() -> Result<()> {
     drop(env_logger::try_init());
 
     let mut resolver = Resolve::default();
-    let package_id = resolver
-        .push_path("tests/wit/parse-dir/wit/deps/bar/bar.wit")?
-        .0[0];
-    resolver.select_world(package_id, "bar-world".into())?;
+    let (package_ids, _) = resolver.push_path("tests/wit/parse-dir/wit/deps/bar/bar.wit")?;
+    resolver.select_world(&package_ids, "bar-world".into())?;
     assert!(resolver
         .interfaces
         .iter()
diff --git a/tests/local/shared-everything-threads/global-heap-types.wast b/tests/local/shared-everything-threads/global-heap-types.wast
new file mode 100644
index 00000000..4b887fc1
--- /dev/null
+++ b/tests/local/shared-everything-threads/global-heap-types.wast
@@ -0,0 +1,311 @@
+;; Check shared attributes for global heap types.
+
+;; Concrete heap types cannot be marked shared yet (TODO: this is only possible
+;; once composite types can be marked shared).
+;;
+;; (assert_invalid
+;;   (module
+;;     (type $t (shared anyref))
+;;     (global (shared (ref null (shared $t))))))
+;;   "shared value type")
+
+;; `func` references.
+(module
+  ;; Imported (long/short forms, mut, null).
+  (global (import "spectest" "global_funcref") (shared (shared funcref)))
+  (global (import "spectest" "global_mut_funcref") (shared mut (shared funcref)))
+  (global (import "spectest" "global_ref_null_func") (shared (ref null (shared func))))
+  (global (import "spectest" "global_mut_ref_null_func") (shared mut (ref null (shared func))))
+  (global (import "spectest" "global_ref_func") (shared (ref (shared func))))
+  (global (import "spectest" "global_mut_ref_func") (shared mut (ref (shared func))))
+
+  ;; Initialized (long/short form).
+  (global (shared (shared funcref)) (ref.null (shared func)))
+  (global (shared (ref null (shared func))) (ref.null (shared func)))
+)
+
+(assert_invalid
+  (module (global (shared funcref)))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (shared (ref null func))))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (ref (shared func))))
+  "type mismatch")
+
+;; `extern` references.
+(module
+  ;; Imported (long/short forms, mut, null).
+  (global (import "spectest" "global_externref") (shared (shared externref)))
+  (global (import "spectest" "global_mut_externref") (shared mut (shared externref)))
+  (global (import "spectest" "global_ref_null_extern") (shared (ref null (shared extern))))
+  (global (import "spectest" "global_mut_ref_null_extern") (shared mut (ref null (shared extern))))
+  (global (import "spectest" "global_ref_extern") (shared (ref (shared extern))))
+  (global (import "spectest" "global_mut_ref_extern") (shared mut (ref (shared extern))))
+
+  ;; Initialized (long/short form).
+  (global (shared (shared externref)) (ref.null (shared extern)))
+  (global (shared (ref null (shared extern))) (ref.null (shared extern)))
+)
+
+(assert_invalid
+  (module (global (shared externref)))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (shared (ref null extern))))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (ref (shared extern))))
+  "type mismatch")
+
+;; `exn` references.
+(module
+  ;; Imported (long/short forms, mut, null).
+  (global (import "spectest" "global_exnref") (shared (shared exnref)))
+  (global (import "spectest" "global_mut_exnref") (shared mut (shared exnref)))
+  (global (import "spectest" "global_ref_null_exn") (shared (ref null (shared exn))))
+  (global (import "spectest" "global_mut_ref_null_exn") (shared mut (ref null (shared exn))))
+  (global (import "spectest" "global_ref_exn") (shared (ref (shared exn))))
+  (global (import "spectest" "global_mut_ref_exn") (shared mut (ref (shared exn))))
+
+  ;; Initialized (long/short form).
+  (global (shared (shared exnref)) (ref.null (shared exn)))
+  (global (shared (ref null (shared exn))) (ref.null (shared exn)))
+)
+
+(assert_invalid
+  (module (global (shared exnref)))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (shared (ref null exn))))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (ref (shared exn))))
+  "type mismatch")
+
+;; `any` references.
+(module
+  ;; Imported (long/short forms, mut, null).
+  (global (import "spectest" "global_anyref") (shared (shared anyref)))
+  (global (import "spectest" "global_mut_anyref") (shared mut (shared anyref)))
+  (global (import "spectest" "global_ref_null_any") (shared (ref null (shared any))))
+  (global (import "spectest" "global_mut_ref_null_any") (shared mut (ref null (shared any))))
+  (global (import "spectest" "global_ref_any") (shared (ref (shared any))))
+  (global (import "spectest" "global_mut_ref_any") (shared mut (ref (shared any))))
+
+  ;; Initialized (long/short form).
+  (global (shared (shared anyref)) (ref.null (shared any)))
+  (global (shared (ref null (shared any))) (ref.null (shared any)))
+)
+
+(assert_invalid
+  (module (global (shared anyref)))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (shared (ref null any))))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (ref (shared any))))
+  "type mismatch")
+
+;; `eq` references.
+(module
+  ;; Imported (long/short forms, mut, null).
+  (global (import "spectest" "global_eqref") (shared (shared eqref)))
+  (global (import "spectest" "global_mut_eqref") (shared mut (shared eqref)))
+  (global (import "spectest" "global_ref_null_eq") (shared (ref null (shared eq))))
+  (global (import "spectest" "global_mut_ref_null_eq") (shared mut (ref null (shared eq))))
+  (global (import "spectest" "global_ref_eq") (shared (ref (shared eq))))
+  (global (import "spectest" "global_mut_ref_eq") (shared mut (ref (shared eq))))
+
+  ;; Initialized (long/short form).
+  (global (shared (shared eqref)) (ref.null (shared eq)))
+  (global (shared (ref null (shared eq))) (ref.null (shared eq)))
+)
+
+(assert_invalid
+  (module (global (shared eqref)))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (shared (ref null eq))))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (ref (shared eq))))
+  "type mismatch")
+
+;; `struct` references.
+(module
+  ;; Imported (long/short forms, mut, null).
+  (global (import "spectest" "global_structref") (shared (shared structref)))
+  (global (import "spectest" "global_mut_structref") (shared mut (shared structref)))
+  (global (import "spectest" "global_ref_null_struct") (shared (ref null (shared struct))))
+  (global (import "spectest" "global_mut_ref_null_struct") (shared mut (ref null (shared struct))))
+  (global (import "spectest" "global_ref_struct") (shared (ref (shared struct))))
+  (global (import "spectest" "global_mut_ref_struct") (shared mut (ref (shared struct))))
+
+  ;; Initialized (long/short form).
+  (global (shared (shared structref)) (ref.null (shared struct)))
+  (global (shared (ref null (shared struct))) (ref.null (shared struct)))
+)
+
+(assert_invalid
+  (module (global (shared structref)))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (shared (ref null struct))))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (ref (shared struct))))
+  "type mismatch")
+
+;; `array` references.
+(module
+  ;; Imported (long/short forms, mut, null).
+  (global (import "spectest" "global_arrayref") (shared (shared arrayref)))
+  (global (import "spectest" "global_mut_arrayref") (shared mut (shared arrayref)))
+  (global (import "spectest" "global_ref_null_array") (shared (ref null (shared array))))
+  (global (import "spectest" "global_mut_ref_null_array") (shared mut (ref null (shared array))))
+  (global (import "spectest" "global_ref_array") (shared (ref (shared array))))
+  (global (import "spectest" "global_mut_ref_array") (shared mut (ref (shared array))))
+
+  ;; Initialized (long/short form).
+  (global (shared (shared arrayref)) (ref.null (shared array)))
+  (global (shared (ref null (shared array))) (ref.null (shared array)))
+)
+
+(assert_invalid
+  (module (global (shared arrayref)))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (shared (ref null array))))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (ref (shared array))))
+  "type mismatch")
+
+;; `i31` references.
+(module
+  ;; Imported (long/short forms, mut, null).
+  (global (import "spectest" "global_i31ref") (shared (shared i31ref)))
+  (global (import "spectest" "global_mut_i31ref") (shared mut (shared i31ref)))
+  (global (import "spectest" "global_ref_null_i31") (shared (ref null (shared i31))))
+  (global (import "spectest" "global_mut_ref_null_i31") (shared mut (ref null (shared i31))))
+  (global (import "spectest" "global_ref_i31") (shared (ref (shared i31))))
+  (global (import "spectest" "global_mut_ref_i31") (shared mut (ref (shared i31))))
+
+  ;; Initialized (long/short form).
+  (global (shared (shared i31ref)) (ref.null (shared i31)))
+  (global (shared (ref null (shared i31))) (ref.null (shared i31)))
+)
+
+(assert_invalid
+  (module (global (shared i31ref)))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (shared (ref null i31))))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (ref (shared i31))))
+  "type mismatch")
+
+;; `nofunc` references.
+(module
+  ;; Imported (long/short forms, mut, null).
+  (global (import "spectest" "global_nullfuncref") (shared (shared nullfuncref)))
+  (global (import "spectest" "global_mut_nullfuncref") (shared mut (shared nullfuncref)))
+  (global (import "spectest" "global_ref_null_nofunc") (shared (ref null (shared nofunc))))
+  (global (import "spectest" "global_mut_ref_null_nofunc") (shared mut (ref null (shared nofunc))))
+  (global (import "spectest" "global_ref_nofunc") (shared (ref (shared nofunc))))
+  (global (import "spectest" "global_mut_ref_nofunc") (shared mut (ref (shared nofunc))))
+
+  ;; Initialized (long/short form).
+  (global (shared (shared nullfuncref)) (ref.null (shared nofunc)))
+  (global (shared (ref null (shared nofunc))) (ref.null (shared nofunc)))
+)
+
+(assert_invalid
+  (module (global (shared nullfuncref)))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (shared (ref null nofunc))))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (ref (shared nofunc))))
+  "type mismatch")
+
+;; `noextern` references.
+(module
+  ;; Imported (long/short forms, mut, null).
+  (global (import "spectest" "global_nullexternref") (shared (shared nullexternref)))
+  (global (import "spectest" "global_mut_nullexternref") (shared mut (shared nullexternref)))
+  (global (import "spectest" "global_ref_null_noextern") (shared (ref null (shared noextern))))
+  (global (import "spectest" "global_mut_ref_null_noextern") (shared mut (ref null (shared noextern))))
+  (global (import "spectest" "global_ref_noextern") (shared (ref (shared noextern))))
+  (global (import "spectest" "global_mut_ref_noextern") (shared mut (ref (shared noextern))))
+
+  ;; Initialized (long/short form).
+  (global (shared (shared nullexternref)) (ref.null (shared noextern)))
+  (global (shared (ref null (shared noextern))) (ref.null (shared noextern)))
+)
+
+(assert_invalid
+  (module (global (shared nullexternref)))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (shared (ref null noextern))))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (ref (shared noextern))))
+  "type mismatch")
+
+;; `noexn` references.
+(module
+  ;; Imported (long/short forms, mut, null).
+  (global (import "spectest" "global_nullexnref") (shared (shared nullexnref)))
+  (global (import "spectest" "global_mut_nullexnref") (shared mut (shared nullexnref)))
+  (global (import "spectest" "global_ref_null_noexn") (shared (ref null (shared noexn))))
+  (global (import "spectest" "global_mut_ref_null_noexn") (shared mut (ref null (shared noexn))))
+  (global (import "spectest" "global_ref_noexn") (shared (ref (shared noexn))))
+  (global (import "spectest" "global_mut_ref_noexn") (shared mut (ref (shared noexn))))
+
+  ;; Initialized (long/short form).
+  (global (shared (shared nullexnref)) (ref.null (shared noexn)))
+  (global (shared (ref null (shared noexn))) (ref.null (shared noexn)))
+)
+
+(assert_invalid
+  (module (global (shared nullexnref)))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (shared (ref null noexn))))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (ref (shared noexn))))
+  "type mismatch")
+
+;; `none` references.
+(module
+  ;; Imported (long/short forms, mut, null).
+  (global (import "spectest" "global_nullref") (shared (shared nullref)))
+  (global (import "spectest" "global_mut_nullref") (shared mut (shared nullref)))
+  (global (import "spectest" "global_ref_null_none") (shared (ref null (shared none))))
+  (global (import "spectest" "global_mut_ref_null_none") (shared mut (ref null (shared none))))
+  (global (import "spectest" "global_ref_none") (shared (ref (shared none))))
+  (global (import "spectest" "global_mut_ref_none") (shared mut (ref (shared none))))
+
+  ;; Initialized (long/short form).
+  (global (shared (shared nullref)) (ref.null (shared none)))
+  (global (shared (ref null (shared none))) (ref.null (shared none)))
+)
+
+(assert_invalid
+  (module (global (shared nullref)))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (shared (ref null none))))
+  "shared globals must have a shared value type")
+(assert_invalid
+  (module (global (ref (shared none))))
+  "type mismatch")
+
diff --git a/tests/local/shared-everything-threads/global.wast b/tests/local/shared-everything-threads/global.wast
index ad33bf2b..9d47be9f 100644
--- a/tests/local/shared-everything-threads/global.wast
+++ b/tests/local/shared-everything-threads/global.wast
@@ -35,7 +35,6 @@
   "shared value type")
 
 ;; Check global.atomic.get, global.atomic.set.
-
 (module
   (global $a (import "spectest" "global_i32") (shared i32))
   (global $b (import "spectest" "global_i64") (shared mut i64))
diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast.json b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast.json
new file mode 100644
index 00000000..526b9da7
--- /dev/null
+++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast.json
@@ -0,0 +1,317 @@
+{
+  "source_filename": "tests/local/shared-everything-threads/global-heap-types.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 13,
+      "filename": "global-heap-types.0.wasm"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 28,
+      "filename": "global-heap-types.1.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 31,
+      "filename": "global-heap-types.2.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 34,
+      "filename": "global-heap-types.3.wasm",
+      "text": "type mismatch",
+      "module_type": "binary"
+    },
+    {
+      "type": "module",
+      "line": 38,
+      "filename": "global-heap-types.4.wasm"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 53,
+      "filename": "global-heap-types.5.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 56,
+      "filename": "global-heap-types.6.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 59,
+      "filename": "global-heap-types.7.wasm",
+      "text": "type mismatch",
+      "module_type": "binary"
+    },
+    {
+      "type": "module",
+      "line": 63,
+      "filename": "global-heap-types.8.wasm"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 78,
+      "filename": "global-heap-types.9.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 81,
+      "filename": "global-heap-types.10.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 84,
+      "filename": "global-heap-types.11.wasm",
+      "text": "type mismatch",
+      "module_type": "binary"
+    },
+    {
+      "type": "module",
+      "line": 88,
+      "filename": "global-heap-types.12.wasm"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 103,
+      "filename": "global-heap-types.13.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 106,
+      "filename": "global-heap-types.14.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 109,
+      "filename": "global-heap-types.15.wasm",
+      "text": "type mismatch",
+      "module_type": "binary"
+    },
+    {
+      "type": "module",
+      "line": 113,
+      "filename": "global-heap-types.16.wasm"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 128,
+      "filename": "global-heap-types.17.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 131,
+      "filename": "global-heap-types.18.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 134,
+      "filename": "global-heap-types.19.wasm",
+      "text": "type mismatch",
+      "module_type": "binary"
+    },
+    {
+      "type": "module",
+      "line": 138,
+      "filename": "global-heap-types.20.wasm"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 153,
+      "filename": "global-heap-types.21.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 156,
+      "filename": "global-heap-types.22.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 159,
+      "filename": "global-heap-types.23.wasm",
+      "text": "type mismatch",
+      "module_type": "binary"
+    },
+    {
+      "type": "module",
+      "line": 163,
+      "filename": "global-heap-types.24.wasm"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 178,
+      "filename": "global-heap-types.25.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 181,
+      "filename": "global-heap-types.26.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 184,
+      "filename": "global-heap-types.27.wasm",
+      "text": "type mismatch",
+      "module_type": "binary"
+    },
+    {
+      "type": "module",
+      "line": 188,
+      "filename": "global-heap-types.28.wasm"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 203,
+      "filename": "global-heap-types.29.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 206,
+      "filename": "global-heap-types.30.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 209,
+      "filename": "global-heap-types.31.wasm",
+      "text": "type mismatch",
+      "module_type": "binary"
+    },
+    {
+      "type": "module",
+      "line": 213,
+      "filename": "global-heap-types.32.wasm"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 228,
+      "filename": "global-heap-types.33.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 231,
+      "filename": "global-heap-types.34.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 234,
+      "filename": "global-heap-types.35.wasm",
+      "text": "type mismatch",
+      "module_type": "binary"
+    },
+    {
+      "type": "module",
+      "line": 238,
+      "filename": "global-heap-types.36.wasm"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 253,
+      "filename": "global-heap-types.37.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 256,
+      "filename": "global-heap-types.38.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 259,
+      "filename": "global-heap-types.39.wasm",
+      "text": "type mismatch",
+      "module_type": "binary"
+    },
+    {
+      "type": "module",
+      "line": 263,
+      "filename": "global-heap-types.40.wasm"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 278,
+      "filename": "global-heap-types.41.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 281,
+      "filename": "global-heap-types.42.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 284,
+      "filename": "global-heap-types.43.wasm",
+      "text": "type mismatch",
+      "module_type": "binary"
+    },
+    {
+      "type": "module",
+      "line": 288,
+      "filename": "global-heap-types.44.wasm"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 303,
+      "filename": "global-heap-types.45.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 306,
+      "filename": "global-heap-types.46.wasm",
+      "text": "shared globals must have a shared value type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 309,
+      "filename": "global-heap-types.47.wasm",
+      "text": "type mismatch",
+      "module_type": "binary"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/0.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/0.print
new file mode 100644
index 00000000..c9606e27
--- /dev/null
+++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/0.print
@@ -0,0 +1,10 @@
+(module
+  (import "spectest" "global_funcref" (global (;0;) (shared (ref null (shared func)))))
+  (import "spectest" "global_mut_funcref" (global (;1;) (shared mut (ref null (shared func)))))
+  (import "spectest" "global_ref_null_func" (global (;2;) (shared (ref null (shared func)))))
+  (import "spectest" "global_mut_ref_null_func" (global (;3;) (shared mut (ref null (shared func)))))
+  (import "spectest" "global_ref_func" (global (;4;) (shared (ref (shared func)))))
+  (import "spectest" "global_mut_ref_func" (global (;5;) (shared mut (ref (shared func)))))
+  (global (;6;) (shared (ref null (shared func))) ref.null (shared func))
+  (global (;7;) (shared (ref null (shared func))) ref.null (shared func))
+)
diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/12.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/12.print
new file mode 100644
index 00000000..0c408cd8
--- /dev/null
+++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/12.print
@@ -0,0 +1,10 @@
+(module
+  (import "spectest" "global_anyref" (global (;0;) (shared (ref null (shared any)))))
+  (import "spectest" "global_mut_anyref" (global (;1;) (shared mut (ref null (shared any)))))
+  (import "spectest" "global_ref_null_any" (global (;2;) (shared (ref null (shared any)))))
+  (import "spectest" "global_mut_ref_null_any" (global (;3;) (shared mut (ref null (shared any)))))
+  (import "spectest" "global_ref_any" (global (;4;) (shared (ref (shared any)))))
+  (import "spectest" "global_mut_ref_any" (global (;5;) (shared mut (ref (shared any)))))
+  (global (;6;) (shared (ref null (shared any))) ref.null (shared any))
+  (global (;7;) (shared (ref null (shared any))) ref.null (shared any))
+)
diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/16.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/16.print
new file mode 100644
index 00000000..63238fb9
--- /dev/null
+++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/16.print
@@ -0,0 +1,10 @@
+(module
+  (import "spectest" "global_eqref" (global (;0;) (shared (ref null (shared eq)))))
+  (import "spectest" "global_mut_eqref" (global (;1;) (shared mut (ref null (shared eq)))))
+  (import "spectest" "global_ref_null_eq" (global (;2;) (shared (ref null (shared eq)))))
+  (import "spectest" "global_mut_ref_null_eq" (global (;3;) (shared mut (ref null (shared eq)))))
+  (import "spectest" "global_ref_eq" (global (;4;) (shared (ref (shared eq)))))
+  (import "spectest" "global_mut_ref_eq" (global (;5;) (shared mut (ref (shared eq)))))
+  (global (;6;) (shared (ref null (shared eq))) ref.null (shared eq))
+  (global (;7;) (shared (ref null (shared eq))) ref.null (shared eq))
+)
diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/20.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/20.print
new file mode 100644
index 00000000..364673e0
--- /dev/null
+++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/20.print
@@ -0,0 +1,10 @@
+(module
+  (import "spectest" "global_structref" (global (;0;) (shared (ref null (shared struct)))))
+  (import "spectest" "global_mut_structref" (global (;1;) (shared mut (ref null (shared struct)))))
+  (import "spectest" "global_ref_null_struct" (global (;2;) (shared (ref null (shared struct)))))
+  (import "spectest" "global_mut_ref_null_struct" (global (;3;) (shared mut (ref null (shared struct)))))
+  (import "spectest" "global_ref_struct" (global (;4;) (shared (ref (shared struct)))))
+  (import "spectest" "global_mut_ref_struct" (global (;5;) (shared mut (ref (shared struct)))))
+  (global (;6;) (shared (ref null (shared struct))) ref.null (shared struct))
+  (global (;7;) (shared (ref null (shared struct))) ref.null (shared struct))
+)
diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/24.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/24.print
new file mode 100644
index 00000000..834454e7
--- /dev/null
+++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/24.print
@@ -0,0 +1,10 @@
+(module
+  (import "spectest" "global_arrayref" (global (;0;) (shared (ref null (shared array)))))
+  (import "spectest" "global_mut_arrayref" (global (;1;) (shared mut (ref null (shared array)))))
+  (import "spectest" "global_ref_null_array" (global (;2;) (shared (ref null (shared array)))))
+  (import "spectest" "global_mut_ref_null_array" (global (;3;) (shared mut (ref null (shared array)))))
+  (import "spectest" "global_ref_array" (global (;4;) (shared (ref (shared array)))))
+  (import "spectest" "global_mut_ref_array" (global (;5;) (shared mut (ref (shared array)))))
+  (global (;6;) (shared (ref null (shared array))) ref.null (shared array))
+  (global (;7;) (shared (ref null (shared array))) ref.null (shared array))
+)
diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/28.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/28.print
new file mode 100644
index 00000000..b2ececf7
--- /dev/null
+++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/28.print
@@ -0,0 +1,10 @@
+(module
+  (import "spectest" "global_i31ref" (global (;0;) (shared (ref null (shared i31)))))
+  (import "spectest" "global_mut_i31ref" (global (;1;) (shared mut (ref null (shared i31)))))
+  (import "spectest" "global_ref_null_i31" (global (;2;) (shared (ref null (shared i31)))))
+  (import "spectest" "global_mut_ref_null_i31" (global (;3;) (shared mut (ref null (shared i31)))))
+  (import "spectest" "global_ref_i31" (global (;4;) (shared (ref (shared i31)))))
+  (import "spectest" "global_mut_ref_i31" (global (;5;) (shared mut (ref (shared i31)))))
+  (global (;6;) (shared (ref null (shared i31))) ref.null (shared i31))
+  (global (;7;) (shared (ref null (shared i31))) ref.null (shared i31))
+)
diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/32.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/32.print
new file mode 100644
index 00000000..8a79cdb6
--- /dev/null
+++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/32.print
@@ -0,0 +1,10 @@
+(module
+  (import "spectest" "global_nullfuncref" (global (;0;) (shared (ref null (shared nofunc)))))
+  (import "spectest" "global_mut_nullfuncref" (global (;1;) (shared mut (ref null (shared nofunc)))))
+  (import "spectest" "global_ref_null_nofunc" (global (;2;) (shared (ref null (shared nofunc)))))
+  (import "spectest" "global_mut_ref_null_nofunc" (global (;3;) (shared mut (ref null (shared nofunc)))))
+  (import "spectest" "global_ref_nofunc" (global (;4;) (shared (ref (shared nofunc)))))
+  (import "spectest" "global_mut_ref_nofunc" (global (;5;) (shared mut (ref (shared nofunc)))))
+  (global (;6;) (shared (ref null (shared nofunc))) ref.null (shared nofunc))
+  (global (;7;) (shared (ref null (shared nofunc))) ref.null (shared nofunc))
+)
diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/36.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/36.print
new file mode 100644
index 00000000..a4665230
--- /dev/null
+++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/36.print
@@ -0,0 +1,10 @@
+(module
+  (import "spectest" "global_nullexternref" (global (;0;) (shared (ref null (shared noextern)))))
+  (import "spectest" "global_mut_nullexternref" (global (;1;) (shared mut (ref null (shared noextern)))))
+  (import "spectest" "global_ref_null_noextern" (global (;2;) (shared (ref null (shared noextern)))))
+  (import "spectest" "global_mut_ref_null_noextern" (global (;3;) (shared mut (ref null (shared noextern)))))
+  (import "spectest" "global_ref_noextern" (global (;4;) (shared (ref (shared noextern)))))
+  (import "spectest" "global_mut_ref_noextern" (global (;5;) (shared mut (ref (shared noextern)))))
+  (global (;6;) (shared (ref null (shared noextern))) ref.null (shared noextern))
+  (global (;7;) (shared (ref null (shared noextern))) ref.null (shared noextern))
+)
diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/4.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/4.print
new file mode 100644
index 00000000..be075a72
--- /dev/null
+++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/4.print
@@ -0,0 +1,10 @@
+(module
+  (import "spectest" "global_externref" (global (;0;) (shared (ref null (shared extern)))))
+  (import "spectest" "global_mut_externref" (global (;1;) (shared mut (ref null (shared extern)))))
+  (import "spectest" "global_ref_null_extern" (global (;2;) (shared (ref null (shared extern)))))
+  (import "spectest" "global_mut_ref_null_extern" (global (;3;) (shared mut (ref null (shared extern)))))
+  (import "spectest" "global_ref_extern" (global (;4;) (shared (ref (shared extern)))))
+  (import "spectest" "global_mut_ref_extern" (global (;5;) (shared mut (ref (shared extern)))))
+  (global (;6;) (shared (ref null (shared extern))) ref.null (shared extern))
+  (global (;7;) (shared (ref null (shared extern))) ref.null (shared extern))
+)
diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/40.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/40.print
new file mode 100644
index 00000000..c1270ef9
--- /dev/null
+++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/40.print
@@ -0,0 +1,10 @@
+(module
+  (import "spectest" "global_nullexnref" (global (;0;) (shared (ref null (shared noexn)))))
+  (import "spectest" "global_mut_nullexnref" (global (;1;) (shared mut (ref null (shared noexn)))))
+  (import "spectest" "global_ref_null_noexn" (global (;2;) (shared (ref null (shared noexn)))))
+  (import "spectest" "global_mut_ref_null_noexn" (global (;3;) (shared mut (ref null (shared noexn)))))
+  (import "spectest" "global_ref_noexn" (global (;4;) (shared (ref (shared noexn)))))
+  (import "spectest" "global_mut_ref_noexn" (global (;5;) (shared mut (ref (shared noexn)))))
+  (global (;6;) (shared (ref null (shared noexn))) ref.null (shared noexn))
+  (global (;7;) (shared (ref null (shared noexn))) ref.null (shared noexn))
+)
diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/44.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/44.print
new file mode 100644
index 00000000..b141dfd3
--- /dev/null
+++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/44.print
@@ -0,0 +1,10 @@
+(module
+  (import "spectest" "global_nullref" (global (;0;) (shared (ref null (shared none)))))
+  (import "spectest" "global_mut_nullref" (global (;1;) (shared mut (ref null (shared none)))))
+  (import "spectest" "global_ref_null_none" (global (;2;) (shared (ref null (shared none)))))
+  (import "spectest" "global_mut_ref_null_none" (global (;3;) (shared mut (ref null (shared none)))))
+  (import "spectest" "global_ref_none" (global (;4;) (shared (ref (shared none)))))
+  (import "spectest" "global_mut_ref_none" (global (;5;) (shared mut (ref (shared none)))))
+  (global (;6;) (shared (ref null (shared none))) ref.null (shared none))
+  (global (;7;) (shared (ref null (shared none))) ref.null (shared none))
+)
diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/8.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/8.print
new file mode 100644
index 00000000..293ad9db
--- /dev/null
+++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/8.print
@@ -0,0 +1,10 @@
+(module
+  (import "spectest" "global_exnref" (global (;0;) (shared (ref null (shared exn)))))
+  (import "spectest" "global_mut_exnref" (global (;1;) (shared mut (ref null (shared exn)))))
+  (import "spectest" "global_ref_null_exn" (global (;2;) (shared (ref null (shared exn)))))
+  (import "spectest" "global_mut_ref_null_exn" (global (;3;) (shared mut (ref null (shared exn)))))
+  (import "spectest" "global_ref_exn" (global (;4;) (shared (ref (shared exn)))))
+  (import "spectest" "global_mut_ref_exn" (global (;5;) (shared mut (ref (shared exn)))))
+  (global (;6;) (shared (ref null (shared exn))) ref.null (shared exn))
+  (global (;7;) (shared (ref null (shared exn))) ref.null (shared exn))
+)
diff --git a/tests/snapshots/local/shared-everything-threads/global.wast.json b/tests/snapshots/local/shared-everything-threads/global.wast.json
index 250e98b1..3d4ab677 100644
--- a/tests/snapshots/local/shared-everything-threads/global.wast.json
+++ b/tests/snapshots/local/shared-everything-threads/global.wast.json
@@ -22,150 +22,150 @@
     },
     {
       "type": "module",
-      "line": 39,
+      "line": 38,
       "filename": "global.3.wasm"
     },
     {
       "type": "assert_invalid",
-      "line": 52,
+      "line": 51,
       "filename": "global.4.wasm",
       "text": "global is immutable",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 59,
+      "line": 58,
       "filename": "global.5.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 66,
+      "line": 65,
       "filename": "global.6.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 73,
+      "line": 72,
       "filename": "global.7.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 80,
+      "line": 79,
       "filename": "global.8.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 87,
+      "line": 86,
       "filename": "global.9.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 94,
+      "line": 93,
       "filename": "global.10.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 101,
+      "line": 100,
       "filename": "global.11.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 108,
+      "line": 107,
       "filename": "global.12.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 115,
+      "line": 114,
       "filename": "global.13.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 122,
+      "line": 121,
       "filename": "global.14.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 129,
+      "line": 128,
       "filename": "global.15.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 136,
+      "line": 135,
       "filename": "global.16.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 143,
+      "line": 142,
       "filename": "global.17.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 150,
+      "line": 149,
       "filename": "global.18.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 157,
+      "line": 156,
       "filename": "global.19.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 164,
+      "line": 163,
       "filename": "global.20.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 171,
+      "line": 170,
       "filename": "global.21.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 178,
+      "line": 177,
       "filename": "global.22.wasm",
       "text": "invalid type",
       "module_type": "binary"
     },
     {
       "type": "module",
-      "line": 185,
+      "line": 184,
       "filename": "global.23.wasm"
     },
     {
       "type": "module",
-      "line": 278,
+      "line": 277,
       "filename": "global.24.wasm"
     }
   ]