feat(lgbgen): compress the embedded core bundle (default off) - #502
feat(lgbgen): compress the embedded core bundle (default off)#502mparrett wants to merge 3 commits into
Conversation
|
Tracked under Epic #503 (Footprint & constrained targets). |
|
One note from a multi-agent pass (which found nothing wrong with this PR itself — the flag plumbing and the generated.sums change both verified correct): the bundle write in |
nooga
left a comment
There was a problem hiding this comment.
This PR's own diff looks good — small, correct, and I independently reproduced the numbers in the description exactly (244,131 → 77,039 bytes, ~2x decode cost). Not requesting changes here since there's nothing wrong in these ~65 lines; the actual blocker is upstream in #501 (which this stacks directly on) — its rebase needs to land first, since #501's boot-path code has drifted from what main now has in boot.go/run.go.
One pre-existing item worth picking up while you're in this area, since it's specifically about the write path this PR adds compression to: writeBundle (main.go:591) does os.Create + direct-write-through-Encode, so an encode failure partway leaves a truncated core_compiled.lgb on disk — and that's the file checked into the repo, so a failed go generate would corrupt the committed artifact for the next build. nnunley flagged this back on 07-15 under Epic #503; write-to-temp + rename would close it. Not blocking this PR specifically, just flagging since it's adjacent to what you're touching.
Whether to default this on for the embedded core is the open policy question under #503 — no action needed from me there, that's yours to decide whenever you're ready.
d59e5ea to
7e61f8d
Compare
|
Followed up on the review comments in
@nooga / @nnunley, this should close the write-path concern. The only remaining question is the intentionally deferred default-on policy, including the stripping nuance noted for later discussion. |
7e61f8d to
e016854
Compare
|
Reads clean on top of #501. The atomic write nooga asked for is here — Confirmed the committed core is untouched: Two nits: no directory fsync after the rename (durability, not atomicity), and the size is |
Program bundles are raw serialized bytecode today. A `FlagCompressed` header bit deflates the module body (string table, chunks, consts, NS table) while leaving the magic, version, flags, and capability payload — including the opcode-set signature — in plaintext, so a version/opcode mismatch is still rejected before any inflate. A one-byte codec tag after the header (currently flate) reserves room for zstd without spending a second flag bit. Opt-in at compile time: `lg -c -z` and `lg -b -z`. A bundle without the bit is byte-identical to before; decode auto-detects and inflates transparently, so the full `lg`, `lg-runtime`, and the appended `-b` payload all read compressed bundles with no caller change. The byte-backed decode path (embedded-core / zero-copy source maps) inflates into a resident buffer and re-wraps it, preserving deferred source-map slicing. xsofy main.lg: 1,151,139 -> 394,052 bytes (2.9x). Stacks on #424 (the runtime-only / self-contained-artifact work); compressing the embedded core_compiled.lgb — the universal per-binary win — is the follow-up, kept separate because it touches the boot hot path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Positional iota flag bits with per-version admitted sets close the silent bit-collision failure mode with #624. Also normalize encode version from flags, clarify lg -version, and reject bare -z. Co-authored-by: Cursor <cursoragent@cursor.com>
Teach the core generator to emit a v3 DEFLATE-compressed bundle while keeping the committed core uncompressed by default. The generator now writes through a synced same-directory temporary file and renames only after encode and close succeed, so a failed generation cannot truncate the checked-in core artifact. Fresh Apple M2 measurements on current main: core bundle 297,359 -> 89,776 bytes (3.31x); stripped native binary 13,844,450 -> 13,629,794 bytes (-214,656); warm end-to-end boot 7.9 +/- 0.8 -> 9.8 +/- 1.1 ms. In-process decode averaged 2.23 -> 3.40 ms, +91,824 B/op and +303 allocs/op. Compressed generation is intentionally off the boot path but is more expensive: core encode averaged 1.35 -> 24.37 ms and 344,159 -> 2,220,688 B/op. Paired encode/decode benchmarks keep this tradeoff reproducible without committing a compressed core. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
e016854 to
e0d8ad2
Compare

Stacked on #501 (base branch
feat/lgb-compression).What it does
Adds
lgbgen --compressfor emitting a compressed embedded-core bundle. The flag is off by default: the committed core remains uncompressed unless the maintainer explicitly chooses the size/startup tradeoff under #503.The bundle is written atomically through a same-directory temporary file. Encoding, sync, chmod, and close must all succeed before rename, so a failed generation leaves the existing checked-in
core_compiled.lgbintact.Fresh measurements
Measured on an Apple M2 after rebasing onto the reviewed #501 format:
.lgblgbinaryCompression primarily benefits larger bundles; for smaller programs, the framing/codec overhead can consume or exceed the byte savings. The committed core is large enough to show a clear size win, but the startup regression makes default-on a policy decision rather than a mechanical one.
Encode cost for the core rose from about 1.35 ms to 24.37 ms and from 344,159 B/op to 2,220,688 B/op. That cost is paid during generation, not process startup.
Validation
pkg/bytecode.go test ./...passes locally.Policy
Keep compression opt-in for now. The default-on decision remains under #503, including the stripping nuance and any target-specific defaults.