Skip to content

Add ALSA audio backend and espeak-ng browser demo#698

Draft
mho22 wants to merge 18 commits into
explore-direct-rendering-infrastructurefrom
explore-dri-evdev-and-alsa
Draft

Add ALSA audio backend and espeak-ng browser demo#698
mho22 wants to merge 18 commits into
explore-direct-rendering-infrastructurefrom
explore-dri-evdev-and-alsa

Conversation

@mho22

@mho22 mho22 commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Purpose

Give Kandelo a working audio stack and prove it end-to-end with a real Wasm program. The kernel previously had no audio devices, so packages that rely on /dev/snd/* (espeak-ng via pcaudiolib, SDL audio, mpg123) could not run unmodified. This PR adds the ALSA subsystem on both hosts, ports espeak-ng with a kandelo pcaudiolib backend, and surfaces the result as a browser demo that plays a spoken greeting.

Summary

  • add a kernel ALSA subsystem under crates/kernel/src/audio/: /dev/snd/{pcmC0D0p, controlC0} devfs nodes, PCM ioctl dispatch, an mmap-able ring of pages backed by SharedArrayBuffer, a period-driven tick, poll readiness, and fork/exec state serialisation for both PCM and control fds
  • add three additive kernel-wasm exports — kernel_audio_init_sab, kernel_audio_period_tick, kernel_audio_get_appl_ptr — and the matching shared::abi constants; the existing audio.rs OSS-emulation module moves under audio/oss.rs
  • add a host AudioDriver interface plus BrowserAudioDriver (AudioWorklet + monotonic hwPtr + producer-pointer gate to prevent head truncation + drain-on-stop) and NodeAudioDriver (headless null sink so vitest exercises the same code paths)
  • wire both hosts in the same commit per CLAUDE.md §"Two hosts": host/src/{browser,node}-kernel-{host,protocol,worker-entry}.ts plus the shared kernel-worker.ts and kernel.ts
  • factor instrumentAudioDriver(inner, onFramesConsumed?) into host/src/audio/instrumented-audio-driver.ts so demos can observe framesConsumed totals without re-implementing the forwarding contract on every call site
  • vendor pcaudiolib with a new kandelo audio_object backend (audio_kandelo.c) and cross-compile espeak-ng with the English-only data dir for size; recipe + build script live under packages/registry/espeak-ng/
  • bake the espeak-ng binary and espeak-ng-data/ into shell.vfs.zst via a durable install_local_binary shell … step in the image builder, with a package-system test that pins the install
  • add a kandelo "ALSA – espeak-ng" demo preset and a Playwright spec that asserts the synthesized greeting plays end-to-end
  • vendor sound/asound.h under libc/musl-overlay/include/ so libc consumers see the same UAPI the kernel implements; regenerate abi/snapshot.json, host/src/generated/abi.ts, and libc/glue/abi_constants.h

Notes

  • ABI delta is additive-compatible: three new exports, two new virtual-device entries, no removed or renamed existing surface. ABI_VERSION is unchanged and scripts/check-abi-version.sh confirms the delta vs origin/main.
  • /dev/snd/controlC0 opens succeed through devfs but ioctls fall through to the default EINVAL. espeak-ng does not call them; the node exists so ALSA consumers that probe the control device before opening the PCM device see a real fd rather than ENOENT.
  • BrowserAudioDriver.stop() defers audioCtx.close() by (pending / sampleRate) * 1000 + 100 ms to drain the AudioContext output queue. The 100 ms margin is empirical and browser-dependent; the rationale is inline in host/src/audio/browser-audio-driver.ts.
  • New vitest coverage: audio-driver.test.ts, browser-audio-driver-drain.test.ts (synchronous close when pending == 0, deferred close path with exact drain time, clearInterval on stop), and instrumented-audio-driver.test.ts (forwarding all seven start() args + getApplPtr identity + framesConsumed accumulation + stop() delegation — pins the argument-dropping regression that previously silenced the demo).

Testing

  • cargo test -p kandelo --target aarch64-apple-darwin --lib → 1047/1047 pass
  • cargo test -p wasm-posix-shared --target aarch64-apple-darwin → 27/27 pass
  • npx vitest run host/test/audio-driver.test.ts host/test/browser-audio-driver-drain.test.ts host/test/instrumented-audio-driver.test.ts tests/package-system/ → 22/22 pass across 6 files
  • bash scripts/check-abi-version.sh → in sync; additive-compatible diff vs origin/main, no ABI_VERSION bump required
  • bash build.sh → fresh local-binaries/kernel.wasm exports kernel_audio_init_sab, kernel_audio_period_tick, kernel_audio_get_appl_ptr
  • browser demo: ./run.sh browser /?demo=espeak plays "Welcome to Kandelo, the WebAssembly POSIX kernel"
  • scripts/run-libc-tests.sh and scripts/run-posix-tests.sh not run in this checkout (submodules absent); the audio surface is purely additive and has no libc/POSIX touchpoints these suites exercise

mho22 and others added 17 commits June 11, 2026 15:43
PAGE_FLIP now queues into pending_flips and the host's 60 Hz vblank
pump drains every process's queue into the per-fd event_ring,
stamped with the new sequence + host monotonic time. Replaces the
v1 synchronous in-ioctl drain so libdrm's PageFlip + drmHandleEvent
loop returns at monitor refresh, not ioctl rate.

Three issues had to land together:

* startVblankPump() now runs unconditionally at end of init() (was
  only started by attachKmsCanvas / attachKmsStats). Vitest fixtures
  exercising PAGE_FLIP without attaching a canvas would otherwise
  block on poll() indefinitely. tickVblank no-ops gracefully when
  no canvas or stats SAB is attached.

* poll_check(card0) gates POLLIN on event_ring being non-empty
  (mirrors /dev/input/mice). Without this a poll + read +
  drmHandleEvent loop races the pump: poll reports ready, read
  returns 0, drmHandleEvent reports a short read.

* libdrm_stub.drmHandleEvent polls before read. Real libdrm relies
  on Linux blocking-mode read on /dev/dri/cardN; sys_read on card0
  returns Ok(0) on an empty ring (centralized mode has no SYS_READ
  EAGAIN-retry plumbing), so a straight read would race the vblank
  tick and the caller's pending flip would stay queued, hitting
  EBUSY on the next PageFlip.

The PAGE_FLIP unit test in syscalls.rs is rewritten to drive the
drain via dri::drain_pending_flips_for_process (the per-process
variant the global pump invokes), keeping it free of
GLOBAL_PROCESS_TABLE side effects.

ABI: kernel_vblank's exported signature is unchanged (still () -> u32);
no new exports, no marshalled struct changes. Snapshot stays in sync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nstants

First slice of plan 5 / evdev. Adds pub mod input as a sibling of
pub mod dri: WpkInputEvent (24-byte repr(C) with explicit _pad at
offset 12 so ev_type lands at offset 16 where C's struct timeval
puts it), WpkInputId, WpkInputAbsinfo, EV_/KEY_/BTN_/REL_/ABS_/SYN_
codes, BUS_VIRTUAL, EVIOCGVERSION/EVIOCGID/EVIOCGRAB plus the
variable-length nr bases for EVIOCGNAME / EVIOCGBIT / EVIOCGABS.

KEY_* covers 0..248 — the full Linux input-event-codes.h surface
that browsers can emit through KeyboardEvent.code, so Phase B2's
translation table is just a key-by-key lookup.

input_tests verifies struct sizes (24/8/24), field offsets (the
_pad is load-bearing; without it ev_type sits at offset 12 and
the C reader silently misreads every record), and re-derives each
EVIOC* number from (dir, magic, nr, size) so a copy-paste typo
cannot survive.

Purely additive — no ABI_VERSION bump; snapshot regen lands in A7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Second slice of plan 5 / evdev. Extends VirtualDevice with the
InputEvent { device: u8 } struct variant (kbd / ptr; host_handle
-10 / -11), wires match_virtual_device to recognise event0 +
event1 (event2+ deliberately returns None), and adds the per-OFD
InputFdState sidecar: device + 24 KiB event ring + grabbed flag
+ dropped flag + ring_high_water diagnostic. Sidecar is parallel
to dri_state, not nested — disjoint state machines.

devfs lists event0 + event1 alongside the existing mice entry.

sys_read on an evdev fd is a placeholder Ok(0) for now — A5
lands the ring drain + SYN_DROPPED resync semantics. Fork
deserialise leaves input_state None with a TODO(A4/A5)
breadcrumb; no observable consumer exists yet.

Two pre-existing assertions adjusted:
  * match_virtual_device_recognizes_mice no longer claims
    /dev/input/event0 returns None.
  * test_virtual_device_roundtrip's "first sentinel past the
    range" moves from -10 to -12 since -10 / -11 are now kbd / ptr.

Purely additive — no ABI_VERSION bump.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ndling

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…OPPED resync

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sys_close snapshots OFD.input_state on last-ref and drops it via new
input_release_ofd_state helper (mirrors dri_release_ofd_state; v1 body
is a no-op drop, signature reserved for plan-9 grab-released hook).

Fork/exec serialise the per-OFD ring + grab + dropped flags + high
water mark across write_input_state / read_input_state (modelled on
write_dri_state / read_dri_state). Reader bounds-checks against
INPUT_RING_MAX_BYTES and rejects non-record-aligned lengths as EINVAL.

Tests:
- close_releases_grab_so_next_open_can_grab — OFD slot is freed, a
  fresh open on the same node comes up clean and re-grabbable.
- fork_then_close_in_child_keeps_grab_on_parent — child inherits grab
  + queued events; closing the child's fd doesn't touch parent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A4 added `kernel_input_event` (host → kernel event producer) and
`kernel_set_input_canvas_dims` (host → kernel pointer canvas geometry).
Both are pure additions to the kernel-wasm export set — no existing
entry changes — so per CLAUDE.md ABI policy this is additive-compatible
and `ABI_VERSION` stays at 14.

Note: `scripts/check-abi-version.sh` continues to flag
`kernel_reserve_host_region` + `kernel_reserve_host_region_at` as
"removed" and reports `host_adapter` + `process_memory_layout` as
reshaped vs `upstream/main`. That is the same pre-existing upstream
snapshot drift from PR #629 ("Make pthread control slots dynamic")
already documented in 00d123b — not introduced by this branch and
not addressed here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase A artifacts removed:

- ring_high_water field on InputFdState: debug-only counter no
  producer reads; carried 4 bytes per OFD across the fork wire.
- input::wait::wake_event_reader stub + was_empty/woken_ofds
  bookkeeping in push_event: empty no-op. Phase B will add real
  wake routing where it is actually wired.
- input_release_ofd_state + the sys_close snapshot block: 4-param
  helper whose body was `let _ = state;`. Slot Drop already
  releases the box on dec_ref — the take/helper pattern only
  exists for dri_state because that path calls into the host.
- VirtualDevice::InputEvent host_handle catchall returning -10:
  collapsed to `device => -10 - device as i64`.

Trim the matching test (push_event_tracks_high_water) and the
sys_read comment that referenced wake_event_reader by name. ABI
snapshot byte-identical; cargo test 983/983.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds host/src/input/input-source.ts with the InputEvent record
(device 0|1, ev_type, code, value) and the InputSource interface
(start(dispatch), stop()) that subsequent commits implement per host:
BrowserInputSource captures DOM events (keyboard/pointer/wheel) and
translates to evdev codes; NodeInputSource is a null-source for
headless test runs.

Convention encoded in the doc-comment: the source emits the
type-specific record then a SYN_REPORT to close the logical frame,
mirroring Linux evdev. Host wires dispatch to
kernel.exports.kernel_input_event at boot, after
kernel.exports.kernel_set_input_canvas_dims.

Vitest is import-and-instantiate sanity only — a StubSource records
two dispatches, stop() clears the dispatch handle, post-stop emits
are dropped.

No kernel or ABI surface change; pure-additive host module with no
consumers until B4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…_* translation

Adds host/src/input/browser-input-source.ts (DOM keyboard/pointer/wheel
capture, evdev translation, SYN_REPORT framing) and
host/src/input/key-code-table.ts (~130-entry KeyboardEvent.code →
KEY_* lookup mirroring shared::input KEY_* values — Linux UAPI
verbatim, same numeric space SDL2's evdev backend consumes on real
Linux).

Coordinate convention: pointer-lock active → REL_X/REL_Y deltas
(movementX/Y); inactive → ABS_X/ABS_Y absolute (offsetX/Y). A bare
SYN_REPORT on pointerlockchange gives libinput / SDL2 a re-sync point
so a stale axis value doesn't carry across the lock-mode transition.

Wheel normalisation handles browser deltaMode quanta: PIXEL (Chromium
±100/±120 per notch, Safari ±1–10), LINE (Firefox ±3 per notch). A
small-but-nonzero delta clamps to ±1 tick so continuous-trackpad
scrolls still emit at least one record (otherwise Math.trunc(0.3 /
120) = 0 swallows the entire scroll).

Vitest covers every translation path with 17 specs: keydown
down/repeat/unknown, keyup, pointermove ABS vs REL with mocked
pointerLockElement, single-axis movement skip, three pointer buttons,
wheel PIXEL/LINE/HWHEEL/small-delta/zero-delta, lock-change SYN, and
stop() listener removal. Uses a FakeTarget EventTarget stub and
vi.stubGlobal('document', …) — no jsdom/happy-dom dependency.

The unused canvas constructor parameter is intentionally stashed for
B4, which calls kernel_set_input_canvas_dims off it.

No kernel or ABI surface change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds host/src/input/node-input-source.ts implementing InputSource as a
pair of no-ops. There's no DOM in Node, and the integration tests
drive evdev events directly via kernel.exports.kernel_input_event(…)
instead of synthesising KeyboardEvent / PointerEvent. The Node host
still registers an InputSource at boot so the init path is symmetric
with the browser-side one (CLAUDE.md §"Two hosts" — dual-host parity
is load-bearing).

Vitest: start() registers but emits no records; start()/stop() are
safe to call repeatedly.

No kernel or ABI surface change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the host-side plumbing for evdev: both BrowserKernel and
NodeKernelHost gain injectInputEvent / setInputCanvasDims raw entry
points plus an attachInputSource(source, dims) helper that mirrors
the boot pattern across the two hosts. CLAUDE.md §"Two hosts —
DUAL-HOST PARITY IS LOAD-BEARING": every layer gets a parallel diff
in the same commit (protocol message type, host class method, worker
entry switch case, shared kernel-worker.ts wrapper, shared kernel.ts
export wrapper).

Three layers, symmetric on both sides:

  1. host/src/kernel.ts (WasmPosixKernel) — calls the new
     kernel_input_event / kernel_set_input_canvas_dims exports from
     A4 + A7. Silent-drop pattern if the kernel module isn't yet
     instantiated, same as injectMouseEvent.

  2. host/src/kernel-worker.ts (CentralizedKernelWorker) — wraps the
     calls and schedules a blocked-reader wake on injectInputEvent
     so processes parked in sys_read on /dev/input/event{0,1} get
     re-poked. Picks "extend pendingPipeReaders" for the wake
     mechanism (handoff-28's B4-open choice) by reusing
     scheduleWakeBlockedRetries — same path mice uses.

  3. host/src/{browser,node}-kernel-protocol.ts +
     host/src/{browser,node}-kernel-worker-entry.ts +
     host/src/{browser,node}-kernel-host.ts — main↔worker message
     plumbing and public host-class API. attachInputSource(source,
     dims) sets canvas dims then starts the source with a dispatch
     callback that funnels each emitted record through
     injectInputEvent. Both hosts share the same method shape so
     callers see identical surface; only constructor patterns for
     the InputSource differ (BrowserInputSource on the browser,
     NodeInputSource null-source on Node).

Vitest:
  - input-attach-source.test.ts: attachInputSource posts dims-msg
    once, calls source.start once, routes dispatched records through
    injectInputEvent → input_event_inject. setInputCanvasDims and
    injectInputEvent also tested standalone. Bypasses init() (which
    spawns a real worker_thread) by monkey-patching sendToWorker;
    the constructor only stores options, so a bare new
    NodeKernelHost() is safe.
  - Browser-side end-to-end exercised at Phase C via Playwright per
    plan §C; pure-logic translation already covered by
    browser-input-source.test.ts (B2, 17 specs).

Symmetry sweep verified: every new symbol
(input_event_inject / set_input_canvas_dims / injectInputEvent /
setInputCanvasDims / attachInputSource) shows parallel hits in
browser-* and node-* trees at every layer.

No Rust changes; no ABI surface change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
B5 closes Phase B with an end-to-end gate for the evdev path.
`input-evdev-smoke.c` opens /dev/input/event0 + event1, does
EVIOCGNAME / EVIOCGABS, and drains the per-OFD rings. Each phase
gates on a stdin byte so the host injects events AFTER the OFD
exists — `kernel_input_event` fans out at push time and a
pre-open injection would land nowhere.

The vitest drives `NodeKernelHost.injectInputEvent` (B4) directly
and asserts: EVIOCGNAME returns "wpk virtual keyboard";
EVIOCGABS(ABS_X).maximum reports canvas_w-1; KEY_A↓+SYN_REPORT
round-trip with monotonic-non-decreasing CLOCK_MONOTONIC stamps;
REL_X=+5 + SYN_REPORT; and overflow drains to 1025 records (1
synthesised SYN_DROPPED at index 0, then 1024 surviving ring
records) with the last record still an EV_KEY/KEY_A — i.e. the
incoming records were the ones discarded, Linux semantics.

`it.skipIf(!fixtureBinary)` keeps the suite green when the wasm
fixture isn't built locally, matching the dri-kms-pageflip pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… browser spec

Closes the evdev plan's Phase C (PR #3 scope) plus a build-script
regression that blocked the demo from compiling.

- `libc/musl-overlay/include/linux/input.h` +
  `libc/musl-overlay/include/linux/input-event-codes.h` — vendored
  minimal subset (C1). `programs/evdev_demo.c` is the lone consumer
  in tree; its `_Static_assert(sizeof(struct input_event) == 24)`
  is the compile-time guard against wasm32 layout drift.
- `programs/evdev_demo.c` (~100 LoC) — opens /dev/input/event{0,1},
  prints EVIOCGNAME for both, then polls forever and logs every key
  and pointer event. Free-running (no stdin-barrier harness) so it
  works as the interactive Kandelo pane (C2).
- `apps/browser-demos/pages/kandelo/presets.ts` +
  `kernel-host/live-setup.ts` — wire an `evdev` preset that stages
  the binary into /usr/local/bin, attaches a BrowserInputSource to
  window so DOM key/pointer events reach `kernel_input_event`, and
  runs the demo through bash so its stdout lands in the Shell pane.
  Boot path mirrors the existing `modeset` preset.
- `apps/browser-demos/test/kandelo-evdev.spec.ts` — Playwright spec
  that drives KeyA + pointer moves and asserts the on-canvas log
  contains `key down: code=30` and `ptr (abs|rel) code=N value=N`.
  Proves the B4 dual-host parity claim end-to-end in a real browser.
- `scripts/build-musl.sh` steps 10-11 — restores libdrm.a + libgbm.a
  compile-and-archive steps that were lost when commit b25ef59
  ported the DRI sources forward but missed the build wiring from
  commits a091a5b and 2f7fead. Without these, programs/dri-modeset
  (and friends) fail to link with `no such file or directory: libgbm.a`.

Manual browser verification (CLAUDE.md item 6 / C4) passes: keystrokes
and pointer movement appear in the on-canvas log under
`./run.sh browser` → `?demo=evdev`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First pass dropped the unused `canvas` ctor arg on BrowserInputSource and
trimmed top-of-file what-narration in input.h / evdev_demo.c /
live-setup.ts / input/mod.rs.

Second pass strips the param-table doc on `kernel_input_event` +
`kernel_set_input_canvas_dims`, collapses the per-case EVIOCG* headers
and ioctl-decode preamble in `handle_input_ioctl`, tightens the
SYN_DROPPED / blocking-read narration in the evdev read drain and poll
gate, collapses the dispatch.rs file-doc, drops the three-phase prose
and ioctl-encoding paragraph from `input-evdev-smoke.c`, and trims
test-internal narration that restates test names across `syscalls.rs`,
`ofd.rs`, `input/mod.rs`, and `devfs.rs`.

Kept the load-bearing WHYs: ENOTTY-not-EINVAL for SDL2 probe walking,
SYN_DROPPED resync after overflow, blocking-read-returns-0 retry
contract, concurrency-justification for tests that mutate
canvas-dim globals.

Net across both passes: -184 LoC (12 files).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire a Kandelo ALSA subsystem (devfs nodes, PCM ioctl dispatch, SAB ring,
mmap pages, tick, poll, fork serialisation) and a host audio stack
(`AudioDriver`, Browser + Node drivers, AudioWorklet, drain-on-stop,
instrumented wrapper) on both Node and browser hosts. Port espeak-ng
with a vendored pcaudiolib `audio_kandelo` backend, bake the binary and
English data dir into `shell.vfs.zst`, and expose a kandelo
"ALSA – espeak-ng" demo preset with a Playwright spec. ABI delta is
additive (three new exports, two new virtual devices) and does not
bump `ABI_VERSION`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mho22 mho22 changed the base branch from main to explore-direct-rendering-infrastructure June 15, 2026 13:06
…ructure' into explore-dri-evdev-and-alsa

# Conflicts:
#	apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts
@github-actions

Copy link
Copy Markdown

Phase B-1 matrix build status — pr-698-staging

ABI v15. 62 built, 4 failed, 66 total.

Package Arch Status Sha
libcurl wasm32 built 05aae726
libcxx wasm32 built 7924fbe0
libcxx wasm64 built cfe94678
libpng wasm32 built e11caae0
libxml2 wasm32 built 6db06345
libxml2 wasm64 built bfabb4ff
openssl wasm32 built 2708b58d
openssl wasm64 built a1be8942
sqlite wasm32 built 0affae3a
sqlite wasm64 built 2638b6ad
zlib wasm32 built a74da2ed
zlib wasm64 built 76101599
bc wasm32 built 46973bfb
bzip2 wasm32 built ffa5f4c4
coreutils wasm32 built 4d8239d1
curl wasm32 built dca90723
dash wasm32 built ad59e65d
diffutils wasm32 built d67cb386
dinit wasm32 built 6780ca3d
fbdoom wasm32 built a4dcc46f
file wasm32 built d2312c00
findutils wasm32 built 3767b4e1
gawk wasm32 built faedad8e
git wasm32 built f50d2974
grep wasm32 built b9b6b1e6
gzip wasm32 built 539fee04
kandelo-sdk wasm32 built bdddb4fe
kernel wasm32 built 51062af5
less wasm32 built 5180eac9
lsof wasm32 built 96fa5be8
m4 wasm32 built 0aa7ce01
make wasm32 built 5cdd9bd8
mariadb wasm32 built 4c185e36
mariadb wasm64 built ec3ea5f8
msmtpd wasm32 built aa3de664
nano wasm32 built 4a52b067
ncurses wasm32 built ff1ca5a3
netcat wasm32 built 20b49fdf
nginx wasm32 built 5c15a366
php wasm32 built f1bfba4b
posix-utils-lite wasm32 built df2ce6e9
sed wasm32 built 80e62002
spidermonkey wasm32 built 0b3b3066
tar wasm32 built 0532dd66
tcl wasm32 built eba6fe94
unzip wasm32 built b4f32850
userspace wasm32 built 1d4c6a6f
vim wasm32 built 9895fd65
wget wasm32 built 6c82e2c4
xz wasm32 built 7bf9b496
zip wasm32 built 261d8148
zstd wasm32 built 0c16fdb0
bash wasm32 built 15fc009b
mariadb-test wasm32 built 884a67b1
mariadb-vfs wasm32 built dd1e248f
mariadb-vfs wasm64 built f3286762
nethack wasm32 built 73993e86
node wasm32 built 2e1b58a3
spidermonkey-node wasm32 built 22cfbf7c
vim-browser-bundle wasm32 built 0deeb2d2
nethack-browser-bundle wasm32 built 8561a152
rootfs wasm32 built 74c566e1
shell wasm32 failed
lamp wasm32 failed
node-vfs wasm32 failed
wordpress wasm32 failed

Auto-generated; replaced on each push. Raw data in the publish-status workflow artifact.

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