Skip to content

feat(host)!: replace wasmer-wasmi backend with wasmer-v8 - #1

Closed
synchwire wants to merge 25 commits into
mainfrom
swap-wasmi-for-v8
Closed

feat(host)!: replace wasmer-wasmi backend with wasmer-v8#1
synchwire wants to merge 25 commits into
mainfrom
swap-wasmi-for-v8

Conversation

@synchwire

Copy link
Copy Markdown
Owner

Summary

  • Swap the wasmer-wasmi backend for a new wasmer-v8 backend, patching in the synchwire/wasmer fork via [patch.crates-io] so we pick up the iOS V8 target + Engine::set_flags_from_string for --jitless.
  • feat: drop WAMR and Wasmi support wasmerio/wasmer#6500 removed wasmi from upstream wasmer; V8 is now the only non-JIT-dependent wasmer backend.
  • Rename the module/feature/scripts/CI references from wasmi → v8 throughout.

Non-obvious bits

  • Root `Cargo.toml` pins wasmer at `7.2.0-alpha.1` and adds `[patch.crates-io]` entries for every wasmer sub-crate pointing at `synchwire/wasmer` main. The fork is intentionally unpublished; we don't plan to ship a crates.io release of it.
  • iOS mobile job installs brew `llvm` (for `llvm-objcopy`, which wasmer's v8 build.rs shells out to) and adds the `aarch64-apple-ios` rustup target.
  • `test-windows` drops the v8 matrix row — v8-custom-builds' Windows tarball ships `v8_monolith.lib` which doesn't export the `wasm_*` C-API symbols wasmer links against. That's a fork-side constraint we surface explicitly.
  • Android matrix excludes `x86_64-linux-android` and `armv7-linux-androideabi` from v8 — the fork only builds V8 for aarch64-android.

Known issue (to investigate)

`cargo test -p tests --features wasmer-v8` crashes with SIGSEGV after the second test. Single tests pass. The crash is in V8's per-store lifecycle — likely a wasmer-side concern around wasm-c-api `wasm_store_t` ↔ V8 Isolate ordering. Flagged as follow-up.

Test plan

  • CI `test-and-bench` matrix: `wasmer-sys-cranelift`, `wasmer-sys-llvm`, `wasmer-v8` on ubuntu-latest + macos-latest (wasmer-v8 is expected to hit the SIGSEGV above)
  • CI `test-all-backends`: builds+tests with every backend enabled simultaneously. Routes through `sys` at test time so should pass.
  • CI `test-windows`: `wasmer-sys-cranelift` + `wasmer-sys-llvm` (v8 intentionally dropped)
  • CI `mobile.android`: aarch64 × {cranelift, v8}, x86_64 + armv7 × cranelift only
  • CI `mobile.ios`: aarch64-apple-ios × wasmer-v8
  • Locally: `cargo build -p holochain_wasmer_host --features wasmer-v8` ✓ (linux-amd64, green)
  • Locally: root workspace tests with wasmer-v8 ✓ (green)

synchwire and others added 25 commits April 7, 2026 11:29
…oolchain.toml

Remove the holonix dependency and add direct nixpkgs, flake-parts, and
rust-overlay inputs. The Rust toolchain is now loaded from a new
rust-toolchain.toml (pinned to 1.85.0) which also specifies
wasm32-unknown-unknown and aarch64-apple-ios targets.

Changes:
- Replace holonix input with nixpkgs, flake-parts, and rust-overlay
- Add rust-toolchain.toml with Rust 1.85.0, minimal profile, clippy,
  rustfmt, and required targets
- Remove dtolnay/rust-toolchain from Windows CI jobs in test.yml
- Remove rustup target add from iOS mobile CI job
- Delete stale flake.lock (regenerated on first nix evaluation)
- Fix new Rust lints: remove #[inline(always)] from #[no_mangle]
  extern fns, replace clone-to-slice with std::slice::from_ref

Closes holochain#166
Bumps wasmer and wasmer-middlewares from 6.0.0 to 7.1.0 (closes holochain#167).

Wasmer 7.1.0 requires rustc >= 1.91, so the rust-toolchain.toml is moved
from a pinned 1.85.0 to `stable`. The nix flake is updated from LLVM 18
to LLVM 21 to match wasmer's new llvm-sys 211 dependency for the
production (LLVM) backend.

The wasmer wamr backend has a UB bug exposed by this upgrade: in
`Function::call`, `slice::from_raw_parts` is invoked on the results
vector without checking that the data pointer is non-null when the wasm
function has zero return values, which trips Rust's stabilized
unsafe-precondition check in debug builds. This is reported upstream as
wasmerio/wasmer#6392; until that is fixed, the wamr workspace tests are
run with --release (where the precondition check is compiled out).

A few incidental fixups required by the newer rustc/clippy: drop unused
imports in the wamr module file and replace a `repeat().take()` with
`repeat_n()` in a test wasm.

No source-level wasmer API changes were required.
The wasmer 7.x upgrade switched the LLVM backend from llvm-sys 180 to
llvm-sys 211. The Linux/macOS jobs run via the nix flake which is
already updated, but the Windows jobs download a custom LLVM build
directly and set LLVM_SYS_180_PREFIX. Bump both the download URL
(wasmerio/llvm-custom-builds 18.x -> 21.x) and the env var name
(LLVM_SYS_180_PREFIX -> LLVM_SYS_211_PREFIX).
The previous `which llvm-config` lookup was non-deterministic — `clang`
is also in the dev shell and could shadow llvm-config in PATH. Set the
env var directly from the LLVM 21 dev output so it always resolves to
the right LLVM version regardless of PATH ordering.
Adds the pure-Rust `wasmi` interpreter as a third holochain_wasmer_host
backend alongside `wasmer_sys` and `wasmer_wamr`, exposed via the new
`wasmer_wasmi` cargo feature. wasmi is built on top of wasmer's
wasm-c-api binding and gives us an iOS-buildable interpreter (the wamr
backend cannot link on iOS because the upstream WAMR iOS build only
ships `iwasm.dylib` and not the `vmlib` wasmer expects).

The three backend features (`wasmer_sys`, `wasmer_wamr`,
`wasmer_wasmi`) are mutually exclusive; lib.rs is updated to enforce
that and to require exactly one. The wasmi module hands out a
process-wide `OnceLock<Engine>` so every module, store and instance
share the same function-type registry — wasmi 1.x panics with
"encountered foreign entity in func type registry" if a module from
one engine is instantiated against a store backed by another.

Also fans the iOS mobile workflow into a matrix that builds both
`wasmer_wamr` (advisory, marked `continue-on-error`) and
`wasmer_wasmi` so the new backend is exercised on aarch64-apple-ios in
CI.
Mirrors the existing `wasmer_wamr` plumbing in the tests crate so the
same suite runs against the wasmi interpreter, plus matching
`scripts/{test,bench}-wasmer_wasmi.sh` and a `wasmer_wasmi` matrix
entry in the test-and-bench CI workflow.

The test harness builds the Store from `make_engine()` (instead of
`Store::default()`) on the wasmi backend so module/store/instance all
share the same engine; wasmi's per-engine function-type registry
otherwise panics on instantiation. `tests::short_circuit` is ignored
under `wasmer_wasmi` because wasmer 7.1.0's wasmi backend builds a
wasm trap from a non-NUL-terminated byte vector in
`backend/wasmi/error.rs::Trap::into_wasm_trap`, which trips a
non-unwinding panic inside `wasmi_c_api_impl::wasm_trap_new` and
aborts the test process. Tracked upstream as wasmerio/wasmer#6397.

With these in place 25/26 root-workspace tests pass on wasmi (the one
ignored is `short_circuit`; `decrease_points_test` and
`infinite_loop` were already ignored on the interpreter backends).
The wasmer_wamr backend has been a chronic source of pain — most
recently the upstream WAMR iOS build only producing iwasm.dylib
(prevented us from supporting iOS) and the slice::from_raw_parts UB in
wasmer's wamr Function::call (forced wamr tests onto --release).
With wasmer_wasmi now wired up as an iOS-buildable replacement we no
longer need it, so drop it everywhere:

- crates/host: drop the `wasmer_wamr` cargo feature, delete
  `module/wasmer_wamr.rs`, and collapse the lib.rs feature gate to
  the two surviving backends.
- test-crates/tests: drop the `wasmer_wamr` feature, collapse the
  `any(wamr, wasmi)` cfg branches to plain `wasmer_wasmi`, and
  retitle the metering stub error message.
- scripts: delete `{test,bench,fuzz}-wasmer_wamr.sh` and drop the
  wamr lines from the dispatcher scripts.
- flake.nix: drop the wamr-only `cmake` and `ninja` packages from
  the dev shell. `clang` / `libclang` / `LIBCLANG_PATH` stay
  because wasmer's build script still runs bindgen against the wasmi
  C API headers when the wasmi feature is enabled.

The two surviving backends (wasmer_sys and wasmer_wasmi) continue to
pass their full test suites locally.
test.yml:
- Trigger on push to main (was push to a non-existent `develop`
  branch) and pull_request as before.
- Drop wasmer_wamr from the test-and-bench matrix and remove the now-
  empty bench-on-macOS exclusion list. wasmer_wasmi runs everywhere on
  ubuntu and macOS without exceptions.
- Add wasmer_wasmi to the test-windows matrix so all three surviving
  backends are exercised on every supported OS, set LIBCLANG_PATH to
  the LLVM bundle's bin/ so bindgen can find libclang.dll, and delete
  the now-redundant test-windows-wamr job and the Ninja install (Ninja
  was wamr-only).
- Drop the dead `if: ! contains(matrix.platform.runs-on, ...)` guard
  on cachix-action — `matrix.platform` was never defined.

mobile.yml:
- Tighten the trigger from `push: {}` (every push to every branch) to
  the same `push: branches: [main] + pull_request` shape used by
  test.yml, and add a concurrency group so re-pushes to a PR cancel
  the in-flight run.
- Bump `actions/checkout` from v4 to v6.
- Fan the android job into a target × wasmer-feature matrix so we
  cross-build holochain_wasmer_host for all three Android targets
  against both wasmer_sys_dev and wasmer_wasmi (it previously only
  built the workspace's default features).
- Collapse the iOS job to a single wasmer_wasmi build now that the
  wamr leg has been removed. Drop the matrix and the `continue-on-
  error` shim that existed only to keep wamr advisory.
Addresses the check issue holochain#141 ('review wasm validation and ensure we
are following best practices'). No behaviour change — the conclusion
of the review is that we are already validating correctly via
`Module::from_binary`, and the filesystem cache deserialize path is
trusted by design per wasmer's `unsafe` contract. Capture both facts
as doc comments on `ModuleBuilder::from_binary` and
`ModuleBuilder::from_serialized_module` so the question doesn't need
to be re-derived from the wasmer source.
The Dockerfile is based on `holochain/fuzzbox:base`, uses
`nix-shell` rather than `nix develop` (the repo migrated to flakes),
and was last touched in late 2022. The accompanying
`.github/workflows/build.yml` rebuilds and pushes
`holochain/fuzzbox:holochain-wasmer` to Docker Hub on every push to
`main`, with no documented consumer. The README points users at the
same broken pipeline.

Remove all three. Closes holochain#169.
The `test-fuzz` setup has been in limbo since 2023: the targets were
written, the developer who set them up reported finding bugs at the
time, and the harness has not actually been run productively since
then. The most recent commit on it was Oct 2024 — "Fix fuzz scripts,
although fuzzing doesn't just work". The targets themselves
(round_trip_u32 / _u64 / _usize, round_trip_allocation, alloc_dealloc,
process_string_fuzz) are property tests over small, deterministic
helpers; reviving the harness as-is would not find new bugs because
the bugs it caught in 2023 are baked in as fixes. The kinds of inputs
where fuzzing would actually pay off (structured-but-invalid wasm,
mutated serialized artifacts, adversarial wire-format messages) are
not what the existing targets cover.

Rather than perpetuate a half-deleted harness, drop it entirely:

- Delete `scripts/fuzz.sh`, `scripts/fuzz-wasmer_sys_dev.sh` and
  `scripts/fuzz-wasmer_sys_prod.sh`.
- Drop the `test-fuzz` workspace dependency, the `fuzzing` cargo
  feature on `holochain_wasmer_common`, and the `test-fuzz` dep
  declarations in the common, guest and tests crates. Cargo.lock loses
  ~210 lines as the AFL/test-fuzz dependency tree drops out.
- Replace the six `#[test_fuzz::test_fuzz]` annotated functions with
  plain `#[test]` fns that exercise the same property over a small
  fixed seed table. The `some_*` sibling `#[test]` cases are folded
  into the new seed tables. The `#[cfg(not(target_os = "windows"))]`
  workarounds for trailofbits/test-fuzz#171 go away with them.

If at some future point the team wants to fuzz the high-value targets
above, `cargo-fuzz` (libFuzzer-based, the de-facto standard) is the
natural starting point — it doesn't need to inherit any of this.

Closes holochain#115. Closes holochain#122.
`WasmError` previously carried a `file: String` populated by the
`file!()` macro at the call site of `wasm_error!`. `file!()` produces
whatever path rustc has for the source file, which for path
dependencies and registry crates is an absolute filesystem path baked
in at compile time. The result was that wasm guest errors shipped to
end users showed paths like `/home/matt/Projects/Holochain/.../src/lib.rs`
or `/home/$USER/.cargo/registry/src/index.crates.io-.../src/lib.rs` —
the build machine's directory layout, leaked across the wasm boundary.

Replace the field with `module_path: String`, populated from
[`std::module_path!`]. The Rust module path of the call site is
machine-independent, includes the crate name automatically, and works
the same regardless of how the calling crate was pulled in (in-
workspace, path dep, registry). The two macros that constructed
`WasmError` (`wasm_error!` in the common crate and `wasm_host_error!`
in the host crate) are updated in lockstep, the destructure in
`crates/host/src/guest.rs` and the literal-construction assertions in
`test-crates/tests/src/test.rs` follow.

While touching the type, also replace `WasmError`'s `Display` impl —
which previously just delegated to `Debug` — with a deliberate
`module::path:line: <inner>` format. This decouples Display from
Debug, makes the rendered errors consistent with how panics and
tracing spans report call sites, and gives consumers a stable
user-facing format we can iterate on independently of the struct's
debug representation.

This is a breaking change to the public `WasmError` API. The wire
format of serialized errors changes too; old serialized payloads will
not deserialize against the new struct. Both are intentional and
align with an upcoming Holochain release that doesn't preserve any
application data.

Closes holochain#139. Closes holochain#140.
The cargo feature flags for the wasmer backends were defined as
mutually exclusive, with a `compile_error!` in the host crate's lib.rs
forbidding any combination of `wasmer_sys` and `wasmer_wasmi`. That
violates Rust's additivity contract for cargo features: in a dependency
graph where crate A transitively pulls in
`holochain_wasmer_host/wasmer_sys_dev` and crate B pulls in
`holochain_wasmer_host/wasmer_wasmi`, cargo unifies the feature set
and the resulting build doesn't compile. Today the only consumer is
holochain so this hasn't bitten anyone, but it's a real footgun for any
future caller.

Verified empirically that wasmer 7.x supports enabling its own
backends (`sys`, `cranelift`, `llvm`, `wasmi`) simultaneously: the
upstream lib.rs only forbids combinations of the `*-default` umbrella
features, which we don't use. So the constraint is purely on our side
and can be removed.

This commit restructures the host crate so every backend feature can
be enabled in the same build:

- Drop the `compile_error!` mutex in lib.rs and replace it with two
  weaker checks: "at least one backend must be enabled" and "if
  wasmer_sys is enabled, at least one of its compiler sub-features
  must be enabled".
- Rename `wasmer_sys_dev` -> `wasmer_sys_cranelift` and
  `wasmer_sys_prod` -> `wasmer_sys_llvm`. The dev/prod naming was
  misleading — these are compiler choices, not deployment modes — and
  the rename makes it possible to have both compilers enabled at once.
- Make `wasmer_sys_cranelift` and `wasmer_sys_llvm` independent
  cargo features. `wasmer_sys` is the umbrella that pulls in
  `wasmer-middlewares` and `wasmer/sys`; the compiler sub-features
  add their respective compiler crate.
- Restructure the host module layout: `module/wasmer_sys.rs` ->
  `module/sys.rs` exposed as `pub mod sys`, `module/wasmer_wasmi.rs`
  -> `module/wasmi.rs` exposed as `pub mod wasmi`. Drop the
  `pub use ...::*` glob re-exports that previously made the engine
  factories appear at `module::make_engine` etc — those globs were
  the symbol-collision mechanism that necessitated mutual exclusion.
  The factories now live at fully-qualified
  `module::sys::make_cranelift_engine` /
  `module::sys::make_llvm_engine` / `module::wasmi::make_engine`.
- Split the sys engine factory into per-compiler functions
  (`make_cranelift_engine`, `make_llvm_engine`) so both compilers
  can coexist in the same build. The metering and tunables wiring
  is shared via small helpers `configure_compiler` and
  `apply_tunables`. A `make_engine` shorthand picks Cranelift if
  it's enabled and falls back to LLVM otherwise — convenient for the
  test harness, equivalent to the old single-compiler shape.
- `ModuleBuilder::new` now takes both `make_engine` and
  `make_runtime_engine` as explicit `fn() -> Engine` parameters.
  Previously it relied on a glob re-export to resolve a single
  `make_runtime_engine` symbol; making the choice an explicit
  parameter is what shifts the backend decision from compile time to
  the call site.
- `ModuleCache::new` gains the same two parameters and forwards them
  to `ModuleBuilder::new`. The convenience constructor stays useful
  for callers that don't need a custom builder, but no longer hides
  the backend choice.

The test harness (`test-crates/tests/src/wasms.rs`) is updated so the
`module()` and `instance()` methods are gated as
"sys when sys is available, wasmi otherwise". With both backends
enabled, sys takes priority because it's the metered backend and
exercises more of the codepaths under test; the wasmi-only matrix leg
covers wasmi's runtime behaviour. The cfg_attr ignore on
`tests::short_circuit` is updated to fire only on the wasmi-only
path, matching the upstream wasmer 7.1.0 wasm_trap_new bug
(wasmerio/wasmer#6397) it works around.

CI changes:
- The test-and-bench, test-windows and android-mobile matrices are
  renamed to use the new feature names.
- A new `test-all-backends` job is added to the test workflow that
  builds and runs the host crate with every backend feature enabled
  simultaneously. This is the CI guarantee that the additivity
  property doesn't regress.

Scripts `{test,bench}-wasmer_sys_dev.sh` and `{test,bench}-wasmer_sys_prod.sh`
are renamed to `{test,bench}-wasmer_sys_cranelift.sh` and
`{test,bench}-wasmer_sys_llvm.sh` to match the feature rename, and
the dispatcher scripts `test.sh` / `bench.sh` are updated.

This is a breaking change to the public cargo feature graph and the
host crate's public module path. Consumers that today depend on
`holochain_wasmer_host` with `features = ["wasmer_sys_dev"]` need
to update to `features = ["wasmer_sys", "wasmer_sys_cranelift"]`
(the equivalent of the old default), and any code that calls
`module::make_engine()` or `module::build_module()` directly needs to
qualify those calls with the appropriate backend submodule
(`module::sys::make_cranelift_engine()` etc). This aligns with the
upcoming holochain major release that's already absorbing other
breaking changes.
The cargo feature flags in `holochain_wasmer_common` and
`holochain_wasmer_host` were defined in snake_case
(`wasmer_sys_cranelift`, `error_as_host`, etc). Cargo accepts both
conventions but kebab-case is the documented Cargo recommendation
and is what the rest of the crates published from this org use. The
inconsistency was paper cuts every time someone copied a feature list
between Cargo.toml files.

Rename, with no behavioural changes:

- error_as_host        -> error-as-host
- debug_memory         -> debug-memory
- wasmer_sys           -> wasmer-sys
- wasmer_sys_cranelift -> wasmer-sys-cranelift
- wasmer_sys_llvm      -> wasmer-sys-llvm
- wasmer_wasmi         -> wasmer-wasmi

Touches the three `[features]` tables, every `cfg(feature = "...")`
site in the host and common crates and the test harness, the user-
facing doc comments that name the features, both GitHub workflows,
and the per-backend test/bench scripts (which are also git-mv'd to
the new names so the workflow matrix entries continue to resolve).

This is a breaking change to the public cargo feature graph.
Downstream consumers must update their `Cargo.toml` feature lists
accordingly. This lands alongside the other breaking changes already
queued for the next release.
Working towards a 1.0 of these crates. Several pre-1.0 untidiness
points around documentation that should be fixed before declaring
the API stable.

- Add a crate-level `//!` doc to `holochain_wasmer_host`'s lib.rs
  that gives a one-paragraph overview of what the crate does and
  documents every cargo feature (the four wasmer backend features,
  `error-as-host`, `debug-memory`) including which ones are on by
  default and what each one is for. The intent is that when a new
  user lands on the crate's docs.rs page, the feature matrix is
  immediately visible without needing to read Cargo.toml.

- Add a similar (smaller) crate-level doc to `holochain_wasmer_common`
  documenting its single `error-as-host` feature.

- Add `[package.metadata.docs.rs]` to the host crate so docs.rs
  builds with `wasmer-sys`, `wasmer-sys-cranelift`, `wasmer-wasmi`
  and `error-as-host` enabled. This is necessary for the intra-doc
  links from the new lib.rs doc into `module::sys::*` and
  `module::wasmi::*` to resolve. `wasmer-sys-llvm` is intentionally
  omitted because `llvm-sys` requires a prebuilt LLVM toolchain that
  the docs.rs builder doesn't provide; the LLVM compiler factory is
  mentioned in prose without an intra-doc link.

- Refresh `module.rs`'s top-level `//!` doc. The previous wording was
  written before the additive-features change landed and reads like
  the two backends are mutually exclusive choices; rewrite it to
  match the actual current shape (both backends can coexist, picked
  per-call-site via the engine factory passed to `ModuleBuilder` /
  `ModuleCache`) and cross-link to the crate-level feature matrix
  rather than duplicating it.

- Fix a pre-existing rustdoc bug in `WasmError`'s doc comment in
  common/src/result.rs: the example module path was
  `holochain_wasmer_host::module::wasmer_sys`, but the actual Rust
  module is named `sys`, not `wasmer_sys` — that path has never
  existed. The example now reads `holochain_wasmer_host::module::sys`,
  matching reality.

- Drop the spurious `pub` on the `tests` module in
  `holochain_wasmer_common`'s lib.rs. The module is gated on
  `#[cfg(test)]` so the `pub` was harmless but stylistically odd; in a
  1.0 codebase it shouldn't be there.

No code changes outside of doc comments and one Cargo.toml metadata
addition. `cargo doc` for both crates is verified clean for any
warning introduced by these changes (the few remaining doc warnings
are pre-existing in `guest.rs`/`env.rs` and out of scope for this
commit).

This branch is stacked on top of the kebab-case feature rename PR
since the new feature-flag docs reference the kebab-case names.
Working towards a 1.0 of these crates. The public API has accumulated
several items over the pre-release lifetime that aren't actually
consumed by anything: dead structs, vestigial stubs from old refactors,
and a vendored helper module that was never used externally. Removing
or tightening them now means less to support across the 1.0 stability
guarantee.

Each removal was checked against `holochain/holochain` on `develop`
(the only published consumer) to confirm no current direct usage; the
breaking-change list below is what downstream callers will need to
adapt.

- Delete `holochain_wasmer_host::module::ModuleWithStore`. Defined as
  `pub` but never referenced anywhere in the workspace and never
  consumed by holochain. Looks like the abandoned half of an early
  refactor that introduced `InstanceWithStore` alongside it; only
  the instance variant ever got used.

- Delete `holochain_wasmer_host::module::sys::build_module`. This was
  a `pub fn` that always panicked with `unimplemented!()`. It existed
  because the old module layout had a single top-level
  `module::build_module` symbol that the wasmi side implemented and
  the sys side had to match for the glob re-export to resolve. The
  additive-features refactor (commit d275f1f) moved both sides under
  fully-qualified `module::sys::*` / `module::wasmi::*` paths and the
  glob re-export went away — at which point the sys-side stub stopped
  serving any purpose, but wasn't deleted. Worse than dead: it's a
  footgun, since anything that finds it via tab-completion gets a
  runtime panic instead of a compile error. With the stub gone, the
  wasmi backend's `module::wasmi::build_module` is the only
  `build_module` in the public API and the call site is unambiguous.

- Demote `holochain_wasmer_host::plru` from `pub` to `pub(crate)`.
  This is a vendored copy of the `ticki/plru` cache crate. Only
  `MicroCache` is actually consumed (by the `InMemoryModuleCache`
  in `module.rs`); the other size aliases (`SmallCache`, `MediumCache`,
  `BigCache`, `HugeCache`, `DynamicCache`), the `create()` constructor
  and most of the `Cache<B>` methods (`new`, `trash`, `len`,
  `is_empty`, `is_hot`) are never called from anywhere in the
  workspace, and nothing in holochain reaches into
  `holochain_wasmer_host::plru` either. There's no reason for this
  to be exposed as part of the host crate's stable surface — it's a
  cache implementation detail. The module body is left intact and
  marked `#[allow(dead_code)]` rather than carving out the unused
  half, since the file is essentially vendored upstream and tracking
  drift is easier when the shape stays close to the original.

- Demote `holochain_wasmer_host::guest::{read_bytes, write_bytes,
  from_guest_ptr}` from `pub` to `pub(crate)`. These are the lower-
  level memory-copy and deserialisation primitives that
  `guest::call` and `env.rs` use under the hood. They were exposed
  as `pub` but the only actual consumer in the entire workspace is
  the host crate itself, and holochain only reaches for `guest::call`
  (also via the prelude). Tightening these to `pub(crate)` makes
  `guest::call` the unambiguous supported entry point and means we
  don't need to keep the lower-level signatures stable across 1.x
  point releases.

  Note: README.md mentions `host::guest::from_guest_ptr` as part of
  a usage walkthrough. That reference is now stale and will be
  updated separately when the README is rewritten — flagging it
  here so it doesn't get missed.

The ribosome-side adaptation in holochain is straightforward:

- `module::ModuleWithStore` — was unused; nothing to migrate.
- `module::sys::build_module` — was unused; if anything ever called
  it, it would have panicked. Switch to `module::wasmi::build_module`
  if you genuinely want the wasmi direct-build path, or use
  `ModuleCache` for the sys path.
- `plru::*` — was unused; nothing to migrate. Vendor your own copy
  if you somehow needed it.
- `guest::{read_bytes, write_bytes, from_guest_ptr}` — replace with
  `guest::call`, which is the supported entry point and what every
  current ribosome host_fn already uses via `prelude::*`.

`cargo check` and `cargo test` clean across the host and common
crates and the test workspace, including the all-backends-enabled
matrix.
The "Holochain core" and "Being a good wasm guest" sections of the
README had been steadily decaying since the wasmer 1.x era and
documented an API that no longer exists: a `host::instantiate()`
function that was never replaced after the additive-features refactor,
the wasmer 1.x `&mut Ctx` host-function shape (current wasmer is
`FunctionEnv` / `FunctionEnvMut`), the long-renamed `ImportObject`
type (current wasmer is `Imports`), and method signatures that no
longer typecheck against the current `Env` impl. README examples
aren't compiled by `cargo test` so there's no mechanism that catches
this kind of drift, and updating the snippets in place wouldn't fix
the underlying problem — the next refactor would re-rot them.

Restructure as follows:

- Replace the entire stale `### Holochain core` and
  `### Being a good wasm guest` subtree (lines 73–445 of the old
  README) with a much shorter `## How to use` section that names the
  two host-side entry points (`ModuleCache` and `guest::call`),
  cross-links them to their docs.rs rustdoc, points at the test
  crates as the canonical worked examples, and defers guest-side
  documentation to the guest crate's own rustdoc. Net README size
  goes from 681 lines to 349.

- Keep all the conceptual content (`## Why?`, `## What`,
  `## Background information` and its subsections about wasm data
  types, memory model, and the host↔guest data protocol). That
  material is timeless, hard to find anywhere else, and was the
  reason the README is worth keeping at all. Only the in-prose
  reference to `host::guest::from_guest_ptr()` in the "Guest calling
  host" section is reworded to talk about "the host crate's internal
  byte-reading helpers" instead, since that helper is being
  tightened to `pub(crate)` in holochain#183 and the README should not name
  it as a public API.

- Adapt the guest-side walkthrough into a crate-level `//!` doc on
  `holochain_wasmer_guest`. Same conceptual sections (declaring
  externs, writing extern functions, receiving input, calling host
  functions, returning to host, immediate-error rule), rewritten
  against the current API rather than the wasmer 1.x version that
  was in the README. The macro examples use the actual
  `host_externs!(name:version)` shape, the extern functions use the
  current `(usize, usize) -> DoubleUSize` signature, and `host_call`
  is shown both standalone (returning `Result`) and wrapped in
  `try_ptr!` for use inside an extern. Doctests are marked `ignore`
  because guest code targets `wasm32-unknown-unknown` and isn't
  meaningfully runnable as a host-side doctest, but they document
  the same set of patterns the README used to spell out.

- Add a runnable doctest to `ModuleCache::new` showing the simplest
  end-to-end use: build a `ModuleCache` against the cranelift sys
  backend, compile a trivial wat module via `wasmer::wat2wasm`, and
  look it up by cache key. Gated on `wasmer-sys-cranelift` so the
  same example renders correctly under the docs.rs feature set
  configured in holochain#182. This is the first runnable example a
  newcomer browsing the docs.rs landing for the host crate will
  see.

The dead links to docs.wasmer.io that the previous commit on this
branch left in place are also fixed: `docs.wasmer.io/integrations/...`
returned 404, replaced with the live equivalents in the
[wasmerio/wasmer examples directory] and the [wasmer rustdoc on
docs.rs]. The link to the long-abandoned `holochain/holochain-rust`
PR is dropped in favour of the current test-crates examples.

`cargo doc` is verified clean for any warning introduced by these
changes (the few remaining are pre-existing in `guest.rs`/`env.rs`
and the unresolved `wasmi::*` / `sys::make_llvm_engine` links that
will be fixed by the in-flight holochain#182 once it merges).
`cargo test --doc` clean across host and guest crates: the new
`ModuleCache::new` doctest passes; the six guest doctests are
correctly ignored.

[wasmerio/wasmer examples directory]: https://github.com/wasmerio/wasmer/tree/main/examples
[wasmer rustdoc on docs.rs]: https://docs.rs/wasmer/latest/wasmer/
Bumps the call into `holochain/actions/.github/workflows/prepare-release.yml`
from `@v1.6.0` to `@v1.7.0`, which adds an optional `setup_script`
input (holochain/actions#8). Use that hook to download the same
prebuilt LLVM 21 bundle the Windows test job already uses and export
`LLVM_SYS_211_PREFIX` into `$GITHUB_ENV` so the `Prepare release`
step can build the host crate with `--all-features` enabled —
including `wasmer-sys-llvm`, which transitively pulls in
`llvm-sys = "211"` and fails out without an LLVM toolchain on disk.

Replaces the previous tactical workaround that passed
`--i-am-so-sorry-but-my-features-clash` via `extra_release_util_args`
to downgrade `cargo-semver-checks` to `--default-features` only. With
the LLVM toolchain available, semver checks now run against the
full feature matrix again and we get the coverage that the
non-default backends (`wasmer-sys-llvm`, `wasmer-wasmi`,
`debug-memory`) deserve.

The inline comment in the workflow file documents the why so this
doesn't get reverted by accident the next time someone wonders why
the release workflow is downloading LLVM.
Picks up holochain/actions#9, which fixes the broken
`if: ${{ inputs.skip_semver_checks == 'false' }}` conditional on
the cargo-semver-checks install step. v1.7.0 had that conditional
always evaluating false (boolean compared to a string in GitHub
Actions expression syntax), so the install step was always skipped
and the prepare step then died with `no such command:
'semver-checks'`. v1.8.0 evaluates the boolean directly.

The setup_script hook from holochain#8 (which downloads LLVM for the
`wasmer-sys-llvm` semver check) is unchanged and still wired up
in this workflow file.
The previous setup_script downloaded the LLVM bundle into
`$PWD/.llvm`, which is inside `$GITHUB_WORKSPACE`. The prepare step
itself ran fine and `cargo-semver-checks` passed, but the downstream
`peter-evans/create-pull-request` step then tried to commit
everything in the workspace into the auto-generated release PR
branch — including the entire LLVM bundle. The push got rejected by
GitHub's pre-receive hook because `clang-21` is 143 MB, well over
the 100 MB hard file size limit:

    remote: error: File .llvm/bin/clang-21 is 143.01 MB; this exceeds
    GitHub's file size limit of 100.00 MB

`$RUNNER_TEMP` is the canonical "persistent within the job, outside
the workspace" location for files like this on a GitHub-hosted
runner. Putting LLVM under `$RUNNER_TEMP/llvm` keeps the prefix
reachable from later steps via `LLVM_SYS_211_PREFIX` while making
the bundle invisible to git operations against the repo, so the
release PR commit no longer accidentally vacuums it up.

The in-file comment is expanded to document why this matters, so
the next person to look at this doesn't move it back under the
workspace and rediscover the failure mode.
Swaps the pure-Rust wasmi interpreter for the V8-backed wasmer
backend, patching in the synchwire/wasmer fork so we pick up the
iOS V8 target and the Engine::set_flags_from_string plumbing the
fork adds. The fork is not published; wasmer's full workspace is
patched in via `[patch.crates-io]` on the git branch.

Why:
- wasmerio/wasmer#6500 (merged 2026-04-20) removed both WAMR and
  wasmi from the wasmer crate. Staying on the wasmi backend meant
  pinning to an unmaintained wasmer line.
- V8 with `--jitless` gives an interpreter-only execution mode
  that works on iOS (Apple's no-JIT sandbox). That is what makes
  this the only viable non-JIT wasmer backend for the mobile
  story going forward.

Scope of the swap:
- `crates/host`: rename `module/wasmi.rs` to `module/v8.rs`,
  rename the `wasmer-wasmi` cargo feature to `wasmer-v8`, and
  expose `module::v8::set_flags_from_string` so iOS embedders can
  push `--jitless` before the engine is constructed.
- `test-crates/tests`: update feature flags, cfg gates, and the
  metering stub's error message to reference v8. Drop the
  wasmi-specific `short_circuit` ignore (wasmerio/wasmer#6397
  was a wasmi backend bug; V8 has its own error path).
- `scripts`: rename the two wasmer-wasmi helpers to `-v8` and
  drop the --release workaround that existed for
  wasmerio/wasmer#6392 (a wasmi-only debug-assert issue).
- CI: swap wasmer-wasmi for wasmer-v8 in test.yml and mobile.yml.
  Drop wasmer-v8 from test-windows (v8-custom-builds Windows
  tarball ships v8_monolith.lib, which lacks the wasm-c-api
  symbols wasmer links against). Exclude the x86_64 and armv7
  Android targets from wasmer-v8 (fork only builds aarch64).
  The iOS mobile job now installs brew LLVM for llvm-objcopy and
  adds the aarch64-apple-ios target.
- `flake.nix`: the clang/libclang and LIBCLANG_PATH wiring was
  previously annotated as "for the wasmi feature"; retarget the
  comments at v8 since bindgen still runs on the wasm-c-api
  header.

Known outstanding issue: `cargo test -p tests --features
wasmer-v8` crashes with SIGSEGV when running the second test in
the same process. Single tests pass; the crash is in V8's
per-store lifecycle — a wasmer-side concern rather than a
holochain-wasmer one. Tracked as follow-up.
@synchwire

Copy link
Copy Markdown
Owner Author

Moved to upstream: https://github.com/holochain/holochain-wasmer/pulls (see upstream PR)

@synchwire synchwire closed this Apr 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants