Skip to content

feat: add wasm-gc support for timer, event loop, and time#314

Open
duobei wants to merge 5 commits intomoonbitlang:mainfrom
duobei:feat/wasm-gc-timer-event-loop
Open

feat: add wasm-gc support for timer, event loop, and time#314
duobei wants to merge 5 commits intomoonbitlang:mainfrom
duobei:feat/wasm-gc-timer-event-loop

Conversation

@duobei
Copy link
Copy Markdown

@duobei duobei commented Mar 12, 2026

Summary

Add wasm-gc implementations for timer, event loop scheduling, and wall-clock time, enabling @async.sleep(), @async.Timer, and @async.now() on the wasm-gc target when running in a JavaScript host (browser or Node.js).

Changes

  • timer.wasm-gc.mbtTimer::new / Timer::cancel via WASM imports (setTimeout / clearTimeout)
  • event_loop.wasm-gc.mbt — cooperative reschedule() via setTimeout(0, ...), mirrors event_loop.js.mbt
  • time.mbtms_since_epoch() for wasm-gc via WASM import (Date.now()), wasm target remains abort
  • moon.pkg — target config for new files, unimplemented.mbt narrowed to wasm only
  • wasm-gc-imports.js — reference JS import object for the host

Design

Uses WASM import syntax (fn f() = "module" "func") instead of extern "js" (unsupported on wasm-gc). The host must provide import modules moonbitlang_async_timer and moonbitlang_async_time — see wasm-gc-imports.js for the reference implementation.

The implementation mirrors the existing JS backend (timer.js.mbt / event_loop.js.mbt) as closely as possible.

Verification

  • moon check passes on all 4 targets: native, js, wasm, wasm-gc
  • moon build --target wasm-gc passes
  • moon test --target native — no regressions (8 pre-existing socket test failures unrelated to this change)

Limitations

  • Partial fix for support wasm backends #233 — only timer/event-loop/time; fs, process, socket, websocket remain unimplemented on wasm-gc
  • No wasm-gc runtime tests — moon test --target wasm-gc requires the host to provide the import object, which the default test runner does not support

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:26
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 for the async runtime’s wall-clock time and cooperative event-loop/timer scheduling when running under a JavaScript host (browser/Node), enabling @async.sleep(), @async.Timer, and @async.now() on wasm-gc.

Changes:

  • Add wasm-gc timer implementation backed by WASM imports to setTimeout/clearTimeout.
  • Add wasm-gc event-loop reschedule() implementation using setTimeout(0, ...) to cooperatively yield.
  • Implement ms_since_epoch() for wasm-gc via a WASM import to Date.now(), keeping plain wasm as unimplemented.

Reviewed changes

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

Show a summary per file
File Description
src/internal/time/time.mbt Adds wasm-gc ms_since_epoch() via imported Date.now() equivalent.
src/internal/event_loop/wasm-gc-imports.js Provides reference JS import modules for wasm-gc timer and time.
src/internal/event_loop/timer.wasm-gc.mbt Implements Timer::new/Timer::cancel for wasm-gc using WASM imports.
src/internal/event_loop/event_loop.wasm-gc.mbt Implements wasm-gc reschedule() mirroring the JS backend behavior.
src/internal/event_loop/moon.pkg Wires new wasm-gc files into target selection; narrows unimplemented.mbt to wasm only.

💡 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.

- Mention time module alongside timer and event loop
- Show correct import usage with named exports
@duobei
Copy link
Copy Markdown
Author

duobei commented Mar 12, 2026

The sanitizer-check-windows failure is unrelated to this PR — it's process/env_test.mbt:34 ("set_env no inherit") failing with OSError("@process.spawn(): The handle is invalid.").

This test passes on the main branch's latest CI run, so it appears to be a flaky test on the Windows sanitizer runner. Our changes only touch src/internal/time and src/internal/event_loop (wasm-gc target files), which are not involved in the process/env tests at all.

@duobei
Copy link
Copy Markdown
Author

duobei commented Mar 12, 2026

All CI checks are now passing ✅ (17/17 green).

The previous failures were transient GitHub Actions infrastructure issues (auth/checkout failures) and a flaky Windows timing test — all unrelated to this PR's changes.

Ready for review when you have a chance!

@Guest0x0
Copy link
Copy Markdown
Collaborator

Just some random notes here: maybe we should provide a small document telling people how to manually import stuff or where can they find the wasm-gc-import.js file and how to use it.

Testing seems difficult because there is currently no way for moon to add JS stub for wasm-gc backend when testing. Some dirty hack may be necessary here, so I'll leave testing to future work.

- Use #cfg(target) to share Timer FFI in timer.js.mbt for both js and
  wasm-gc backends, replacing the duplicate timer.wasm-gc.mbt
- Extend event_loop.js.mbt target to [js, wasm-gc] in moon.pkg,
  replacing the duplicate event_loop.wasm-gc.mbt
- Rename wasm-gc FFI imports to camelCase (setTimeout/clearTimeout)
  to match JS native names and js backend convention
- Fix comment typo: untin -> until
duobei

This comment was marked as off-topic.

@duobei
Copy link
Copy Markdown
Author

duobei commented Mar 16, 2026

Just some random notes here: maybe we should provide a small document telling people how to manually import stuff or where can they find the wasm-gc-import.js file and how to use it.

Testing seems difficult because there is currently no way for moon to add JS stub for wasm-gc backend when testing. Some dirty hack may be necessary here, so I'll leave testing to future work.

Thanks for the review! Both suggestions adopted in the latest commit (8d0e632):

  • FFI naming: renamed set_timeout/clear_timeout to setTimeout/clearTimeout in both timer.js.mbt and wasm-gc-imports.js to match JS native names
  • Code sharing via #cfg: merged timer.wasm-gc.mbt into timer.js.mbt using #cfg(target="js")/#cfg(target="wasm-gc") for the FFI declarations, sharing Timer::new. Extended event_loop.js.mbt to target ["js", "wasm-gc"] in moon.pkg and deleted the duplicate event_loop.wasm-gc.mbt. Net result: −79 / +21 lines.

@Guest0x0
Copy link
Copy Markdown
Collaborator

Guest0x0 commented Mar 18, 2026

This PR itself looks good to me. But moon is currently lacking support for this kind of usage (wasm-gc + JS stub), and as a result we cannot run moon test for wasm-gc with JS stub. So I'll put this on hold before we have more ideas about how the wasm-gc + JS stub combination should work in the MoonBit ecosystem.

It would also be very helpful if you could share your current scenario and workflow with wasm-gc + JS stub. That would give us a good reference of what users expect from the wasm-gc + JS combination.

If your intention is to share code between browser and native, you could use JS backend for the browser. Currently native support for moonbitlang/async is a superset of JS support, and moonbitlang/async is fully browser-compatible on JS backend (i.e. no node-specific API)

@duobei
Copy link
Copy Markdown
Author

duobei commented Mar 18, 2026

Thanks for the review and the suggestion about JS backend — that's a fair point for browser use cases.

The main scenarios where wasm-gc would differ from JS backend:

  1. Edge/WASI runtimes — Cloudflare Workers, Deno Deploy, Fastly etc. natively support WASM but may not provide a full JS runtime
  2. Single portable target — native for desktop/server + wasm-gc for browser and edge, rather than maintaining native + JS

That said, these are forward-looking rather than urgent. Happy to wait for moon toolchain support for wasm-gc + JS stub testing before pushing further. No rush on our end.

@Guest0x0
Copy link
Copy Markdown
Collaborator

Thanks for sharing. My thought on this:

  1. Edge/WASI runtimes. These runtimes have two problems:

    • few of them support wasm-gc
    • WASI is quite different from browser JS API

    So in the roadmap of moonbitlang/async, wasm1 backend will be prioritized for this scenario, and IO functionality would be implemented via WASI or custom runtime, instead of JS API

  2. Currently, the JS backend of MoonBit and moonbitlang/async allows full code sharing with native. For example, you can write a HTTP client program using moonbitlang/async, and compile it to browser-compatible JS via --target js or native executable via --target native, without the need for any platform specific code

That said, wasm-gc + JS stub is still useful for scenario such as high performance computation for the Web, so we will definitely look into support for it. But for many non computation-heavy program, the JS backend of MoonBit and moonbitlang/async should be sufficient.

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.

3 participants