Repository: bytecodealliance/wasmtime
Pull request #7995 (2024-02-24 00:49:26)

Add a "custom" platform configuration for Wasmtime
This commit leverages adds a new "platform" to Wasmtime to be supported in the `crates/runtime/src/sys` folder. This joins preexisting platforms such as Unix and Windows. The goal of this platform is to be an opt-in way to build Wasmtime for targets that don't have a predefined way to run.

The new "custom" platform requires `--cfg wasmtime_custom_platform` to be passed to the Rust compiler, for example by using `RUSTFLAGS`. This new platform bottoms out in a C API that is intended to be small and Linux-like. The C API is effectively the interface to virtual memory that Wasmtime requires. This C API is also available as a header file at `examples/min-platform/embedding/wasmtime-platform.h` (generated by `cbindgen`).

The main purpose of this is to make it easier to experiment with porting Wasmtime to new platforms. By decoupling a platform implementation from Wasmtime itself it should be possible to run these experiments out-of-tree. An example of this I've been working on is getting Wasmtime running on bare-metal with a custom kernel. This support enables defining the platform interface of the custom kernel's syscalls outside of Wasmtime.

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->


alexcrichton: Ok I've updated to make the C API fallible and Wasmtime propagates the error codes (thanks for pushing back on that @bjorn3!)

Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 74ce0cb54..494311255 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -56,6 +56,8 @@ jobs:
         GH_TOKEN: ${{ github.token }}
 
   # Check Code style quickly by running `clang-format` over all the C/C++ code
+  #
+  # Note that `wasmtime-platform.h` is excluded here as it's auto-generated.
   clangformat:
     name: Clang format
     runs-on: ubuntu-latest
@@ -63,7 +65,7 @@ jobs:
     - uses: actions/checkout@v4
       with:
         submodules: true
-    - run: git ls-files '*.h' '*.c' '*.cpp' | xargs clang-format-15 --dry-run --Werror --verbose
+    - run: git ls-files '*.h' '*.c' '*.cpp' | grep -v wasmtime-platform.h | xargs clang-format-15 --dry-run --Werror --verbose
 
     # common logic to cancel the entire run if this job fails
     - run: gh run cancel ${{ github.run_id }}
@@ -134,7 +136,7 @@ jobs:
       test-matrix: ${{ steps.calculate.outputs.test-matrix }}
       build-matrix: ${{ steps.calculate.outputs.build-matrix }}
       test-capi: ${{ steps.calculate.outputs.test-capi }}
-      build-fuzz: ${{ steps.calculate.outputs.build-fuzz }}
+      test-nightly: ${{ steps.calculate.outputs.test-nightly }}
       audit: ${{ steps.calculate.outputs.audit }}
       preview1-adapter: ${{ steps.calculate.outputs.preview1-adapter }}
     steps:
@@ -162,7 +164,10 @@ jobs:
             echo test-capi=true >> $GITHUB_OUTPUT
           fi
           if grep -q fuzz names.log; then
-            echo build-fuzz=true >> $GITHUB_OUTPUT
+            echo test-nightly=true >> $GITHUB_OUTPUT
+          fi
+          if grep -q sys.custom names.log; then
+            echo test-nightly=true >> $GITHUB_OUTPUT
           fi
           if grep -q Cargo.lock names.log; then
             echo audit=true >> $GITHUB_OUTPUT
@@ -184,7 +189,7 @@ jobs:
         if [ "$run_full" = "true" ]; then
             echo run-full=true >> $GITHUB_OUTPUT
             echo test-capi=true >> $GITHUB_OUTPUT
-            echo build-fuzz=true >> $GITHUB_OUTPUT
+            echo test-nightly=true >> $GITHUB_OUTPUT
             echo audit=true >> $GITHUB_OUTPUT
             echo preview1-adapter=true >> $GITHUB_OUTPUT
         fi
@@ -378,31 +383,43 @@ jobs:
       env:
         GH_TOKEN: ${{ github.token }}
 
-  # Verify all fuzz targets compile successfully
-  fuzz_targets:
+  # Run tests that require a nightly compiler, such as building fuzz targets.
+  test_nightly:
     needs: determine
-    if: needs.determine.outputs.build-fuzz
-    name: Fuzz Targets
+    if: needs.determine.outputs.test-nightly
+    name: Nightly tests
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@v4
       with:
         submodules: true
-    # Note that building with fuzzers requires nightly since it uses unstable
-    # flags to rustc.
+    # Note that nightly is pinned here to insulate us from breakage that might
+    # happen upstream. This is periodically updated through a PR.
     - uses: ./.github/actions/install-rust
       with:
         toolchain: nightly-2024-02-22
-    - run: cargo install cargo-fuzz --vers "^0.11"
+
+    # Ensure that fuzzers sitll build.
+    #
     # Install the OCaml packages necessary for fuzz targets that use the
     # `wasm-spec-interpreter`.
+    - run: cargo install cargo-fuzz --vers "^0.11"
     - run: sudo apt-get update && sudo apt install -y ocaml-nox ocamlbuild ocaml-findlib libzarith-ocaml-dev
     - run: cargo fetch
       working-directory: ./fuzz
     - run: cargo fuzz build --dev -s none
-    # Check that the ISLE fuzz targets build too.
     - run: cargo fuzz build --dev -s none --fuzz-dir ./cranelift/isle/fuzz
 
+    # Verify the "min platform" example still works.
+    #
+    # Afterwards make sure the generated header file is up to date by ensuring
+    # that the regeneration process didn't change anything in-tree.
+    - run: cargo install cbindgen --vers "^0.26"
+    - run: rustup component add rust-src
+    - run: ./build.sh ./embedding/x86_64-unknown-unknown.json
+      working-directory: ./examples/min-platform
+    - run: git diff --exit-code
+
     # common logic to cancel the entire run if this job fails
     - run: gh run cancel ${{ github.run_id }}
       if: failure() && github.event_name != 'pull_request'
@@ -599,6 +616,7 @@ jobs:
 
   build-wasmtime-target-wasm32:
     name: Build wasmtime-target-wasm32
+    if: needs.determine.outputs.run-full
     needs: determine
     runs-on: ubuntu-latest
     steps:
@@ -802,18 +820,20 @@ jobs:
       - test
       - build
       - rustfmt
+      - clangformat
       - cargo_deny
       - cargo_vet
       - doc
       - checks
       - checks_winarm64
-      - fuzz_targets
+      - test_nightly
       - bench
       - meta_deterministic_check
       - verify-publish
       - determine
       - miri
       - build-preview1-component-adapter
+      - build-wasmtime-target-wasm32
     if: always()
     steps:
     - name: Dump needs context
diff --git a/Cargo.lock b/Cargo.lock
index 545d0d943..83fd45e37 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -653,7 +653,7 @@ dependencies = [
  "target-lexicon",
  "thiserror",
  "toml",
- "wasmparser",
+ "wasmparser 0.201.0",
  "wat",
 ]
 
@@ -832,7 +832,7 @@ dependencies = [
  "serde_derive",
  "smallvec",
  "target-lexicon",
- "wasmparser",
+ "wasmparser 0.201.0",
  "wasmtime-types",
  "wat",
 ]
@@ -993,6 +993,15 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "dlmalloc"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "203540e710bfadb90e5e29930baf5d10270cec1f43ab34f46f78b147b2de715a"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "downcast-rs"
 version = "1.2.0"
@@ -1019,6 +1028,15 @@ version = "1.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
 
+[[package]]
+name = "embedding"
+version = "19.0.0"
+dependencies = [
+ "anyhow",
+ "dlmalloc",
+ "wasmtime",
+]
+
 [[package]]
 name = "encode_unicode"
 version = "0.3.6"
@@ -1765,6 +1783,15 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "min-platform-host"
+version = "19.0.0"
+dependencies = [
+ "anyhow",
+ "libloading",
+ "object",
+]
+
 [[package]]
 name = "miniz_oxide"
 version = "0.7.1"
@@ -2620,7 +2647,7 @@ version = "0.0.0"
 dependencies = [
  "cargo_metadata",
  "heck",
- "wit-component",
+ "wit-component 0.201.0",
 ]
 
 [[package]]
@@ -2930,7 +2957,7 @@ name = "verify-component-adapter"
 version = "19.0.0"
 dependencies = [
  "anyhow",
- "wasmparser",
+ "wasmparser 0.201.0",
  "wat",
 ]
 
@@ -3021,7 +3048,7 @@ dependencies = [
  "byte-array-literals",
  "object",
  "wasi",
- "wasm-encoder",
+ "wasm-encoder 0.201.0",
  "wit-bindgen",
 ]
 
@@ -3088,6 +3115,15 @@ dependencies = [
  "leb128",
 ]
 
+[[package]]
+name = "wasm-encoder"
+version = "0.201.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9c7d2731df60006819b013f64ccc2019691deccf6e11a1804bc850cd6748f1a"
+dependencies = [
+ "leb128",
+]
+
 [[package]]
 name = "wasm-metadata"
 version = "0.200.0"
@@ -3100,36 +3136,52 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "spdx",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.200.0",
+ "wasmparser 0.200.0",
+]
+
+[[package]]
+name = "wasm-metadata"
+version = "0.201.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fd83062c17b9f4985d438603cde0a5e8c5c8198201a6937f778b607924c7da2"
+dependencies = [
+ "anyhow",
+ "indexmap 2.0.0",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "spdx",
+ "wasm-encoder 0.201.0",
+ "wasmparser 0.201.0",
 ]
 
 [[package]]
 name = "wasm-mutate"
-version = "0.200.0"
+version = "0.201.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "940164dfefc300dbe5e2c310d7b67e9a502a03083bcec9fcbc8e852b79214135"
+checksum = "e4998a6d2c774d9ead7fd7e4d4c932b78c665ac893fd067ae7e18a62c3c4af71"
 dependencies = [
  "egg",
  "log",
  "rand",
  "thiserror",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.201.0",
+ "wasmparser 0.201.0",
 ]
 
 [[package]]
 name = "wasm-smith"
-version = "0.200.0"
+version = "0.201.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c94f72d4693c627c5d0b73b95f24f73566a610fd748b57216ea90f400d09f55"
+checksum = "61ff53a54a853f174b0df74cdb1553f1451e7bcdc23b26b1379f664ee1913d1a"
 dependencies = [
  "anyhow",
  "arbitrary",
  "flagset",
  "indexmap 2.0.0",
  "leb128",
- "wasm-encoder",
+ "wasm-encoder 0.201.0",
 ]
 
 [[package]]
@@ -3183,6 +3235,17 @@ dependencies = [
  "semver",
 ]
 
+[[package]]
+name = "wasmparser"
+version = "0.201.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708"
+dependencies = [
+ "bitflags 2.4.1",
+ "indexmap 2.0.0",
+ "semver",
+]
+
 [[package]]
 name = "wasmparser-nostd"
 version = "0.100.1"
@@ -3194,12 +3257,12 @@ dependencies = [
 
 [[package]]
 name = "wasmprinter"
-version = "0.200.0"
+version = "0.201.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bd153b0dce4e727565ccb3bd41b0bd2b62b2b9b6b1057d1cdb7fe8e0f3e06ab"
+checksum = "a67e66da702706ba08729a78e3c0079085f6bfcb1a62e4799e97bbf728c2c265"
 dependencies = [
  "anyhow",
- "wasmparser",
+ "wasmparser 0.201.0",
 ]
 
 [[package]]
@@ -3231,8 +3294,8 @@ dependencies = [
  "target-lexicon",
  "tempfile",
  "wasi-common",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.201.0",
+ "wasmparser 0.201.0",
  "wasmtime-cache",
  "wasmtime-component-macro",
  "wasmtime-component-util",
@@ -3363,7 +3426,7 @@ dependencies = [
  "tracing",
  "walkdir",
  "wasi-common",
- "wasmparser",
+ "wasmparser 0.201.0",
  "wasmtime",
  "wasmtime-cache",
  "wasmtime-cli-flags",
@@ -3377,10 +3440,10 @@ dependencies = [
  "wasmtime-wasi-nn",
  "wasmtime-wasi-threads",
  "wasmtime-wast",
- "wast 200.0.0",
+ "wast 201.0.0",
  "wat",
  "windows-sys 0.52.0",
- "wit-component",
+ "wit-component 0.201.0",
 ]
 
 [[package]]
@@ -3409,7 +3472,7 @@ dependencies = [
  "wasmtime",
  "wasmtime-component-util",
  "wasmtime-wit-bindgen",
- "wit-parser",
+ "wit-parser 0.201.0",
 ]
 
 [[package]]
@@ -3433,7 +3496,7 @@ dependencies = [
  "object",
  "target-lexicon",
  "thiserror",
- "wasmparser",
+ "wasmparser 0.201.0",
  "wasmtime-cranelift-shared",
  "wasmtime-environ",
  "wasmtime-versioned-export-macros",
@@ -3472,8 +3535,8 @@ dependencies = [
  "serde_derive",
  "target-lexicon",
  "thiserror",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.201.0",
+ "wasmparser 0.201.0",
  "wasmprinter",
  "wasmtime-component-util",
  "wasmtime-types",
@@ -3488,7 +3551,7 @@ dependencies = [
  "component-fuzz-util",
  "env_logger",
  "libfuzzer-sys",
- "wasmparser",
+ "wasmparser 0.201.0",
  "wasmprinter",
  "wasmtime-environ",
  "wat",
@@ -3545,7 +3608,7 @@ dependencies = [
  "rand",
  "smallvec",
  "target-lexicon",
- "wasmparser",
+ "wasmparser 0.201.0",
  "wasmtime",
  "wasmtime-fuzzing",
 ]
@@ -3565,12 +3628,12 @@ dependencies = [
  "target-lexicon",
  "tempfile",
  "v8",
- "wasm-encoder",
+ "wasm-encoder 0.201.0",
  "wasm-mutate",
  "wasm-smith",
  "wasm-spec-interpreter",
  "wasmi",
- "wasmparser",
+ "wasmparser 0.201.0",
  "wasmprinter",
  "wasmtime",
  "wasmtime-wast",
@@ -3617,7 +3680,7 @@ dependencies = [
  "rand",
  "rustix",
  "sptr",
- "wasm-encoder",
+ "wasm-encoder 0.201.0",
  "wasmtime-asm-macros",
  "wasmtime-environ",
  "wasmtime-fiber",
@@ -3639,7 +3702,7 @@ dependencies = [
  "serde",
  "serde_derive",
  "thiserror",
- "wasmparser",
+ "wasmparser 0.201.0",
 ]
 
 [[package]]
@@ -3744,7 +3807,7 @@ dependencies = [
  "anyhow",
  "log",
  "wasmtime",
- "wast 200.0.0",
+ "wast 201.0.0",
 ]
 
 [[package]]
@@ -3756,7 +3819,7 @@ dependencies = [
  "gimli",
  "object",
  "target-lexicon",
- "wasmparser",
+ "wasmparser 0.201.0",
  "wasmtime-cranelift-shared",
  "wasmtime-environ",
  "winch-codegen",
@@ -3769,7 +3832,7 @@ dependencies = [
  "anyhow",
  "heck",
  "indexmap 2.0.0",
- "wit-parser",
+ "wit-parser 0.201.0",
 ]
 
 [[package]]
@@ -3787,24 +3850,24 @@ dependencies = [
 
 [[package]]
 name = "wast"
-version = "200.0.0"
+version = "201.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1810d14e6b03ebb8fb05eef4009ad5749c989b65197d83bce7de7172ed91366"
+checksum = "1ef6e1ef34d7da3e2b374fd2b1a9c0227aff6cad596e1b24df9b58d0f6222faa"
 dependencies = [
  "bumpalo",
  "leb128",
  "memchr",
  "unicode-width",
- "wasm-encoder",
+ "wasm-encoder 0.201.0",
 ]
 
 [[package]]
 name = "wat"
-version = "1.200.0"
+version = "1.201.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "776cbd10e217f83869beaa3f40e312bb9e91d5eee29bbf6f560db1261b6a4c3d"
+checksum = "453d5b37a45b98dee4f4cb68015fc73634d7883bbef1c65e6e9c78d454cf3f32"
 dependencies = [
- "wast 200.0.0",
+ "wast 201.0.0",
 ]
 
 [[package]]
@@ -3929,7 +3992,7 @@ dependencies = [
  "regalloc2",
  "smallvec",
  "target-lexicon",
- "wasmparser",
+ "wasmparser 0.201.0",
  "wasmtime-environ",
 ]
 
@@ -3945,7 +4008,7 @@ dependencies = [
  "similar",
  "target-lexicon",
  "toml",
- "wasmparser",
+ "wasmparser 0.201.0",
  "wasmtime-environ",
  "wat",
  "winch-codegen",
@@ -3975,7 +4038,7 @@ dependencies = [
  "similar",
  "target-lexicon",
  "toml",
- "wasmparser",
+ "wasmparser 0.201.0",
  "wasmtime-environ",
  "wat",
  "winch-codegen",
@@ -4145,9 +4208,9 @@ dependencies = [
 
 [[package]]
 name = "wit-bindgen"
-version = "0.19.1"
+version = "0.19.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4ee004d9efcb9931de5eb7ac424d6f32c176ad81297636302b66e4fedf7bd02"
+checksum = "b37d270da94012e0ac490ac633ad5bdd76a10a3fb15069edb033c1b771ce931f"
 dependencies = [
  "bitflags 2.4.1",
  "wit-bindgen-rust-macro",
@@ -4155,32 +4218,32 @@ dependencies = [
 
 [[package]]
 name = "wit-bindgen-core"
-version = "0.19.1"
+version = "0.19.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81b9b74b29ab2b8652cd8fbefcf93616bd5415088b539f74edb277d94a3d9765"
+checksum = "9121070bebf9dda946f7ecffc9ec23a890b46c79be84ff6359f0c588223b20e7"
 dependencies = [
  "anyhow",
- "wit-parser",
+ "wit-parser 0.200.0",
 ]
 
 [[package]]
 name = "wit-bindgen-rust"
-version = "0.19.1"
+version = "0.19.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bafb494693e7380aafc96a4911c6f4e57abc507b5f9694b7c5fbe88b07101f7a"
+checksum = "a1753606a880b4c7701da8870d3935e550e667261a9b570f709cfefc22136ea5"
 dependencies = [
  "anyhow",
  "heck",
- "wasm-metadata",
+ "wasm-metadata 0.200.0",
  "wit-bindgen-core",
- "wit-component",
+ "wit-component 0.200.0",
 ]
 
 [[package]]
 name = "wit-bindgen-rust-macro"
-version = "0.19.1"
+version = "0.19.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b62c1deb957177fb2f072e33ce24016d68162bc3863661dec85e9b81ae91900f"
+checksum = "171482ae1eb3c417b61e7e1b487e4dded906a136e1ac5da35eab38bdff5554c5"
 dependencies = [
  "anyhow",
  "proc-macro2",
@@ -4203,10 +4266,29 @@ dependencies = [
  "serde",
  "serde_derive",
  "serde_json",
- "wasm-encoder",
- "wasm-metadata",
- "wasmparser",
- "wit-parser",
+ "wasm-encoder 0.200.0",
+ "wasm-metadata 0.200.0",
+ "wasmparser 0.200.0",
+ "wit-parser 0.200.0",
+]
+
+[[package]]
+name = "wit-component"
+version = "0.201.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825"
+dependencies = [
+ "anyhow",
+ "bitflags 2.4.1",
+ "indexmap 2.0.0",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "wasm-encoder 0.201.0",
+ "wasm-metadata 0.201.0",
+ "wasmparser 0.201.0",
+ "wit-parser 0.201.0",
 ]
 
 [[package]]
@@ -4224,7 +4306,25 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "unicode-xid",
- "wasmparser",
+ "wasmparser 0.200.0",
+]
+
+[[package]]
+name = "wit-parser"
+version = "0.201.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6"
+dependencies = [
+ "anyhow",
+ "id-arena",
+ "indexmap 2.0.0",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid",
+ "wasmparser 0.201.0",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 76cc5a55c..94af5ce17 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -119,6 +119,8 @@ members = [
   "examples/wasi/wasm",
   "examples/tokio/wasm",
   "examples/component/wasm",
+  "examples/min-platform",
+  "examples/min-platform/embedding",
   "fuzz",
   "winch",
   "winch/codegen", "crates/slab",
@@ -226,18 +228,18 @@ io-lifetimes = { version = "2.0.3", default-features = false }
 io-extras = "0.18.1"
 rustix = "0.38.31"
 # wit-bindgen:
-wit-bindgen = { version = "0.19.1", default-features = false }
+wit-bindgen = { version = "0.19.2", default-features = false }
 
 # wasm-tools family:
-wasmparser = "0.200.0"
-wat = "1.200.0"
-wast = "200.0.0"
-wasmprinter = "0.200.0"
-wasm-encoder = "0.200.0"
-wasm-smith = "0.200.0"
-wasm-mutate = "0.200.0"
-wit-parser = "0.200.0"
-wit-component = "0.200.0"
+wasmparser = "0.201.0"
+wat = "1.201.0"
+wast = "201.0.0"
+wasmprinter = "0.201.0"
+wasm-encoder = "0.201.0"
+wasm-smith = "0.201.0"
+wasm-mutate = "0.201.0"
+wit-parser = "0.201.0"
+wit-component = "0.201.0"
 
 # Non-Bytecode Alliance maintained dependencies:
 # --------------------------
diff --git a/cranelift/codegen/src/opts/README.md b/cranelift/codegen/src/opts/README.md
index d7b09e4ad..4f586a1ad 100644
--- a/cranelift/codegen/src/opts/README.md
+++ b/cranelift/codegen/src/opts/README.md
@@ -85,3 +85,41 @@ of it boils down to the fact that, unlike traditional e-graphs, our rules are
    One day we intend to extend ISLE with built-in support for commutativity, so
    we don't need to author the redundant commutations ourselves:
    https://github.com/bytecodealliance/wasmtime/issues/6128
+
+4. Be careful with (ideally avoid) multiple matches on the same `Value`, as
+   they can result in surprising multi-matching behavior. Be skeptical of
+   helpers that can inadvertently create this behavior.
+
+   In our mid-end ISLE environment, a `Value` corresponds to an eclass, with
+   multiple possible representations. A rule that matches on a `Value` will
+   traverse all enodes in the eclass, looking for a match. This is usually
+   exactly what we want: it is what allows a pattern like `(iadd (iconst k) x)`
+   to find the `iconst` amongst multiple possibilities for the argument.
+
+   However, this can also result in surprising behavior. If one has a helper
+   and a simplify rule like
+
+       (decl suitable_for_rewrite (Value) Value)
+       (rule (suitable_for_rewrite x @ (iadd ...)) x)
+       (rule (suitable_for_rewrite x @ (isub ...)) x)
+
+       (rule (simplify (ireduce _ x))
+         (if-let _ (suitable_for_rewrite x))
+         x)
+
+    Then this can result in the extremely surprising behavior that `(ireduce
+    (other_op ...))` matches, if `(other_op ...)` is in the same eclass as an
+    `iadd` or `isub`. This happens because the left-hand side binds `x`, which
+    describes the entire eclass; and `suitable_for_rewrite` matches if *any*
+    representation of `x` matches.
+
+    This resulted in a real bug in #7999. The best guidance is to keep rules
+    simple and direct: rather than attempting to abstract out helpers and
+    perform multiple, separate, matches on a `Value`, write patterns directly.
+    This has the additional benefit that the rewrites are more clearly visible
+    to the casual reader. For example:
+
+        (rule (simplify (ireduce _ (iadd ...)))
+              (iadd ...))
+        (rule (simplify (ireduce _ (isub ...)))
+              (isub ...))
diff --git a/crates/fuzzing/src/generators/single_inst_module.rs b/crates/fuzzing/src/generators/single_inst_module.rs
index 9a66fcdf1..e368686ce 100644
--- a/crates/fuzzing/src/generators/single_inst_module.rs
+++ b/crates/fuzzing/src/generators/single_inst_module.rs
@@ -595,7 +595,8 @@ mod test {
     i32.add
   )
   (export "test" (func 0))
-)"#
+)
+"#
         )
     }
 
diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs
index 515b0acd4..9ad701432 100644
--- a/crates/runtime/src/instance.rs
+++ b/crates/runtime/src/instance.rs
@@ -835,13 +835,11 @@ impl Instance {
                     .get(src..)
                     .and_then(|s| s.get(..len))
                     .ok_or(Trap::TableOutOfBounds)?;
-                table.init(
+                table.init_func(
                     dst,
-                    elements.iter().map(|idx| {
-                        TableElement::FuncRef(
-                            self.get_func_ref(*idx).unwrap_or(std::ptr::null_mut()),
-                        )
-                    }),
+                    elements
+                        .iter()
+                        .map(|idx| self.get_func_ref(*idx).unwrap_or(std::ptr::null_mut())),
                 )?;
             }
             TableSegmentElements::Expressions(exprs) => {
@@ -850,11 +848,11 @@ impl Instance {
                     .get(src..)
                     .and_then(|s| s.get(..len))
                     .ok_or(Trap::TableOutOfBounds)?;
-                table.init(
-                    dst,
-                    exprs.iter().map(|expr| match ty {
-                        TableElementType::Func => {
-                            let funcref = match expr {
+                match ty {
+                    TableElementType::Func => {
+                        table.init_func(
+                            dst,
+                            exprs.iter().map(|expr| match expr {
                                 TableElementExpression::Null => std::ptr::null_mut(),
                                 TableElementExpression::Function(idx) => {
                                     self.get_func_ref(*idx).unwrap()
@@ -863,22 +861,23 @@ impl Instance {
                                     let global = self.defined_or_imported_global_ptr(*idx);
                                     unsafe { (*global).as_func_ref() }
                                 }
-                            };
-                            TableElement::FuncRef(funcref)
-                        }
-                        TableElementType::Extern => {
-                            let externref = match expr {
+                            }),
+                        )?;
+                    }
+                    TableElementType::Extern => {
+                        table.init_extern(
+                            dst,
+                            exprs.iter().map(|expr| match expr {
                                 TableElementExpression::Null => None,
                                 TableElementExpression::Function(_) => unreachable!(),
                                 TableElementExpression::GlobalGet(idx) => {
                                     let global = self.defined_or_imported_global_ptr(*idx);
                                     unsafe { (*global).as_externref().clone() }
                                 }
-                            };
-                            TableElement::ExternRef(externref)
-                        }
-                    }),
-                )?;
+                            }),
+                        )?;
+                    }
+                }
             }
         }
 
diff --git a/crates/runtime/src/instance/allocator.rs b/crates/runtime/src/instance/allocator.rs
index 071b28a41..f6683a35b 100644
--- a/crates/runtime/src/instance/allocator.rs
+++ b/crates/runtime/src/instance/allocator.rs
@@ -2,7 +2,7 @@ use crate::imports::Imports;
 use crate::instance::{Instance, InstanceHandle};
 use crate::memory::Memory;
 use crate::mpk::ProtectionKey;
-use crate::table::Table;
+use crate::table::{Table, TableElementType};
 use crate::{CompiledModuleId, ModuleRuntimeInfo, Store};
 use anyhow::{anyhow, bail, Result};
 use std::{alloc, any::Any, mem, ptr, sync::Arc};
@@ -531,14 +531,25 @@ fn initialize_tables(instance: &mut Instance, module: &Module) -> Result<()> {
             TableInitialValue::FuncRef(idx) => {
                 let funcref = instance.get_func_ref(*idx).unwrap();
                 let table = unsafe { &mut *instance.get_defined_table(table) };
-                table.init_func(funcref)?;
+                let init = (0..table.size()).map(|_| funcref);
+                table.init_func(0, init)?;
             }
 
             TableInitialValue::GlobalGet(idx) => unsafe {
                 let global = instance.defined_or_imported_global_ptr(*idx);
-                let funcref = (*global).as_func_ref();
                 let table = &mut *instance.get_defined_table(table);
-                table.init_func(funcref)?;
+                match table.element_type() {
+                    TableElementType::Func => {
+                        let funcref = (*global).as_func_ref();
+                        let init = (0..table.size()).map(|_| funcref);
+                        table.init_func(0, init)?;
+                    }
+                    TableElementType::Extern => {
+                        let externref = (*global).as_externref();
+                        let init = (0..table.size()).map(|_| externref.clone());
+                        table.init_extern(0, init)?;
+                    }
+                }
             },
         }
     }
diff --git a/crates/runtime/src/sys/custom/capi.rs b/crates/runtime/src/sys/custom/capi.rs
new file mode 100644
index 000000000..0afc7774e
--- /dev/null
+++ b/crates/runtime/src/sys/custom/capi.rs
@@ -0,0 +1,189 @@
+#![allow(non_camel_case_types)]
+
+// Flags to either `wasmtime_mmap_{new,remap}` or `wasmtime_mprotect`.
+
+/// Indicates that the memory region should be readable.
+pub const WASMTIME_PROT_READ: u32 = 1 << 0;
+/// Indicates that the memory region should be writable.
+pub const WASMTIME_PROT_WRITE: u32 = 1 << 1;
+/// Indicates that the memory region should be executable.
+pub const WASMTIME_PROT_EXEC: u32 = 1 << 2;
+
+pub use WASMTIME_PROT_EXEC as PROT_EXEC;
+pub use WASMTIME_PROT_READ as PROT_READ;
+pub use WASMTIME_PROT_WRITE as PROT_WRITE;
+
+/// Handler function for traps in Wasmtime passed to `wasmtime_init_traps`.
+///
+/// This function is invoked whenever a trap is caught by the system. For
+/// example this would be invoked during a signal handler on Linux. This
+/// function is passed a number of parameters indicating information about the
+/// trap:
+///
+/// * `ip` - the instruction pointer at the time of the trap.
+/// * `fp` - the frame pointer register's value at the time of the trap.
+/// * `has_faulting_addr` - whether this trap is associated with an access
+///   violation (e.g. a segfault) meaning memory was accessed when it shouldn't
+///   be. If this is `true` then the next parameter is filled in.
+/// * `faulting_addr` - if `has_faulting_addr` is true then this is the address
+///   that was attempted to be accessed. Otherwise this value is not used.
+///
+/// If this function returns then the trap was not handled by Wasmtime. This
+/// means that it's left up to the embedder how to deal with the trap/signal
+/// depending on its default behavior. This could mean forwarding to a
+/// non-Wasmtime handler, aborting the process, logging then crashing, etc. The
+/// meaning of a trap that's not handled by Wasmtime depends on the context in
+/// which the trap was generated.
+///
+/// When this function does not return it's because `wasmtime_longjmp` is
+/// used to handle a Wasm-based trap.
+pub type wasmtime_trap_handler_t =
+    extern "C" fn(ip: usize, fp: usize, has_faulting_addr: bool, faulting_addr: usize);
+
+/// Abstract pointer type used in the `wasmtime_memory_image_*` APIs which
+/// is defined by the embedder.
+pub enum wasmtime_memory_image {}
+
+extern "C" {
+    /// Creates a new virtual memory mapping of the `size` specified with
+    /// protection bits specified in `prot_flags`.
+    ///
+    /// Memory can be lazily committed.
+    ///
+    /// Stores the base pointer of the new mapping in `ret` on success.
+    ///
+    /// Returns 0 on success and an error code on failure.
+    ///
+    /// Similar to `mmap(0, size, prot_flags, MAP_PRIVATE, 0, -1)` on Linux.
+    pub fn wasmtime_mmap_new(size: usize, prot_flags: u32, ret: &mut *mut u8) -> i32;
+
+    /// Remaps the virtual memory starting at `addr` going for `size` bytes to
+    /// the protections specified with a new blank mapping.
+    ///
+    /// This will unmap any prior mappings and decommit them. New mappings for
+    /// anonymous memory are used to replace these mappings and the new area
+    /// should have the protection specified by `prot_flags`.
+    ///
+    /// Returns 0 on success and an error code on failure.
+    ///
+    /// Similar to `mmap(addr, size, prot_flags, MAP_PRIVATE | MAP_FIXED, 0, -1)` on Linux.
+    pub fn wasmtime_mmap_remap(addr: *mut u8, size: usize, prot_flags: u32) -> i32;
+
+    /// Unmaps memory at the specified `ptr` for `size` bytes.
+    ///
+    /// The memory should be discarded and decommitted and should generate a
+    /// segfault if accessed after this function call.
+    ///
+    /// Returns 0 on success and an error code on failure.
+    ///
+    /// Similar to `munmap` on Linux.
+    pub fn wasmtime_munmap(ptr: *mut u8, size: usize) -> i32;
+
+    /// Configures the protections associated with a region of virtual memory
+    /// starting at `ptr` and going to `size`.
+    ///
+    /// Returns 0 on success and an error code on failure.
+    ///
+    /// Similar to `mprotect` on Linux.
+    pub fn wasmtime_mprotect(ptr: *mut u8, size: usize, prot_flags: u32) -> i32;
+
+    /// Returns the page size, in bytes, of the current system.
+    pub fn wasmtime_page_size() -> usize;
+
+    /// Used to setup a frame on the stack to longjmp back to in the future.
+    ///
+    /// This function is used for handling traps in WebAssembly and is paried
+    /// with `wasmtime_longjmp`.
+    ///
+    /// * `jmp_buf` - this argument is filled in with a pointer which if used
+    ///   will be passed to `wasmtime_longjmp` later on by the runtime.
+    /// * `callback` - this callback should be invoked after `jmp_buf` is
+    ///   configured.
+    /// * `payload` and `callee` - the two arguments to pass to `callback`.
+    ///
+    /// Returns 0 if `wasmtime_longjmp` was used to return to this function.
+    /// Returns 1 if `wasmtime_longjmp` was not called and `callback` returned.
+    pub fn wasmtime_setjmp(
+        jmp_buf: *mut *const u8,
+        callback: extern "C" fn(*mut u8, *mut u8),
+        payload: *mut u8,
+        callee: *mut u8,
+    ) -> i32;
+
+    /// Paired with `wasmtime_setjmp` this is used to jump back to the `setjmp`
+    /// point.
+    ///
+    /// The argument here was originally passed to `wasmtime_setjmp` through its
+    /// out-param.
+    ///
+    /// This function cannot return.
+    ///
+    /// This function may be invoked from the `wasmtime_trap_handler_t`
+    /// configured by `wasmtime_init_traps`.
+    pub fn wasmtime_longjmp(jmp_buf: *const u8) -> !;
+
+    /// Initializes trap-handling logic for this platform.
+    ///
+    /// Wasmtime's implementation of WebAssembly relies on the ability to catch
+    /// signals/traps/etc. For example divide-by-zero may raise a machine
+    /// exception. Out-of-bounds memory accesses may also raise a machine
+    /// exception. This function is used to initialize trap handling.
+    ///
+    /// The `handler` provided is a function pointer to invoke whenever a trap
+    /// is encountered. The `handler` is invoked whenever a trap is caught by
+    /// the system.
+    ///
+    /// Returns 0 on success and an error code on failure.
+    pub fn wasmtime_init_traps(handler: wasmtime_trap_handler_t) -> i32;
+
+    /// Attempts to create a new in-memory image of the `ptr`/`len` combo which
+    /// can be mapped to virtual addresses in the future.
+    ///
+    /// On successed the returned `wasmtime_memory_image` pointer is stored into `ret`.
+    /// This value stored can be `NULL` to indicate that an image cannot be
+    /// created but no failure occurred. The structure otherwise will later be
+    /// deallocated with `wasmtime_memory_image_free` and
+    /// `wasmtime_memory_image_map_at` will be used to map the image into new
+    /// regions of the address space.
+    ///
+    /// The `ptr` and `len` arguments are only valid for this function call, if
+    /// the image needs to refer to them in the future then it must make a copy.
+    ///
+    /// Both `ptr` and `len` are guaranteed to be page-aligned.
+    ///
+    /// Returns 0 on success and an error code on failure. Note that storing
+    /// `NULL` into `ret` is not considered a failure, and failure is used to
+    /// indicate that something fatal has happened and Wasmtime will propagate
+    /// the error upwards.
+    pub fn wasmtime_memory_image_new(
+        ptr: *const u8,
+        len: usize,
+        ret: &mut *mut wasmtime_memory_image,
+    ) -> i32;
+
+    /// Maps the `image` provided to the virtual address at `addr` and `len`.
+    ///
+    /// This semantically should make it such that `addr` and `len` looks the
+    /// same as the contents of what the memory image was first created with.
+    /// The mappings of `addr` should be private and changes do not reflect back
+    /// to `wasmtime_memory_image`.
+    ///
+    /// In effect this is to create a copy-on-write mapping at `addr`/`len`
+    /// pointing back to the memory used by the image originally.
+    ///
+    /// Note that the memory region will be unmapped with `wasmtime_munmap` in
+    /// the future.
+    ///
+    /// Aborts the process on failure.
+    pub fn wasmtime_memory_image_map_at(
+        image: *mut wasmtime_memory_image,
+        addr: *mut u8,
+        len: usize,
+    ) -> i32;
+
+    /// Deallocates the provided `wasmtime_memory_image`.
+    ///
+    /// Note that mappings created from this image are not guaranteed to be
+    /// deallocated and/or unmapped before this is called.
+    pub fn wasmtime_memory_image_free(image: *mut wasmtime_memory_image);
+}
diff --git a/crates/runtime/src/sys/custom/mmap.rs b/crates/runtime/src/sys/custom/mmap.rs
new file mode 100644
index 000000000..02cb0bf8f
--- /dev/null
+++ b/crates/runtime/src/sys/custom/mmap.rs
@@ -0,0 +1,111 @@
+use super::cvt;
+use crate::sys::capi;
+use crate::SendSyncPtr;
+use anyhow::{bail, Result};
+use std::fs::File;
+use std::ops::Range;
+use std::path::Path;
+use std::ptr::{self, NonNull};
+
+#[derive(Debug)]
+pub struct Mmap {
+    memory: SendSyncPtr<[u8]>,
+}
+
+impl Mmap {
+    pub fn new_empty() -> Mmap {
+        Mmap {
+            memory: SendSyncPtr::from(&mut [][..]),
+        }
+    }
+
+    pub fn new(size: usize) -> Result<Self> {
+        let mut ptr = ptr::null_mut();
+        cvt(unsafe {
+            capi::wasmtime_mmap_new(size, capi::PROT_READ | capi::PROT_WRITE, &mut ptr)
+        })?;
+        let memory = std::ptr::slice_from_raw_parts_mut(ptr.cast(), size);
+        let memory = SendSyncPtr::new(NonNull::new(memory).unwrap());
+        Ok(Mmap { memory })
+    }
+
+    pub fn reserve(size: usize) -> Result<Self> {
+        let mut ptr = ptr::null_mut();
+        cvt(unsafe { capi::wasmtime_mmap_new(size, 0, &mut ptr) })?;
+        let memory = std::ptr::slice_from_raw_parts_mut(ptr.cast(), size);
+        let memory = SendSyncPtr::new(NonNull::new(memory).unwrap());
+        Ok(Mmap { memory })
+    }
+
+    pub fn from_file(_path: &Path) -> Result<(Self, File)> {
+        bail!("not supported on this platform");
+    }
+
+    pub fn make_accessible(&mut self, start: usize, len: usize) -> Result<()> {
+        let ptr = self.memory.as_ptr().cast::<u8>();
+        unsafe {
+            cvt(capi::wasmtime_mprotect(
+                ptr.add(start).cast(),
+                len,
+                capi::PROT_READ | capi::PROT_WRITE,
+            ))?;
+        }
+
+        Ok(())
+    }
+
+    #[inline]
+    pub fn as_ptr(&self) -> *const u8 {
+        self.memory.as_ptr() as *const u8
+    }
+
+    #[inline]
+    pub fn as_mut_ptr(&mut self) -> *mut u8 {
+        self.memory.as_ptr().cast()
+    }
+
+    #[inline]
+    pub fn len(&self) -> usize {
+        unsafe { (*self.memory.as_ptr()).len() }
+    }
+
+    pub unsafe fn make_executable(
+        &self,
+        range: Range<usize>,
+        enable_branch_protection: bool,
+    ) -> Result<()> {
+        let base = self.memory.as_ptr().cast::<u8>().add(range.start).cast();
+        let len = range.end - range.start;
+
+        // not mapped into the C API at this time.
+        let _ = enable_branch_protection;
+
+        cvt(capi::wasmtime_mprotect(
+            base,
+            len,
+            capi::PROT_READ | capi::PROT_EXEC,
+        ))?;
+        Ok(())
+    }
+
+    pub unsafe fn make_readonly(&self, range: Range<usize>) -> Result<()> {
+        let base = self.memory.as_ptr().cast::<u8>().add(range.start).cast();
+        let len = range.end - range.start;
+
+        cvt(capi::wasmtime_mprotect(base, len, capi::PROT_READ))?;
+        Ok(())
+    }
+}
+
+impl Drop for Mmap {
+    fn drop(&mut self) {
+        unsafe {
+            let ptr = self.memory.as_ptr().cast();
+            let len = (*self.memory.as_ptr()).len();
+            if len == 0 {
+                return;
+            }
+            cvt(capi::wasmtime_munmap(ptr, len)).unwrap();
+        }
+    }
+}
diff --git a/crates/runtime/src/sys/custom/mod.rs b/crates/runtime/src/sys/custom/mod.rs
new file mode 100644
index 000000000..31de2a3cc
--- /dev/null
+++ b/crates/runtime/src/sys/custom/mod.rs
@@ -0,0 +1,24 @@
+//! Custom platform support in Wasmtime.
+//!
+//! This module contains an implementation of defining Wasmtime's platform
+//! support in terms of a minimal C API. This API can be found in the `capi`
+//! module and all other functionality here is implemented in terms of that
+//! module.
+//!
+//! For more information about this see `./examples/min-platform` as well as
+//! `./docs/examples-minimal.md`.
+
+use std::io;
+
+pub mod capi;
+pub mod mmap;
+pub mod traphandlers;
+pub mod unwind;
+pub mod vm;
+
+fn cvt(rc: i32) -> io::Result<()> {
+    match rc {
+        0 => Ok(()),
+        code => Err(io::Error::from_raw_os_error(code)),
+    }
+}
diff --git a/crates/runtime/src/sys/custom/traphandlers.rs b/crates/runtime/src/sys/custom/traphandlers.rs
new file mode 100644
index 000000000..7c2508c50
--- /dev/null
+++ b/crates/runtime/src/sys/custom/traphandlers.rs
@@ -0,0 +1,51 @@
+use crate::traphandlers::tls;
+use crate::VMContext;
+use std::mem;
+
+pub use crate::sys::capi::{self, wasmtime_longjmp};
+
+#[allow(missing_docs)]
+pub type SignalHandler<'a> = dyn Fn() + Send + Sync + 'a;
+
+pub unsafe fn wasmtime_setjmp(
+    jmp_buf: *mut *const u8,
+    callback: extern "C" fn(*mut u8, *mut VMContext),
+    payload: *mut u8,
+    callee: *mut VMContext,
+) -> i32 {
+    let callback = mem::transmute::<
+        extern "C" fn(*mut u8, *mut VMContext),
+        extern "C" fn(*mut u8, *mut u8),
+    >(callback);
+    capi::wasmtime_setjmp(jmp_buf, callback, payload, callee.cast())
+}
+
+pub fn platform_init(_macos_use_mach_ports: bool) {
+    unsafe {
+        capi::wasmtime_init_traps(handle_trap);
+    }
+}
+
+extern "C" fn handle_trap(ip: usize, fp: usize, has_faulting_addr: bool, faulting_addr: usize) {
+    tls::with(|info| {
+        let info = match info {
+            Some(info) => info,
+            None => return,
+        };
+        let faulting_addr = if has_faulting_addr {
+            Some(faulting_addr)
+        } else {
+            None
+        };
+        let ip = ip as *const u8;
+        let jmp_buf = info.take_jmp_buf_if_trap(ip, |_handler| {
+            panic!("custom signal handlers are not supported on this platform");
+        });
+        if !jmp_buf.is_null() {
+            info.set_jit_trap(ip, fp, faulting_addr);
+            unsafe { wasmtime_longjmp(jmp_buf) }
+        }
+    })
+}
+
+pub fn lazy_per_thread_init() {}
diff --git a/crates/runtime/src/sys/custom/unwind.rs b/crates/runtime/src/sys/custom/unwind.rs
new file mode 100644
index 000000000..d6181940c
--- /dev/null
+++ b/crates/runtime/src/sys/custom/unwind.rs
@@ -0,0 +1,17 @@
+#![allow(missing_docs)]
+
+use anyhow::Result;
+
+pub struct UnwindRegistration {}
+
+impl UnwindRegistration {
+    pub const SECTION_NAME: &'static str = ".eh_frame";
+
+    pub unsafe fn new(
+        _base_address: *const u8,
+        _unwind_info: *const u8,
+        _unwind_len: usize,
+    ) -> Result<UnwindRegistration> {
+        Ok(UnwindRegistration {})
+    }
+}
diff --git a/crates/runtime/src/sys/custom/vm.rs b/crates/runtime/src/sys/custom/vm.rs
new file mode 100644
index 000000000..e9785b781
--- /dev/null
+++ b/crates/runtime/src/sys/custom/vm.rs
@@ -0,0 +1,108 @@
+use super::cvt;
+use crate::sys::capi;
+use crate::SendSyncPtr;
+use std::fs::File;
+use std::io;
+use std::ptr::{self, NonNull};
+use std::sync::Arc;
+
+pub unsafe fn expose_existing_mapping(ptr: *mut u8, len: usize) -> io::Result<()> {
+    cvt(capi::wasmtime_mprotect(
+        ptr.cast(),
+        len,
+        capi::PROT_READ | capi::PROT_WRITE,
+    ))
+}
+
+pub unsafe fn hide_existing_mapping(ptr: *mut u8, len: usize) -> io::Result<()> {
+    cvt(capi::wasmtime_mprotect(ptr.cast(), len, 0))
+}
+
+pub unsafe fn erase_existing_mapping(ptr: *mut u8, len: usize) -> io::Result<()> {
+    cvt(capi::wasmtime_mmap_remap(ptr.cast(), len, 0))
+}
+
+#[cfg(feature = "pooling-allocator")]
+pub unsafe fn commit_table_pages(_addr: *mut u8, _len: usize) -> io::Result<()> {
+    // Table pages are always READ | WRITE so there's nothing that needs to be
+    // done here.
+    Ok(())
+}
+
+#[cfg(feature = "pooling-allocator")]
+pub unsafe fn decommit_table_pages(addr: *mut u8, len: usize) -> io::Result<()> {
+    if len == 0 {
+        return Ok(());
+    }
+
+    cvt(capi::wasmtime_mmap_remap(
+        addr,
+        len,
+        capi::PROT_READ | capi::PROT_WRITE,
+    ))
+}
+
+pub fn get_page_size() -> usize {
+    unsafe { capi::wasmtime_page_size() }
+}
+
+pub fn supports_madvise_dontneed() -> bool {
+    false
+}
+
+pub unsafe fn madvise_dontneed(_ptr: *mut u8, _len: usize) -> io::Result<()> {
+    unreachable!()
+}
+
+#[derive(PartialEq, Debug)]
+pub struct MemoryImageSource {
+    data: SendSyncPtr<capi::wasmtime_memory_image>,
+}
+
+impl MemoryImageSource {
+    pub fn from_file(_file: &Arc<File>) -> Option<MemoryImageSource> {
+        None
+    }
+
+    pub fn from_data(data: &[u8]) -> io::Result<Option<MemoryImageSource>> {
+        unsafe {
+            let mut ptr = ptr::null_mut();
+            cvt(capi::wasmtime_memory_image_new(
+                data.as_ptr(),
+                data.len(),
+                &mut ptr,
+            ))?;
+            match NonNull::new(ptr) {
+                Some(ptr) => Ok(Some(MemoryImageSource {
+                    data: SendSyncPtr::new(ptr),
+                })),
+                None => Ok(None),
+            }
+        }
+    }
+
+    pub unsafe fn map_at(&self, base: *mut u8, len: usize, offset: u64) -> io::Result<()> {
+        assert_eq!(offset, 0);
+        cvt(capi::wasmtime_memory_image_map_at(
+            self.data.as_ptr(),
+            base,
+            len,
+        ))
+    }
+
+    pub unsafe fn remap_as_zeros_at(&self, base: *mut u8, len: usize) -> io::Result<()> {
+        cvt(capi::wasmtime_mmap_remap(
+            base.cast(),
+            len,
+            capi::PROT_READ | capi::PROT_WRITE,
+        ))
+    }
+}
+
+impl Drop for MemoryImageSource {
+    fn drop(&mut self) {
+        unsafe {
+            capi::wasmtime_memory_image_free(self.data.as_ptr());
+        }
+    }
+}
diff --git a/crates/runtime/src/sys/mod.rs b/crates/runtime/src/sys/mod.rs
index e88435682..841b26444 100644
--- a/crates/runtime/src/sys/mod.rs
+++ b/crates/runtime/src/sys/mod.rs
@@ -18,6 +18,9 @@ cfg_if::cfg_if! {
     } else if #[cfg(unix)] {
         mod unix;
         pub use unix::*;
+    } else if #[cfg(wasmtime_custom_platform)] {
+        mod custom;
+        pub use custom::*;
     } else {
         compile_error!(
             "Wasmtime is being compiled for a platform \
diff --git a/crates/runtime/src/table.rs b/crates/runtime/src/table.rs
index 804a2e174..b07670739 100644
--- a/crates/runtime/src/table.rs
+++ b/crates/runtime/src/table.rs
@@ -55,64 +55,6 @@ unsafe impl Send for TableElement where VMExternRef: Send {}
 unsafe impl Sync for TableElement where VMExternRef: Sync {}
 
 impl TableElement {
-    /// Consumes the given raw table element value into a table element.
-    ///
-    /// # Safety
-    ///
-    /// This is unsafe as it will *not* clone any externref, leaving the reference count unchanged.
-    ///
-    /// This should only be used if the raw pointer is no longer in use.
-    unsafe fn from_table_value(ty: TableElementType, ptr: TableValue) -> Self {
-        match (ty, ptr) {
-            (TableElementType::Func, None) => Self::UninitFunc,
-            (TableElementType::Func, Some(ptr)) => {
-                let ptr = ptr.as_ptr();
-                let masked = Strict::map_addr(ptr, |a| a & FUNCREF_MASK);
-                Self::FuncRef(masked.cast())
-            }
-            (TableElementType::Extern, None) => Self::ExternRef(None),
-            (TableElementType::Extern, Some(ptr)) => {
-                Self::ExternRef(VMExternRef::from_raw(ptr.as_ptr()))
-            }
-        }
-    }
-
-    /// Clones a table element from the underlying table element.
-    ///
-    /// # Safety
-    ///
-    /// This is unsafe as it will clone any externref, incrementing the reference count.
-    unsafe fn clone_from_table_value(ty: TableElementType, ptr: TableValue) -> Self {
-        match ty {
-            // Functions have no ownership, so defer to the prior method.
-            TableElementType::Func => TableElement::from_table_value(ty, ptr),
-
-            TableElementType::Extern => {
-                Self::ExternRef(ptr.and_then(|p| VMExternRef::clone_from_raw(p.as_ptr())))
-            }
-        }
-    }
-
-    /// Consumes a table element into a raw table element value. This
-    /// includes any tag bits or other storage details that we
-    /// maintain in the table slot.
-    ///
-    /// # Safety
-    ///
-    /// This is unsafe as it will consume any underlying externref into a raw pointer without modifying
-    /// the reference count.
-    ///
-    unsafe fn into_table_value(self) -> TableValue {
-        match self {
-            Self::UninitFunc => None,
-            Self::FuncRef(e) => {
-                let tagged = Strict::map_addr(e, |e| e | FUNCREF_INIT_BIT);
-                Some(NonNull::new(tagged.cast()).unwrap().into())
-            }
-            Self::ExternRef(e) => e.map(|e| NonNull::new(e.into_raw()).unwrap().into()),
-        }
-    }
-
     /// Consumes a table element into a pointer/reference, as it
     /// exists outside the table itself. This strips off any tag bits
     /// or other information that only lives inside the table.
@@ -160,6 +102,33 @@ impl From<VMExternRef> for TableElement {
     }
 }
 
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+struct TaggedFuncRef(*mut VMFuncRef);
+
+impl TaggedFuncRef {
+    const UNINIT: TaggedFuncRef = TaggedFuncRef(ptr::null_mut());
+
+    /// Converts the given `ptr`, a valid funcref pointer, into a tagged pointer
+    /// by adding in the `FUNCREF_INIT_BIT`.
+    fn from(ptr: *mut VMFuncRef) -> Self {
+        let masked = Strict::map_addr(ptr, |a| a | FUNCREF_INIT_BIT);
+        TaggedFuncRef(masked)
+    }
+
+    /// Converts a tagged pointer into a `TableElement`, returning `UninitFunc`
+    /// for null (not a tagged value) or `FuncRef` for otherwise tagged values.
+    fn into_table_element(self) -> TableElement {
+        let ptr = self.0;
+        if ptr.is_null() {
+            TableElement::UninitFunc
+        } else {
+            let unmasked = Strict::map_addr(ptr, |a| a & FUNCREF_MASK);
+            TableElement::FuncRef(unmasked)
+        }
+    }
+}
+
 /// Represents an instance's table.
 pub enum Table {
     /// A "static" table where storage space is managed externally, currently
@@ -290,12 +259,25 @@ impl Table {
     }
 
     /// Initializes the contents of this table to the specified function
-    pub fn init_func(&mut self, init: *mut VMFuncRef) -> Result<(), Trap> {
-        assert!(self.element_type() == TableElementType::Func);
-        for slot in self.elements_mut().iter_mut() {
-            unsafe {
-                *slot = TableElement::FuncRef(init).into_table_value();
-            }
+    ///
+    /// # Panics
+    ///
+    /// Panics if the table is not a function table.
+    pub fn init_func(
+        &mut self,
+        dst: u32,
+        items: impl ExactSizeIterator<Item = *mut VMFuncRef>,
+    ) -> Result<(), Trap> {
+        let dst = usize::try_from(dst).map_err(|_| Trap::TableOutOfBounds)?;
+
+        let elements = self
+            .funcrefs_mut()
+            .get_mut(dst..)
+            .and_then(|s| s.get_mut(..items.len()))
+            .ok_or(Trap::TableOutOfBounds)?;
+
+        for (item, slot) in items.zip(elements) {
+            *slot = TaggedFuncRef::from(item);
         }
         Ok(())
     }
@@ -303,27 +285,21 @@ impl Table {
     /// Fill `table[dst..]` with values from `items`
     ///
     /// Returns a trap error on out-of-bounds accesses.
-    pub fn init(
+    pub fn init_extern(
         &mut self,
         dst: u32,
-        items: impl ExactSizeIterator<Item = TableElement>,
+        items: impl ExactSizeIterator<Item = Option<VMExternRef>>,
     ) -> Result<(), Trap> {
-        let ty = self.element_type();
+        let dst = usize::try_from(dst).map_err(|_| Trap::TableOutOfBounds)?;
 
-        let elements = match self
-            .elements_mut()
-            .get_mut(usize::try_from(dst).unwrap()..)
+        let elements = self
+            .externrefs_mut()
+            .get_mut(dst..)
             .and_then(|s| s.get_mut(..items.len()))
-        {
-            Some(elements) => elements,
-            None => return Err(Trap::TableOutOfBounds),
-        };
+            .ok_or(Trap::TableOutOfBounds)?;
 
         for (item, slot) in items.zip(elements) {
-            debug_assert!(ty.matches(&item));
-            unsafe {
-                *slot = item.into_table_value();
-            }
+            *slot = item;
         }
         Ok(())
     }
@@ -331,6 +307,10 @@ impl Table {
     /// Fill `table[dst..dst + len]` with `val`.
     ///
     /// Returns a trap error on out-of-bounds accesses.
+    ///
+    /// # Panics
+    ///
+    /// Panics if `val` does not have a type that matches this table.
     pub fn fill(&mut self, dst: u32, val: TableElement, len: u32) -> Result<(), Trap> {
         let start = dst as usize;
         let end = start
@@ -341,15 +321,16 @@ impl Table {
             return Err(Trap::TableOutOfBounds);
         }
 
-        debug_assert!(self.type_matches(&val));
-
-        let ty = self.element_type();
-        if let Some((last, elements)) = self.elements_mut()[start..end].split_last_mut() {
-            for e in elements {
-                Self::set_raw(ty, e, val.clone());
+        match val {
+            TableElement::FuncRef(f) => {
+                self.funcrefs_mut()[start..end].fill(TaggedFuncRef::from(f));
+            }
+            TableElement::ExternRef(e) => {
+                self.externrefs_mut()[start..end].fill(e);
+            }
+            TableElement::UninitFunc => {
+                self.funcrefs_mut()[start..end].fill(TaggedFuncRef::UNINIT);
             }
-
-            Self::set_raw(ty, last, val);
         }
 
         Ok(())
@@ -362,6 +343,10 @@ impl Table {
     /// Returns `None` if table can't be grown by the specified amount of
     /// elements, or if the `init_value` is the wrong kind of table element.
     ///
+    /// # Panics
+    ///
+    /// Panics if `init_value` does not have a type that matches this table.
+    ///
     /// # Unsafety
     ///
     /// Resizing the table can reallocate its internal elements buffer. This
@@ -424,9 +409,19 @@ impl Table {
     ///
     /// Returns `None` if the index is out of bounds.
     pub fn get(&self, index: u32) -> Option<TableElement> {
-        self.elements()
-            .get(index as usize)
-            .map(|p| unsafe { TableElement::clone_from_table_value(self.element_type(), *p) })
+        let index = usize::try_from(index).ok()?;
+        match self.element_type() {
+            TableElementType::Func => self
+                .funcrefs()
+                .get(index)
+                .copied()
+                .map(|e| e.into_table_element()),
+            TableElementType::Extern => self
+                .externrefs()
+                .get(index)
+                .cloned()
+                .map(TableElement::ExternRef),
+        }
     }
 
     /// Set reference to the specified element.
@@ -435,14 +430,23 @@ impl Table {
     ///
     /// Returns an error if `index` is out of bounds or if this table type does
     /// not match the element type.
+    ///
+    /// # Panics
+    ///
+    /// Panics if `elem` is not of the right type for this table.
     pub fn set(&mut self, index: u32, elem: TableElement) -> Result<(), ()> {
-        if !self.type_matches(&elem) {
-            return Err(());
+        let index = usize::try_from(index).map_err(|_| ())?;
+        match elem {
+            TableElement::FuncRef(f) => {
+                *self.funcrefs_mut().get_mut(index).ok_or(())? = TaggedFuncRef::from(f);
+            }
+            TableElement::UninitFunc => {
+                *self.funcrefs_mut().get_mut(index).ok_or(())? = TaggedFuncRef::UNINIT;
+            }
+            TableElement::ExternRef(e) => {
+                *self.externrefs_mut().get_mut(index).ok_or(())? = e;
+            }
         }
-
-        let ty = self.element_type();
-        let e = self.elements_mut().get_mut(index as usize).ok_or(())?;
-        Self::set_raw(ty, e, elem);
         Ok(())
     }
 
@@ -507,27 +511,57 @@ impl Table {
         self.element_type().matches(val)
     }
 
-    fn elements(&self) -> &[TableValue] {
+    fn raw_elements(&self) -> &[TableValue] {
         match self {
             Table::Static { data, size, .. } => unsafe { &data.as_ref()[..*size as usize] },
             Table::Dynamic { elements, .. } => &elements[..],
         }
     }
 
-    fn elements_mut(&mut self) -> &mut [TableValue] {
+    fn raw_elements_mut(&mut self) -> &mut [TableValue] {
         match self {
             Table::Static { data, size, .. } => unsafe { &mut data.as_mut()[..*size as usize] },
             Table::Dynamic { elements, .. } => &mut elements[..],
         }
     }
 
-    fn set_raw(ty: TableElementType, elem: &mut TableValue, val: TableElement) {
+    fn funcrefs(&self) -> &[TaggedFuncRef] {
+        assert_eq!(self.element_type(), TableElementType::Func);
         unsafe {
-            let old = *elem;
-            *elem = val.into_table_value();
+            let (a, b, c) = self.raw_elements().align_to();
+            assert!(a.is_empty());
+            assert!(c.is_empty());
+            b
+        }
+    }
 
-            // Drop the old element
-            let _ = TableElement::from_table_value(ty, old);
+    fn funcrefs_mut(&mut self) -> &mut [TaggedFuncRef] {
+        assert_eq!(self.element_type(), TableElementType::Func);
+        unsafe {
+            let (a, b, c) = self.raw_elements_mut().align_to_mut();
+            assert!(a.is_empty());
+            assert!(c.is_empty());
+            b
+        }
+    }
+
+    fn externrefs(&self) -> &[Option<VMExternRef>] {
+        assert_eq!(self.element_type(), TableElementType::Extern);
+        unsafe {
+            let (a, b, c) = self.raw_elements().align_to();
+            assert!(a.is_empty());
+            assert!(c.is_empty());
+            b
+        }
+    }
+
+    fn externrefs_mut(&mut self) -> &mut [Option<VMExternRef>] {
+        assert_eq!(self.element_type(), TableElementType::Extern);
+        unsafe {
+            let (a, b, c) = self.raw_elements_mut().align_to_mut();
+            assert!(a.is_empty());
+            assert!(c.is_empty());
+            b
         }
     }
 
@@ -545,41 +579,34 @@ impl Table {
         match ty {
             TableElementType::Func => {
                 // `funcref` are `Copy`, so just do a mempcy
-                dst_table.elements_mut()[dst_range]
-                    .copy_from_slice(&src_table.elements()[src_range]);
+                dst_table.funcrefs_mut()[dst_range]
+                    .copy_from_slice(&src_table.funcrefs()[src_range]);
             }
             TableElementType::Extern => {
-                // We need to clone each `externref`
-                let dst = dst_table.elements_mut();
-                let src = src_table.elements();
-                for (s, d) in src_range.zip(dst_range) {
-                    let elem = unsafe { TableElement::clone_from_table_value(ty, src[s]) };
-                    Self::set_raw(ty, &mut dst[d], elem);
-                }
+                dst_table.externrefs_mut()[dst_range]
+                    .clone_from_slice(&src_table.externrefs()[src_range]);
             }
         }
     }
 
     fn copy_elements_within(&mut self, dst_range: Range<usize>, src_range: Range<usize>) {
         let ty = self.element_type();
-        let dst = self.elements_mut();
         match ty {
             TableElementType::Func => {
                 // `funcref` are `Copy`, so just do a memmove
-                dst.copy_within(src_range, dst_range.start);
+                self.funcrefs_mut().copy_within(src_range, dst_range.start);
             }
             TableElementType::Extern => {
                 // We need to clone each `externref` while handling overlapping
                 // ranges
+                let elements = self.externrefs_mut();
                 if dst_range.start <= src_range.start {
                     for (s, d) in src_range.zip(dst_range) {
-                        let elem = unsafe { TableElement::clone_from_table_value(ty, dst[s]) };
-                        Self::set_raw(ty, &mut dst[d], elem);
+                        elements[d] = elements[s].clone();
                     }
                 } else {
                     for (s, d) in src_range.rev().zip(dst_range.rev()) {
-                        let elem = unsafe { TableElement::clone_from_table_value(ty, dst[s]) };
-                        Self::set_raw(ty, &mut dst[d], elem);
+                        elements[d] = elements[s].clone();
                     }
                 }
             }
@@ -589,16 +616,17 @@ impl Table {
 
 impl Drop for Table {
     fn drop(&mut self) {
-        let ty = self.element_type();
+        match self.element_type() {
+            // `funcref` tables don't need drops.
+            TableElementType::Func => {}
 
-        // `funcref` tables don't need drops.
-        if let TableElementType::Func = ty {
-            return;
-        }
-
-        // Properly drop any table elements stored in the table
-        for element in self.elements() {
-            drop(unsafe { TableElement::from_table_value(ty, *element) });
+            // `externref` tables are null'd out to ensure that no strong
+            // references are preserved.
+            TableElementType::Extern => {
+                for e in self.externrefs_mut() {
+                    let _ = e.take();
+                }
+            }
         }
     }
 }
diff --git a/docs/examples-minimal.md b/docs/examples-minimal.md
index e007e701c..4e516b87f 100644
--- a/docs/examples-minimal.md
+++ b/docs/examples-minimal.md
@@ -279,3 +279,48 @@ to hear embedder use cases/profiles if Wasmtime is not suitable for binary size
 reasons today. Please feel free to [open an
 issue](https://github.com/bytecodealliance/wasmtime/issues/new) and let us know
 and we'd be happy to discuss more how best to handle a particular use case.
+
+# Building Wasmtime for a Custom Platform
+
+If you're not running on a built-in supported platform such as Windows, macOS,
+or Linux, then Wasmtime won't work out-of-the-box for you. Wasmtime includes a
+compilation mode, however, that enables you to define how to work with the
+platform externally.
+
+This mode is enabled when `--cfg wasmtime_custom_platform` is passed to rustc,
+via `RUSTFLAGS` for example when building through Cargo, when an existing
+platform is not matched. This means that with this configuration Wasmtime may be
+compiled for custom or previously unknown targets.
+
+Wasmtime's current "platform embedding API" which is required to operate is
+defined at `examples/min-platform/embedding/wasmtime-platform.h`. That directory
+additionally has an example of building a minimal `*.so` on Linux which has the
+platform API implemented in C using Linux syscalls. While a bit contrived it
+effectively shows a minimal Wasmtime embedding which has no dependencies other
+than the platform API.
+
+Building Wasmtime for a custom platform is not a turnkey process right now,
+there are a number of points that need to be considered:
+
+* For a truly custom platform you'll probably want to create a [custom Rust
+  target](https://docs.rust-embedded.org/embedonomicon/custom-target.html). This
+  means that Nightly Rust will be required.
+
+* Wasmtime and its dependencies require the Rust standard library `std` to be
+  available. The Rust standard library can be compiled for any target with
+  unsupported functionality being stubbed out. This mode of compiling the Rust
+  standard library is not stable, however. Currently this is done through the
+  `-Zbuild-std` argument to Cargo along with a
+  `+RUSTC_BOOTSTRAP_SYNTHETIC_TARGET=1` environment variable.
+
+* Wasmtime additionally depends on the availability of a memory allocator (e.g.
+  `malloc`). Wasmtime assumes that failed memory allocation aborts the process.
+
+* Not all features for Wasmtime can be built for custom targets. For example
+  WASI support does not work on custom targets. When building Wasmtime you'll
+  probably want `--no-default-features` and will then want to incrementally add
+  features back in as needed.
+
+The `examples/min-platform` directory has an example of building this minimal
+embedding and some necessary steps. Combined with the above features about
+producing a minimal build currently produces a 400K library on Linux.
diff --git a/docs/stability-platform-support.md b/docs/stability-platform-support.md
index b9d8e68c5..a725e6a3d 100644
--- a/docs/stability-platform-support.md
+++ b/docs/stability-platform-support.md
@@ -37,11 +37,11 @@ much else will be needed.
 ## What about `#[no_std]`?
 
 The `wasmtime` project does not currently use `#[no_std]` for its crates, but
-this is not because it won't support it! At this time we're still gathering use
-cases for for what `#[no_std]` might entail, so if you're interested in this
-we'd love to hear about your use case! Feel free to [open an
+this is not because it won't support it! For information on building Wasmtime
+for a custom target see [the minimal build
+  documentation](./examples-minimal.md). Please [open an
 issue](https://github.com/bytecodealliance/wasmtime/issues/new) on the
-`wasmtime` repository to discuss this.
+`wasmtime` repository to request support for a specific platform.
 
 This is a common question we are asked, however, so to provide some more context
 on why Wasmtime is the way it is, here's some responses to frequent points
diff --git a/examples/min-platform/.gitignore b/examples/min-platform/.gitignore
new file mode 100644
index 000000000..29fa3497b
--- /dev/null
+++ b/examples/min-platform/.gitignore
@@ -0,0 +1,1 @@
+libwasmtime-platform.so
diff --git a/examples/min-platform/Cargo.toml b/examples/min-platform/Cargo.toml
new file mode 100644
index 000000000..0bb0249a1
--- /dev/null
+++ b/examples/min-platform/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "min-platform-host"
+version.workspace = true
+authors.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+
+[lints]
+workspace = true
+
+[dependencies]
+anyhow = { workspace = true }
+libloading = "0.8"
+object = { workspace = true }
diff --git a/examples/min-platform/README.md b/examples/min-platform/README.md
new file mode 100644
index 000000000..766865136
--- /dev/null
+++ b/examples/min-platform/README.md
@@ -0,0 +1,76 @@
+# Example: Minimal Platform Build of Wasmtime
+
+This example is a showcase of what it looks like to build Wasmtime with a
+minimal set of platform dependencies. This might be suitable when running
+WebAssembly outside of Linux on a smaller system with a custom operating system
+for example. Support here is built on Wasmtime's support of "custom platforms"
+and more details can be found [online as
+well](https://docs.wasmtime.dev/examples-minimal.html).
+
+The example is organized into a few locations:
+
+* `examples/min-platform/embedding/{Cargo.toml,src}` - source code for the
+  embedding of Wasmtime itself. This is compiled to the target architecture
+  and will have a minimal set of dependencies.
+
+* `examples/min-platform/embedding/*.json` - custom Rust target definitions
+  which are used when compiling this example. These are the custom target files
+  that are the compilation target of the `embedding` crate. This is a feature
+  of nightly Rust to be able to use these. Note that the contents can be
+  customized and these files are only examples.
+
+* `examples/min-platform/embedding/wasmtime-platform.{h,c}` - an example
+  implementation of the platform dependencies that Wasmtime requires. This
+  is defined and documented in `crates/runtime/src/sys/custom/capi.rs`. The
+  example here implements the required functions with Linux syscalls.
+
+* `examples/min-platform/{Cargo.toml,src}` - an example "host embedding" which
+  loads and runs the `embedding` from above. This is a bit contrived and mostly
+  serves as a bit of a test case for Wasmtime itself to execute in CI. The
+  general idea though is that this is a Linux program which will load the
+  `embedding` project above and execute it to showcase that the code works.
+
+* `examples/min-platform/build.sh` - a script to build/run this example.
+
+Taken together this example is unlikely to satisfy any one individual use case
+but should set up the scaffolding to show how Wasmtime can be built for a
+nonstandard platform. Wasmtime effectively only has one requirement from the
+system which is management of virtual memory, and beyond that everything else
+can be internalized.
+
+Note that at this time this support all relies on the fact that the Rust
+standard library can be built for a custom target. Most of the Rust standard
+library will be "stubbed out" however and won't work (e.g. opening a file would
+return an error). This means that not all of the `wasmtime` crate will work, nor
+will all features of the `wasmtime` crate, but the set of features activated
+here should suffice.
+
+## Description
+
+This example will compile Wasmtime to a custom Rust target specified in
+`*.json` files. This custom target, for the example, is modeled after Linux
+except for the fact that Rust won't be able to know that (e.g. the `#[cfg]`
+directives aren't set so code won't know it actually runs on Linux). The
+embedding will run a few small examples of WebAssembly modules and then return.
+
+The host for this is a Linux program which supplies the platform dependencies
+that the embedding requires, for example the `wasmtime_*` symbols. This host
+program will load the embedding and execute it.
+
+## Points of Note
+
+* Due to the usage of custom `*.json` targets, this example requires a nightly
+  Rust compiler.
+* Compiling the embedding requires `--cfg wasmtime_custom_platform` in the
+  `RUSTFLAGS` environment variable. to indicate that Wasmtime's custom C
+  API-based definition of platform support is desired.
+* Due to the usage of a custom target most of libstd doesn't work. For example
+  panics can't print anything and the process can only abort.
+* Due to the custom target not all features of Wasmtime can be enabled because
+  some crates may require platform functionality which can't be defined due to
+  the lack of knowledge of what platform is being targeted.
+
+## Running this example
+
+This example can be built and run with the `./build.sh` script in this
+directory. Example output looks like.
diff --git a/examples/min-platform/build.sh b/examples/min-platform/build.sh
new file mode 100755
index 000000000..33a7893c1
--- /dev/null
+++ b/examples/min-platform/build.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+# An example script to build and run the `min-platform` example by building both
+# the embedding itself as well as the example host which will run it.
+#
+# This script takes a single argument which is a path to a Rust target json
+# file. Examples are provided in `embedding/*.json`.
+#
+# This script must be executed with the current-working-directory as
+# `examples/min-platform`.
+
+target=$1
+if [ "$target" = "" ]; then
+  echo "Usage: $0 <target-json-file>"
+  exit 1
+fi
+
+set -ex
+
+# First compile the C implementation of the platform symbols that will be
+# required by our embedding. This is the `embedding/wasmtime-platform.c` file.
+# The header file used is generated from Rust source code with the `cbindgen`
+# utility which can be installed with:
+#
+#   cargo install cbindgen
+#
+# which ensures that Rust & C agree on types and such.
+cbindgen ../../crates/runtime/src/sys/custom/capi.rs \
+    --lang C \
+    --cpp-compat > embedding/wasmtime-platform.h
+clang -shared -O2 -o libwasmtime-platform.so ./embedding/wasmtime-platform.c \
+  -D_GNU_SOURCE
+
+# Next the embedding itself is built. Points of note here:
+#
+# * `RUSTC_BOOTSTRAP_SYNTHETIC_TARGET=1` - this "fools" the Rust standard
+#   library to falling back to an "unsupported" implementation of primitives by
+#   default but without marking the standard library as requiring
+#   `feature(restricted_std)`. This is probably something that should be
+#   coordinated with upstream rust-lang/rust and get better support.
+# * `--cfg=wasmtime_custom_platform` - this flag indicates to Wasmtime that the
+#   minimal platform support is being opted into.
+# * `-Zbuild-std=std,panic_abort` - this is a nightly Cargo feature to build the
+#   Rust standard library from source.
+#
+# The final artifacts will be placed in Cargo's standard target directory.
+RUSTC_BOOTSTRAP_SYNTHETIC_TARGET=1 \
+RUSTFLAGS="--cfg=wasmtime_custom_platform" \
+  cargo build -Zbuild-std=std,panic_abort \
+    --manifest-path embedding/Cargo.toml \
+    --target $target \
+    --release
+
+# The final step here is running the host, in the current directory, which will
+# load the embedding and execute it.
+cargo run --release -- $target
diff --git a/examples/min-platform/embedding/Cargo.toml b/examples/min-platform/embedding/Cargo.toml
new file mode 100644
index 000000000..7fbe05954
--- /dev/null
+++ b/examples/min-platform/embedding/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "embedding"
+version.workspace = true
+authors.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+
+[lints]
+workspace = true
+
+[dependencies]
+anyhow = { workspace = true }
+
+# Note that default-features of wasmtime are disabled and only those required
+# are enabled, in this case compilation is done in the guest from the wasm text
+# format so `cranelift` and `wat` are enabled.
+wasmtime = { workspace = true, features = ['cranelift', 'wat', 'runtime'] }
+
+# Memory allocator used in this example (not required, however)
+dlmalloc = "0.2.4"
+
+[lib]
+crate-type = ['cdylib']
+test = false
+doctest = false
diff --git a/examples/min-platform/embedding/aarch64-unknown-unknown.json b/examples/min-platform/embedding/aarch64-unknown-unknown.json
new file mode 100644
index 000000000..e95e609db
--- /dev/null
+++ b/examples/min-platform/embedding/aarch64-unknown-unknown.json
@@ -0,0 +1,18 @@
+{
+  "arch": "aarch64",
+  "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128",
+  "dynamic-linking": true,
+  "has-thread-local": true,
+  "is-builtin": false,
+  "llvm-target": "aarch64-unknown-linux-gnu",
+  "max-atomic-width": 64,
+  "os": "minwasmtime",
+  "position-independent-executables": true,
+  "panic-strategy": "abort",
+  "frame-pointer": "always",
+  "relro-level": "full",
+  "stack-probes": {
+    "kind": "inline"
+  },
+  "target-pointer-width": "64"
+}
diff --git a/examples/min-platform/embedding/src/allocator.rs b/examples/min-platform/embedding/src/allocator.rs
new file mode 100644
index 000000000..a360c97a6
--- /dev/null
+++ b/examples/min-platform/embedding/src/allocator.rs
@@ -0,0 +1,112 @@
+//! An allocator definition for this embedding.
+//!
+//! The Rust standard library and Wasmtime require a memory allocator to be
+//! configured. For custom embeddings of Wasmtime this might likely already be
+//! defined elsewhere in the system in which case that should be used. This file
+//! contains an example implementation using the Rust `dlmalloc` crate using
+//! memory created by `wasmtime_*` platform symbols. This provides a file that
+//! manages memory without any extra runtime dependencies, but this is just an
+//! example.
+//!
+//! Allocators in Rust are configured with the `#[global_allocator]` attribute
+//! and the `GlobalAlloc for T` trait impl. This should be used when hooking
+//! up to an allocator elsewhere in the system.
+
+use dlmalloc::Dlmalloc;
+use std::alloc::{GlobalAlloc, Layout};
+use std::ptr;
+use std::sync::Mutex;
+
+#[global_allocator]
+static MALLOC: MyGlobalDmalloc = MyGlobalDmalloc {
+    dlmalloc: Mutex::new(Dlmalloc::new_with_allocator(MyAllocator)),
+};
+
+struct MyGlobalDmalloc {
+    dlmalloc: Mutex<Dlmalloc<MyAllocator>>,
+}
+
+unsafe impl Send for MyGlobalDmalloc {}
+unsafe impl Sync for MyGlobalDmalloc {}
+
+struct MyAllocator;
+
+unsafe impl GlobalAlloc for MyGlobalDmalloc {
+    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
+        self.dlmalloc
+            .lock()
+            .unwrap()
+            .malloc(layout.size(), layout.align())
+    }
+
+    unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
+        self.dlmalloc
+            .lock()
+            .unwrap()
+            .calloc(layout.size(), layout.align())
+    }
+
+    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
+        self.dlmalloc
+            .lock()
+            .unwrap()
+            .realloc(ptr, layout.size(), layout.align(), new_size)
+    }
+
+    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
+        self.dlmalloc
+            .lock()
+            .unwrap()
+            .free(ptr, layout.size(), layout.align())
+    }
+}
+
+// Hand-copied from `crates/runtime/src/sys/custom/capi.rs`.
+const PROT_READ: u32 = 1 << 0;
+const PROT_WRITE: u32 = 1 << 1;
+extern "C" {
+    fn wasmtime_mmap_new(size: usize, prot_flags: u32, ret: &mut *mut u8) -> i32;
+    fn wasmtime_page_size() -> usize;
+    fn wasmtime_munmap(ptr: *mut u8, size: usize) -> i32;
+}
+
+unsafe impl dlmalloc::Allocator for MyAllocator {
+    fn alloc(&self, size: usize) -> (*mut u8, usize, u32) {
+        unsafe {
+            let mut ptr = ptr::null_mut();
+            let rc = wasmtime_mmap_new(size, PROT_READ | PROT_WRITE, &mut ptr);
+            if rc != 0 {
+                (ptr::null_mut(), 0, 0)
+            } else {
+                (ptr, size, 0)
+            }
+        }
+    }
+
+    fn remap(&self, _ptr: *mut u8, _old: usize, _new: usize, _can_move: bool) -> *mut u8 {
+        std::ptr::null_mut()
+    }
+
+    fn free_part(&self, _ptr: *mut u8, _old: usize, _new: usize) -> bool {
+        false
+    }
+
+    fn free(&self, ptr: *mut u8, size: usize) -> bool {
+        unsafe {
+            wasmtime_munmap(ptr, size);
+            true
+        }
+    }
+
+    fn can_release_part(&self, _flags: u32) -> bool {
+        false
+    }
+
+    fn allocates_zeros(&self) -> bool {
+        true
+    }
+
+    fn page_size(&self) -> usize {
+        unsafe { wasmtime_page_size() }
+    }
+}
diff --git a/examples/min-platform/embedding/src/lib.rs b/examples/min-platform/embedding/src/lib.rs
new file mode 100644
index 000000000..aabc8484e
--- /dev/null
+++ b/examples/min-platform/embedding/src/lib.rs
@@ -0,0 +1,71 @@
+use anyhow::Result;
+use wasmtime::{Engine, Instance, Linker, Module, Store};
+
+mod allocator;
+
+#[no_mangle]
+pub unsafe extern "C" fn run(buf: *mut u8, size: usize) -> usize {
+    let buf = std::slice::from_raw_parts_mut(buf, size);
+    match run_result() {
+        Ok(()) => 0,
+        Err(e) => {
+            let msg = format!("{e:?}");
+            let len = buf.len().min(msg.len());
+            buf[..len].copy_from_slice(&msg.as_bytes()[..len]);
+            len
+        }
+    }
+}
+
+fn run_result() -> Result<()> {
+    smoke()?;
+    simple_add()?;
+    simple_host_fn()?;
+    Ok(())
+}
+
+fn smoke() -> Result<()> {
+    let engine = Engine::default();
+    let module = Module::new(&engine, "(module)")?;
+    Instance::new(&mut Store::new(&engine, ()), &module, &[])?;
+    Ok(())
+}
+
+fn simple_add() -> Result<()> {
+    let engine = Engine::default();
+    let module = Module::new(
+        &engine,
+        r#"
+            (module
+                (func (export "add") (param i32 i32) (result i32)
+                    (i32.add (local.get 0) (local.get 1)))
+            )
+        "#,
+    )?;
+    let mut store = Store::new(&engine, ());
+    let instance = Instance::new(&mut store, &module, &[])?;
+    let func = instance.get_typed_func::<(u32, u32), u32>(&mut store, "add")?;
+    assert_eq!(func.call(&mut store, (2, 3))?, 5);
+    Ok(())
+}
+
+fn simple_host_fn() -> Result<()> {
+    let engine = Engine::default();
+    let module = Module::new(
+        &engine,
+        r#"
+            (module
+                (import "host" "multiply" (func $multiply (param i32 i32) (result i32)))
+                (func (export "add_and_mul") (param i32 i32 i32) (result i32)
+                    (i32.add (call $multiply (local.get 0) (local.get 1)) (local.get 2)))
+            )
+        "#,
+    )?;
+    let mut linker = Linker::<()>::new(&engine);
+    linker.func_wrap("host", "multiply", |a: u32, b: u32| a.saturating_mul(b))?;
+    let mut store = Store::new(&engine, ());
+    let instance = linker.instantiate(&mut store, &module)?;
+    let func = instance.get_typed_func::<(u32, u32, u32), u32>(&mut store, "add_and_mul")?;
+    assert_eq!(func.call(&mut store, (2, 3, 4))?, 10);
+    Ok(())
+}
diff --git a/examples/min-platform/embedding/wasmtime-platform.c b/examples/min-platform/embedding/wasmtime-platform.c
new file mode 100644
index 000000000..22acd96ba
--- /dev/null
+++ b/examples/min-platform/embedding/wasmtime-platform.c
@@ -0,0 +1,135 @@
+#include <assert.h>
+#include <errno.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/ucontext.h>
+#include <unistd.h>
+
+#include "wasmtime-platform.h"
+
+static int wasmtime_to_mmap_prot_flags(uint32_t prot_flags) {
+  int flags = 0;
+  if (prot_flags & WASMTIME_PROT_READ)
+    flags |= PROT_READ;
+  if (prot_flags & WASMTIME_PROT_WRITE)
+    flags |= PROT_WRITE;
+  if (prot_flags & WASMTIME_PROT_EXEC)
+    flags |= PROT_EXEC;
+  return flags;
+}
+
+int wasmtime_mmap_new(uintptr_t size, uint32_t prot_flags, uint8_t **ret) {
+  void *rc = mmap(NULL, size, wasmtime_to_mmap_prot_flags(prot_flags),
+                  MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (rc == MAP_FAILED)
+    return errno;
+  *ret = rc;
+  return 0;
+}
+
+int wasmtime_mmap_remap(uint8_t *addr, uintptr_t size, uint32_t prot_flags) {
+  void *rc = mmap(addr, size, wasmtime_to_mmap_prot_flags(prot_flags),
+                  MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (rc == MAP_FAILED)
+    return errno;
+  return 0;
+}
+
+int wasmtime_munmap(uint8_t *ptr, uintptr_t size) {
+  int rc = munmap(ptr, size);
+  if (rc != 0)
+    return errno;
+  return 0;
+}
+
+int wasmtime_mprotect(uint8_t *ptr, uintptr_t size, uint32_t prot_flags) {
+  int rc = mprotect(ptr, size, wasmtime_to_mmap_prot_flags(prot_flags));
+  if (rc != 0)
+    return errno;
+  return 0;
+}
+
+uintptr_t wasmtime_page_size(void) { return sysconf(_SC_PAGESIZE); }
+
+int32_t wasmtime_setjmp(const uint8_t **jmp_buf_out,
+                        void (*callback)(uint8_t *, uint8_t *),
+                        uint8_t *payload, uint8_t *callee) {
+  jmp_buf buf;
+  if (setjmp(buf) != 0)
+    return 0;
+  *jmp_buf_out = (uint8_t *)&buf;
+  callback(payload, callee);
+  return 1;
+}
+
+void wasmtime_longjmp(const uint8_t *jmp_buf_ptr) {
+  longjmp(*(jmp_buf *)jmp_buf_ptr, 1);
+}
+
+static wasmtime_trap_handler_t g_handler = NULL;
+
+static void handle_signal(int signo, siginfo_t *info, void *context) {
+  assert(g_handler != NULL);
+  uintptr_t ip, fp;
+#if defined(__aarch64__)
+  ucontext_t *cx = context;
+  ip = cx->uc_mcontext.pc;
+  fp = cx->uc_mcontext.regs[29];
+#elif defined(__x86_64__)
+  ucontext_t *cx = context;
+  ip = cx->uc_mcontext.gregs[REG_RIP];
+  fp = cx->uc_mcontext.gregs[REG_RBP];
+#else
+#error "Unsupported platform"
+#endif
+
+  bool has_faulting_addr = signo == SIGSEGV;
+  uintptr_t faulting_addr = 0;
+  if (has_faulting_addr)
+    faulting_addr = (uintptr_t)info->si_addr;
+  g_handler(ip, fp, has_faulting_addr, faulting_addr);
+
+  // If wasmtime didn't handle this trap then reset the handler to the default
+  // behavior which will probably abort the process.
+  signal(signo, SIG_DFL);
+}
+
+int wasmtime_init_traps(wasmtime_trap_handler_t handler) {
+  int rc;
+  g_handler = handler;
+
+  struct sigaction action;
+  memset(&action, 0, sizeof(action));
+
+  action.sa_sigaction = handle_signal;
+  action.sa_flags = SA_SIGINFO | SA_NODEFER;
+  sigemptyset(&action.sa_mask);
+
+  rc = sigaction(SIGILL, &action, NULL);
+  if (rc != 0)
+    return errno;
+  rc = sigaction(SIGSEGV, &action, NULL);
+  if (rc != 0)
+    return errno;
+  rc = sigaction(SIGFPE, &action, NULL);
+  if (rc != 0)
+    return errno;
+  return 0;
+}
+
+int wasmtime_memory_image_new(const uint8_t *ptr, uintptr_t len,
+                              struct wasmtime_memory_image **ret) {
+  *ret = NULL;
+  return 0;
+}
+
+int wasmtime_memory_image_map_at(struct wasmtime_memory_image *image,
+                                 uint8_t *addr, uintptr_t len) {
+  abort();
+}
+
+void wasmtime_memory_image_free(struct wasmtime_memory_image *image) {
+  abort();
+}
diff --git a/examples/min-platform/embedding/wasmtime-platform.h b/examples/min-platform/embedding/wasmtime-platform.h
new file mode 100644
index 000000000..e60b0ff2d
--- /dev/null
+++ b/examples/min-platform/embedding/wasmtime-platform.h
@@ -0,0 +1,222 @@
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/**
+ * Indicates that the memory region should be readable.
+ */
+#define WASMTIME_PROT_READ (1 << 0)
+
+/**
+ * Indicates that the memory region should be writable.
+ */
+#define WASMTIME_PROT_WRITE (1 << 1)
+
+/**
+ * Indicates that the memory region should be executable.
+ */
+#define WASMTIME_PROT_EXEC (1 << 2)
+
+/**
+ * Abstract pointer type used in the `wasmtime_memory_image_*` APIs which
+ * is defined by the embedder.
+ */
+typedef struct wasmtime_memory_image wasmtime_memory_image;
+
+/**
+ * Handler function for traps in Wasmtime passed to `wasmtime_init_traps`.
+ *
+ * This function is invoked whenever a trap is caught by the system. For
+ * example this would be invoked during a signal handler on Linux. This
+ * function is passed a number of parameters indicating information about the
+ * trap:
+ *
+ * * `ip` - the instruction pointer at the time of the trap.
+ * * `fp` - the frame pointer register's value at the time of the trap.
+ * * `has_faulting_addr` - whether this trap is associated with an access
+ *   violation (e.g. a segfault) meaning memory was accessed when it shouldn't
+ *   be. If this is `true` then the next parameter is filled in.
+ * * `faulting_addr` - if `has_faulting_addr` is true then this is the address
+ *   that was attempted to be accessed. Otherwise this value is not used.
+ *
+ * If this function returns then the trap was not handled by Wasmtime. This
+ * means that it's left up to the embedder how to deal with the trap/signal
+ * depending on its default behavior. This could mean forwarding to a
+ * non-Wasmtime handler, aborting the process, logging then crashing, etc. The
+ * meaning of a trap that's not handled by Wasmtime depends on the context in
+ * which the trap was generated.
+ *
+ * When this function does not return it's because `wasmtime_longjmp` is
+ * used to handle a Wasm-based trap.
+ */
+typedef void (*wasmtime_trap_handler_t)(uintptr_t ip,
+                                        uintptr_t fp,
+                                        bool has_faulting_addr,
+                                        uintptr_t faulting_addr);
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/**
+ * Creates a new virtual memory mapping of the `size` specified with
+ * protection bits specified in `prot_flags`.
+ *
+ * Memory can be lazily committed.
+ *
+ * Stores the base pointer of the new mapping in `ret` on success.
+ *
+ * Returns 0 on success and an error code on failure.
+ *
+ * Similar to `mmap(0, size, prot_flags, MAP_PRIVATE, 0, -1)` on Linux.
+ */
+extern int32_t wasmtime_mmap_new(uintptr_t size, uint32_t prot_flags, uint8_t **ret);
+
+/**
+ * Remaps the virtual memory starting at `addr` going for `size` bytes to
+ * the protections specified with a new blank mapping.
+ *
+ * This will unmap any prior mappings and decommit them. New mappings for
+ * anonymous memory are used to replace these mappings and the new area
+ * should have the protection specified by `prot_flags`.
+ *
+ * Returns 0 on success and an error code on failure.
+ *
+ * Similar to `mmap(addr, size, prot_flags, MAP_PRIVATE | MAP_FIXED, 0, -1)` on Linux.
+ */
+extern int32_t wasmtime_mmap_remap(uint8_t *addr, uintptr_t size, uint32_t prot_flags);
+
+/**
+ * Unmaps memory at the specified `ptr` for `size` bytes.
+ *
+ * The memory should be discarded and decommitted and should generate a
+ * segfault if accessed after this function call.
+ *
+ * Returns 0 on success and an error code on failure.
+ *
+ * Similar to `munmap` on Linux.
+ */
+extern int32_t wasmtime_munmap(uint8_t *ptr, uintptr_t size);
+
+/**
+ * Configures the protections associated with a region of virtual memory
+ * starting at `ptr` and going to `size`.
+ *
+ * Returns 0 on success and an error code on failure.
+ *
+ * Similar to `mprotect` on Linux.
+ */
+extern int32_t wasmtime_mprotect(uint8_t *ptr, uintptr_t size, uint32_t prot_flags);
+
+/**
+ * Returns the page size, in bytes, of the current system.
+ */
+extern uintptr_t wasmtime_page_size(void);
+
+/**
+ * Used to setup a frame on the stack to longjmp back to in the future.
+ *
+ * This function is used for handling traps in WebAssembly and is paried
+ * with `wasmtime_longjmp`.
+ *
+ * * `jmp_buf` - this argument is filled in with a pointer which if used
+ *   will be passed to `wasmtime_longjmp` later on by the runtime.
+ * * `callback` - this callback should be invoked after `jmp_buf` is
+ *   configured.
+ * * `payload` and `callee` - the two arguments to pass to `callback`.
+ *
+ * Returns 0 if `wasmtime_longjmp` was used to return to this function.
+ * Returns 1 if `wasmtime_longjmp` was not called and `callback` returned.
+ */
+extern int32_t wasmtime_setjmp(const uint8_t **jmp_buf,
+                               void (*callback)(uint8_t*, uint8_t*),
+                               uint8_t *payload,
+                               uint8_t *callee);
+
+/**
+ * Paired with `wasmtime_setjmp` this is used to jump back to the `setjmp`
+ * point.
+ *
+ * The argument here was originally passed to `wasmtime_setjmp` through its
+ * out-param.
+ *
+ * This function cannot return.
+ *
+ * This function may be invoked from the `wasmtime_trap_handler_t`
+ * configured by `wasmtime_init_traps`.
+ */
+extern void wasmtime_longjmp(const uint8_t *jmp_buf);
+
+/**
+ * Initializes trap-handling logic for this platform.
+ *
+ * Wasmtime's implementation of WebAssembly relies on the ability to catch
+ * signals/traps/etc. For example divide-by-zero may raise a machine
+ * exception. Out-of-bounds memory accesses may also raise a machine
+ * exception. This function is used to initialize trap handling.
+ *
+ * The `handler` provided is a function pointer to invoke whenever a trap
+ * is encountered. The `handler` is invoked whenever a trap is caught by
+ * the system.
+ *
+ * Returns 0 on success and an error code on failure.
+ */
+extern int32_t wasmtime_init_traps(wasmtime_trap_handler_t handler);
+
+/**
+ * Attempts to create a new in-memory image of the `ptr`/`len` combo which
+ * can be mapped to virtual addresses in the future.
+ *
+ * On successed the returned `wasmtime_memory_image` pointer is stored into `ret`.
+ * This value stored can be `NULL` to indicate that an image cannot be
+ * created but no failure occurred. The structure otherwise will later be
+ * deallocated with `wasmtime_memory_image_free` and
+ * `wasmtime_memory_image_map_at` will be used to map the image into new
+ * regions of the address space.
+ *
+ * The `ptr` and `len` arguments are only valid for this function call, if
+ * the image needs to refer to them in the future then it must make a copy.
+ *
+ * Both `ptr` and `len` are guaranteed to be page-aligned.
+ *
+ * Returns 0 on success and an error code on failure. Note that storing
+ * `NULL` into `ret` is not considered a failure, and failure is used to
+ * indicate that something fatal has happened and Wasmtime will propagate
+ * the error upwards.
+ */
+extern int32_t wasmtime_memory_image_new(const uint8_t *ptr,
+                                         uintptr_t len,
+                                         struct wasmtime_memory_image **ret);
+
+/**
+ * Maps the `image` provided to the virtual address at `addr` and `len`.
+ *
+ * This semantically should make it such that `addr` and `len` looks the
+ * same as the contents of what the memory image was first created with.
+ * The mappings of `addr` should be private and changes do not reflect back
+ * to `wasmtime_memory_image`.
+ *
+ * In effect this is to create a copy-on-write mapping at `addr`/`len`
+ * pointing back to the memory used by the image originally.
+ *
+ * Note that the memory region will be unmapped with `wasmtime_munmap` in
+ * the future.
+ *
+ * Aborts the process on failure.
+ */
+extern int32_t wasmtime_memory_image_map_at(struct wasmtime_memory_image *image,
+                                            uint8_t *addr,
+                                            uintptr_t len);
+
+/**
+ * Deallocates the provided `wasmtime_memory_image`.
+ *
+ * Note that mappings created from this image are not guaranteed to be
+ * deallocated and/or unmapped before this is called.
+ */
+extern void wasmtime_memory_image_free(struct wasmtime_memory_image *image);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif // __cplusplus
diff --git a/examples/min-platform/embedding/x86_64-unknown-unknown.json b/examples/min-platform/embedding/x86_64-unknown-unknown.json
new file mode 100644
index 000000000..1265585d6
--- /dev/null
+++ b/examples/min-platform/embedding/x86_64-unknown-unknown.json
@@ -0,0 +1,20 @@
+{
+  "arch": "x86_64",
+  "cpu": "x86-64",
+  "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
+  "features": "+sse4.2,+avx,+fma,+popcnt",
+  "dynamic-linking": true,
+  "has-thread-local": true,
+  "is-builtin": false,
+  "llvm-target": "x86_64-unknown-linux-gnu",
+  "max-atomic-width": 64,
+  "os": "minwasmtime",
+  "position-independent-executables": true,
+  "panic-strategy": "abort",
+  "frame-pointer": "always",
+  "relro-level": "full",
+  "stack-probes": {
+    "kind": "inline"
+  },
+  "target-pointer-width": "64"
+}
diff --git a/examples/min-platform/src/main.rs b/examples/min-platform/src/main.rs
new file mode 100644
index 000000000..3ecd9e5a7
--- /dev/null
+++ b/examples/min-platform/src/main.rs
@@ -0,0 +1,82 @@
+use anyhow::Result;
+
+#[cfg(not(target_os = "linux"))]
+fn main() -> Result<()> {
+    eprintln!("This example only runs on Linux right now");
+    Ok(())
+}
+
+#[cfg(target_os = "linux")]
+fn main() -> Result<()> {
+    use libloading::os::unix::{Library, Symbol, RTLD_GLOBAL, RTLD_NOW};
+    use object::{Object, ObjectSymbol, SymbolKind};
+    use std::io::Write;
+    use std::path::Path;
+
+    let target = std::env::args().nth(1).unwrap();
+    let target = Path::new(&target).file_stem().unwrap().to_str().unwrap();
+    // Path to the artifact which is the build of the embedding.
+    //
+    // In this example this is a dynamic library intended to be run on Linux.
+    // Note that this is just an example of an artifact and custom build
+    // processes can produce different kinds of artifacts.
+    let lib = format!("../../target/{target}/release/libembedding.so");
+    let binary = std::fs::read(&lib)?;
+    let object = object::File::parse(&binary[..])?;
+
+    // Showcase verification that the dynamic library in question doesn't depend
+    // on much. Wasmtime build in a "minimal platform" mode is allowed to
+    // depend on some standard C symbols such as `memcpy` but any OS-related
+    // symbol must be prefixed by `wasmtime_*` and be documented in
+    // `crates/runtime/src/sys/custom/capi.rs`.
+    //
+    // This is effectively a double-check of the above assesrtion and showing
+    // how running `libembedding.so` in this case requires only minimal
+    // dependencies.
+    for sym in object.symbols() {
+        if !sym.is_undefined() || sym.is_weak() || sym.kind() == SymbolKind::Null {
+            continue;
+        }
+
+        match sym.name()? {
+            "memmove" | "memset" | "memcmp" | "memcpy" | "bcmp" | "__tls_get_addr" => {}
+            s if s.starts_with("wasmtime_") => {}
+            other => {
+                panic!("unexpected dependency on symbol `{other}`")
+            }
+        }
+    }
+
+    // Next is an example of running this embedding, which also serves as test
+    // that basic functionality actually works.
+    //
+    // Here the `wasmtime_*` symbols are implemented by
+    // `./embedding/wasmtime-platform.c` which is an example implementation
+    // against glibc on Linux. This library is compiled into
+    // `libwasmtime-platform.so` and is dynamically opened here to make it
+    // available for later symbol resolution. This is just an implementation
+    // detail of this exable to enably dynamically loading `libembedding.so`
+    // next.
+    //
+    // Next the `libembedding.so` library is opened and the `run` symbol is
+    // run. The dependencies of `libembedding.so` are either satisfied by our
+    // ambient libc (e.g. `memcpy` and friends) or `libwasmtime-platform.so`
+    // (e.g. `wasmtime_*` symbols).
+    //
+    // The embedding is then run to showcase an example and then an error, if
+    // any, is written to stderr.
+    unsafe {
+        let _platform_symbols =
+            Library::open(Some("./libwasmtime-platform.so"), RTLD_NOW | RTLD_GLOBAL)?;
+
+        let lib = Library::new(&lib)?;
+        let run: Symbol<extern "C" fn(*mut u8, usize) -> usize> = lib.get(b"run")?;
+
+        let mut buf = Vec::with_capacity(1024);
+        let len = run(buf.as_mut_ptr(), buf.capacity());
+        buf.set_len(len);
+
+        std::io::stderr().write_all(&buf).unwrap();
+    }
+    Ok(())
+}
diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml
index 505b56aa4..8b6560f7a 100644
--- a/supply-chain/audits.toml
+++ b/supply-chain/audits.toml
@@ -313,6 +313,22 @@ start = "2022-08-22"
 end = "2024-06-26"
 notes = "The Bytecode Alliance is the author of this crate."
 
+[[wildcard-audits.wasmtime-c-api-impl]]
+who = "Trevor Elliott <telliott@fastly.com>"
+criteria = "safe-to-deploy"
+user-id = 73222 # wasmtime-publish
+start = "2024-02-20"
+end = "2025-02-28"
+notes = "The Bytecode Alliance is the author of this crate."
+
+[[wildcard-audits.wasmtime-c-api-macros]]
+who = "Trevor Elliott <telliott@fastly.com>"
+criteria = "safe-to-deploy"
+user-id = 73222 # wasmtime-publish
+start = "2024-02-20"
+end = "2025-02-28"
+notes = "The Bytecode Alliance is the author of this crate."
+
 [[wildcard-audits.wasmtime-cache]]
 who = "Bobby Holley <bobbyholley@gmail.com>"
 criteria = "safe-to-deploy"
@@ -3270,6 +3286,12 @@ user-id = 696 # Nick Fitzgerald (fitzgen)
 start = "2019-07-30"
 end = "2025-02-12"
 
+[[trusted.dlmalloc]]
+criteria = "safe-to-deploy"
+user-id = 1 # Alex Crichton (alexcrichton)
+start = "2020-05-19"
+end = "2025-02-23"
+
 [[trusted.equivalent]]
 criteria = "safe-to-deploy"
 user-id = 539 # Josh Stone (cuviper)
diff --git a/supply-chain/config.toml b/supply-chain/config.toml
index 91df64277..f66e4fcdb 100644
--- a/supply-chain/config.toml
+++ b/supply-chain/config.toml
@@ -85,6 +85,12 @@ audit-as-crates-io = true
 [policy.wasmtime-asm-macros]
 audit-as-crates-io = true
 
+[policy.wasmtime-c-api-impl]
+audit-as-crates-io = true
+
+[policy.wasmtime-c-api-macros]
+audit-as-crates-io = true
+
 [policy.wasmtime-cache]
 audit-as-crates-io = true
 
diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock
index 5ad0c81a8..dbd0ec849 100644
--- a/supply-chain/imports.lock
+++ b/supply-chain/imports.lock
@@ -81,6 +81,14 @@ audited_as = "17.0.1"
 version = "19.0.0"
 audited_as = "17.0.1"
 
+[[unpublished.wasmtime-c-api-impl]]
+version = "19.0.0"
+audited_as = "18.0.1"
+
+[[unpublished.wasmtime-c-api-macros]]
+version = "19.0.0"
+audited_as = "18.0.1"
+
 [[unpublished.wasmtime-cache]]
 version = "19.0.0"
 audited_as = "17.0.1"
@@ -466,6 +474,13 @@ user-id = 696
 user-login = "fitzgen"
 user-name = "Nick Fitzgerald"
 
+[[publisher.dlmalloc]]
+version = "0.2.4"
+when = "2022-08-17"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.equivalent]]
 version = "1.0.1"
 when = "2023-07-10"
@@ -848,6 +863,12 @@ when = "2024-02-15"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wasm-encoder]]
+version = "0.201.0"
+when = "2024-02-27"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wasm-metadata]]
 version = "0.10.20"
 when = "2024-02-12"
@@ -861,6 +882,12 @@ when = "2024-02-15"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wasm-metadata]]
+version = "0.201.0"
+when = "2024-02-27"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wasm-mutate]]
 version = "0.2.48"
 when = "2024-02-12"
@@ -874,6 +901,12 @@ when = "2024-02-15"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wasm-mutate]]
+version = "0.201.0"
+when = "2024-02-27"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wasm-smith]]
 version = "0.16.1"
 when = "2024-02-12"
@@ -887,6 +920,12 @@ when = "2024-02-15"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wasm-smith]]
+version = "0.201.0"
+when = "2024-02-27"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wasmparser]]
 version = "0.121.2"
 when = "2024-02-12"
@@ -900,6 +939,12 @@ when = "2024-02-15"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wasmparser]]
+version = "0.201.0"
+when = "2024-02-27"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wasmprinter]]
 version = "0.2.80"
 when = "2024-02-12"
@@ -913,6 +958,12 @@ when = "2024-02-15"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wasmprinter]]
+version = "0.201.0"
+when = "2024-02-27"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wasmtime]]
 version = "17.0.1"
 when = "2024-02-07"
@@ -925,6 +976,18 @@ when = "2024-02-07"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wasmtime-c-api-impl]]
+version = "18.0.1"
+when = "2024-02-20"
+user-id = 73222
+user-login = "wasmtime-publish"
+
+[[publisher.wasmtime-c-api-macros]]
+version = "18.0.1"
+when = "2024-02-20"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wasmtime-cache]]
 version = "17.0.1"
 when = "2024-02-07"
@@ -1070,6 +1133,12 @@ when = "2024-02-15"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wast]]
+version = "201.0.0"
+when = "2024-02-27"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wat]]
 version = "1.0.88"
 when = "2024-02-12"
@@ -1083,6 +1152,12 @@ when = "2024-02-15"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wat]]
+version = "1.201.0"
+when = "2024-02-27"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wiggle]]
 version = "17.0.1"
 when = "2024-02-07"
@@ -1281,6 +1356,12 @@ when = "2024-02-23"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wit-bindgen]]
+version = "0.19.2"
+when = "2024-02-26"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wit-bindgen-core]]
 version = "0.17.0"
 when = "2024-02-05"
@@ -1294,6 +1375,12 @@ when = "2024-02-23"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wit-bindgen-core]]
+version = "0.19.2"
+when = "2024-02-26"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wit-bindgen-rust]]
 version = "0.17.0"
 when = "2024-02-05"
@@ -1307,6 +1394,12 @@ when = "2024-02-23"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wit-bindgen-rust]]
+version = "0.19.2"
+when = "2024-02-26"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wit-bindgen-rust-macro]]
 version = "0.17.0"
 when = "2024-02-05"
@@ -1320,6 +1413,12 @@ when = "2024-02-23"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wit-bindgen-rust-macro]]
+version = "0.19.2"
+when = "2024-02-26"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wit-component]]
 version = "0.20.1"
 when = "2024-01-29"
@@ -1340,6 +1439,12 @@ when = "2024-02-15"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wit-component]]
+version = "0.201.0"
+when = "2024-02-27"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wit-parser]]
 version = "0.13.1"
 when = "2024-01-09"
@@ -1360,6 +1465,12 @@ when = "2024-02-15"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wit-parser]]
+version = "0.201.0"
+when = "2024-02-27"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.witx]]
 version = "0.9.1"
 when = "2021-06-22"


Test patch:
diff --git a/tests/all/gc.rs b/tests/all/gc.rs
index 2f7efad07..7910e5739 100644
--- a/tests/all/gc.rs
+++ b/tests/all/gc.rs
@@ -651,3 +651,81 @@ fn gc_and_tail_calls_and_stack_arguments() -> anyhow::Result<()> {
 
     Ok(())
 }
+
+#[test]
+#[cfg_attr(miri, ignore)]
+fn no_leak_with_global_get_elem_segment() -> anyhow::Result<()> {
+    let dropped = Arc::new(AtomicBool::new(false));
+
+    let engine = Engine::default();
+    let mut store = Store::new(&engine, ());
+    let module = Module::new(
+        &engine,
+        r#"
+            (module
+                (import "" "" (global $init externref))
+                (start $f)
+                (table $t 1 externref)
+                (elem $e externref (global.get $init))
+
+                (func $f
+                    i32.const 0
+                    i32.const 0
+                    i32.const 1
+                    table.init $t $e
+
+                    i32.const 0
+                    i32.const 0
+                    i32.const 1
+                    table.init $t $e
+                )
+            )
+        "#,
+    )?;
+
+    let global = Global::new(
+        &mut store,
+        GlobalType::new(ValType::EXTERNREF, Mutability::Const),
+        Val::ExternRef(Some(ExternRef::new(SetFlagOnDrop(dropped.clone())))),
+    )?;
+
+    Instance::new(&mut store, &module, &[global.into()])?;
+
+    drop(store);
+
+    assert!(dropped.load(SeqCst));
+    Ok(())
+}
+
+#[test]
+#[cfg_attr(miri, ignore)]
+fn table_init_with_externref_global_get() -> anyhow::Result<()> {
+    let dropped = Arc::new(AtomicBool::new(false));
+
+    let mut config = Config::new();
+    config.wasm_function_references(true);
+    let engine = Engine::new(&config)?;
+    let mut store = Store::new(&engine, ());
+    let module = Module::new(
+        &engine,
+        r#"
+            (module
+                (import "" "" (global $init externref))
+                (table $t 1 externref (global.get $init))
+            )
+        "#,
+    )?;
+
+    let global = Global::new(
+        &mut store,
+        GlobalType::new(ValType::EXTERNREF, Mutability::Const),
+        Val::ExternRef(Some(ExternRef::new(SetFlagOnDrop(dropped.clone())))),
+    )?;
+
+    Instance::new(&mut store, &module, &[global.into()])?;
+
+    drop(store);
+
+    assert!(dropped.load(SeqCst));
+    Ok(())
+}