feat(lg): split debug info from emitted bytecode - #624
Conversation
|
So, this is technically fine. But from a high level, I have concerns. I think a better high level strategy would be to have the symbolmap put in some sort of external file that could be loaded, included a on-demand wasi-filesystem for the wasm side of things, or fetchable from our distribution channels. Stripped binaries/cores need some way of re-inflating their sources so you can get proper stack traces when errors happen. So either we preserve the sourcemap somewhere, and provide a way of fixing stack-traces after the fact, or we figure out a more compact representation (like the zstandard compression you introduced, or storing interned symbols in a prefix or suffix trie based table) We could potentially make the stack traces more like https://github.com/theseion/Fuel (from smalltalk). |
nnunley
left a comment
There was a problem hiding this comment.
I'd suggest you create a sister option to generate a sourcemap connected to the stripped .lgb file. My broader concern with this is that you end up with a non-debuggable binary that will fail with no way of reconciling error tracebacks from stack traces.
|
Yeah I appreciate the ideas and direction you shared, that's probably the right way to go. I explored some of those options but haven't got to publishing yet. 100% valid concern. |
Adds bytecode.StripDebug (decode, drop per-chunk source maps and local-variable tables, clear FlagLocalVars, re-encode) and a -strip flag applying it to -c and -b outputs. Execution is unaffected; runtime errors lose source locations. Saves ~16-20% of bundle bytes (fib .lgb 753 -> 634; the core bundle measures -19.8% under the same strip). Scope: strips the emitted program bytecode only — the core bundle embedded in the lg base binary is fixed when lg itself is built. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
StripDebug's Decode→Encode path (and EncodeCompilation generally) resolved TagFunc chunk indices by code content only, collapsing distinct chunks that share a body onto the first hit. Pass ModuleBuilder's live chunk map into encode, rebuild StripDebug via DecodeToExecUnit, and cover the MaxStack collision with a regression test. Co-authored-by: Cursor <cursoragent@cursor.com>
Pointer-accurate TagFunc bindings change the emitted bytes slightly (264072 → 264081). check-generated is green. Co-authored-by: Cursor <cursoragent@cursor.com>
a3c4333 to
65a67a8
Compare
|
@nnunley I addressed the debuggability concern in 65a67a8.
This does not add network/WASI fetching yet, but the versioned companion format and explicit filesystem path provide the hook for that follow-up. I added end-to-end coverage for restored tracebacks, companion-free execution, mismatch rejection, custom paths, bundles, and |
|
You could add a sister wasm bundle that could be pulled in on the wasm side. A more ambitious project on the wasm side would be: https://tc39.es/ecma426/, generating an actual wasm source map. But that might actually require us to compile to wasm, which is a different can of worms, but possible. |
|
I dug into the ECMA-426 direction. It is worth investigating, but it is a different debugging layer from the companion added here. ECMA-426 WebAssembly mappings map byte offsets in the generated Wasm binary back to original sources. Our current Wasm path embeds LGB bytecode as data inside a Go/TinyGo-compiled interpreter. The debug data in this PR maps LGB instruction pointers to A practical follow-up is still the sister asset you suggested: for A genuinely useful ECMA-426 map back to |
|
The split-debug design is right, and the digest-before-parse ordering is the part I most 1. Flag-bit collision with #501, and it is not just textual. This PR is the second of the two by nine days, so it is the one that moves — but "move to I have put the structural fix on #501, since that PR already owns the version/mask machinery 2. 3. The encoder fix is unmentioned and is what makes this CONFLICTING. Commits 4. Measured, since the body quotes numbers: on a Smaller: |
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>
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>
Summary
Adds
lg -stripfor-cand-b, producing a smaller runtime artifact while preserving its source maps and local-variable tables in a digest-bound debug companion.By default:
lg -strip -c app.lgb app.lgwritesapp.lgbandapp.lgb.debug.lg -strip -b app app.lgwritesappandapp.debug.-debug-output <path>selects a separate symbol-store path.Why
Bundle-composition work in #623 showed source maps and local-variable tables are the largest removable bytecode sections, while execution does not consult them. The original draft simply discarded those sections, which made production failures impossible to reconcile with source locations.
This revision treats stripping as split debug information: deployments can ship the smaller artifact while retaining the companion in build artifacts, a symbol store, or an on-demand filesystem.
On the current fib sample:
.lgb: 1,565 bytes.lgb: 1,421 bytes (−9.2%)The companion is archival/debug data; it is not required for execution.
Design
bytecode.SplitDebugemits the stripped LGB plus a versioned companion containing source maps and local-variable tables by chunk index.FlagDebugSplitmarks artifacts whose debug information was externalized.lg app.lgb, standalone bundles, andlg-runtimeautomatically load a co-located.debugcompanion.LG_DEBUG_FILE=/path/to/app.debugloads one from another location;LG_DEBUG_FILE=explicitly disables loading.Scope
This applies to program bytecode emitted by
-cand-b. Stripping the embedded core and extending the mechanism to-w/WASI remain follow-ups.Validation
go test -count=1 ./pkg/bytecodego test -count=1 -run TestStripDebugCompanion ./test/e2ego test -count=1 -run TestRuntimeOnly ./test/e2ego vet ./...lg-runtime