prototypes/wo-rt-c — single-file C reference of the writeonce runtime layer, zero deps beyond libc + kernel uapi: thread-per-core io_uring event loops (raw syscalls, no liburing), SO_REUSEPORT listeners, one mlock'd mmap arena sharded by address, WAL dual-write with group commit (HTTP ack only after the fsync CQE), boot-time snapshot + WAL replay recovery, and a bench harness with a Go net/http comparison server. Measured on 20 cores: 908k reads/s p99 154us and 643k fsync-acked commits/s p99 177us on 8 shards, vs Go net/http 495k/355k (no durability) on 20 cores. Crash-under-load testing found and fixed an ack-before-fsync race and an fd-reuse ABA hazard in commit-ack parking. docs/plan/exploration/c-runtime — the phased plan (00, exit evidence per phase), the one-address architecture trace (01), and the single-binary end-goal contract (02). justfile carries the demo and bench recipes; .gitignore covers binaries and data dirs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7.5 KiB
02 — The end goal: the writeonce single binary on this runtime environment
Context sources: 00-plan.md (the runtime-environment phases, A–B ✅), 01-architecture.md (the one-address trace), ../../../runtime/wo-language.md ("one binary per project; no runtime to install on the target host"; .wo has "its own lexer, parser, analyzer, and bytecode"), ../../09-concurrency-scaleout.md–12 (the Rust product track this proves out), ../../../runtime/database/02-wo-language.md (catalog + transaction semantics the payload carries).
The end goal, stated once
A developer writes
.wofiles.wo buildemits one static binary. That binary is the runtime environment described in this track — thread-per-core shards, the mlock'd RAM arena, io_uring event loops, WAL dual-write, boot-time recovery — with the application inside it as data and bytecode. Deploy = copy one file to a Linux host. Nothing to install; the only "VM" underneath is the Linux kernel.
This resolves the phrase "runs in this runtime environment": writeonce is the Go model, not the JVM model. The environment is not a process you start and feed programs to — it is a runtime kernel statically linked into every application binary. What wo-rt-c builds phase by phase is that kernel, proven in C, ported to Rust per plans 09–12.
flowchart TB
subgraph BIN["ONE BINARY (output of `wo build`)"]
subgraph APP["application payload — compiled from .wo"]
CAT["catalog<br/>types/classes → slot geometry"]
RT2["route table<br/>service decls → method+path+op"]
BC["logic bytecode<br/>fn methods, triggers, queries"]
UIA["UI assets<br/>SSR templates + manifest + CSS"]
end
subgraph KERN["runtime kernel — this track"]
BOOT["boot loader<br/>size arena from catalog, WAL replay"]
SCHED["shard scheduler<br/>thread-per-core, SO_REUSEPORT"]
LOOP["event loops<br/>epoll → io_uring"]
ENG["engine primitives<br/>arena slots, bitmaps, indexes"]
WAL["commit pipeline<br/>RAM apply → WAL → group fsync → ack"]
HTTP["HTTP + WS"]
end
end
LINUX["Linux kernel — epoll/io_uring, mmap/mlock, signalfd/eventfd, page cache"]
APP -->|"consumed at boot<br/>and at dispatch"| KERN
KERN --> LINUX
The embedding contract
The seam between the two halves is narrow and data-shaped — the compiler emits tables, the kernel consumes them. Five entries:
| Payload artifact | Emitted from | Consumed by | Exists today as |
|---|---|---|---|
| Catalog — per type/class: field layout, slot size, unique keys, shard key | type/class decls (crates/rt/src/compile.rs) |
boot loader: arena geometry (arena_hdr is its miniature); engine: row codecs |
rt::compile::Catalog, built per wo run |
Route table — (method, path pattern, operation, type id) |
service blocks |
HTTP dispatch | rt::server::router |
| Logic bytecode — method/trigger/query programs | fn bodies, on blocks, schema-layer DML |
a bytecode interpreter running inside the owning shard's thread — serial execution = ACID isolation for free | parse-and-discard (13b lands the executor) |
UI assets — SSR templates, manifest, flattened CSS, wo-runtime.js |
##ui triplets (plan 14) |
HTTP static + SSR renderer | design (plans 13d/14) |
| App manifest — name, version, default port/threads | wo.toml |
boot banner, env-var defaults | wo.toml parsing TBD |
Two consequences worth locking:
- The shard key comes from the catalog, not the kernel.
wo-rt-chashes connections (4-tuple) because it has no catalog; the product routes operations by the catalog's shard key (plan 09 decision 5: customer id, author id) — a request landing on any thread sends an in-process message to the owning shard. Phase A'sSO_REUSEPORTspread is the transport layer of that story, not the final routing. - Bytecode runs where the data lives. A
set_pricemethod executes on the shard that owns the product row — the interpreter is invoked from the event loop, runs to completion, commits through the WAL pipeline. No cross-thread data access; cross-shard transactions escalate to 2PC (plan 09e).
Boot sequence of the single binary
What ./myapp does before serving — each step already prototyped or phased:
| # | Step | Proven by |
|---|---|---|
| 1 | Read embedded catalog → compute arena geometry (shards × slot families) | arena_hdr (phase B ✅) |
| 2 | mmap + MAP_POPULATE + mlock the arena (hugepages, 4 K fallback) |
phase B ✅ |
| 3 | Replay per-shard WAL/snapshot from disk into the arena, in parallel | phase E |
| 4 | Spawn WO_THREADS pinned shard threads, each with ring + SO_REUSEPORT listener |
phase A ✅ / phase C |
| 5 | Install route table + bytecode programs per shard | 13b / this contract |
| 6 | Listeners open; signalfd→eventfd shutdown armed | phase A ✅ |
Steps 1–2 and 4–6 exist in wo-rt-c today with the notes store standing in for the catalog. The end state swaps the hard-coded slot_note for catalog-driven slot families — the kernel code does not otherwise change shape.
What runs where — .wo construct → runtime home
.wo construct |
At runtime, inside the binary |
|---|---|
type / class fields |
a slot family in each shard's arena slice |
class fn methods |
bytecode executed serially in the owning shard's thread (POST /api/<t>/:id/<m>) |
service rest … expose |
route-table entries dispatched by the HTTP layer |
on <event> triggers |
bytecode hooked into the commit pipeline, same transaction |
LIVE select / subscribe |
per-shard subscription registry; deltas fan out one message per shard (09d) |
##ui screens |
SSR render + manifest at GET routes; live patches ride the same deltas |
policy rules |
predicate rewrites applied before the engine touches slots |
main { … } |
bytecode run once at boot step 5½, before listeners open |
Division of labor (locked by the C-vs-Rust decision)
wo-rt-c(C) — proves each kernel syscall sequence first: threads/arena (✅), io_uring, WAL, recovery, bench. It will never parse.wo; its notes store is the stand-in payload.crates/rt(Rust) — the product: owns the compiler front-end today (lexer→catalog, Stage 2 shipped) and absorbs each proven kernel sequence per plans 09–12, where ownership makes the shard discipline a compile-time guarantee.- Optional phase G (named in
00-plan.md): splice thewo-dbC++ query engine ontowo-rt-cas an end-to-end C-family demonstrator of this document — valuable as proof, never the product.
Cross-references
00-plan.md— the kernel phases;01-architecture.md— the one-address trace through the same stack.../../../runtime/wo-language.md— the user-facing single-binary promise this document implements.../../13-class-model-live-pricing.md(13b methods),../../14-mvc-ui-implementation.md(UI assets) — the payload-side tracks.../../09-concurrency-scaleout.md— shard-key routing and 2PC the contract defers to.