From bf86331b8bdeedf8afef0033a31567dcecfc9705 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 16 Sep 2026 11:47:18 -0700 Subject: [PATCH] the browser builds run on every engine: memory64 lowered at link, relaxed SIMD out, the Safari gates gone Every compiled web artifact stays a wasm64 cross-compile inside - host and target pointer widths match, so the baked layouts stay honest - and now links -sMEMORY64=2: the objects and archives keep their 8-byte pointers, the JS boundary still passes them as BigInt, and Binaryen lowers the memory itself to 32-bit, so an engine without memory64 runs the module. WebKit is that engine, on every Mac, iPhone and iPad. daspkg release wasm, dasLLVM's link_wasm (the playground's module rail) and the web tree all say so in one place each. +relaxed-simd leaves the wasm feature string. WebKit implements none of the relaxed opcodes, and the feature put f32x4.relaxed_max - the backend's lowering of an nnan vector max, which sec.12 believed it never emitted - into every release, so Safari refused the whole module at validation once memory64 was out of the way. The one thing the feature bought, f32x4.relaxed_madd, costs nothing to lose: Pocket TTS on the storywish page reads the same real-time factor (0.167-0.171) with and without it. The codegen version moves so no cached object keeps the old string. The gates that named memory64 are gone: the three dasllama.io shells, the daslang.io examples page and its coi service worker, the playground's engine radio and its tests, and the node 24 step pages.yml needed only because a true memory64 module refuses node 22. dasllama.io's /examples/* sends COEP require-corp, the one value WebKit implements; the analytics script, the only cross-origin subresource, loads with crossorigin since its host allows CORS. test_metadata.py now fails a shell that names memory64 or Safari. Both sites get a news post. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/REVIEW.md | 25 +++--- .github/workflows/pages.yml | 32 +++---- CHANGELIST.md | 1 + doc/source/reference/utils/daspkg.rst | 3 +- examples/REVIEW.md | 4 +- examples/dasLLAMA/REVIEW.md | 27 +++--- examples/dasLLAMA/parrot/web_shell.html | 33 ++----- examples/dasLLAMA/storyteller/web_shell.html | 34 ++------ examples/dasLLAMA/storywish/web_shell.html | 33 ++----- modules/dasLLAMA/PERF_LEDGER.md | 22 +++++ modules/dasLLAMA/REVIEW.md | 6 +- .../dasLLVM/ARCHITECTURE_TARGET_FEATURES.md | 24 ++--- modules/dasLLVM/REVIEW.md | 35 ++++---- modules/dasLLVM/daslib/llvm_jit_common.das | 5 +- modules/dasLLVM/daslib/llvm_jit_plan.das | 2 +- modules/dasLLVM/daslib/llvm_jit_run.das | 2 +- site-dasllama/REVIEW.md | 16 ++-- .../_news/2026-09-16-every-browser.md | 10 +++ site-dasllama/examples.html | 16 ++-- site-dasllama/feed.xml | 12 ++- site-dasllama/index.html | 11 ++- site-dasllama/serve.py | 2 +- site-dasllama/sitemap.xml | 2 +- site-dasllama/test_metadata.py | 46 ++++++++-- site/REVIEW.md | 11 +-- ...-browser-examples-now-run-on-safari-too.md | 6 ++ site/coi-serviceworker.js | 29 ++----- site/examples.html | 4 +- site/examples/_interp.html | 2 +- site/files/examples.js | 87 +++++-------------- site/playground/index.html | 4 +- site/tests/playground/REVIEW.md | 10 ++- site/tests/playground/engine-toggle.spec.js | 33 +------ skills/daspkg.md | 2 +- src/builtin/module_jit.cpp | 7 +- tests/jit_tests/wasm_idot_lowering.das | 43 ++++++++- utils/daspkg/README.md | 2 +- utils/daspkg/REVIEW.md | 13 +-- utils/daspkg/commands.das | 8 +- utils/internal/dasllama-ladder/README.md | 9 ++ utils/internal/dasllama-ladder/REVIEW.md | 19 ++-- utils/internal/dasllama-ladder/caddy.snippet | 8 +- .../dasllama-ladder/dasllama-deploy.sh | 27 ++++-- web/CMakeLists.txt | 9 +- web/examples/ui/REVIEW.md | 22 ++--- web/examples/ui/src/main.js | 23 +---- 46 files changed, 398 insertions(+), 383 deletions(-) create mode 100644 site-dasllama/_news/2026-09-16-every-browser.md create mode 100644 site/_news/2026-09-16-the-browser-examples-now-run-on-safari-too.md diff --git a/.github/workflows/REVIEW.md b/.github/workflows/REVIEW.md index 063fd62f51..3e55ae3e07 100644 --- a/.github/workflows/REVIEW.md +++ b/.github/workflows/REVIEW.md @@ -2,15 +2,16 @@ **Read `REVIEW_COMMON.md` (repo root) first - its contract binds this checklist.** Architecture doc: `skills/internal/preflight.md` (repo root). A per-PR check is a step, a -matrix cell, or a workflow trigger whose failure turns a branch or a published artifact red -before a human merges or ships it - a `pull_request` lane's step and a branch-push lane's -smoke alike. +matrix cell, or a workflow trigger whose failure means the tree or a published artifact is +wrong, and that turns the lane red before a human merges or ships it - a `pull_request` lane's +step and a branch-push lane's smoke alike. A provisioning step - a toolchain install, a version +pin - is not itself a per-PR check. -**A diff that weakens a per-PR check is a defect: deleting it while no per-PR lane still -runs its cases, stopping its failure from failing the lane (`continue-on-error`, a trailing -`|| true`, a swallowed exit code), shrinking what it checks, or narrowing its condition to -anything but a `matrix.role` condition that still runs it on every pull request or the -nightly cron.** +**A diff that weakens a per-PR check is a defect: deleting it, or a step it depends on, while +no per-PR lane still runs its cases, stopping its failure from failing the lane +(`continue-on-error`, a trailing `|| true`, a swallowed exit code), shrinking what it checks, or +narrowing its condition to anything but a `matrix.role` condition that still runs it on every +pull request.** **A per-PR check the diff adds fails the lane when it finds a defect.** @@ -29,10 +30,10 @@ preflight gate - a check `preflight` runs locally before a push - that keeps it (`skills/internal/preflight.md` sec."extended_checks.yml") or states the platform no per-PR cell has.** A per-PR job fits 35 minutes; what does not fit moves. -**A diff that adds or changes a per-PR check states a run of that check's command, on the -lane's platform, in its PR body or commit message; a green run of that lane on the PR's head -commit is that evidence.** A check that fails for a non-defect turns a green branch red for -everyone. +**A diff that adds or changes a per-PR check, or adds, changes, or removes a step a per-PR +check depends on, states a run of that check's command, on the lane's platform, in its PR body +or commit message; a green run of that lane on the PR's head commit is that evidence.** A check +that fails for a non-defect turns a green branch red for everyone. **A step in `pages.yml` that names more than one id under `examples/games/` spells them as a `for g in ; do` loop, never inline.** `examples/games/REVIEW.das` (repo root) reads the diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 669964d255..0da83354d0 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -39,14 +39,6 @@ jobs: - name: "Install CMake and Ninja" uses: lukka/get-cmake@latest - # The wasm64 (MEMORY64) modules emscripten emits refuse to load under a node older than 23 - # ("This emscripten-generated code requires node v23.0.0"); ubuntu-latest ships 22, and the - # DlimConfiguration probe below runs one of those modules under the runner's node. - - name: "Install node 24 (the wasm64 probe runs under it)" - uses: actions/setup-node@v4 - with: - node-version: 24 - # The host daslang now builds with dasGlfw/dasOpenGL ON so it can drive the # /examples game cross-compile (daspkg release wasm). dasGlfw's # find_package(OpenGL REQUIRED) needs the GL + X11 dev headers. @@ -213,9 +205,9 @@ jobs: # WASM build: the wasm32 interpreter playground (daslang_static) + the wasm64 # compiled path (runtime/module archives, compute samples, and the /examples # games). Skipped on a cache hit — the cached web/output + web/output64 are - # reused. wasm32 is the universal interpreter fallback; everything COMPILED - # is wasm64 (the cross-compile is layout-correct only when host ptr width == - # target ptr width). + # reused. Everything COMPILED is cross-compiled as wasm64 (layout-correct only + # when host ptr width == target ptr width) and lowered to a 32-bit memory at + # link (-sMEMORY64=2), so it runs on every engine, WebKit included. - name: "Build WASM (Emscripten): playground + wasm64 examples" if: steps.wasm-cache.outputs.cache-hit != 'true' run: | @@ -241,7 +233,7 @@ jobs: # 1. wasm32 interpreter playground (daslang_static) + vendored UI + the example .das # sources staged into site/playground. THREADED build (-pthread + the imgui family), # so new_thread / JobQue map to Web Workers and every playground sample runs - # cross-origin-isolated (coi-serviceworker drops the memory64 gate for /playground). + # cross-origin-isolated (coi-serviceworker). # stage_site_playground also generates the furier + path_tracer_lab samples from the # canonical examples/graphics sources. If the imgui-family build breaks (an external's # master regressed), degrade rather than blocking every deploy: drop the clones and @@ -298,8 +290,8 @@ jobs: # drives its .das_package release_wasm_build to produce the 3 wasm64 # archives on demand — no daspkg install step (the example declares # no deps). Non-fatal — a wasm-build hiccup must not red the whole - # deploy; the furier card is skipped at stage time below (examples.js - # shows a "needs memory64" note when the iframe is unavailable). + # deploy; the furier card is skipped at stage time below (a placeholder + # page stands in for the iframe). if ./bin/daslang utils/daspkg/main.das -- \ release wasm --root examples/graphics/furier --out "$REPO/web/output64/examples"; then echo "furier wasm build OK" @@ -545,9 +537,9 @@ jobs: fi # /examples/ runtime: the interpreted runner harness + per-game artifacts. - # Each game dir holds BOTH the compiled wasm64 build (loaded in an iframe on - # memory64 engines) AND the .das sources + HUD font the interpreted fallback - # (_interp.html) mounts on Safari/iOS. The page UI (examples.html, posters, + # Each game dir holds BOTH the compiled build (loaded in an iframe on every + # engine) AND the .das sources + HUD font the interpreted runner + # (_interp.html) mounts under ?force=interp. The page UI (examples.html, posters, # files/examples.js) was already staged above via cp site/examples.html + # cp -r site/files. mkdir -p _site/examples @@ -568,11 +560,11 @@ jobs: cp modules/dasStbImage/fonts/droidsansmono.ttf "_site/examples/$g/" done - # furier — the ImGui-in-wasm showcase. wasm64-only: it bundles the compiled + # furier — the ImGui-in-wasm showcase. Compiled-only: it bundles the compiled # dasImgui module, which the universal interpreter can't bind, so there is NO - # interpreted fallback (no .das sources to stage). The card is always listed + # interpreted form (no .das sources to stage). The card is always listed # in examples.js, so stage the compiled build only when ALL THREE outputs are - # present; otherwise stage a small placeholder furier.html so a memory64 engine + # present; otherwise stage a small placeholder furier.html so the player # loads a friendly "being rebuilt" note instead of 404-ing the iframe (and # flipping the player to a bogus "running" on the 404 page's load event). mkdir -p _site/examples/furier diff --git a/CHANGELIST.md b/CHANGELIST.md index 1a41a07bd4..681af21422 100644 --- a/CHANGELIST.md +++ b/CHANGELIST.md @@ -95,6 +95,7 @@ A public site and service for the `[tune]` ecosystem: upload a tuned box's sidec - **On-demand wasm compilation** (#3628, #3630, #3633) - a fresh pre-warmed throwaway runtime per run, a content-addressed build queue behind a rootless-podman network-none sandboxed builder (`dasweb-buildd`), and standalone graphics/audio pages served from `run.daslang.io` - **The sample verifiers** (#3645, #3649, #3679) - a tier-1 batch compile gate over all 39 curated samples per PR, and a nightly playwright leg that drives the live playground per sample per engine (first sweep 70/77, zero false positives) - **Stability round** (#3635, #3648, #3650, #3652, #3667, #3714) - canvas size reconciled to the CSS box every frame (retiring three workarounds), wasm memory growth to 2 GB with reported OOM, channel `gather` outside the lock (the "dead window" wedge), the runtime wasm compiled once per page (the browser-tab OOM cliff), and a scripted fail-closed toolchain-bump protocol +- **Every browser** (#4051) - the compiled web builds link `-sMEMORY64=2` (wasm64 pointers over a memory lowered to 32-bit) and drop `+relaxed-simd`, so the example cards, the playground's wasm engine and the dasllama.io examples run on Safari and iOS; the memory64 gates are gone from both sites, and dasllama.io serves its examples under COEP `require-corp` #### The Fast Dev Loop: Module Cache + Split JIT (#3683, #3687, #3692, #3693, #3701, #3707, #3936) diff --git a/doc/source/reference/utils/daspkg.rst b/doc/source/reference/utils/daspkg.rst index e8a53d61d5..9e1dfee9b4 100644 --- a/doc/source/reference/utils/daspkg.rst +++ b/doc/source/reference/utils/daspkg.rst @@ -133,7 +133,8 @@ Options: - ``--quick`` -- on ``release``, accept a complete existing ``[tune]`` sidecar instead of re-minting (``--paranoid`` is accepted for compatibility; the tuner runs one margin-decided protocol). -- ``--wasm`` -- on ``build``, target wasm64 (memory64). +- ``--wasm`` -- on ``build``, target wasm64 (memory64, lowered to a 32-bit + memory at link so every browser runs the artifact). - ``--wasm-lib-dir `` -- directory holding the wasm64 archives; default ``/web/output64/lib``. diff --git a/examples/REVIEW.md b/examples/REVIEW.md index bd2928dbd0..94ce6075d3 100644 --- a/examples/REVIEW.md +++ b/examples/REVIEW.md @@ -5,8 +5,8 @@ **A `.das` in this folder tree that a game's `main.das` requires, directly or through another required file, answers to `games/REVIEW.md` too - wherever the diff puts it.** -**A folder with a `web_shell.html`, and every `.das` and `models.json` in it, answers to -`dasLLAMA/REVIEW.md` too, wherever the diff puts it.** +**Every file in a folder that holds a `web_shell.html` answers to `dasLLAMA/REVIEW.md` (beside +this file) too - wherever in this tree that folder sits.** **A diff that drops `require live/audio_live` from a program states, in the PR body, why that program's audio is meant to die with a reload** - `audio_live` is what carries the audio diff --git a/examples/dasLLAMA/REVIEW.md b/examples/dasLLAMA/REVIEW.md index 21428448bd..6c9f08b3ae 100644 --- a/examples/dasLLAMA/REVIEW.md +++ b/examples/dasLLAMA/REVIEW.md @@ -7,20 +7,27 @@ A browser example is a subfolder here with a `web_shell.html` (`ARCHITECTURE.md` rules below bind browser examples; a rule naming `library/` binds that folder instead. **A browser example reads its input inside its own frame - a `glfwSet*Callback` written in -`.das` is a defect; an example that draws its own text polls each input every frame and -edge-detects it (`glfwGetKey`, `glfwGetMouseButton`), and one on the imgui harness reads ImGui's -state.** In the browser build a callback lambda fires outside any frame of the program and the -program traps (`ARCHITECTURE.md` sec. 3.3). - -**A browser example's `web_shell.html` gives the canvas element `max-width` / `max-height` so the -element's box is exactly the rendered image - never `object-fit` on a canvas stretched to fill the -page area around it.** A click maps through the element's box with one ratio per axis, so a -stretched box mis-maps every click (`ARCHITECTURE.md` sec. 3.3). +`.das` is a defect; an example that draws its own text polls each input every frame and acts +when it goes down, never on every frame it stays down - a repeat comes from the example's own +hold timer (`glfwGetKey`, `glfwGetMouseButton`) - and one on the imgui harness reads ImGui's +state.** In the browser build a callback lambda fires outside any frame +of the program and the program traps (`ARCHITECTURE.md` sec. 3.3). + +**A browser example whose `main.das` reads the mouse - `glfwGetMouseButton`, or ImGui's mouse +state through the imgui harness - gives the canvas element `max-width` / `max-height` in its +`web_shell.html` - never `object-fit` on a canvas stretched to fill the page area around it.** A +click maps through the element's box with one ratio per axis, so a stretched box mis-maps every +click (`ARCHITECTURE.md` sec. 3.3). **A browser example's `web_shell.html` must reload a page the browser restored from its back-forward cache - a `pageshow` handler that reloads when `persisted` is set.** Such a page comes back with its workers and audio output frozen out of step (`ARCHITECTURE.md` sec. 3.1). +**A browser example's `web_shell.html` never refuses a browser on a WebAssembly feature probe - +it gates on the page's cross-origin isolation (`crossOriginIsolated`, `SharedArrayBuffer`) +alone.** The build runs on every engine, so a feature gate only refuses browsers that would have +run it. + **A diff that plays a second sound creates a new status box (`set_status_update`) for it and releases the first when its sound ends - never reuse a box across sounds.** The mixer's last report for the first sound lands after the second attaches, and reads as the second's stop @@ -43,6 +50,6 @@ writes nothing (`ARCHITECTURE.md` sec. 3.8). **A diff that adds an entry point to `library/dasllama_lib.das` gives it `[export_c]` and a result `daslib/c_api_header.das` can spell in C - a scalar, a string, a pointer, an enum, a -vector or a POD struct, never an array, a fixed array, a table or a tuple.** An `[export_c]` +vector, a POD struct, or nothing at all, never an array, a fixed array, a table or a tuple.** An `[export_c]` whose signature that describer refuses is a hard emit error, not a skipped export (`ARCHITECTURE.md` sec. 3.8). diff --git a/examples/dasLLAMA/parrot/web_shell.html b/examples/dasLLAMA/parrot/web_shell.html index 3973e44e56..9bfc221f47 100644 --- a/examples/dasLLAMA/parrot/web_shell.html +++ b/examples/dasLLAMA/parrot/web_shell.html @@ -11,7 +11,7 @@ - +