From 25ae5d0384592110b98807b7dfc0efc6aaffa029 Mon Sep 17 00:00:00 2001
From: Ralf Anton Beier Wasm Research Day 2026 · 30 minutes Most embedded stacks make the operating system platform-specific
+ and hope the application is portable. We are inverting that. pulseengine.eu · every number on these slides says where it came from Act I · the inversion The OS is written for the chip. The application is written against the OS
+ and hoped to be portable. Every new board re-opens the OS. The OS itself is WebAssembly components. The Component Model is the
+ integration step — between OS components, between the OS and its drivers,
+ and between the OS and the tenants above it. Act I · the inversion Think of the OS and everything above it as the car, and the
+ handful of native functions that actually touch the hardware as the tires. For new terrain — another chip, another board — you change the
+ tires, not the car. This talk spends most of its time on the factory that
+ builds the car, because that is the part that has to be qualified once. Act II · the car A driver sits below the OS: it may import Act II · the car Wasm is where the pieces are joined and checked. It is not present at run
+ time — there is no interpreter, no JIT, and no engine resident on the chip. Act II · the car captured 2026-08-04, all three probes attached at once The watchdog legs are one happy path on two dies. They do
+ not evidence the cannot-un-start property — the firmware never attempts an
+ un-start. That stays a source-level proof. Act II · the car The three relocations are Act III · the tires Answered by a Rust array under a component runtime. Same script, same
+ assertions. Answered by real registers. The composite is lowered first; nothing
+ interprets it. The published artifact is the same one in all three:
+ Act IV · the factory — our own words, from a post two years before this talk This is why the factory matters more than the car. A car you qualify once is
+ one car. A factory you qualify once is every car it will ever build. Act IV · the factory The loop that files findings between repos — and the governance that keeps 30+ repos to the same rules. Act IV · the factory When Act IV · the factory Not a model — a measured floor, soundness-gated. The elision is only legal
+ because something upstream proved the bound. A compiler with no
+ verifier in its pipeline structurally cannot reach it. Act IV · the factory Certification asks: why do you believe the solver? "Qualify Z3" is not a
+ tractable answer. So the solver stays untrusted and emits a certificate. Only a small,
+ dependency-free checker is trusted — and that checker's soundness is
+ machine-checked in Lean 4, with no This turns unchecked-solver evidence into the argument
+ a verified compiler makes: don't trust the tool, check its output. Act IV · the factory The remedy is not more gates. It is that every check must be able to go red
+ for a reason you can state in advance. Act V · what is missing driver object .text, core module → component Roughly +700 B fixed per driver for canonical-ABI glue
+ — a constant, not a proportion. On a chip with 8 KB of SRAM that is a real
+ number, and it is filed upstream rather than absorbed quietly. Act V · what is missing Proofs run on a verification schedule, not on every commit. Our own docs say
+ this out loud: closer to "well-specified and comprehensively tested with
+ local formal verification" than "continuously formally verified." Act V · what is missing Plugging in the third board made the first one unreachable — two probes,
+ an interactive prompt, and a harness with no terminal. The all-three-at-once
+ case was exactly the case nothing had run. The RISC-V figure is recorded with a byte-reproducible command. The wasm
+ input to that command is not in the repository — it lived in a scratch
+ directory that no longer exists. Act V · what is missing The OS as components is the demonstration. The factory
+ that lowers it — and can say why each stage is believed — is the part that
+ generalizes. pulseengine.eu · every tool named here is open source Wasm Research Day 2026 · 30 minutes Most embedded stacks make the operating system platform-specific
- and hope the application is portable. We are inverting that. Wasm Research Day 2026 · 25 + 5 The Component Model as the integration step for an OS
+ written in WebAssembly. pulseengine.eu · every number on these slides says where it came from Act I · the inversion Each of these is one ingredient. This talk is what happens
+ when you need all four at once — on a chip where the runtime the third
+ one measures does not fit at all. Act I · the inversion and turned a "# correctness:"
log line into an
. Evidence is now several
blocks in a .stack
rather than one with blank lines in it.
Branch only. Not for merge or deploy before the talk.
Co-Authored-By: Claude Opus 5
The OS as components,
+
and the factory that lowers itPortability is usually asked of the wrong layer
+ the usual arrangement
+ what we are trying
+ The car and the tires
+ Two seams, and everything hangs off them
+ gust:os — what tenants see
+ time · log · spawn
+exec · timer · taskdisp
+channel · io
+ gust:hal — what touches metal
+ mmio · irq · gpio
+spi · timer · uart · dma
+ gust:hal and
+ must not depend upward on gust:os. That is not a convention — it is
+ a gate that fails the build.Integration step, not a build step
+ Three dies, one session
+ Cortex-M4 · NUCLEO-G474RE
+gust-wdg-silicon OK: IWDG watchdog reset CONFIRMED on real STM32G474
+silicon (RCC_CSR=0x34000000, IWDGRSTF=1)
+ Cortex-M3 · STM32F100 VLDISCOVERY — the same .o, a second die
+gust-wdg-silicon OK: IWDG watchdog reset CONFIRMED on real STM32F100
+silicon (RCC_CSR=0x34000000, IWDGRSTF=1)
+ RISC-V · ESP32-C3 rev v0.4
+correctness: IDENTICAL ok over [0,2047]
+gust_mix_native 271 milliticks/call
+gust_mix_dissolved 499 milliticks/call
+ratio_x1000 1839 (mismatch=0)
+ The tires are smaller than you would guess
+ mmio_read32, mmio_write32,
+ irq_poll. Everything above them — the protocol, the state machine,
+ the error handling — is wasm that got lowered.Same bytes. Three different answerers.
+ in a browser tab
+ gust:hal/mmio answered by a JS array. Pulled from the
+ registry, signature verified, transpiled — 10 / 10
+ behavioural checks pass.on a host
+ on silicon
+ ghcr.io/pulseengine/gale-nano:0.6.0, signed, pulled — not rebuilt
+ per target.The pipeline is qualified once; every product
+ that uses it inherits that qualification.
+ Six faces, not four tools
+ architect
spar — AADL / SysML. The WIT is generated from the model, not hand-written.build
meld fuse · loom optimize · synth lower · sigil attesting across all of it.verify
witness MC/DC on the shipped wasm · scry abstract interpretation · Verus, Rocq, Lean, Kani as build rules.trace
rivet — typed requirements, decisions and tests. Broken links fail the build.run
kiln runtime · gale verified kernel primitives · the applications above them.agent
No stage trusts the one above it
+ loom proves a value range, it does not just use it and
+ discard it. It writes it into a custom section — wsc.facts — keyed
+ to values, not positions, so renumbering cannot silently re-point a fact.synth never re-derives that fact and never takes it on faith. It
+ proves its own specialization correct given the fact, per site, and
+ emits a certificate.Faster because it is proven
+ Qualify the checker, not the prover
+ sorry anywhere in its kernel.Every one of these gates was green for the wrong reason
+ The failure produced the same observable as
+ success.
+ What componentizing actually costs
+ gpio 502 → 1196 B spi 454 → 1244 B
+timer 204 → 828 B wdg 638 → 1718 B
+.data / .bss, every one of them 0 → 0
+ Where the verification story actually stands
+ Two things building this talk taught us
+ the runner broke on success
+ a reproducible number that isn't
+ A bench whose input is not committed is not
+ reproducible, however precisely its output is recorded.
+Before the rest of the vision holds
+
+
+Change the tires,
+
not the car elements rather
+// than one with blank lines in it: a blank line inside a raw-HTML block ends
+// the block, and the markdown parser resumes *inside* the evidence.
+.stack {
+ display: flex;
+ flex-direction: column;
+ gap: .5rem;
+ width: 100%;
+}
+
+.evidence__label {
+ font-family: $font-mono;
+ font-size: clamp(.66rem, 1.32vmin, .84rem);
+ letter-spacing: .06em;
+ text-transform: uppercase;
+ color: $text-faint;
+ margin: 0 0 -.5rem;
+}
+
+.ok { color: $green; }
+.bad { color: $red; }
+.warn { color: $amber; }
+.dim { color: $text-faint; }
+.hi { color: $accent; }
+
+// ─── Claim ledger ─────────────────────────────────────────────────────
+// The provenance marker is the point of this deck: every number says whether
+// it was measured for this talk or is a standing project claim.
+
+.ledger {
+ display: grid;
+ gap: .5rem;
+ width: 100%;
+ max-width: 60ch;
+}
+
+.ledger__row {
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ align-items: baseline;
+ gap: .9rem;
+ padding: .55rem .8rem;
+ background: $surface;
+ border: 1px solid $border-subtle;
+ border-radius: 8px;
+ font-size: clamp(.8rem, 1.75vmin, 1.05rem);
+}
+
+.ledger__tag {
+ font-family: $font-mono;
+ font-size: .78em;
+ letter-spacing: .05em;
+ text-transform: uppercase;
+ white-space: nowrap;
+}
+
+.ledger__val {
+ font-family: $font-mono;
+ font-variant-numeric: tabular-nums;
+ white-space: nowrap;
+ color: $text;
+}
+
+// ─── Stage chain (the pipeline, as a row of stages) ───────────────────
+
+.chain {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: stretch;
+ gap: .5rem;
+ width: 100%;
+}
+
+.chain__stage {
+ flex: 1 1 8rem;
+ min-width: 7rem;
+ background: $surface;
+ border: 1px solid $border-subtle;
+ border-radius: 10px;
+ padding: .75rem .85rem;
+ display: flex;
+ flex-direction: column;
+ gap: .3rem;
+}
+
+.chain__name {
+ font-family: $font-mono;
+ font-size: clamp(.85rem, 1.9vmin, 1.15rem);
+ font-weight: 600;
+ color: $accent;
+}
+
+.chain__what {
+ font-size: clamp(.7rem, 1.45vmin, .92rem);
+ color: $text-dim;
+ line-height: 1.35;
+}
+
+// ─── Two-column split ─────────────────────────────────────────────────
+
+.split {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
+ gap: 1.2rem;
+ width: 100%;
+}
+
+.split__col h3 {
+ font-family: $font-mono;
+ font-size: clamp(.72rem, 1.45vmin, .9rem);
+ letter-spacing: .1em;
+ text-transform: uppercase;
+ color: $text-faint;
+ margin: 0 0 .55rem;
+ font-weight: 500;
+}
+
+// ─── Chrome: progress, counter, help ──────────────────────────────────
+
+.deck__progress {
+ position: absolute;
+ left: 0; bottom: 0;
+ height: 2px;
+ background: $accent;
+ transition: width .28s ease;
+ z-index: 2;
+}
+
+.deck__counter {
+ position: absolute;
+ right: var(--safe-r);
+ bottom: calc(var(--safe-b) + 3.1rem);
+ font-family: $font-mono;
+ font-variant-numeric: tabular-nums;
+ font-size: .8rem;
+ color: $text-faint;
+ z-index: 2;
+}
+
+.deck__hint {
+ position: absolute;
+ left: var(--safe-l);
+ bottom: calc(var(--safe-b) + .9rem);
+ font-family: $font-mono;
+ font-size: .8rem;
+ color: $text-faint;
+ z-index: 2;
+}
+
+// ─── On-screen controls ───────────────────────────────────────────────
+// The only way to drive the deck on a tablet.
+
+.deck__nav {
+ position: absolute;
+ right: var(--safe-r);
+ bottom: var(--safe-b);
+ display: flex;
+ gap: .4rem;
+ z-index: 3;
+}
+
+.deck__btn {
+ // 44px is Apple's minimum comfortable touch target.
+ min-width: 44px;
+ min-height: 44px;
+ display: grid;
+ place-items: center;
+ font-family: $font-mono;
+ font-size: 1.05rem;
+ line-height: 1;
+ color: $text-dim;
+ background: $surface;
+ border: 1px solid $border-subtle;
+ border-radius: 10px;
+ cursor: pointer;
+ // Suppress the iOS tap flash and the double-tap-to-zoom delay.
+ -webkit-tap-highlight-color: transparent;
+ touch-action: manipulation;
+ transition: color .15s ease, border-color .15s ease;
+
+ &:hover { color: $text; border-color: $border; }
+ &:active { background: $surface-raised; }
+
+ &:focus-visible {
+ outline: 2px solid $accent;
+ outline-offset: 2px;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .deck__btn { transition: none; }
+}
+
+// On a pointer device the deck is driven from the keyboard; keep the buttons
+// available but quiet until approached.
+@media (hover: hover) {
+ .deck__nav { opacity: .35; transition: opacity .2s ease; }
+ .deck:hover .deck__nav,
+ .deck__nav:focus-within { opacity: 1; }
+}
+
+// ─── Overview (press o) ───────────────────────────────────────────────
+
+.deck.is-overview {
+ .deck__stage {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
+ gap: .8rem;
+ padding: 1.2rem;
+ padding-bottom: calc(3.6rem + var(--safe-b));
+ overflow-y: auto;
+ -webkit-overflow-scrolling: touch;
+ align-content: start;
+ }
+
+ .slide {
+ grid-area: auto;
+ visibility: visible;
+ opacity: .55;
+ aspect-ratio: 16 / 10;
+ padding: .8rem;
+ border: 1px solid $border-subtle;
+ border-radius: 8px;
+ background: $bg-subtle;
+ cursor: pointer;
+ overflow: hidden;
+ gap: .35rem;
+ transition: opacity .15s ease, border-color .15s ease;
+
+ &.is-current { opacity: 1; border-color: $accent; }
+ &:hover { opacity: .9; }
+
+ // Shrink everything so a whole slide fits the thumbnail.
+ h1 { font-size: 1.05rem; }
+ h2 { font-size: .88rem; }
+ p, li { font-size: .62rem; max-width: none; }
+ .slide__quote { font-size: .8rem; padding-left: .5rem; max-width: none; }
+ .slide__lead { font-size: .68rem !important; }
+ .evidence { font-size: .5rem; padding: .35rem .45rem; }
+ .ledger__row { padding: .2rem .35rem; font-size: .55rem; gap: .35rem; }
+ .chain__stage { padding: .3rem .35rem; }
+ .chain__name { font-size: .6rem; }
+ .chain__what { font-size: .48rem; }
+ }
+}
+
+// Presenter notes never render on the slide; they are for the speaker's
+// source file only.
+.notes { display: none; }
+
+@media print {
+ .deck { position: static; }
+ .deck__stage { display: block; }
+ .slide { visibility: visible; opacity: 1; page-break-after: always; min-height: 100vh; }
+ .deck__progress, .deck__counter, .deck__hint { display: none; }
+}
diff --git a/sass/main.scss b/sass/main.scss
index 191d8e9..b7a9f40 100644
--- a/sass/main.scss
+++ b/sass/main.scss
@@ -7,3 +7,4 @@
@import "pipeline";
@import "tree";
@import "responsive";
+@import "talk";
diff --git a/templates/talk.html b/templates/talk.html
new file mode 100644
index 0000000..5834ae5
--- /dev/null
+++ b/templates/talk.html
@@ -0,0 +1,145 @@
+{# Slide deck. Deliberately does NOT extend base.html: a deck wants the whole
+ viewport with no nav, no footer, no background canvas. It reuses main.css
+ so the tokens, fonts and colours are the site's. #}
+
+
+
+
+ {# viewport-fit=cover so the deck reaches under an iPad's rounded corners;
+ the safe-area insets in _talk.scss keep the chrome clear of them. #}
+
+
+ The OS as components,
-
and the factory that lowers itChange the Tires,
+
Not the CarWhat this room has already established
+ Portability is usually asked of the wrong layer
@@ -33,17 +47,9 @@ slot = "30 minutes, remote"
and between the OS and the tenants above it.
Act I · the inversion
-Think of the OS and everything above it as the car, and the - handful of native functions that actually touch the hardware as the tires.
-For new terrain — another chip, another board — you change the - tires, not the car.
-This talk spends most of its time on the factory that - builds the car, because that is the part that has to be qualified once.
+The OS and everything above it is the car. The handful of native functions + that actually touch the hardware are the tires. For new terrain — another chip, + another board — you change the tires, not the car.
Act II · the car
Wasm is where the pieces are joined and checked. It is not present at run - time — there is no interpreter, no JIT, and no engine resident on the chip.
+ + +Act II · the car
+interface mmio {
+ read32: func(addr: u32) -> u32;
+ write32: func(addr: u32, val: u32);
+ read8: func(addr: u32) -> u8;
+ write8: func(addr: u32, val: u8);
+}
+world wdg-driver { import mmio; export wdg; }
+ wac compose fused-gustos.wac -o fused.wasm +meld fuse --memory shared +loom optimize --passes inline +synth compile --target cortex-m3 \ + --all-exports --relocatable+
A driver's capability is checked against a typed contract at composition
+ time. Before this it was an untyped env extern that only had to
+ match by name at native link.
Act II · the car
+A watchdog you can accidentally switch off is worthless. So the interface + offers no way to switch it off:
+interface wdg {
+ unlock · configure · lock · start · refresh · is-running
+ — there is no stop, and no disable
+}
+ and the FSM proves the absence, rather than relying on it
+fn p2_cannot_un_start() {
+ let w = Iwdg { phase: Running, .. };
+ if let Ok(n) = refresh(w) { assert_eq!(n.phase, Running); }
+ assert!(unlock(w).is_err()); // no escape from Running
+}
+ The contract itself cannot express the one transition the + proof forbids. That is the argument for putting the seam in a type + system rather than in a comment.
Act II · the car
-The three relocations are mmio_read32, mmio_write32,
- irq_poll. Everything above them — the protocol, the state machine,
- the error handling — is wasm that got lowered.
Act III · the tires
Act III · the tires
+web/shim-mmio.js — what a browser tab supplies
+const REGS = new Uint32Array(64);
+const TIM2_CNT = 0x40000024; // the one clock register the OS reads
+export function read32(addr) {
+ const a = addr >>> 0;
+ return a === TIM2_CNT ? clock : REGS[(a >>> 2) & 63];
+}
+ on silicon — same import, answered by the bus
+#[no_mangle] extern "C" fn read32(addr: u32) -> u32 {
+ unsafe { core::ptr::read_volatile(addr as *const u32) }
+}
+ Nothing else about the component changes. That substitution + is the entire thesis — and it is small enough to read on one slide.
+read32, write32, irq_poll326 B · 0 SRAM · 3 relocsAct IV · the factory
The pipeline is qualified once; every product @@ -218,6 +304,20 @@ ratio_x1000 1839 (mismatch=0) a verified compiler makes: don't trust the tool, check its output.
Act IV · the factory
+Bounded model checking is bounded. Proofs run on a verification schedule, + not on every commit. Our own docs say it out loud: closer to "well-specified + and comprehensively tested with local formal verification" than + "continuously formally verified."
+Act IV · the factory
Act V · what is missing
-Proofs run on a verification schedule, not on every commit. Our own docs say - this out loud: closer to "well-specified and comprehensively tested with - local formal verification" than "continuously formally verified."
-Act V · what is missing
Act I · the inversion
Each of these is one ingredient. This talk is what happens - when you need all four at once — on a chip where the runtime the third - one measures does not fit at all.
+Each is one ingredient. This talk needs all four at + once — on a chip where the runtime the third one measures does not fit.
The OS and everything above it is the car. The handful of native functions that actually touch the hardware are the tires. For new terrain — another chip, another board — you change the tires, not the car.
+Declaring the bias: I work in automotive, so to me + everything is a car. The substrate is not — which is the next slide.
Act II · the car
captured 2026-08-04, all three probes attached at once
Cortex-M4 · NUCLEO-G474RE -gust-wdg-silicon OK: IWDG watchdog reset CONFIRMED on real STM32G474 -silicon (RCC_CSR=0x34000000, IWDGRSTF=1)-
Cortex-M3 · STM32F100 VLDISCOVERY — the same .o, a second die -gust-wdg-silicon OK: IWDG watchdog reset CONFIRMED on real STM32F100 -silicon (RCC_CSR=0x34000000, IWDGRSTF=1)+IWDG reset CONFIRMED on real STM32G474 +RCC_CSR 0x14000000 → 0x34000000 IWDGRSTF=1 +
Cortex-M3 · STM32F100 — the same .o, a second die +IWDG reset CONFIRMED on real STM32F100 +RCC_CSR 0x14000000 → 0x34000000 IWDGRSTF=1
RISC-V · ESP32-C3 rev v0.4 -correctness: IDENTICAL ok over [0,2047] -gust_mix_native 271 milliticks/call -gust_mix_dissolved 499 milliticks/call -ratio_x1000 1839 (mismatch=0)+native 271 dissolved 499 milliticks/call ratio 1.839× +correctness IDENTICAL over [0,2047] mismatch=0
The watchdog legs are one happy path on two dies. They do - not evidence the cannot-un-start property — the firmware never attempts an - un-start. That stays a source-level proof.
+Captured 2026-08-04, all three probes attached at once. + The watchdog legs are one happy path on two dies — they do not evidence + cannot-un-start; the firmware never attempts one.
Nothing else about the component changes. That substitution - is the entire thesis — and it is small enough to read on one slide.
+ is the entire thesis.read32, write32, irq_poll326 B · 0 SRAM · 3 relocshad min-height:0 inside a flex column, so siblings squashed it to a few pixels — you saw the first commands and nothing else. It now claims its space, and "run it" hides the flow diagram so the recording gets the whole slide. It auto-scrolls, ending on the payoff. - `.slide p` (0,2,0) beat `.slide__act` and `.slide__cite` (0,1,0), so the act eyebrow and every citation had rendered at full body size since the first commit. Qualified as `.slide p.`. Playback stops and resets when you leave the slide, so it never runs unseen. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo --- content/talks/wasm-research-day-2026.md | 138 ++++++++++++------------ sass/_talk.scss | 97 ++++++++++++++--- static/casts/dissolve.cast | 21 ++++ templates/talk.html | 125 +++++++++++++++++++++ 4 files changed, 294 insertions(+), 87 deletions(-) create mode 100644 static/casts/dissolve.cast diff --git a/content/talks/wasm-research-day-2026.md b/content/talks/wasm-research-day-2026.md index 96a1426..2e3a979 100644 --- a/content/talks/wasm-research-day-2026.md +++ b/content/talks/wasm-research-day-2026.md @@ -21,13 +21,13 @@ slot = "25 + 5, remote" Act I · the inversion
What this room has already established
--2022Fixpoint — wasm lowered ahead of time, invoked with no engineserver, x86-64-2023Orchestration at the edge — pluggable HALs via the Component Modelpreliminary-2025WASI performance on IoT and embedded — a runtime's overhead on a small deviceruntime present-todayWasmBounds — eliding bounds checks by abstract interpretationtwo hours ago+2022lowered ahead of time, no enginex86-64+2023pluggable HALs via the Component Modelpreliminary+2025a runtime's overhead on a small deviceruntime present+todayeliding bounds checks, soundlytwo hours agoEach is one ingredient. This talk needs all four at - once — on a chip where the runtime the third one measures does not fit.
+Four ingredients. This talk needs all four at once — + where the runtime does not fit.
The OS is written for the chip. The application is written against the OS - and hoped to be portable. Every new board re-opens the OS.
+OS written for the chip. Application hoped to be portable. + Every new board re-opens the OS.
The OS itself is WebAssembly components. The Component Model is the - integration step — between OS components, between the OS and its drivers, - and between the OS and the tenants above it.
+The OS is components. The Component Model is the integration + step — OS to OS, OS to drivers, OS to tenants.
The OS and everything above it is the car. The handful of native functions - that actually touch the hardware are the tires. For new terrain — another chip, - another board — you change the tires, not the car.
-Declaring the bias: I work in automotive, so to me - everything is a car. The substrate is not — which is the next slide.
+The bias, declared: I work in automotive, so to me + everything is a car.
interface mmio {
- read32: func(addr: u32) -> u32;
- write32: func(addr: u32, val: u32);
- read8: func(addr: u32) -> u8;
- write8: func(addr: u32, val: u8);
+ read32: func(addr: u32) -> u32;
+ write32: func(addr: u32, val: u32);
+ read8 / write8 likewise
}
-world wdg-driver { import mmio; export wdg; }
+world wdg-driver {
+ import mmio; export wdg;
+}
wac compose fused-gustos.wac -o fused.wasm +wac compose fused-gustos.wac meld fuse --memory shared loom optimize --passes inline synth compile --target cortex-m3 \ - --all-exports --relocatable+ --all-exports --relocatable
A driver's capability is checked against a typed contract at composition @@ -145,8 +146,9 @@ synth compile --target cortex-m3 \
A watchdog you can accidentally switch off is worthless. So the interface offers no way to switch it off:
interface wdg {
- unlock · configure · lock · start · refresh · is-running
- — there is no stop, and no disable
+ unlock · configure · lock
+ start · refresh · is-running
+ — no stop. no disable.
}
and the FSM proves the absence, rather than relying on it
fn p2_cannot_un_start() {
@@ -164,11 +166,9 @@ synth compile --target cortex-m3 \
Three dies, one session
Cortex-M4 · NUCLEO-G474RE
-IWDG reset CONFIRMED on real STM32G474
-RCC_CSR 0x14000000 → 0x34000000 IWDGRSTF=1
- Cortex-M3 · STM32F100 — the same .o, a second die
-IWDG reset CONFIRMED on real STM32F100
-RCC_CSR 0x14000000 → 0x34000000 IWDGRSTF=1
+IWDG reset CONFIRMED IWDGRSTF=1
+ Cortex-M3 · STM32F100 — the same .o
+IWDG reset CONFIRMED IWDGRSTF=1
RISC-V · ESP32-C3 rev v0.4
native 271 dissolved 499 milliticks/call ratio 1.839×
correctness IDENTICAL over [0,2047] mismatch=0
@@ -218,8 +218,7 @@ export function read32(addr) {
#[no_mangle] extern "C" fn read32(addr: u32) -> u32 {
unsafe { core::ptr::read_volatile(addr as *const u32) }
}
- Nothing else about the component changes. That substitution
- is the entire thesis.
+ That substitution is the entire thesis.
seamA whole STM32 USART driver, dissolved326 B · 0 SRAM · 3 relocs
@@ -239,22 +238,22 @@ export function read32(addr) {
Six faces, not four tools
- architect
spar — AADL / SysML. The WIT is generated from the model, not hand-written.
+ architect
spar — the WIT is generated from the model
- build
meld fuse · loom optimize · synth lower · sigil attesting across all of it.
+ build
meld · loom · synth, with sigil attesting across all of it
- verify
witness MC/DC on the shipped wasm · scry abstract interpretation · Verus, Rocq, Lean, Kani as build rules.
+ verify
witness MC/DC on the shipped wasm · Verus, Rocq, Lean, Kani
- trace
rivet — typed requirements, decisions and tests. Broken links fail the build.
+ trace
rivet — typed artifacts; broken links fail the build
- run
kiln runtime · gale verified kernel primitives · the applications above them.
+ run
kiln · gale verified primitives
- agent
The loop that files findings between repos — and the governance that keeps 30+ repos to the same rules.
+ agent
the loop that files findings between repos
@@ -277,41 +276,38 @@ export function read32(addr) {
Act IV · the factory
Faster because it is proven
- baselinenative LLVM, full clamp0.50 cyc/call · 1.00×
- todaydissolved, as shipped0.83 cyc/call · 1.65×
- measured floorclamp elided because a proof allows it0.23 cyc/call · 0.45×
+ native LLVMfull clamp0.50 cyc · 1.00×
+ dissolvedas shipped today0.83 cyc · 1.65×
+ proof-carryingclamp elided because a proof allows it0.23 cyc · 0.45×
- Not a model — a measured floor, soundness-gated. The elision is only legal
- because something upstream proved the bound. A compiler with no
- verifier in its pipeline structurally cannot reach it.
+ Legal only because something upstream proved the bound.
+ A compiler with no verifier cannot reach it.
Act IV · the factory
Qualify the checker, not the prover
- Certification asks: why do you believe the solver? "Qualify Z3" is not a
+
Certification asks why you believe the solver. "Qualify Z3" is not a
tractable answer.
- So the solver stays untrusted and emits a certificate. Only a small,
- dependency-free checker is trusted — and that checker's soundness is
- machine-checked in Lean 4, with no sorry anywhere in its kernel.
+ So the solver stays untrusted and emits a certificate. Only a small
+ checker is trusted — and its soundness is machine-checked in Lean 4, with no
+ sorry in its kernel.
shippedbit-vector obligations re-discharged with re-checkable certificates62 / 62
- This turns unchecked-solver evidence into the argument
- a verified compiler makes: don't trust the tool, check its output.
+ Don't trust the tool — check its output.
Act IV · the factory
Exactly how far this goes — and no further
- shippingTheorem proving · SMT contracts · bounded model checking · translation validation
- partialRefinement to Lean · mutation testing · abstract interpretation
+ shippingtheorem proving · SMT · bounded MC · translation validation
+ partialrefinement to Lean · mutation · abstract interpretation
not yetAn authority audit. We have not been audited.
- Bounded model checking is bounded. Proofs run on a verification schedule,
- not on every commit. Our own docs say it out loud: closer to "well-specified
- and comprehensively tested with local formal verification" than
+
Bounded model checking is bounded. Proofs run on a schedule, not every
+ commit. Our own docs say "comprehensively tested", not
"continuously formally verified."
@@ -319,25 +315,25 @@ export function read32(addr) {
Act IV · the factory
Every one of these gates was green for the wrong reason
- scrya proof that never ran — the theorem was false when it did
- synthtwo validators, same direction, same blind spot
- looma gate with passing tests and zero callers
- meldverified by a test on a path the shipped code didn't take
- galea count over modules that cannot see instances
+ scrya proof that never ran
+ synthtwo validators, one blind spot
+ looma gate with zero callers
+ melda test on a path the shipped code skipped
+ galea count that cannot see instances
The failure produced the same observable as
success.
- The remedy is not more gates. It is that every check must be able to go red
- for a reason you can state in advance.
+ The remedy is not more gates — every check must be able
+ to go red for a reason you can state in advance.
Act V · what is missing
What componentizing actually costs
driver object .text, core module → component
- gpio 502 → 1196 B spi 454 → 1244 B
-timer 204 → 828 B wdg 638 → 1718 B
-.data / .bss, every one of them 0 → 0
+ gpio 502 → 1196 B spi 454 → 1244 B
+timer 204 → 828 B wdg 638 → 1718 B
+.data / .bss, all of them 0 → 0
Roughly +700 B fixed per driver for canonical-ABI glue
— a constant, not a proportion. On a chip with 8 KB of SRAM that is a real
number, and it is filed upstream rather than absorbed quietly.
@@ -349,15 +345,13 @@ timer 204 → 828 B wdg 638 → 1718 B
the runner broke on success
- Plugging in the third board made the first one unreachable — two probes,
- an interactive prompt, and a harness with no terminal. The all-three-at-once
- case was exactly the case nothing had run.
+ Plugging in the third board made the first unreachable. The
+ all-three-at-once case was the one nothing had run.
a reproducible number that isn't
- The RISC-V figure is recorded with a byte-reproducible command. The wasm
- input to that command is not in the repository — it lived in a scratch
- directory that no longer exists.
+ A byte-reproducible command whose wasm input is not in the repo — it
+ lived in a scratch directory that no longer exists.
A bench whose input is not committed is not
diff --git a/sass/_talk.scss b/sass/_talk.scss
index 4e55e28..9030768 100644
--- a/sass/_talk.scss
+++ b/sass/_talk.scss
@@ -69,7 +69,7 @@
// Sized in vmin so a slide reads the same projected, shared, or in a tab.
.slide h1 {
- font-size: clamp(2.5rem, 6.4vmin, 4.8rem);
+ font-size: clamp(2.8rem, 8.4vmin, 6rem);
line-height: 1.1;
font-weight: 700;
letter-spacing: -0.02em;
@@ -78,7 +78,7 @@
}
.slide h2 {
- font-size: clamp(1.85rem, 4.5vmin, 3.2rem);
+ font-size: clamp(2.1rem, 6vmin, 4.2rem);
line-height: 1.15;
font-weight: 600;
letter-spacing: -0.015em;
@@ -88,7 +88,7 @@
.slide p,
.slide li {
- font-size: clamp(1.2rem, 3vmin, 2.1rem);
+ font-size: clamp(1.45rem, 4.2vmin, 2.9rem);
line-height: 1.55;
max-width: 46ch;
margin: 0;
@@ -101,7 +101,7 @@
.slide p.slide__act,
.slide__act {
font-family: $font-mono;
- font-size: clamp(.85rem, 1.8vmin, 1.15rem);
+ font-size: clamp(1rem, 2.3vmin, 1.5rem);
letter-spacing: .14em;
text-transform: uppercase;
color: $text-faint;
@@ -109,14 +109,14 @@
}
.slide__lead {
- font-size: clamp(1.4rem, 3.5vmin, 2.5rem) !important;
+ font-size: clamp(1.7rem, 5vmin, 3.4rem) !important;
color: $text-dim;
max-width: 40ch;
}
// A pull-quote slide: the project's own words, attributed.
.slide__quote {
- font-size: clamp(1.8rem, 4.3vmin, 3.1rem);
+ font-size: clamp(2.1rem, 5.6vmin, 4rem);
line-height: 1.3;
font-weight: 500;
max-width: 26ch;
@@ -128,7 +128,7 @@
.slide p.slide__cite,
.slide__cite {
font-family: $font-mono;
- font-size: clamp(.9rem, 1.95vmin, 1.25rem);
+ font-size: clamp(1.05rem, 2.5vmin, 1.6rem);
color: $text-faint;
}
@@ -138,7 +138,7 @@
.evidence {
font-family: $font-mono;
- font-size: clamp(.95rem, 2.25vmin, 1.5rem);
+ font-size: clamp(1.15rem, 3.2vmin, 2rem);
line-height: 1.5;
background: $bg-subtle;
border: 1px solid $border-subtle;
@@ -163,7 +163,7 @@
.slide p.evidence__label,
.evidence__label {
font-family: $font-mono;
- font-size: clamp(.8rem, 1.7vmin, 1.05rem);
+ font-size: clamp(.95rem, 2.2vmin, 1.4rem);
letter-spacing: .06em;
text-transform: uppercase;
color: $text-faint;
@@ -176,6 +176,73 @@
.dim { color: $text-faint; }
.hi { color: $accent; }
+// ─── Terminal recording ───────────────────────────────────────────────
+// A real capture replayed inline. Text, not video: selectable, crisp at any
+// projection size, ~2 KB on disk, and it degrades to a disabled button rather
+// than a broken embed if the file cannot be read.
+
+.cast {
+ display: flex;
+ flex-direction: column;
+ gap: .5rem;
+ width: 100%;
+ min-height: 0;
+}
+
+.cast__play {
+ align-self: flex-start;
+ font-family: $font-mono;
+ font-size: clamp(.85rem, 1.85vmin, 1.1rem);
+ padding: .45rem 1rem;
+ min-height: 44px; // same touch target as the deck controls
+ color: $text;
+ background: $surface;
+ border: 1px solid $border;
+ border-radius: 8px;
+ cursor: pointer;
+ -webkit-tap-highlight-color: transparent;
+ touch-action: manipulation;
+
+ &::before { content: '▶ '; color: $green; }
+ &:hover:not(:disabled) { border-color: $accent; }
+ &:disabled { color: $text-faint; cursor: default; &::before { content: ''; } }
+ &:focus-visible { outline: 2px solid $accent; outline-offset: 2px; }
+}
+
+.cast__screen {
+ font-family: $font-mono;
+ font-size: clamp(1.05rem, 2.9vmin, 1.8rem);
+ line-height: 1.45;
+ white-space: pre-wrap;
+ word-break: break-word;
+ background: #0b0d13; // a terminal is a terminal in both themes
+ color: #d7dae3;
+ border: 1px solid $border-subtle;
+ border-radius: 10px;
+ padding: .8rem 1rem;
+ margin: 0;
+ overflow-y: auto;
+ display: none;
+ // In a flex column a with min-height:0 is squashed to nothing by its
+ // siblings — the recording played into a box a few pixels tall. Claim the
+ // room explicitly instead.
+ flex: 1 1 auto;
+ min-height: 46vmin;
+}
+
+.cast.is-open {
+ flex: 1 1 auto;
+ min-height: 0;
+ .cast__screen { display: block; }
+}
+
+// Running the recording hands the slide over to it: the diagram has made its
+// point by then, and the terminal is the evidence for the same claim.
+.slide.cast-open {
+ justify-content: flex-start;
+ .flow { display: none; }
+}
+
// ─── Claim ledger ─────────────────────────────────────────────────────
// The provenance marker is the point of this deck: every number says whether
// it was measured for this talk or is a standing project claim.
@@ -196,7 +263,7 @@
background: $surface;
border: 1px solid $border-subtle;
border-radius: 8px;
- font-size: clamp(.98rem, 2.25vmin, 1.45rem);
+ font-size: clamp(1.15rem, 3vmin, 1.9rem);
}
.ledger__tag {
@@ -237,7 +304,7 @@
.flow__stage {
font-family: $font-mono;
- font-size: clamp(1rem, 2.2vmin, 1.4rem);
+ font-size: clamp(1.2rem, 3vmin, 1.9rem);
font-weight: 600;
color: $accent;
}
@@ -245,7 +312,7 @@
.flow__in,
.flow__out {
font-family: $font-mono;
- font-size: clamp(.88rem, 2vmin, 1.25rem);
+ font-size: clamp(1.05rem, 2.7vmin, 1.7rem);
line-height: 1.35;
}
@@ -255,7 +322,7 @@
.flow__arrow {
font-family: $font-mono;
color: $text-faint;
- font-size: clamp(1rem, 2.1vmin, 1.3rem);
+ font-size: clamp(1.2rem, 2.9vmin, 1.8rem);
}
// Stack the row on a narrow screen rather than crushing four columns.
@@ -269,7 +336,7 @@
// Code inside a two-column split has roughly half the width, so it takes one
// step down — still well above the 11.6px it used to be, and it stops the
// WIT block scrolling sideways inside its column.
-.split .evidence { font-size: clamp(.85rem, 1.95vmin, 1.25rem); padding: .7rem .85rem; }
+.split .evidence { font-size: clamp(1rem, 2.7vmin, 1.7rem); padding: .7rem .85rem; }
// ─── Two-column split ─────────────────────────────────────────────────
@@ -282,7 +349,7 @@
.split__col h3 {
font-family: $font-mono;
- font-size: clamp(.88rem, 1.85vmin, 1.15rem);
+ font-size: clamp(1rem, 2.4vmin, 1.5rem);
letter-spacing: .1em;
text-transform: uppercase;
color: $text-faint;
diff --git a/static/casts/dissolve.cast b/static/casts/dissolve.cast
new file mode 100644
index 0000000..5ca299d
--- /dev/null
+++ b/static/casts/dissolve.cast
@@ -0,0 +1,21 @@
+{"version":3,"term":{"cols":80,"rows":24},"timestamp":1785871650,"command":"bash demo.sh","env":{"SHELL":"/bin/zsh"}}
+[0.005, "o", "\u001b[38;5;245m# a watchdog driver, written as a wasm component\u001b[0m\r\n\r\n"]
+[0.912, "o", "\u001b[38;5;68m$\u001b[0m wasm-tools component wit wdg.component.wasm | head -6\r\n"]
+[0.568, "o", "package root:component;\r\n\r\nworld root {\r\n import gust:hal/mmio@0.1.0;\r\n\r\n export gust:hal/wdg@0.1.0;\r\n"]
+[0.000, "o", "\r\n"]
+[0.714, "o", "\u001b[38;5;245m# optimize it, then lower it to a Cortex-M3 object\u001b[0m\r\n\r\n"]
+[0.911, "o", "\u001b[38;5;68m$\u001b[0m loom optimize --passes inline core.wasm -o opt.wasm | tail -1\r\n"]
+[0.639, "o", "✅ Optimization complete!\r\n"]
+[0.000, "o", "\r\n"]
+[0.713, "o", "\u001b[38;5;68m$\u001b[0m synth compile opt.wasm --target cortex-m3 --all-exports --relocatable -o wdg.o\r\n"]
+[0.546, "o", "wrote wdg.o\r\n\r\n"]
+[0.714, "o", "\u001b[38;5;245m# what does that object still need from the outside world?\u001b[0m\r\n\r\n"]
+[0.915, "o", "\u001b[38;5;68m$\u001b[0m arm-zephyr-eabi-nm -u wdg.o\r\n"]
+[0.539, "o", " U read32\r\n U write32\r\n"]
+[0.001, "o", "\r\n"]
+[0.714, "o", "\u001b[38;5;245m# and what does it cost?\u001b[0m\r\n\r\n"]
+[0.916, "o", "\u001b[38;5;68m$\u001b[0m arm-zephyr-eabi-size wdg.o\r\n"]
+[0.534, "o", " text\t data\t bss\t dec\t hex\tfilename\r\n 1726\t 0\t 0\t 1726\t 6be\twdg.o\r\n"]
+[0.001, "o", "\r\n"]
+[0.714, "o", "\u001b[38;5;114m# two symbols. zero SRAM. no engine.\u001b[0m\r\n"]
+[2.516, "x", "0"]
diff --git a/templates/talk.html b/templates/talk.html
index 5834ae5..44ac867 100644
--- a/templates/talk.html
+++ b/templates/talk.html
@@ -46,7 +46,11 @@
var i = 0;
+ var shown = -1;
function render() {
+ // Tell the slide we are leaving, so anything playing on it can stop.
+ if (shown !== -1 && shown !== i) slides[shown].dispatchEvent(new CustomEvent('deck:leave'));
+ shown = i;
slides.forEach(function (s, n) { s.classList.toggle('is-current', n === i); });
counter.textContent = (i + 1) + ' / ' + slides.length;
bar.style.width = ((i + 1) / slides.length * 100) + '%';
@@ -137,6 +141,127 @@
document.getElementById('hint').innerHTML = 'swipe to move';
}
+ // ── Terminal recordings ─────────────────────────────────────────────
+ // A real asciinema capture, replayed inline. Not a GIF: the text stays
+ // selectable and crisp at any projection size, and the file is ~2 KB.
+ // Casts are fetched once at load and held in memory, so clicking play
+ // mid-talk never depends on the network.
+
+ // xterm-256 -> css colour, enough for SGR 38;5;N and the reset.
+ function xterm(n) {
+ if (n < 16) {
+ var base = ['#000','#a00','#0a0','#a50','#00a','#a0a','#0aa','#aaa',
+ '#555','#f55','#5f5','#ff5','#55f','#f5f','#5ff','#fff'];
+ return base[n];
+ }
+ if (n < 232) {
+ var i = n - 16, s = [0,95,135,175,215,255];
+ return 'rgb(' + s[Math.floor(i/36)] + ',' + s[Math.floor(i/6)%6] + ',' + s[i%6] + ')';
+ }
+ return 'rgb(' + (8 + (n-232)*10) + ',' + (8 + (n-232)*10) + ',' + (8 + (n-232)*10) + ')';
+ }
+
+ function esc(s) {
+ return s.replace(/&/g,'&').replace(//g,'>');
+ }
+
+ // The recording contains only SGR colour escapes and newlines — verified,
+ // no cursor movement — so append-only rendering is faithful, not an
+ // approximation.
+ function ansi(text) {
+ var out = '', open = false;
+ var re = /\x1b\[([0-9;]*)m/g, last = 0, m;
+ while ((m = re.exec(text)) !== null) {
+ out += esc(text.slice(last, m.index));
+ last = re.lastIndex;
+ var p = m[1];
+ if (p === '' || p === '0') { if (open) { out += ''; open = false; } }
+ else {
+ var c = /^38;5;(\d+)$/.exec(p);
+ if (c) { if (open) out += ''; out += ''; open = true; }
+ }
+ }
+ out += esc(text.slice(last));
+ if (open) out += '';
+ return out;
+ }
+
+ function parseCast(txt) {
+ var lines = txt.split('\n').filter(function (l) { return l.trim(); });
+ if (!lines.length) return [];
+ var ver = 2;
+ try { ver = JSON.parse(lines[0]).version || 2; } catch (e) {}
+ var evs = [], t = 0;
+ for (var i = 1; i < lines.length; i++) {
+ var e;
+ try { e = JSON.parse(lines[i]); } catch (err) { continue; }
+ if (e[1] !== 'o') continue;
+ // v3 intervals are relative to the previous event; v2 are absolute.
+ t = (ver >= 3) ? t + e[0] : e[0];
+ evs.push([t, e[2]]);
+ }
+ return evs;
+ }
+
+ document.querySelectorAll('.cast').forEach(function (box) {
+ var screen = box.querySelector('.cast__screen');
+ var btn = box.querySelector('.cast__play');
+ var evs = null, timers = [], playing = false;
+
+ fetch(box.dataset.cast)
+ .then(function (r) { return r.ok ? r.text() : Promise.reject(r.status); })
+ .then(function (t) { evs = parseCast(t); })
+ .catch(function () {
+ btn.textContent = 'recording unavailable';
+ btn.disabled = true;
+ });
+
+ function stop() { timers.forEach(clearTimeout); timers = []; playing = false; }
+
+ function paint(upto) {
+ var s = '';
+ for (var i = 0; i < evs.length && evs[i][0] <= upto; i++) s += evs[i][1];
+ screen.innerHTML = ansi(s.replace(/\r\n/g, '\n'));
+ screen.scrollTop = screen.scrollHeight;
+ }
+
+ btn.addEventListener('click', function () {
+ if (!evs) return;
+ if (playing) { // a second press skips to the end
+ stop();
+ paint(Infinity);
+ btn.textContent = 'replay';
+ return;
+ }
+ stop();
+ box.classList.add('is-open');
+ box.closest('.slide').classList.add('cast-open');
+ screen.innerHTML = '';
+ playing = true;
+ btn.textContent = 'skip';
+ var acc = '';
+ evs.forEach(function (ev) {
+ timers.push(setTimeout(function () {
+ acc += ev[1];
+ screen.innerHTML = ansi(acc.replace(/\r\n/g, '\n'));
+ screen.scrollTop = screen.scrollHeight;
+ }, ev[0] * 1000));
+ });
+ timers.push(setTimeout(function () {
+ playing = false; btn.textContent = 'replay';
+ }, (evs[evs.length - 1][0] + 0.2) * 1000));
+ });
+
+ // Leaving the slide stops playback so it never runs on unseen slides.
+ box.closest('.slide').addEventListener('deck:leave', function () {
+ stop();
+ box.classList.remove('is-open');
+ box.closest('.slide').classList.remove('cast-open');
+ btn.textContent = 'run it';
+ screen.innerHTML = '';
+ });
+ });
+
var start = parseInt((location.hash || '').slice(1), 10);
go(isNaN(start) ? 0 : start - 1);
}());
From fc850a57cfb72d73977d0a1a48e93ccdcab690c2 Mon Sep 17 00:00:00 2001
From: Ralf Anton Beier
Date: Tue, 4 Aug 2026 21:37:01 +0200
Subject: [PATCH 05/56] =?UTF-8?q?talk:=20slide=205=20shows=20the=20whole?=
=?UTF-8?q?=20chain=20=E2=80=94=20wac=20and=20meld=20included=20=E2=80=94?=
=?UTF-8?q?=20on=20the=20OS=20itself?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The recording covered only loom -> synth, so it silently skipped the first two
rows of the very slide it sits on. Re-recorded against the real 5-provider
composition, and against the OS rather than a single driver:
wac compose 5 components -> 1 component, 20741 B
exports 5 gust:os, imports mmio + taskdisp
meld fuse 11 memories -> 1 core module, 9874 B (-52.4%)
loom optimize
synth compile -> gustos.o
nm -u -> poll-task, read32, write32
size -> text 4812 data 0 bss 0
The whole gust OS as one relocatable object with a three-symbol trusted
surface and zero SRAM. That is a better demonstration than a driver was.
Flow rows re-stated from those measurements rather than from memory: the
slide had claimed "5 linear memories -> one shared memory" where meld
actually reports 11 -> 1 core module at 52% smaller, and the "3 native
functions" claim is now the measured undefined-symbol count, not an estimate.
Co-Authored-By: Claude Opus 5
Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo
---
content/talks/wasm-research-day-2026.md | 10 +++---
static/casts/dissolve.cast | 41 ++++++++++++++-----------
2 files changed, 28 insertions(+), 23 deletions(-)
diff --git a/content/talks/wasm-research-day-2026.md b/content/talks/wasm-research-day-2026.md
index 2e3a979..596b031 100644
--- a/content/talks/wasm-research-day-2026.md
+++ b/content/talks/wasm-research-day-2026.md
@@ -78,13 +78,13 @@ spi · timer · uart · dma
wac compose
5 components
→
- 1 component
exports gust:os · imports gust:hal
+ 1 component · 20 741 B
exports 5 gust:os · imports mmio + taskdisp
meld fuse
- 1 component
5 linear memories
+ 1 component
11 linear memories
→
- 1 core module
one shared memory
+ 1 core module · 9 874 B
shared memory — 52% smaller
loom optimize
@@ -96,11 +96,11 @@ spi · timer · uart · dma
synth compile
module + facts
→
- one relocatable .o + certificates
+ gustos.o · 4 812 B text · 0 SRAM
ld
- .o + the native functions
+ .o + 3 native functions
→
firmware
no engine, no interpreter, no JIT
diff --git a/static/casts/dissolve.cast b/static/casts/dissolve.cast
index 5ca299d..990ef93 100644
--- a/static/casts/dissolve.cast
+++ b/static/casts/dissolve.cast
@@ -1,21 +1,26 @@
-{"version":3,"term":{"cols":80,"rows":24},"timestamp":1785871650,"command":"bash demo.sh","env":{"SHELL":"/bin/zsh"}}
-[0.005, "o", "\u001b[38;5;245m# a watchdog driver, written as a wasm component\u001b[0m\r\n\r\n"]
-[0.912, "o", "\u001b[38;5;68m$\u001b[0m wasm-tools component wit wdg.component.wasm | head -6\r\n"]
-[0.568, "o", "package root:component;\r\n\r\nworld root {\r\n import gust:hal/mmio@0.1.0;\r\n\r\n export gust:hal/wdg@0.1.0;\r\n"]
-[0.000, "o", "\r\n"]
-[0.714, "o", "\u001b[38;5;245m# optimize it, then lower it to a Cortex-M3 object\u001b[0m\r\n\r\n"]
-[0.911, "o", "\u001b[38;5;68m$\u001b[0m loom optimize --passes inline core.wasm -o opt.wasm | tail -1\r\n"]
-[0.639, "o", "✅ Optimization complete!\r\n"]
-[0.000, "o", "\r\n"]
-[0.713, "o", "\u001b[38;5;68m$\u001b[0m synth compile opt.wasm --target cortex-m3 --all-exports --relocatable -o wdg.o\r\n"]
-[0.546, "o", "wrote wdg.o\r\n\r\n"]
-[0.714, "o", "\u001b[38;5;245m# what does that object still need from the outside world?\u001b[0m\r\n\r\n"]
-[0.915, "o", "\u001b[38;5;68m$\u001b[0m arm-zephyr-eabi-nm -u wdg.o\r\n"]
-[0.539, "o", " U read32\r\n U write32\r\n"]
+{"version":3,"term":{"cols":80,"rows":24},"timestamp":1785872149,"command":"bash demo.sh","env":{"SHELL":"/bin/zsh"}}
+[0.006, "o", "\u001b[38;5;245m# five OS components, joined by the Component Model\u001b[0m\r\n\r\n"]
+[0.813, "o", "\u001b[38;5;68m$\u001b[0m wac compose fused-gustos.wac --dep ... -o os.wasm\r\n"]
+[0.532, "o", "20741 bytes\r\n"]
[0.001, "o", "\r\n"]
-[0.714, "o", "\u001b[38;5;245m# and what does it cost?\u001b[0m\r\n\r\n"]
-[0.916, "o", "\u001b[38;5;68m$\u001b[0m arm-zephyr-eabi-size wdg.o\r\n"]
-[0.534, "o", " text\t data\t bss\t dec\t hex\tfilename\r\n 1726\t 0\t 0\t 1726\t 6be\twdg.o\r\n"]
+[0.612, "o", "\u001b[38;5;68m$\u001b[0m wasm-tools component wit os.wasm | grep -E 'import|export'\r\n"]
+[0.520, "o", " import gust:os/taskdisp@0.1.0;\r\n import gust:hal/mmio@0.1.0;\r\n export gust:os/time@0.1.0;\r\n export gust:os/log@0.1.0;\r\n export gust:os/spawn@0.1.0;\r\n export gust:os/exec@0.1.0;\r\n export gust:os/timer@0.1.0;\r\n"]
+[0.000, "o", "\r\n"]
+[0.614, "o", "\u001b[38;5;245m# fuse them: many linear memories become one\u001b[0m\r\n\r\n"]
+[0.816, "o", "\u001b[38;5;68m$\u001b[0m meld fuse os.wasm --memory shared -o core.wasm\r\n"]
+[0.496, "o", "Output: core.wasm (9874 bytes)\r\n Size reduction: 52.4%\r\n"]
[0.001, "o", "\r\n"]
-[0.714, "o", "\u001b[38;5;114m# two symbols. zero SRAM. no engine.\u001b[0m\r\n"]
+[0.614, "o", "\u001b[38;5;245m# optimize, then lower the whole OS to a Cortex-M3 object\u001b[0m\r\n\r\n"]
+[0.816, "o", "\u001b[38;5;68m$\u001b[0m loom optimize --passes inline core.wasm -o opt.wasm\r\n"]
+[0.649, "o", "ok\r\n\r\n"]
+[0.612, "o", "\u001b[38;5;68m$\u001b[0m synth compile opt.wasm --target cortex-m3 --relocatable -o gustos.o\r\n"]
+[0.506, "o", "wrote gustos.o\r\n\r\n"]
+[0.614, "o", "\u001b[38;5;245m# what does the whole OS still need from the outside world?\u001b[0m\r\n\r\n"]
+[0.806, "o", "\u001b[38;5;68m$\u001b[0m arm-zephyr-eabi-nm -u gustos.o\r\n"]
+[0.496, "o", " U poll-task\r\n U read32\r\n U write32\r\n"]
+[0.000, "o", "\r\n"]
+[0.614, "o", "\u001b[38;5;68m$\u001b[0m arm-zephyr-eabi-size gustos.o\r\n"]
+[0.491, "o", " text\t data\t bss\t dec\t hex\tfilename\r\n 4812\t 0\t 0\t 4812\t 12cc\tgustos.o\r\n"]
+[0.000, "o", "\r\n"]
+[0.610, "o", "\u001b[38;5;114m# the whole OS. three symbols. zero SRAM. no engine.\u001b[0m\r\n"]
[2.516, "x", "0"]
From ffabdb74381dee7a4444658a1bfbf1f357fa1a49 Mon Sep 17 00:00:00 2001
From: Ralf Anton Beier
Date: Tue, 4 Aug 2026 21:40:09 +0200
Subject: [PATCH 06/56] talk: the cost slide now carries the measured response,
not just the cost
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The componentization slide named a +700-1100 B canonical-ABI overhead and left
it there. It now also shows what we did about it: our wit-bindgen fork's
cabi-realloc-extern backs cabi_realloc with a bounded arena that traps instead
of growing, worth -318 B (-18%) on wdg-thin, 29% of that driver's overhead.
Measured today with the version bump controlled for — the fork is at 0.58, and
a naive comparison against the shipped 0.52 would have mis-credited the feature.
The slide says the drivers do not use it yet, because they do not.
Turns a slide that admitted a cost into one that shows the loop closing:
name the cost, patch the tool, measure the result, state what is still undone.
Co-Authored-By: Claude Opus 5
Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo
---
content/talks/wasm-research-day-2026.md | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/content/talks/wasm-research-day-2026.md b/content/talks/wasm-research-day-2026.md
index 596b031..2c921c2 100644
--- a/content/talks/wasm-research-day-2026.md
+++ b/content/talks/wasm-research-day-2026.md
@@ -332,11 +332,15 @@ export function read32(addr) {
What componentizing actually costs
driver object .text, core module → component
gpio 502 → 1196 B spi 454 → 1244 B
-timer 204 → 828 B wdg 638 → 1718 B
+timer 204 → 828 B wdg 638 → 1726 B
.data / .bss, all of them 0 → 0
- Roughly +700 B fixed per driver for canonical-ABI glue
- — a constant, not a proportion. On a chip with 8 KB of SRAM that is a real
- number, and it is filed upstream rather than absorbed quietly.
+ and the response, measured for this talk
+ wdg, canonical glue on a growing allocator 1746 B
+ backed by a bounded arena instead 1428 B −318
+ So we patched the bindings generator rather than absorbing the cost:
+ cabi_realloc delegates to an embedder arena that traps instead of
+ growing. 29% of the overhead back — and the drivers do
+ not use it yet.
fn p2_cannot_un_start() {
let w = Iwdg { phase: Running, .. };
if let Ok(n) = refresh(w) { assert_eq!(n.phase, Running); }
- assert!(unlock(w).is_err()); // no escape from Running
+ // no escape from Running:
+ assert!(unlock(w).is_err());
}
The contract itself cannot express the one transition the
proof forbids. That is the argument for putting the seam in a type
@@ -209,7 +210,8 @@ correctness IDENTICAL over [0,2047] mismatch=The whole of a tire
web/shim-mmio.js — what a browser tab supplies The remedy is not more gates — every check must be able
- to go red for a reason you can state in advance.const REGS = new Uint32Array(64);
-const TIM2_CNT = 0x40000024; // the one clock register the OS reads
+// the one clock register the OS reads
+const TIM2_CNT = 0x40000024;
export function read32(addr) {
const a = addr >>> 0;
return a === TIM2_CNT ? clock : REGS[(a >>> 2) & 63];
@@ -323,8 +325,6 @@ export function read32(addr) {
The failure produced the same observable as
success.
-
to finish a sentence. Wrap instead — landscape keeps `pre`.
+@media (max-width: 900px) {
+ .evidence { white-space: pre-wrap; word-break: break-word; }
+}
+
+// ─── Very short viewports ────────────────────────────────────────────
+// The vh cap above handles aspect; this is only a floor for laptop screens
+// short enough that even a height-proportional scale crowds the dense slides.
+@media (max-height: 620px) { .deck { --fs: .9; } }
From 98ba9b526f90fdda2420e8fec4dfc6353cbd31d6 Mon Sep 17 00:00:00 2001
From: Ralf Anton Beier
Date: Tue, 4 Aug 2026 22:11:50 +0200
Subject: [PATCH 08/56] =?UTF-8?q?talk:=20add=20synth's=20architecture=20?=
=?UTF-8?q?=E2=80=94=20where=20the=20evidence=20enters=20the=20pipeline?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The factory act described the toolchain as six cube faces, which is an
abstraction. synth's own architecture overview is better: it shows WHERE
evidence enters, block by block, and it is the project's own diagram rather
than one invented for a slide.
Six stages — upstream/untrusted, front, middle, back, per-compilation, out —
with the evidence-producing and evidence-checking blocks tinted and the one
hazard block in amber: trap re-introduction, because hardware is more total
than Wasm. That amber block is the most honest thing in the deck's middle: a
diagram that is all reassurance is not a diagram anyone should believe.
It also closes a loop with the cost slide. The FRONT column carries `cabi
arena bind` — wit-bindgen's dangling realloc import resolved to a defined
function — which is exactly the mechanism measured at -318 B two slides later.
Fitting it took the diagram its own scale, since a diagram is read rather than
declaimed: six columns across 1180px put the block labels at 11px, so it flows
3-across into two rows and the labels land at 18px with 15px sub-labels. Diagram
slides also get a smaller heading and tighter stacking than prose slides.
Verified at 1180x820: 22 slides, none overflowing, nothing crossing an edge.
Co-Authored-By: Claude Opus 5
Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo
---
content/talks/wasm-research-day-2026.md | 48 ++++++++++++++++
sass/_talk.scss | 75 +++++++++++++++++++++++++
2 files changed, 123 insertions(+)
diff --git a/content/talks/wasm-research-day-2026.md b/content/talks/wasm-research-day-2026.md
index b200dc8..75cd45c 100644
--- a/content/talks/wasm-research-day-2026.md
+++ b/content/talks/wasm-research-day-2026.md
@@ -260,6 +260,54 @@ export function read32(addr) {
Act IV · the factory
+Tinted = produces or checks evidence · amber = this translation's hazard
+Act IV · the factory
Act IV · the factory
Tinted = produces or checks evidence · amber = this translation's hazard
+Tinted = produces or checks evidence · amber = this stage's hazard
to finish a sentence. Wrap instead — landscape keeps `pre`.
+@media (max-width: 900px) {
+ .evidence { white-space: pre-wrap; word-break: break-word; }
+}
+
+// ─── Very short viewports ────────────────────────────────────────────
+// The vh cap above handles aspect; this is only a floor for laptop screens
+// short enough that even a height-proportional scale crowds the dense slides.
+@media (max-height: 620px) { .deck { --fs: .9; } }
+
// ─── Architecture diagram ─────────────────────────────────────────────
// A diagram is read, not spoken word-by-word, so it gets its own smaller
// scale rather than the prose one. Evidence-producing blocks are tinted;
@@ -602,7 +691,7 @@
// A diagram slide is read, not declaimed: it can carry a smaller heading and
// tighter stacking than a prose slide, which buys the blocks their legibility.
-.slide:has(.arch) {
+.slide:has(.archset) {
gap: .55rem;
h2 { font-size: clamp(1.5rem, min(4vmin, 3.2vh), 2.5rem); }
}
diff --git a/templates/talk.html b/templates/talk.html
index 44ac867..faf3043 100644
--- a/templates/talk.html
+++ b/templates/talk.html
@@ -262,6 +262,24 @@
});
});
+ // ── Architecture tabs ───────────────────────────────────────────────
+ // Three tools, one diagram slot. Only one is shown; the others stay in the
+ // DOM so switching is instant and works with no network.
+ document.querySelectorAll('.archtabs').forEach(function (tabs) {
+ var slide = tabs.closest('.slide');
+ tabs.addEventListener('click', function (e) {
+ var btn = e.target.closest('.archtab');
+ if (!btn) return;
+ var want = btn.dataset.arch;
+ tabs.querySelectorAll('.archtab').forEach(function (b) {
+ b.classList.toggle('is-on', b === btn);
+ });
+ slide.querySelectorAll('.archr').forEach(function (d) {
+ d.classList.toggle('is-on', d.dataset.arch === want);
+ });
+ });
+ });
+
var start = parseInt((location.hash || '').slice(1), 10);
go(isNaN(start) ? 0 : start - 1);
}());
From 48aa0a42c24270f2c8eac0bfb60be9753b819b27 Mon Sep 17 00:00:00 2001
From: Ralf Anton Beier
Date: Tue, 4 Aug 2026 22:23:44 +0200
Subject: [PATCH 10/56] talk: show the wsc.facts bytes, and explain what
"faster" actually means
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
SLIDE 14 asserted a fact channel without ever showing one. It now carries a real
wsc.facts payload, computed from loom's own emitter (loom-core/src/lib.rs
build_wsc_facts_payload) rather than invented — schema v1, one value-range fact,
LEB128, nine bytes total:
01 schema v1 · 01 one fact · 01 kind=value-range
03 func_index · 07 value_id · 03 00 ff 0f body: 0 <= v <= 2047
Nine bytes is the point: the whole proof-carrying channel is smaller than the
sentence describing it. The value_id annotation carries the design argument —
keyed to a VALUE, because an optimizer renumbers everything, and a fact keyed to
a position is still true of something while being asserted about something else.
Added loom's own honest scope: emitter and wire format are done and byte-verified
against the consumer, but the source that would populate it at volume is not
wired, so 232->104 is what the channel does when it carries a fact, not evidence
that we produce many.
SLIDE 15 gave three numbers and no mechanism — and mixed ratio directions, so
1.65x read as an improvement when it is a regression. It now shows the actual
function:
gust_mix(ch) = clamp(1500 + (ch - 1024), 1000, 2000)
LLVM must emit the clamp because it cannot know ch. The OS primitives above
carry a proven bound, ch in [524,1524], so ch+476 is provably inside [1000,2000],
both clamp branches are dead, and the function collapses to `add r0,#476; bx lr`.
Ratios are now stated in words — 1.4x SLOWER today, 2.2x FASTER with the proof —
against a stated unit, so the direction cannot be misread.
That is the whole thesis in one function: not out-optimizing LLVM, but optimizing
with information LLVM structurally never had.
Co-Authored-By: Claude Opus 5
Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo
---
content/talks/wasm-research-day-2026.md | 42 ++++++++++++++++++-------
1 file changed, 30 insertions(+), 12 deletions(-)
diff --git a/content/talks/wasm-research-day-2026.md b/content/talks/wasm-research-day-2026.md
index 4caa7bb..c865349 100644
--- a/content/talks/wasm-research-day-2026.md
+++ b/content/talks/wasm-research-day-2026.md
@@ -362,27 +362,45 @@ export function read32(addr) {
Act IV · the factory
No stage trusts the one above it
- When loom proves a value range, it does not just use it and
- discard it. It writes it into a custom section — wsc.facts — keyed
- to values, not positions, so renumbering cannot silently re-point a fact.
- synth never re-derives that fact and never takes it on faith. It
- proves its own specialization correct given the fact, per site, and
- emits a certificate.
+ When loom proves a value range it does not discard it. It writes
+ it into a custom section — and the whole channel is nine bytes:
+ custom section "wsc.facts"
+01 schema v1
+01 one fact
+ 01 kind = value-range
+ 03 func_index 3
+ 07 value_id 7 ← a VALUE, not a position
+ 03 00 ff 0f body: 0 ≤ v ≤ 2047 (sleb128)
+ Keyed to values because an optimizer renumbers everything. Key a fact
+ to an instruction index and the next pass deletes three above it — the fact
+ is still true of something, and now asserted about something else. A fact whose
+ value did not survive is dropped, never re-pointed.
- measuredA bounds-guard sequence, with the fact forwarded232 → 104 B
+ measureda bounds-guard sequence, with the fact forwarded232 → 104 B
+ Honest scope: the emitter, schema and wire format are done
+ and byte-verified against the consumer. The source that would populate
+ this at volume is not wired — 232→104 is what the channel does when it
+ carries a fact, not evidence that we produce many yet.
Act IV · the factory
Faster because it is proven
+ gust_mix(ch) = clamp(1500 + (ch - 1024), 1000, 2000)
+ LLVM must emit the clamp: it cannot know what ch is. But the OS
+ primitives above carry a proven bound — ch ∈ [524, 1524]
+ — so ch + 476 is provably inside [1000, 2000] and
+ both clamp branches are dead code:
+ add r0, #476
+bx lr — the whole function
- native LLVMfull clamp0.50 cyc · 1.00×
- dissolvedas shipped today0.83 cyc · 1.65×
- proof-carryingclamp elided because a proof allows it0.23 cyc · 0.45×
+ native LLVMfull clamp — what LLVM ships0.50 ticks/call
+ dissolved todayclamp still emitted0.70 — 1.4× slower
+ with the proofclamp elided, correct only under the bound0.23 — 2.2× faster
- Legal only because something upstream proved the bound.
- A compiler with no verifier cannot reach it.
+ Soundness-gated: the bench asserts the elided form equals
+ the native one across the proven range. LLVM never had the bound.
From 5d5bdc6ebd0fa045103411bf414c2a1d44abc084 Mon Sep 17 00:00:00 2001
From: Ralf Anton Beier
Date: Tue, 4 Aug 2026 22:28:56 +0200
Subject: [PATCH 11/56] talk: split the dense slides, and stop hiding the
controls
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
CONTROLS — my bug, and a bad one for a presenter. The nav was dimmed to .35
opacity under `@media (hover: hover)` and restored on hover. An iPad with a
keyboard or trackpad attached MATCHES that query while the person is still
touching the screen, so the controls faded to near-invisible with no hover
available to bring them back. They are now always visible (.8, full on
hover/focus). A presenter always needs the controls; a little visual noise is
the correct trade.
SPLIT — 22 -> 29 slides. Six slides were carrying two ideas each:
the pipeline flow | running it
the wdg contract | the proof that the transition is impossible
the wsc.facts bytes | why a value and not a position | what it is worth
the clamp-elision example | the measured numbers
the componentization cost | the patch that recovers 29% of it
the browser shim | the silicon side
Each half now gets a slide. Measured after: average fill 62% of available
height (was crowding 100% on the worst), nothing overflows, densest slide 82%.
29 slides against a 25-minute slot is ~52s each, which suits slides that carry
one idea rather than three.
Co-Authored-By: Claude Opus 5
Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo
---
content/talks/wasm-research-day-2026.md | 80 ++++++++++++++++++-------
sass/_talk.scss | 15 ++---
2 files changed, 67 insertions(+), 28 deletions(-)
diff --git a/content/talks/wasm-research-day-2026.md b/content/talks/wasm-research-day-2026.md
index c865349..a44799e 100644
--- a/content/talks/wasm-research-day-2026.md
+++ b/content/talks/wasm-research-day-2026.md
@@ -105,6 +105,11 @@ spi · timer · uart · dma
firmwareAct II · the car
+and the FSM proves the absence, rather than relying on it
+ + +Act II · the car
+fn p2_cannot_un_start() {
let w = Iwdg { phase: Running, .. };
if let Ok(n) = refresh(w) { assert_eq!(n.phase, Running); }
// no escape from Running:
assert!(unlock(w).is_err());
}
- The contract itself cannot express the one transition the - proof forbids. That is the argument for putting the seam in a type - system rather than in a comment.
+The contract cannot express the one transition the proof + forbids. That is the argument for putting a seam in a type system rather + than in a comment.
on silicon — same import, answered by the bus
+Act III · the tires
+same import, answered by the bus
#[no_mangle] extern "C" fn read32(addr: u32) -> u32 {
unsafe { core::ptr::read_volatile(addr as *const u32) }
}
- That substitution is the entire thesis.
+Nothing else about the component changes. + That substitution is the entire thesis.
Keyed to values because an optimizer renumbers everything. Key a fact - to an instruction index and the next pass deletes three above it — the fact - is still true of something, and now asserted about something else. A fact whose - value did not survive is dropped, never re-pointed.
+ + +Act IV · the factory
+An optimizer renumbers everything. Key a fact to an instruction index, + and the next pass deletes three instructions above it.
+The fact is still true of something — and is now asserted about something + else. A downstream consumer then reasons about the wrong operand, with a valid + module and a machine-checked implication.
+So a fact whose value did not survive the pipeline is + dropped, never re-pointed. An absent fact costs + performance; a mis-keyed one costs correctness.
+Act IV · the factory
+Honest scope: the emitter, schema and wire format are done - and byte-verified against the consumer. The source that would populate - this at volume is not wired — 232→104 is what the channel does when it - carries a fact, not evidence that we produce many yet.
+Honest scope: emitter, schema and wire format are done and + byte-verified against the consumer. The source that would populate this + at volume is not wired — so that is what the channel does when it carries a + fact, not evidence that we produce many yet.
add r0, #476
bx lr — the whole function
+Act IV · the factory
+Soundness-gated: the bench asserts the elided form equals the native one across the proven range. LLVM never had the bound.
@@ -451,13 +485,17 @@ bx lr — the whole functiongpio 502 → 1196 B spi 454 → 1244 B
timer 204 → 828 B wdg 638 → 1726 B
.data / .bss, all of them 0 → 0
- and the response, measured for this talk
+Act V · what is missing
+measured for this talk
wdg, canonical glue on a growing allocator 1746 B
backed by a bounded arena instead 1428 B −318
- So we patched the bindings generator rather than absorbing the cost:
- cabi_realloc delegates to an embedder arena that traps instead of
- growing. 29% of the overhead back — and the drivers do
- not use it yet.
cabi_realloc now delegates to an embedder arena that traps rather
+ than growing. 29% of the overhead back — and the
+ drivers do not use it yet.
The bias, declared: I work in automotive, so to me - everything is a car.
+ everything is a car. The substrate is not — next slide. + + +Act I · the inversion
+Same substrate, a different vehicle. The flight stack and a building-supervision
+ system are built from the same no_std stream-transformer
+ crates, the same WIT interface packages, the same component rules, the same
+ traceability gate.
Stated at the same resolution as the claim: neither + has flown, and neither has shipped to a home. The flight dossier says so in + its own words — "no claim of flight is made anywhere." This is the + outlook the substrate is aimed at, not a result.