Commit graph

5 commits

Author SHA1 Message Date
1f2ef730a2
python tier: SAB-backed pause poll — auto-pause now works on python too
leval() in lumbda.py grows a counter-gated check (every 1024th
iteration) that calls a module-level _lumbda_pause_hook. Native
Python users leave the hook None and the check short-circuits to a
single bitwise AND. The pyodide loader installs a hook that reads
_lumbdaPyPauseRequested (a JS callback over Atomics.load on the
SAB) so the REPL's auto-pause-on-tab-switch flow drops into the
same path on python that it already uses on C.

Also adds setPauseFlag to the python tier's returned object so
runner.setPauseFlag propagates the SAB through worker config.

repl.js's autoPauseTab no longer falls back to hard-cancel when
the active tier is python — the SAB-poll path covers both. Asm
remains on hard-cancel since the WAT tier has no in-eval poll
site yet.

Test suite (tests.py, 571 tests) passes — verified the no-op
hook path doesn't change native eval semantics.
2026-06-15 11:16:50 -04:00
d4380c64c7
repl: portal save/resume — vault-backed tier checkpoints
Adds a portal-bar to the REPL between tabbar and transcript: a save
button + chip strip showing all saved checkpoints for the active tab.
Click a chip to restore, click × to delete.

Per-tier strategy:
  * c, python — call the tier's (portal-snapshot! NAME), then read the
    JSON blob out of MEMFS (Emscripten/Pyodide FS) and stash it in the
    encrypted vault entry. Restore reverses: hydrate MEMFS, then
    (portal-load! NAME) merges the bindings into the live env.
  * asm — no portal serializer in the WAT tier yet (would need a
    Cheney-aware walk). Falls back to transcript replay: save snapshots
    every successful prior input, restore reboots the tier and re-evals
    them in order.

Plumbing:
  * Worker bridge: new portal-save / portal-load message kinds wire
    MEMFS reads/writes to the main thread.
  * runner.js exposes portalSave / portalLoad — null when a tier
    hasn't implemented portals (asm stays grey).
  * C tier: replace EM_JS with extern + --js-library for js_lumbda_bend_call
    (EM_JS-generated declaration was unreachable from wasmImports at
    instantiate time, browsers threw "import object field ... not a
    Function"). FS added to EXPORTED_RUNTIME_METHODS so JS can reach
    pyodide.FS / Module.FS for MEMFS I/O.

Smoke-tested all three tiers headlessly: save → chip render → restore
round-trips clean on c / python / asm, zero console errors.
2026-06-15 07:43:07 -04:00
11470a5fba
bend: wire bend!-call into the pyodide tier — playground python
lumbda-py.js (and the three deployed mirrors) now expose setBendUrl
and register a bend!-call primitive in the pyodide-hosted lumbda
environment.

Implementation:
  - JS loader stashes a `globalThis._lumbdaPyBendCall` function that
    does sync XHR POST to the configured bend URL (legal in Web
    Workers, where the pyodide tier runs in this playground)
  - Python bootstrap imports `_lumbdaPyBendCall` from `js` and binds
    it as a builtin under the symbol `bend!-call`, accepting any
    value and stringifying via lumbda.show before sending
  - setBendUrl(url) on the tier object updates the JS closure; the
    runner.js plumbing already calls it on every tier when the user
    saves a bend URL in the  bar

This brings the pyodide tier to parity with the asm (WAT) tier for
HTTP-mode bend. The emcc C tier still lacks the bind — wiring it
needs a new wasm primitive built via emcc; lands in the next commit.

Tested: bend!-call "(ping)" against the same gpu-worker endpoint
returns the same (ok pong) S-expression the asm tier sees.
2026-06-14 18:25:46 -04:00
ff2ef382c7
gc diagnostics: per-tier heap pressure surfaced in repl tabbar
Step 1 of the GC effort. Each tier loader now exposes heapStats():
  - asm-wasm — lumbda_heap_used / lumbda_heap_total wat exports
  - c-wasm   — emscripten linear memory size (no free path right now,
               so used = total; documented in the loader)
  - python   — pyodide module linear memory size; CPython GC cycles
               this naturally

Worker handles a "heap" message kind that round-trips the active tab's
loaded tiers; repl tabbar shows a compact "py 12M · c 32M · asm 4M"
strip next to the buttons. Polls every 2s.

Doesn't solve the leak — just makes pressure visible so the user knows
when to use "reboot tier". Real GC (Cheney over the WAT bump allocator,
Boehm-em or custom mark-sweep for c-wasm) coming next.
2026-06-14 17:32:48 -04:00
d8ffab5ea6
repl: /repl/ page with encrypted multi-tab sessions
Interactive REPL at lumbda.com/repl with:
  - multi-tab sessions (click + to add, × to close, double-click to rename)
  - per-tab tier selector (python/c/asm/all-three race)
  - persistent transcripts encrypted in localStorage via Web Crypto
    (PBKDF2 + AES-GCM, vault id = SHA-256(password || device-salt) —
    same pattern as unsandbox's vault-encryption-design.md, native
    crypto.subtle API instead of CryptoJS)
  - ephemeral mode (skip vault, transcripts vanish on reload)
  - one worker per (tab × tier) — state persists across evals in a tab
  - reboot tier button (terminate this tab's worker, fresh state next eval)
  - cancel button (kills the running worker in active tab)

Home page now links to both /playground/ and /repl/.

Tier state itself does NOT persist across reloads — the transcript does,
but defines/set!/hash-tables vanish with the worker. Portal save/resume
in WAT (deferred) will let a tier session survive close+reopen.
2026-06-14 12:50:35 -04:00