Skip to content

test(wasm): choose UniFFI-generated moq-ffi as the browser binding - #5838

Merged
kixelated merged 10 commits into
mainfrom
quest/m2/rs2ts/generator
Oct 17, 2026
Merged

kixelated merged 10 commits into
mainfrom
quest/m2/rs2ts/generator

Conversation

@kixelated

@kixelated kixelated commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Completes the browser binding generator quest. The report is test/wasm/generator.md.

Decision

The maintainer chose moq-ffi generated by UniFFI over ubrn's wasm2 player (2026-10-17) over the alternatives:

  • ✅ (a) UniFFI-generated moq-ffi via ubrn: one binding surface for all eight targets, no API break, and full parity today.
  • (b) BoltFFI for the browser, UniFFI elsewhere: fastest and smallest on the slice, but an L to XL port, a public API break, and two generators.
  • (c) hand-written moq-wasm: smallest today, but consume-only and a second surface to grow.

What lands

  • test/wasm/uniffi/, the harness Complete WASM bindings builds on:
    • build.sh builds moq-ffi through ubrn 0.31.0-6. pin.patch pins UniFFI 0.31 in a scratch worktree and never in the tree.
    • parity cases and per-call timings run against all three relays, normally and under a strict CSP;
    • size.ts attributes a .wasm to sections and crates.
  • A uniffi backend in the baseline sweep, interleaved with JS and moq-wasm. just test wasm-baseline --uniffi runs in nightly.
  • The report, including the BoltFFI numbers and blockers that informed the choice.
  • Quests:
    • the generator quest is deleted as complete;
    • the decision is recorded in the rs2ts epic, wasm-bindings, wasm-adopt, boltffi, and ffi-bytes;
    • three follow-ups are added (below).

What was dropped

Results

All numbers come from a shared, loaded host (load average 12 to 32). Candidates were interleaved and cells are medians.

JS moq-wasm moq-ffi, UniFFI slice, UniFFI slice, BoltFFI
shipped gzip, no custom sections 109 KB 738 KB 1,703 KB 748 KB 721 KB
module import and init 48 ms 39 ms 92 ms 50 ms 35 ms
first frame 120 ms 134 ms 180 ms 126 ms 92 ms
CPU per frame vs moq-wasm (geomean, 45 shapes) 0.70x 1x 1.34x 1.18x 1.01x

UniFFI's costs are the size of moq-ffi's surface, which the media layer drives, and per-byte payload copies at about 7 to 8 ns per byte. Both have follow-ups.

Follow-up quests (in this PR)

Folded into existing quests rather than new ones:

  • wasm-bindings gains the parity gaps: no negotiated-version accessor, and no WebSocket fallback on wasm.
  • ffi-bytes gains the browser's per-byte numbers.
  • The ubrn quest carries the two upstream reports, which need the maintainer's approval to post outside our orgs.

Decisions confirmed by the maintainer (2026-10-17)

  1. ✅ Drop the BoltFFI harness, keeping its numbers in the report via a commit link. The alternative is keeping it in-tree for bench(ffi-gen): compare BoltFFI with UniFFI on a moq-ffi slice #5736. Recommendation: drop; bench(ffi-gen): compare BoltFFI with UniFFI on a moq-ffi slice #5736 owns BoltFFI's own spike.
  2. ✅ Make net-only and ubrn 0.32 adoption prerequisites, not completion prerequisites for wasm-bindings. That way parity work proceeds on the pin while size and generation block only shipping. Recommendation: as done.
  3. ✅ lite-06 max sequence goes in m1, as a silent drop on a published wire version. Recommendation: m1; move it to m2 if lite-06 is near retirement.

Since the last review

  • Rebased on main after #5736 merged. Its deletion of the boltffi quest and this PR's deletion of the generator quest both stand.
  • Pointed bench/ffi-gen/README.md's browser notes, which named a stale PR, at this decision.
  • just check, quest check, and just test wasm --uniffi pass again on the rebased head.

Public API / wire

None. Test harness, report, quests, and one nightly flag.

Checks

  • just check passes, and so does quest check.
  • On rebased main without the clock workaround: just test wasm --uniffi passes (the moq-wasm suite 15/15, plus every UniFFI case in both CSP modes), and the --baseline --uniffi smoke passes.
  • The full five-backend sweep (681 samples) and the slice runs behind the report ran before the trim, at 9f557ac2b.
  • Not run: the nightly job itself; Firefox; macOS.

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

@kixelated

Copy link
Copy Markdown
Collaborator Author

Outcome: the harness and report are complete. The binding pick is left open for the maintainer (decision 1, recommending UniFFI moq-ffi via ubrn, with the size, byte-copy, and ubrn 0.32 work as conditions). Every number in the report comes from a loaded shared host, with candidates interleaved. --slice needs #5736 merged before it can run or be wired into CI.

(Written by Claude Opus 5.5)

@kixelated
kixelated force-pushed the quest/m2/rs2ts/generator branch from fdb4f78 to 9f557ac Compare October 17, 2026 04:05
@kixelated kixelated changed the title test(wasm): compare UniFFI, BoltFFI, and moq-wasm browser bindings test(wasm): compare UniFFI, BoltFFI, and moq-wasm browser bindings end to end Oct 17, 2026
@kixelated

Copy link
Copy Markdown
Collaborator Author

Iteration: BoltFFI now runs end to end. The slice gained a browser client (slice/browser.patch), joined the five-backend baseline sweep, and passes every parity case on three relays in both CSP modes. On identical Rust, BoltFFI costs 14% less CPU per frame than UniFFI and matches moq-wasm. BoltFFI still can't bind moq-ffi itself: Arc returns and objects in records fail, async exports need Send futures, and metadata comes from a host build. The report has the full list and what each would take. The binding choice stays open, still recommending UniFFI moq-ffi via ubrn. PR description updated.

(Written by Claude Opus 5.5)

kixelated added a commit that referenced this pull request Oct 17, 2026
…feature

#5776 put uniffi-bindgen's binary behind a `cli` feature, so run.sh now
runs it through `cargo run --features cli` like rs/moq-ffi/build.sh.

`just bench-ffi-gen` takes several steps, and `size` builds the moq-ffi
and moq-wasm references only it needs. The nightly runs `build bench` so
#5478 and #5838 can reuse a slice that still builds. Also fixes clippy on
the standalone crate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated force-pushed the quest/m2/rs2ts/generator branch from 9f557ac to d15b697 Compare October 17, 2026 14:36
@kixelated kixelated changed the title test(wasm): compare UniFFI, BoltFFI, and moq-wasm browser bindings end to end test(wasm): choose UniFFI-generated moq-ffi as the browser binding Oct 17, 2026
@kixelated
kixelated marked this pull request as ready for review October 17, 2026 14:37
@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 23 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5b294a7d-5594-4e80-af1d-eddb6bedbbe7
📥 Commits

Reviewing files that changed from the base of the PR and between 3422f0f and d66f237.

⛔ Files ignored due to path filters: 1
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing: 27
  • .github/workflows/nightly.yml
  • bench/ffi-gen/README.md
  • quest/m1/README.md
  • quest/m1/lite06-max-sequence.md
  • quest/m2/ffi-bytes.md
  • quest/m2/rs2ts/README.md
  • quest/m2/rs2ts/generator.md
  • quest/m2/rs2ts/net-only.md
  • quest/m2/rs2ts/ubrn-032.md
  • quest/m2/rs2ts/wasm-adopt.md
  • quest/m2/rs2ts/wasm-bindings.md
  • test/tsconfig.json
  • test/wasm/README.md
  • test/wasm/baseline/consumer.ts
  • test/wasm/baseline/driver.ts
  • test/wasm/baseline/fixture.ts
  • test/wasm/baseline/uniffi.ts
  • test/wasm/generator.md
  • test/wasm/package.json
  • test/wasm/run.sh
  • test/wasm/uniffi/build.sh
  • test/wasm/uniffi/cases.ts
  • test/wasm/uniffi/check.ts
  • test/wasm/uniffi/driver.ts
  • test/wasm/uniffi/pin.patch
  • test/wasm/uniffi/size.ts
  • test/wasm/uniffi/tsconfig.json
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-17T14:39:52.604348Z d15b697 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d15b697f65

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread test/wasm/uniffi/driver.ts Outdated
import { chromium } from "playwright";
import type { Relay, Report } from "./cases.ts";

const { values } = parseArgs({ options: { relays: { type: "string" }, timeout: { type: "string", default: "120" } } });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor the UniFFI driver's timeout option

When initialization or a benchmarked binding call never settles, page.evaluate() can remain pending until the 60-minute workflow limit because the parsed timeout value is never applied; run.sh also launches this driver without forwarding its configured --timeout. This turns the failure mode the harness is meant to detect into a long, opaque nightly hang, so bound the complete page run using the requested timeout and pass $TIMEOUT to this driver.

AGENTS.md reference: AGENTS.md:L17-L17

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bb55b79. --timeout now bounds each page (init, cases, and bench together), with a default of 300 s. A run with --timeout 1 fails with page did not finish. I didn't forward run.sh's --timeout: that value is a per-case budget (default 30 s), and each UniFFI case already carries its own 15 s bound inside the page, so the driver's page budget is a separate, larger number.

(Written by Claude Opus 5.5)

kixelated and others added 10 commits October 17, 2026 08:35
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
browser.patch gives bench/ffi-gen's slice a WebTransport client, so both
generators bind identical Rust and join the baseline sweep with parity cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…llow-ups

The browser binds moq-ffi through UniFFI over ubrn. Drop the BoltFFI slice
harness (its numbers stay in the report, pointing at the commit that ran
them), drop pin.patch's clock workaround now that moq-mux reads moq-clock's
browser wall clock, and replace the generator quest with its follow-ups.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated force-pushed the quest/m2/rs2ts/generator branch from bb55b79 to d66f237 Compare October 17, 2026 15:40
@kixelated

Copy link
Copy Markdown
Collaborator Author

Merging with the maintainer's approval (2026-10-17).

  • Decision: the browser binds moq-ffi through UniFFI over ubrn. The report is test/wasm/generator.md.
  • Landed:
    • the test/wasm/uniffi/ harness, run as --uniffi in the nightly baseline;
    • the report;
    • quest updates;
    • three follow-up quests: ubrn 0.32, net-only moq-ffi, and lite-06 max sequence.
  • Dropped:
  • Confirmed picks:
    • drop the slice harness;
    • net-only and ubrn 0.32 gate adoption only;
    • lite-06 max sequence goes in m1.
  • Rebased after bench(ffi-gen): compare BoltFFI with UniFFI on a moq-ffi slice #5736 merged. CI, just check, quest check, and just test wasm --uniffi pass on d66f237104d91c7acd2e82b3667c325c965b97df.

(Written by Claude Opus 5.5)

@kixelated
kixelated added this pull request to the merge queue Oct 17, 2026
Merged via the queue into main with commit 74ea02b Oct 17, 2026
12 checks passed
@kixelated
kixelated deleted the quest/m2/rs2ts/generator branch October 17, 2026 16:40
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.

1 participant