Skip to content

debug: allow supported targets to retain DWARF - #2269

Draft
cpunion wants to merge 2 commits into
xgo-dev:mainfrom
cpunion:codex/debug-artifact-policy-2164-20260801
Draft

debug: allow supported targets to retain DWARF#2269
cpunion wants to merge 2 commits into
xgo-dev:mainfrom
cpunion:codex/debug-artifact-policy-2164-20260801

Conversation

@cpunion

@cpunion cpunion commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Part of #2164.

This is the dependency-tree root change. It keeps target debug-artifact policy explicit without pulling any dependent debugger frontend or runtime changes:

  • model DWARF retention and preserve/omit linker flags as target capabilities;
  • allow supported ELF and Wasm linkers to retain DWARF while preserving the existing default omission policy;
  • keep clang-driver and direct-linker arguments separate;
  • verify that a Cortex-M debug ELF retains DWARF and produces identical flashed .bin bytes to the stripped control.

Review follow-ups are included:

  • share the clang-driver DWARF flag policy between native and Wasm paths;
  • document that Use routes wasm/wasi through the driver while the direct wasm-ld policy remains for target-policy callers;
  • document the fail-closed behavior for unknown linker/target pairs and distinguish driver invocation from direct linker invocation.

Validation on the rebased root (e82e95fbe) and review-fix head (a16528cf8):

  • GOMAXPROCS=4 GOMEMLIMIT=8GiB GOFLAGS=-p=1 go test -vet=off -count=1 -timeout=5m ./internal/build -run '^(TestDwarfLinkerArgs|TestDwarfPreserveLinkerArgs|TestEffectiveOmitDWARF|TestShouldEmitDebugInfo|TestValidateLinkOptions|TestDwarfLinkerArgsSuppressNativeInputDWARF|TestTargetDWARFDoesNotChangeLoadableELF|TestCollectFingerprint|TestTargetTripleMethod)$'
  • GOMAXPROCS=4 GOMEMLIMIT=8GiB GOFLAGS=-p=1 go test -vet=off -count=1 -timeout=5m ./internal/crosscompile

The complete internal/build package retains the known TestRunPrintfWithStdioNobuf long-running test; it is not used as the root validation gate. Dependent PRs will be submitted one layer at a time after this root is reviewed.

CI result on a16528c: 41 checks passed, 1 release check skipped by workflow policy, with no failures or cancellations; Codecov reports 100.00% diff coverage (target 89.59%).

@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@cpunion
cpunion marked this pull request as ready for review August 3, 2026 01:24
@cpunion

cpunion commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

Dependency audit: this root PR has no cpunion predecessor. xgo-dev/main already contains the relevant baseline PRs #2141 (DWARF foundation), #2211 (LLDB launcher/schema), and #2240 (strings/slices runtime views); none are duplicated here. The next cpunion layer (#112) depends on this PR and will not be submitted upstream until this root layer is accepted.

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

This is a clean, well-tested refactor of DWARF debug-info handling. Replacing the boolean AlwaysOmit with a typed DebugInfoCapability + CanRetain() and splitting PreserveLinkFlags/OmitLinkFlags reads clearly, and the change is backed by thorough unit tests plus a new end-to-end Cortex-M ELF test. No correctness regressions found across code-quality, performance, security, and documentation passes.

Confirmed as correct-by-design:

  • The -S vs -Wl,-S split is intentional: the use() wasm/native path links through the clang driver (-Wl,-S), while targetDebugInfoPolicy (via UseTarget, direct ld.lld/wasm-ld) uses bare -S.
  • Removing the unconditional -S from UseTarget ldflags is safe: omission still flows through OmitDWARFByDefaultdwarfLinkerArgs["-S"] for retainable targets, covered by the fixed target w test.
  • No security concerns: flags are hardcoded constants appended as argv elements (no shell/injection surface); test exec.Command calls use fixed tool names guarded by LookPath.

The findings below are all low-severity maintainability/documentation notes, left inline.

return policy
}

func targetDebugInfoPolicy(linker, llvmTarget string) DebugInfoPolicy {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

targetDebugInfoPolicy is only reached via UseTarget, and Use() routes every wasm/wasi target name to use() instead (crosscompile.go:753). All four wasm-ld target JSONs (wasm, wasm-unknown, wasip1, wasip2) start with those prefixes, so the wasm-ld branch here is never exercised by a real build — only by TestTargetDebugInfoPolicy. Not a bug, but worth a one-line comment noting that wasm/wasi are handled by use() and that this branch is currently test-only.

Separately, any unrecognized (linker, llvmTarget) combination silently falls through to DebugInfoUnavailable, which disables DWARF entirely for that target (via !CanRetain() in shouldEmitDebugInfo). Documenting that intentional fail-to-omit default would help the next person adding a target with a different linker.

Comment thread internal/crosscompile/crosscompile.go Outdated
return
}
export.DebugInfo.OmitLinkFlags = []string{"-Wl,-S"}
export.DebugInfo = DebugInfoPolicy{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasm policy literal (PreserveLinkFlags: ["-gdwarf-4"], OmitLinkFlags: ["-Wl,-S"]) is identical to what nativeDebugInfoPolicy produces for darwin/linux. Both represent "link through the clang driver", so consider factoring a shared helper (e.g. driverDebugInfoPolicy()) so the -gdwarf-4 / -Wl,-S pairing lives in one place.

Note this also diverges from the wasm-ld branch in targetDebugInfoPolicy (line 88-90), which uses PreserveLinkFlags: nil / OmitLinkFlags: ["-S"] for the same wasm family — a second reason the flag choice tracks "clang driver vs direct linker invocation", not "linker type".

Comment thread internal/build/link_options.go Outdated
}

// dwarfPreserveLinkerArgs returns compiler-driver options needed while linking
// a debug artifact. Direct linkers such as ld.lld retain input DWARF without a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dichotomy here ("direct linkers such as ld.lld ... without a corresponding flag, while clang-compatible drivers accept -gdwarf-4") is slightly oversimplified: the wasm cross-compile path in crosscompile.go:365-368 assigns -gdwarf-4 even though it links with wasm-ld (a direct linker), because linking there goes through the clang driver. The real determinant is whether the invocation goes through the clang driver, not the linker type. Minor wording tweak would make the comment match all paths.

@cpunion
cpunion marked this pull request as draft August 3, 2026 02:50
@cpunion
cpunion force-pushed the codex/debug-artifact-policy-2164-20260801 branch from c9c9171 to a16528c Compare August 9, 2026 19:21
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

LLGo baseline benchmarks

a16528cf86ec | workflow run | long-term charts

Program measurements

Platform Workload File size vs main Build vs main Run vs main
Linux cprintf 18544 B +0.0% 292.361 ms -5.4% (better) 1.259 ms -8.0% (better)
Linux fmtprintf 1877288 B +0.0% (worse) 3.074 s +0.6% (worse) 3.196 ms -10.9% (better)
Linux println 68096 B +0.0% 293.634 ms -4.2% (better) 1.576 ms -11.7% (better)
macOS cprintf 84672 B +0.0% 532.047 ms +72.1% (worse) 5.905 ms +126.7% (worse)
macOS fmtprintf 1888208 B +0.0% 3.557 s +57.0% (worse) 12.077 ms +13.9% (worse)
macOS println 121200 B +0.0% 429.072 ms +50.3% (worse) 5.455 ms +56.8% (worse)
Core language and compiler benchmarks
Platform Benchmark ns/op vs main
Linux BenchmarkLookupPCRandom 13.300 ns/op +8.4% (worse)
Linux BenchmarkMergeCompilerFlags 151.100 ns/op +3.8% (worse)
Linux BenchmarkMergeLinkerFlags 94.580 ns/op -1.4% (better)
Linux BenchmarkChannelBuffered 33.700 ns/op -7.3% (better)
Linux BenchmarkChannelHandoff 27270 ns/op +16.7% (worse)
Linux BenchmarkDefer 48.570 ns/op +2.3% (worse)
Linux BenchmarkDirectCall 1.557 ns/op -11.4% (better)
Linux BenchmarkGlobalRead 1.557 ns/op -11.4% (better)
Linux BenchmarkGlobalWrite 2.487 ns/op -11.5% (better)
Linux BenchmarkGoroutine 30560 ns/op +0.2% (worse)
Linux BenchmarkInterfaceCall 7.779 ns/op -14.9% (better)
Linux BenchmarkRuntimeGetG 1.867 ns/op -11.7% (better)
macOS BenchmarkLookupPCRandom 15.430 ns/op +44.1% (worse)
macOS BenchmarkMergeCompilerFlags 115.600 ns/op +20.9% (worse)
macOS BenchmarkMergeLinkerFlags 78.520 ns/op +28.2% (worse)
macOS BenchmarkChannelBuffered 25.550 ns/op +24.9% (worse)
macOS BenchmarkChannelHandoff 8101 ns/op +25.5% (worse)
macOS BenchmarkDefer 29.770 ns/op +21.1% (worse)
macOS BenchmarkDirectCall 1.120 ns/op +7.8% (worse)
macOS BenchmarkGlobalRead 1.118 ns/op +10.3% (worse)
macOS BenchmarkGlobalWrite 1.120 ns/op +18.9% (worse)
macOS BenchmarkGoroutine 46989 ns/op +51.5% (worse)
macOS BenchmarkInterfaceCall 4.954 ns/op +21.1% (worse)
macOS BenchmarkRuntimeGetG 2.400 ns/op +27.5% (worse)

Compared only with the latest matching platform in the main series.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant