Activation-stack backtrace; trace userland + test-runner integration#176
Merged
Conversation
emit.rs becomes the single owner of linear-memory byte layout. Reserve a 512-byte region at the bottom of memory ([0, RING_BYTES)) for the trace buffer; the literal data pool now starts at RING_BYTES instead of 0. Add a compile-time assert that the data pool cannot grow into the host-IO scratch window, turning a previously silent collision into a hard error.
Add rt/trace.wat: a fixed-size ring of recent user-fn call sites in linear memory. Because Fink compiles every call to a tail call, there is no native wasm stack to walk; this ring is the portable substitute, in linear memory so a host can read it even after a hard trap, on any runtime. trace_push(module_id, cps_id) records a call site and advances the ring index modulo trace_len. Lowering emits it at every user-function call site (the Callable::Val apply_3 arm), before dispatch. Continuation returns and builtin/runtime calls are not instrumented. Both interop WATs import trace_push to keep rt/trace.wat in the link and the func alive. Snapshots re-blessed for the added trace_push lines.
- register_module/get_module_url: a growable module_id->url GC array in rt/modules.wat, populated by each fink_module self-registering its (id, url) at entry; Fn3-callable get_module_url resolves a trace frame's module id back to a source url. - fix: module_id was always 0 during lowering. compile_package set frag.module_id AFTER lower() returned, too late for the constants trace_push/register_module bake in at emit time. Now threaded into lower() and set before any instrumentation is emitted. - trace_push only at userland (source Apply) call sites, gated by is_source_apply; desugar-synthesized applies (pipe, partial app) are no longer traced. - get_trace Fn3 reader over the trace buffer. - snapshots re-blessed for the register_module + trace_push changes; removed a stray pre-existing base64 fragment in test_records.fnk.
Replace the push-only recency ring with a real backtrace: a bounded
stack of userland function activations.
- rt/trace.wat: bounded activation stack (window of 64 16-byte frames
{fn_mid, fn_cid, call_mid, call_cid}). trace_push(mid,cid) on fn entry,
trace_mark(mid,cid) at userland call sites (updates the top frame's
current call site), trace_pop(mid,cid) on return-cont invocation.
read_trace walks newest-first (innermost call site at index 0).
- lowering: push at userland fn-body entry, mark at source-Apply call
sites, pop at App(ContRef(Ret)). A CpsFunction is a real activation
(pushes its own frame); a CpsClosure is a lifted continuation (no push,
its ret-cont pops the enclosing fn) - bundled as TraceFrame.
- get_loc(mid,cid) -> source line: host_resolve_loc host import in the
wasmtime runner resolves a frame to a line via the debug marks; Fn3
wrapper in rt/trace.wat. Makes traces readable as url:line.
- emit.rs: TRACE_BYTES region sized to 64*16=1024 for the wider frames.
- snapshots re-blessed for the push/mark/pop instrumentation.
Synth match-block functions (m_0/mp_N) are classified CpsFunction but are not user-authored, so pushing trace frames for them injected 2-3 :0 frames between a match call site and the match body, eating the backtrace window and hiding the real frame. Gate the push on a new is_source_fn check (origin resolves to NodeKind::Fn), mirroring the existing is_source_apply gate; synth CpsFunctions inherit the enclosing frame like a CpsClosure. Re-bless the match/recursion/ set-destructure snapshots whose synth trace_push lines now drop.
rt/trace.wat is always linked and unconditionally imports env.host_resolve_loc; the wasm+js host did not provide it, so every module failed to instantiate under Node with a LinkError. Stub it to return 0 (the import's documented "unknown" sentinel) like the host's other unsupported imports. Source-line resolution in JS needs the compiled debug-mark map shipped into the artifact -- a follow-up.
Add std/trace.fnk (get_trace/get_module_url/get_loc wrappers over the runtime trace buffer) and std/trace.test.fnk. Wire trace.test.fnk into std/all.test.fnk, and integrate backtraces into the fink-native test runner: equals/fail now render a Traceback from get_trace so a failing assertion shows its source call chain.
|
📦 This PR will release v0.85.0 (minor) when merged. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a portable in-band activation-stack backtrace for fink, exposed as userland
std/trace.fnkwrappers and integrated into the fink-native test runner so a failing assertion shows its source call chain.WasmGC universal tail calls (
return_call) collapse the native wasm stack, soWasmBacktraceonly ever shows one frame. The runtime maintains its own bounded activation-stack ring in linear memory: push on real userland fn entry, pop on return-continuation, mark the call site into the top frame at each user-fn call.get_locresolves(module_id, cps_id)to a source line via the compiled debug marks (host import).Notable fixes in this branch
m_0/mp_N) are classifiedCpsFunctionbut are not user-authored. Pushing frames for them injected 2-3:0frames between a match call site and the match body, eating the backtrace window and hiding the real frame. Now gated onis_source_fn(origin resolves toNodeKind::Fn), mirroring the existingis_source_applygate.host_resolve_locstub.rt/trace.watis always linked and unconditionally importsenv.host_resolve_loc; the wasm+js host did not provide it, causing aLinkErrorunder Node. Stubbed to return 0 (the import's "unknown" sentinel).Testing
Full suite green: 1333 lib + 42 CLI + 1 interop_js, 0 failed, 3 ignored.
get_trace/get_module_urlbacktrace tests pass; the fink-native suite runstrace.test.fnkvia the committed gate.Follow-ups
wasm+jsartifact so the JS host resolves real source lines instead of 0.:0mark-coverage for some cps_ids; a per-writeio-frame balance edge case.