runtime,cl: Go-style panic tracebacks and exact log/slog/testing locations, with gc-verified acceptance suite#2023
Closed
cpunion wants to merge 60 commits into
Closed
runtime,cl: Go-style panic tracebacks and exact log/slog/testing locations, with gc-verified acceptance suite#2023cpunion wants to merge 60 commits into
cpunion wants to merge 60 commits into
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
ab82a5d to
b8f8ff3
Compare
This was referenced Jul 4, 2026
e6a4518 to
5d7c3f0
Compare
5d7c3f0 to
5e895e7
Compare
This was referenced Jul 4, 2026
Open
Open
cpunion
added a commit
to cpunion/llgo
that referenced
this pull request
Jul 6, 2026
…l, 75m job) The macos coverage job's test step already ran at ~44-45m on the xgo-dev#2023 base (job wall 50:56); the recover-ownership tests push it past the 45m internal go-test alarm — killed at 45m with ~10 minutes of suites left. Not a performance regression: local full ssa+cl on the same tree run in ~10 minutes; the mac CI runner is ~5x slower on llgo-building tests.
This was referenced Jul 6, 2026
First stage of doc/design/pclntab-linkphase.md: parse a linked binary's funcinfo entry/stub sections (Mach-O and ELF), deduplicate LTO inline copies against the symbol table's text ranges, sort with a Go-style sentinel, and build findfunctab through internal/pclntab — the faithful port that has been waiting for exactly this caller. Read-only: prints what the P2 build integration would write back. Measured on the 576-target multipkg binaries: - non-LTO: 9319 records -> ftab 3161 + 207 buckets; lookup self-check 3160/3160; site sections 149KB -> 29KB (5.1x) - LTO: 15371 entry records -> 13857 inline copies dropped, 4144 kept; self-check 3045/3045; 299KB -> 28.5KB (10.5x) Findings for P2: on-disk Mach-O pointer slots hold dyld chained-fixup encodings (low 36 bits are the target; decoded here; the write-back design stores anchor-relative offsets and avoids pointers entirely), and some non-LTO stub symbols are absent from the symbol table (records conservatively dropped; needs tightening). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…adoption
pclnpost -write rewrites the entry-site section in place with the
prebuilt table (header + ftab {entryOff,funcIndex} + runtime-layout
findfunctab buckets), resolving funcinfo indexes through the binary's
symbol-index section, and voids the stub section (its records are
merged into the table). ASLR is handled by anchoring on the section's
own link-time address; entries are normalized to true symbol starts,
which retires the entry-PC slack on this path. macOS re-signs with an
ad-hoc codesign after rewriting.
The runtime adopts the table zero-copy when the magic header validates:
lookups binary-search the on-disk ftab directly through the shared
bucket index, nothing is materialized on first use (the funcIndex ->
entry map is built lazily and only for the pcline initializer), and the
cold scan/dladdr path is skipped since adoption is cheap. First-use
construction remains the fallback whenever the header is absent.
Linux end-to-end: entries=prebuilt, FuncForPC/FileLine correct,
first-FuncForPC 110µs (materializing) -> 6-8µs (zero-copy); 13ms on the
original macOS baseline. Known gap: on macOS the on-disk rewrite is
corrupted at load time because dyld still walks the stale chained-fixup
chain over the section; fix (unlinking the section's nodes from the
page chains in LC_DYLD_CHAINED_FIXUPS) is identified and next.
Non-prebuilt paths verified regression-free: cl + test/go suites pass,
smoke behavior unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every llgo-linked executable (linux/darwin, sites enabled) now gets the prebuilt ftab/findfunctab automatically: internal/build runs internal/pclnpost.Rewrite after linkMainPkg, and any failure degrades silently to the first-use construction fallback. Moves the tool core into internal/pclnpost and hardens it: - Canonical-record detection by FNV: a record survives when its anchor's owning symbol hashes to the record's symbolID (or is the __llgo_stub. wrapper of it). The previous one-per-symbolID rule wrongly collapsed a function with its stub — they share the target's symbolID by design — which broke exact-entry lookups (caught by TestRuntimeLineInfoAndStack on Linux). LTO inline copies are now identified exactly: 8.4k/9.5k copies removed in the LTO probes. - Mach-O chained-fixups surgery: unlink the rewritten sections' pointer slots from the dyld page chains (repointing predecessors' next links and page_start entries) so dyld neither rebases slots inside the new table nor skips unrelated fixups after the zeroed stub section, then re-sign ad hoc. Without this the table was corrupted at load. - LTO-safe metadata location: the entry section carries a meta record whose relocations hold the addresses of the symbol-index pointer and count globals; LTO internalization strips those names from the symbol table but relocations always resolve. Runtime skips the meta rows (pc==0 / symbolID==0). - Idempotence guard (already-rewritten binaries are left alone). Runtime fixes that surfaced during validation: - materializePrebuiltEntries is now two-phase so concurrent losers wait for the winner's store instead of reading a nil entries slice. - pcLineFrameForPC rejects nearest-below sites whose entry is unresolved when the caller knows the function entry, instead of leaking a neighboring function's file/line. Validation: macOS cl (full) + test/go + LLDB 194/194; Linux test/go TestRuntime suite; probes on both platforms report entries=prebuilt with first-FuncForPC at 7-21µs (Linux) from 13ms on the original baseline, and LTO builds drop 8-9.5k inline copies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…table On Mach-O, pointer slots that name exported functions — every __llgo_stub.* wrapper and any exported Go function — are emitted as chained-fixup BIND nodes, not rebases. The rewriter only decoded rebase nodes, so all stub records (and some entry records) were dropped as unowned and never reached the prebuilt ftab; FuncForPC on function values silently fell back to dladdr (~6µs per fresh pc on darwin). - Parse the LC_DYLD_CHAINED_FIXUPS imports table and resolve bind ordinals to their in-image definitions. - Match canonical owners against the record symbolID with underscore normalization (debug/macho's suffix-shared string table can surface one mangling underscore more or less than the source-level name). - Splice the prebuilt header's base slot back into the fixup chain as a live rebase node: dyld writes the slid text base at load, so the runtime reads a ready runtime PC with no slide arithmetic (non-PIE ELF link-time values already equal runtime addresses). - LLGO_PCLNPOST=0 escape hatch keeps first-use construction. Fresh-pc FuncForPC slow path: darwin 6-8µs -> 1.2-1.7µs, linux 6.8µs -> 0.5µs; first-in-process lookup: darwin ~32µs -> ~14µs, linux ~6.8µs -> ~4µs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pure-compute probes (recursive fib, JSON round-trip, sort.Ints, map churn) with no runtime introspection, so one harness run covers both the introspection extremes and what the funcinfo machinery costs code that never asks for it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Go's pclntab pages are touched by its own runtime (traceback, GC) long before user code queries it, so its first FuncForPC never pays page-in. Mirror that: when the prebuilt table is present, init adopts it (zero-copy, sub-µs), touches the pages the lookup path reads (blob, funcinfo records, string offsets, strings), runs one synthetic lookup to warm the code paths, and write-warms the FuncForPC cache pages. First-in-process FuncForPC: darwin ~17µs -> ~2.8µs, linux ~6.6µs -> ~1.0µs. Startup cost is page-count-bound (tens of µs on stdlib-sized tables, invisible next to ~3ms process startup; hello-world medians unchanged). Non-prebuilt binaries stay fully lazy: first-use construction allocates, which has no place in init, and programs that never introspect pay nothing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
-depths generates deep_<N> scenarios at configurable call depths; -bigsizes generates bigfunc scenarios (funcs x statements) whose large bodies stress statement-level pcline density, mid-function pc symbolization, and ordinary performance of big method bodies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Blob overflow: function-value stubs can double the row count, and at ~9k functions the prebuilt blob no longer fit the entry section, so the rewrite silently fell back to first-use construction (cold.FirstFuncForPC 96x96 non-LTO: 2.4ms). On overflow, retry with function entries only — stub pcs degrade to dladdr, real entries keep the prebuilt table. - FuncForPC cache thrash: the set-associative pc cache holds 4k entries; batch workloads over 9k+ distinct functions evicted constantly and paid the string-materializing slow path on every call (multipkg.FuncForPCMany 96x96: 8-11ms vs Go 172µs). Add a per-ftab-row *Func cache for exact-entry lookups, so batch lookups are O(binary search) after the first pass at any scale. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…erflow
Function-value stubs can push the row count past what the entry section
holds (~9k functions with taken addresses). Instead of dropping stub
rows, write the full blob into the (larger) stub section and leave a
32-byte redirect header ("LLGOFTB2" + a live-relocation pointer) in the
entry section; the runtime follows it and adopts the same zero-copy
view. Function-value lookups keep the prebuilt table at any scale
instead of degrading to dladdr.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
funcForPCSlow treated any unaligned pc as a shadow-stack synthetic marker. arm64 function entries are always 4-aligned so this never fired, but amd64 function and stub entries need not be: an unaligned function-value pc skipped the prebuilt exact-entry path entirely and fell through to nearest-below symbolization, reporting the previous function's name (test/go TestRuntimeLineInfoAndStack on ubuntu CI, "bad function value func: main.renamedPC"). Hoist the prebuilt exact-entry + per-row-cache lookup ahead of the alignment heuristic; a genuine synthetic pc just misses the cheap search and proceeds as before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The overflow fallback dropped stub rows to fit the entry section. That leaves pc ranges the table claims to cover but does not: a function value whose stub falls in a gap resolves nearest-below to the previous function and silently returns the wrong name — exactly what ubuntu CI caught (amd64 --icf=safe layouts overflow by a few hundred bytes, and non-PIE ELF dladdr cannot rescue). If the blob fits neither the entry section nor the (larger) stub section, skip the rewrite entirely: first-use construction is slower but covers every record. Reproduced and verified on linux/amd64 (qemu): the stub pc had no exact row and nearest-below returned the neighbouring function's name. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rgery Fabricated fixtures make the IO paths testable in-process: a minimal ELF exercises load/Rewrite end to end (in-place, stub-section spill, and the overflow fallback that must leave the binary untouched), and a synthetic Mach-O image drives the chained-fixup chain surgery (remove+splice, empty-page insert, unconsumed-insert error). Package coverage 16% -> 69%. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A fabricated Mach-O (segments, sections, symtab, chained-fixup imports and an empty page chain) drives load, bind-target resolution, record decoding and both Rewrite outcomes (in-place and stub-section spill) end to end. codesign now runs only when the input carries LC_CODE_SIGNATURE: real lld executables always do, unsigned inputs need no signature and codesign rejects them. Also cover asmQuoteELFSymbol, the empty-table initializers and the Rewrite error paths. Package coverage: pclnpost 69% -> 86%. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every Go function on supported targets keeps the frame-pointer chain
("frame-pointer"="non-leaf", gated by Program.NeedsFramePointer to
linux/darwin — on embedded targets the unwinder does not exist and the
layout change perturbed the conservative GC on ESP32-C3). runtime.Caller,
Callers, CallersFrames, Stack and the unrecovered-panic dump walk
[fp]/[fp+w] directly and symbolize through the prebuilt ftab and pcline
tables:
- Return addresses resolve at pc-1 (Go's convention); statement labels
can land exactly on a return address, so raw-pc nearest-below reported
the following line. The convention holds with or without the prebuilt
table (text bounds fall back to the first-use frame table — link-phase
overflow layouts otherwise silently disabled it, the root cause of the
amd64 CI failures).
- The walk is bounded to the program's own text: libc frames without FP
discipline decode as wild pcs that nearest-below would attribute to
arbitrary functions.
- Methods and anonymous functions are now trackable (methods had no
pcline labels; closures lost their innermost frame to tail-call
optimization), and mid-function aligned pcs merge statement records
instead of returning declaration lines.
- frameSymbol results are memoized per pc (deep re-walks paid a dladdr
per frame: 32-frame walks 8µs -> 180ns) and the pcline table is built
during the startup pre-warm (lazily building it inside the first
Caller cost ~200µs at scale).
- Shadow-stack instrumentation is no longer emitted; LLGO_SHADOW_STACK=1
keeps the legacy emitters for one release. Tracked functions retain
noinline, no-tail-call and the data-only pcline records.
- libunwind is gone: the clite stacktrace fallback walks the FP chain
with dladdr names (same output format), and linux binaries no longer
link -lunwind.
Semantics are gc ground truth, verified against go: physical stacks show
every real frame; interface-chain Caller marks land at skip 3 and
closure chains at skip 4 (the old expectations encoded shadow-stack
frame loss). Perf (best-of, mac/linux): hot.Caller0 17/37ns (Go
155/241), deep.Direct512 2.8µs (Go 9.7µs; was 87-95µs), bigfunc.Work
18µs (Go 30µs; was 433µs), binary size unchanged or smaller.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
IR goldens gain the frame-pointer attribute (out.ll files carry no attribute groups and needed no regeneration); the legacy shadow-stack emitter assertions opt into LLGO_SHADOW_STACK; statement-line probes move to gc ground-truth skip counts; NeedsFramePointer target matrix and pclnpost symbolAddr/decodePtr edges covered. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ct log/slog/testing locations An unrecovered panic now prints a Go-style traceback (function names plus file:line per physical frame) through a PanicTraceback hook the public runtime registers; the clite dladdr dump remains the fallback when the FP walk or the tables are unavailable. Caller-frame tracking now applies uniformly: the blanket stdlib exclusion is gone, so the same per-package reaches-runtime.Caller analysis that already covered third-party code tracks log.Output, slog's Logger.log and testing's decorate chains (their thin wrappers were inlined, making fixed Caller depths count past them — log.Lshortfile printed "???:1"). Call sites into caller-pc-consuming functions of other packages get a statement anchor so the attributed frame reports the exact line. The collector also picks up named-type methods declared by the package itself — a type used only concretely never enters RuntimeTypes, which is exactly how slog.(*Logger).Info escaped tracking. hello-world size cost: +368 bytes (the traceback printer). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four scenarios, every expectation verified against gc: unrecovered panic tracebacks; log.Lshortfile and slog AddSource (text+JSON, package funcs and logger methods); a failing t.Errorf under llgo test; and an introspection grab-bag (goroutine/init/defer callers, FuncForPC names for methods, closures and generics, the errors-with-stack capture idiom). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
a5fa2fc to
37c127c
Compare
cpunion
added a commit
to cpunion/llgo
that referenced
this pull request
Jul 8, 2026
…upport Re-expresses the surviving value of xgo-dev#1892 on the xgo-dev#2023 base (its other ~10k lines were an earlier draft of the funcinfo/pclntab machinery, since superseded by xgo-dev#2012/xgo-dev#2016/xgo-dev#2019): - //go:nointerface methods no longer satisfy interfaces: the pragma is recorded at import (cl/import.go -> Program.SetNoInterfaceMethod) and filtered out of abi uncommon method tables; GlobalDCE metadata takes the filtered selection list. Retires the typeparam/mdempsky/15 goroot xfails (validated go1.26 darwin, ci directive mode). - Type-assertion failure between same-named types from different scopes appends '(types from different scopes)' (gc wording). - MatchesClosure unwraps closure types on both sides, fixing assertions between closure and plain function types. - reflect.(*rtype).Method builds the method func value with the closure layout allocation (fixes reflect method-value identity). - xfail: retire fixedbugs/issue16130, fixedbugs/issue29735 (validated go1.24 + go1.26 darwin), and reword typeparam/mdempsky/16 - the panic value now implements runtime.Error; the message still lacks the source interface type and prints the command-line-arguments package path.
cpunion
added a commit
to cpunion/llgo
that referenced
this pull request
Jul 8, 2026
Re-expresses xgo-dev#1918 on the xgo-dev#2023 base (its remaining ~11k diff lines were the pre-xgo-dev#2012 funcinfo draft, superseded by the stage-5 chain): - recover() only succeeds when called directly by a deferred function (gc semantics): the panic node records the owning Defer frame at rethrow (panicKey/panicNode + GoDeferData), and Recover checks the caller is that frame's direct deferred call. Closure wraps carry StartRecoverFrameAlias/EndRecoverFrame so method-value and closure adapters stay transparent to the ownership check. - Rethrow keeps the xgo-dev#2023 PanicTraceback hook on the unrecovered path. - xfail: retire fixedbugs/issue4066 (2m-timeout entries; now runs in ~2.7s), fixedbugs/issue73916 and issue73916b (go1.26 recover semantics), validated on darwin/arm64 go1.26. Supersedes xgo-dev#1918.
cpunion
added a commit
to cpunion/llgo
that referenced
this pull request
Jul 8, 2026
Re-expresses xgo-dev#1906 on the xgo-dev#2023 base (its remaining ~11k diff lines were the pre-xgo-dev#2012 funcinfo draft, superseded by the stage-5 chain): - cl gains a liveness analysis for stack-allocated objects: allocas whose last use has passed are cleared so bdwgc's conservative stack scan stops keeping dead stack objects (and what they point to) alive; pointer registers are clobbered around the trigger points (llgo_clobber_pointer_regs) and dead stack slots holding the target are zeroed (llgo_clear_stack_ptr, pthread stack-bounds walk). - runtime.SetFinalizer paths (mfinal, runtime_gc, bdwgc binding) hook the cleared-slot machinery so finalizers for dead stack objects run. - xfail: retire deferfin.go, stackobj.go, stackobj3.go, validated on darwin/arm64 go1.24 + go1.26 (stackobj2 already passed). Carries the xgo-dev#2035 shared-GOCACHE commit temporarily (same patch-id, auto-dedups when the chain rebases after xgo-dev#2035 merges). Supersedes xgo-dev#1906.
Collaborator
Author
cpunion
added a commit
to cpunion/llgo
that referenced
this pull request
Jul 8, 2026
…upport Re-expresses the surviving value of xgo-dev#1892 on the xgo-dev#2023 base (its other ~10k lines were an earlier draft of the funcinfo/pclntab machinery, since superseded by xgo-dev#2012/xgo-dev#2016/xgo-dev#2019): - //go:nointerface methods no longer satisfy interfaces: the pragma is recorded at import (cl/import.go -> Program.SetNoInterfaceMethod) and filtered out of abi uncommon method tables; GlobalDCE metadata takes the filtered selection list. Retires the typeparam/mdempsky/15 goroot xfails (validated go1.26 darwin, ci directive mode). - Type-assertion failure between same-named types from different scopes appends '(types from different scopes)' (gc wording). - MatchesClosure unwraps closure types on both sides, fixing assertions between closure and plain function types. - reflect.(*rtype).Method builds the method func value with the closure layout allocation (fixes reflect method-value identity). - xfail: retire fixedbugs/issue16130, fixedbugs/issue29735 (validated go1.24 + go1.26 darwin), and reword typeparam/mdempsky/16 - the panic value now implements runtime.Error; the message still lacks the source interface type and prints the command-line-arguments package path.
cpunion
added a commit
to cpunion/llgo
that referenced
this pull request
Jul 8, 2026
Re-expresses xgo-dev#1918 on the xgo-dev#2023 base (its remaining ~11k diff lines were the pre-xgo-dev#2012 funcinfo draft, superseded by the stage-5 chain): - recover() only succeeds when called directly by a deferred function (gc semantics): the panic node records the owning Defer frame at rethrow (panicKey/panicNode + GoDeferData), and Recover checks the caller is that frame's direct deferred call. Closure wraps carry StartRecoverFrameAlias/EndRecoverFrame so method-value and closure adapters stay transparent to the ownership check. - Rethrow keeps the xgo-dev#2023 PanicTraceback hook on the unrecovered path. - xfail: retire fixedbugs/issue4066 (2m-timeout entries; now runs in ~2.7s), fixedbugs/issue73916 and issue73916b (go1.26 recover semantics), validated on darwin/arm64 go1.26. Supersedes xgo-dev#1918.
cpunion
added a commit
to cpunion/llgo
that referenced
this pull request
Jul 8, 2026
Re-expresses xgo-dev#1906 on the xgo-dev#2023 base (its remaining ~11k diff lines were the pre-xgo-dev#2012 funcinfo draft, superseded by the stage-5 chain): - cl gains a liveness analysis for stack-allocated objects: allocas whose last use has passed are cleared so bdwgc's conservative stack scan stops keeping dead stack objects (and what they point to) alive; pointer registers are clobbered around the trigger points (llgo_clobber_pointer_regs) and dead stack slots holding the target are zeroed (llgo_clear_stack_ptr, pthread stack-bounds walk). - runtime.SetFinalizer paths (mfinal, runtime_gc, bdwgc binding) hook the cleared-slot machinery so finalizers for dead stack objects run. - xfail: retire deferfin.go, stackobj.go, stackobj3.go, validated on darwin/arm64 go1.24 + go1.26 (stackobj2 already passed). Carries the xgo-dev#2035 shared-GOCACHE commit temporarily (same patch-id, auto-dedups when the chain rebases after xgo-dev#2035 merges). Supersedes xgo-dev#1906.
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.
End-user acceptance for the Stage 5 unwinder (#2019): make the caller info an application developer actually sees correct — panic tracebacks, log/slog locations, testing failure lines — and lock it in with a gc-verified acceptance suite.
What the acceptance run exposed (before this PR)
[0x… main+0x4, SP = 0x24](dladdr, one frame, no file:line)log.Lshortfilemain.go:11:???:1:main.go:11:AddSourcesource=…/main.go:12source=:0source=…/main.go:12t.Errorfunderllgo testx_test.go:6:???:1:x_test.go:6:Fixes
Go-style panic tracebacks: unrecovered panics print
goroutine 1 [running]:plusfunction(...)/ indentedfile:lineper physical frame, via aPanicTracebackhook the public runtime registers with the runtime core; the clite dladdr dump remains the fallback when the FP walk or tables are unavailable. Runtime-internal plumbing frames are filtered by package path (gc hides runtime frames the same way).Uniform caller-frame tracking — no package whitelist: the blanket stdlib exclusion is gone. The existing per-package "transitively reaches runtime.Caller" analysis now applies to stdlib exactly as it always did to third-party code, so
log.Output, slog'sLogger.logand testing's decorate chains keep physical frames and fixed Caller depths count correctly. Packages that never read caller pcs still track nothing and pay nothing.Cross-package call-site anchors: a call into a caller-pc-consuming function of another package (the frame a fixed depth attributes) gets a statement anchor, so
log.Println("x")reports the exact line even though the calling function itself never reads caller pcs. Decided by the same analysis (memoized per package), not by name.Collector: directly-called methods: a type used only concretely never enters
RuntimeTypes, so its methods escaped tracking — exactly howslog.(*Logger).Infolost its frame. Methods are now collected from the package's own type declarations (both receiver forms).gc-conformant naming and format: the main package's display name is normalized to
mainregardless of the module name (frame filters in the wild match on themain.prefix; linker symbols keep the package path), closures display as gc's.funcNinstead of$N, and traceback lines carry gc's+0x<pc-entry>offset (value is codegen-specific, format matches).Program-unique frames pinned:
main.mainand packageinitrun once, so noinline is free — they are the bottom frames of nearly every panic traceback and now report exact statement lines.//go:noinlinefunctions already keep their frames and get statement anchors too.Conformance bar (agreed): format and user-code file:line match gc exactly; runtime-internal, patched-stdlib and startup frames may differ (different runtime, accepted).
Cost: hello-world grows 368 bytes (the traceback printer); no IR golden changed;
plain.*code paths are untouched.Acceptance suite (
test/go/caller_acceptance_test.go)Four probes, every expectation verified against gc: panic traceback contents;
log.Lshortfile+ slogAddSource(text and JSON handlers, package functions and logger methods); a failingt.Errorfunderllgo test; and an introspection grab-bag — goroutine/init/defer callers,FuncForPCnames for value/pointer methods, closures and generic instantiations, and the errors-with-stack capture idiom (zap/sentry style).Documented divergences observed while writing the suite:
.funcNform (linker symbols keep$N); the suite accepts both forms for robustness.FuncForPCreports the surviving symbol (gc does not fold); probes use distinct bodies.main.main,init),//go:noinlinefunctions, and every attributed frame in the log/slog/testing/Caller scenarios are exact.Manual playground (
test/_manualtest/, committed)Five directories runnable with both
goandllgofor side-by-side comparison:panic/(traceback shape),logging/(log/slog locations),callers/(Caller ladder, frames, FuncForPC names, errors-with-stack),testfail/(llgo testfailure lines),cexcept/(hardware faults in C called from Go — segv recovery verified on darwin/arm64 + linux/arm64/amd64; known gaps recorded in its README: fault-site stack needs the panic-snapshot follow-up, SIGFPE/SIGBUS handlers and sigaltstack not installed yet). Every scenario also has atest/goregression: the acceptance tests above plusTestCallerAcceptanceModuleMainNaming(module-named main package must still reportmain.*) andTestCallerAcceptanceCFaultRecover(C NULL-store converts to a recoverable Go panic).Validation (both platforms, before every push)
cl(725s),ssa,internal/build,test/goall ok;gentestsproduced no golden diffs._demo/godemo passes plain and an LTO (-lto=full -globaldce) subset — including the reflect demos whose ELF pcline sections dangled in the first CI round (anchors in inlinable functions; fixed by making frame-keeping a tracking criterion rather than a per-call exception).+0x30= return address − function entry for the probe).Based on #2019 (contains #2012 and #2016); rebases as those merge.
🤖 Generated with Claude Code