Skip to content

feat: add wasm-gc support for Promise bridging, ReadableStream, and HTTP client#315

Open
duobei wants to merge 8 commits intomoonbitlang:mainfrom
duobei:feat/wasm-gc-promise-http
Open

feat: add wasm-gc support for Promise bridging, ReadableStream, and HTTP client#315
duobei wants to merge 8 commits intomoonbitlang:mainfrom
duobei:feat/wasm-gc-promise-http

Conversation

@duobei
Copy link
Copy Markdown

@duobei duobei commented Mar 12, 2026

Summary

Add wasm-gc implementations for js_async and http packages, building on the timer/event-loop foundation from #314.

  • Promise bridging (js_async): Promise::wait(), run_promise(), Promise::from_async() via Deferred pattern (wasm-gc cannot directly call JS functions received as parameters)
  • ReadableStream (js_async): callback-based byte copying for Bytes/Uint8Array boundary crossing (one FFI call per chunk)
  • HTTP client (http): fetch-based client with RawExternRef + %identity for cross-package #external type limitation in WASM import signatures

New files

File Lines Description
src/js_async/js_async.wasm-gc.mbt 217 Promise + AbortController + JsError
src/js_async/readable_stream.wasm-gc.mbt 180 ReadableStream wrapper + JsReadableStream pipe
src/http/client.wasm-gc.mbt 299 Fetch-based HTTP client
src/js_async/wasm-gc-imports.js 53 JS imports: moonbitlang_async_js (11) + moonbitlang_async_stream (11)
src/http/wasm-gc-imports.js 27 JS imports: moonbitlang_async_http (8)

Key design decisions

  • Deferred pattern replaces new Promise(executor) — wasm-gc closures (funcref) can be called from JS, but JS functions received as parameters cannot be called from MoonBit
  • Callback-based Bytes copyingBytes on wasm-gc is a GC type (not Uint8Array), so JS iterates the Uint8Array and calls a MoonBit set_byte callback; reverse direction uses a get_byte callback
  • RawExternRef + %identity — wasm-gc compiler requires WASM import parameter/return types to be defined in the same compilation unit; cross-package #external types are wrapped via local externref aliases
  • JsHeaders::for_each replaces to_array — wasm-gc Array is not a JS Array

Verification

  • moon check --target wasm-gc --deny-warn
  • moon check --target native --deny-warn
  • moon check --target js --deny-warn
  • moon test --target native — 407/415 passed (8 pre-existing IPv6/WebSocket env failures)

Depends on

Add wasm-gc implementations that delegate to the JS host via WASM imports:

- timer.wasm-gc.mbt: Timer using host-provided setTimeout/clearTimeout
- event_loop.wasm-gc.mbt: cooperative scheduling via setTimeout(0)
- time.mbt: ms_since_epoch via host-provided Date.now()
- wasm-gc-imports.js: JS import object for the host to provide

This enables @async.sleep(), @async.Timer, and @async.now() on the
wasm-gc target when running in a JavaScript host (browser or Node.js).

The WASM import convention uses "moonbitlang_async_timer" and
"moonbitlang_async_time" as module names.

Partial fix for moonbitlang#233 — timer/event-loop/time only; fs/process/socket
remain unimplemented on wasm-gc.
Copilot AI review requested due to automatic review settings March 12, 2026 03:57
- Mention time module alongside timer and event loop
- Show correct import usage with named exports
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds wasm-gc target support across async JS interop and HTTP by introducing Promise/AbortController bridging, ReadableStream wrappers for byte transfer, and a fetch-based HTTP client wired via WASM imports (building on the wasm-gc timer/event-loop foundation).

Changes:

  • Add wasm-gc implementations for @js_async Promise bridging and ReadableStream interop, plus reference JS import objects.
  • Add a wasm-gc fetch-based @http.Client implementation and its reference JS import object.
  • Extend wasm-gc runtime plumbing (event loop + timer + wall-clock time) and update package target mappings.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/js_async/wasm-gc-imports.js Reference host import object for wasm-gc Promise + stream bridging.
src/js_async/readable_stream.wasm-gc.mbt wasm-gc ReadableStream wrapper + pipe-to-stream interop via callback byte copying.
src/js_async/moon.pkg Enables wasm-gc targets for new js_async implementations; narrows unimplemented targets.
src/js_async/js_async.wasm-gc.mbt wasm-gc Promise/AbortController/JsError bridging via Deferred pattern and WASM imports.
src/internal/time/time.mbt Implements ms_since_epoch() for wasm-gc via Date.now() WASM import.
src/internal/event_loop/wasm-gc-imports.js Reference host import object for wasm-gc timer + time imports.
src/internal/event_loop/timer.wasm-gc.mbt wasm-gc timer implementation using setTimeout/clearTimeout WASM imports.
src/internal/event_loop/moon.pkg Registers wasm-gc event loop + timer targets; adjusts unimplemented targets.
src/internal/event_loop/event_loop.wasm-gc.mbt wasm-gc cooperative rescheduling implementation.
src/http/wasm-gc-imports.js Reference host import object for wasm-gc fetch client and Headers/Response helpers.
src/http/moon.pkg Enables wasm-gc target for new HTTP client and for request model package file.
src/http/client.wasm-gc.mbt wasm-gc fetch-based HTTP client using RawExternRef + %identity to bridge externref signatures.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

duobei added 2 commits March 12, 2026 12:07
…TTP client

Port js_async and http packages to wasm-gc target using WASM imports
instead of extern "js". Key design decisions:

- Deferred pattern replaces new Promise(executor) since wasm-gc cannot
  directly call JS functions received as parameters
- Callback-based byte copying for Bytes/Uint8Array boundary crossing
  (one FFI call per chunk, JS loops internally)
- RawExternRef + %identity for cross-package #external types in WASM
  import signatures (compiler restriction workaround)
- JsHeaders::for_each replaces to_array (wasm-gc Array != JS Array)
@duobei duobei force-pushed the feat/wasm-gc-promise-http branch from 9fff7bc to da6b6b8 Compare March 12, 2026 04:08
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