Skip to content

debug: preserve O0 variable locations - #2235

Open
cpunion wants to merge 2 commits into
xgo-dev:mainfrom
cpunion:codex/linux-amd64-dwarf-locations-20260730
Open

debug: preserve O0 variable locations#2235
cpunion wants to merge 2 commits into
xgo-dev:mainfrom
cpunion:codex/linux-amd64-dwarf-locations-20260730

Conversation

@cpunion

@cpunion cpunion commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Fixes #2206.

Problem

With -O0 -ldflags=-w=false on Linux/amd64, LLDB could list variables but fail to read scalar parameters at the first body breakpoint. An updated local aggregate could also show a stale value even when reading the same object through a pointer showed the new value.

Fix

  • Give non-address-backed parameters stable O0 homes and update those homes at SSA debug assignments. The debug-only stores have no source location, so they do not add breakpoint or stepping sites.
  • Keep address-backed variables attached to their real storage with dbg.declare; do not replace them with aggregate value snapshots.
  • Preserve the existing dbg.value path for optimized builds.

Validation

  • macOS/arm64, Apple LLDB 1703: 215/215 LLDB assertions passed, including marker/fallback checks.
  • Linux/amd64, LLDB 19: 215/215 LLDB assertions passed, including marker/fallback checks.
  • ssa full coverage: 93.0%; all new parameter-home helpers are covered 100%.
  • cl full coverage: 97.4%; focused final O0/O2 metadata tests pass on both platforms.
  • Linux/amd64 O2 fixture builds and runs successfully.

@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: preserve O0 variable locations

The core mechanism is sound. At O0, diParam allocates a stable entry-block "home" and emits llvm.dbg.declare against it; later DebugRefs refresh that home via a location-less DIStore, so the debugger reads a live value from a fixed slot. The location save/clear/restore in withoutDebugLocation, entry-block dominance of the home alloca, the nil-map guards, and the debugAllocObjects/debugStableParams double-handling exclusion all check out. No blocking issues; findings below are minor/robustness.

Minor

  • ssa/di.go:673 — dead Load in doConstructDebugAddr. doConstructDebugAddr always emits dbgVal = b.Load(dbgPtr), but its only callers (DIValue at ssa/di.go:696 and now diParam via constructDebugAddr) discard dbgVal and the hardcoded exists=false. This PR extends the alloca/store/Load pattern to every parameter home, so an unused Load is now emitted per param in O0 IR — pure overhead that isn't cleaned up at O0. Consider dropping the Load (and the unused dbgVal/exists return values) since no caller uses them. Debug-build only.

  • cmd/internal/lldb/llgo_plugin.py — unguarded runtime state / value parsing. print_all_variables and print_go_expression walk target → process → thread → frame after _require_supported_target with no frame.IsValid()/process-state check, so invoking llgo vars / llgo print with a loaded-but-not-launched target yields empty/garbage output instead of a clear "no running process" message. Several int(x.GetValue(), 16) calls in format_slice / get_indexed_value / format_string, and the parts[i+1] / int(part[1:-1]) indexing in evaluate_expression, are unguarded against None/non-hex values and malformed tokens, which raise uncaught TypeError/ValueError/IndexError and abort the command. Low severity (interactive input), but worth hardening.

  • cmd/internal/lldb/lldb.go — LLDB version regex on Apple toolchains. parseLLDBMajor captures the first integer from the --version string against minimumLLDBVersion = 18. On macOS, Apple LLDB reports a build number (e.g. lldb-1700.x), so the check passes only coincidentally rather than validating the underlying LLVM major. Works today; consider special-casing or documenting the Apple lldb-<build> format.

Verified as non-issues: the stable.value/block short-circuit and the p.bvals[iv] missing→return path in debugRef match the existing DIValue semantics; debugStableParams is seeded only when DIParamWithHome returns a non-nil home (O0 only); the moved runtest.sh now passes LLDB commands via an argv array instead of eval, closing the earlier injection pattern; comments in internal/debuginfo/builder.go and the lldbtest/README.md are accurate after the move.

Comment thread cmd/llgo/lldbtest/runtest.sh Outdated
@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.82759% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cl/compile.go 92.30% 1 Missing and 1 partial ⚠️
ssa/di.go 96.29% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@cpunion

cpunion commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the #2235-specific review finding in 0d38ab340: constructDebugAddr now returns only the address, the unused O0 Load is gone, and TestDebugParameterHomes asserts that parameter homes emit stores without a load.

Validation after the change:

  • macOS/arm64 LLDB: 215/215 assertions plus marker/fallback checks
  • Linux/amd64: focused TestDebugParameterHomes pass
  • ssa coverage: 93.0%; the changed helper entry points are covered

The runtime-state/value-parsing and Apple version-detection notes concern the parent #2211 launcher/plugin changes, not this compiler-location patch. I am keeping them out of #2235 so this dependent PR stays focused.

@cpunion
cpunion force-pushed the codex/linux-amd64-dwarf-locations-20260730 branch from 0d38ab3 to 919cea3 Compare July 30, 2026 21:12
@cpunion cpunion changed the title [Based on #2211] debug: preserve O0 variable locations debug: preserve O0 variable locations Jul 30, 2026
@github-actions

Copy link
Copy Markdown

LLGo baseline benchmarks

919cea360d33 | workflow run | long-term charts

Program measurements

Platform Workload File size vs main Build vs main Run vs main
Linux cprintf 18544 B new 356.714 ms new 1.266 ms new
Linux fmtprintf 2213712 B new 3.328 s new 2.417 ms new
Linux println 72656 B new 350.513 ms new 1.583 ms new
macOS cprintf 84672 B new 402.307 ms new 2.769 ms new
macOS fmtprintf 2346112 B new 4.252 s new 26.201 ms new
macOS println 127040 B new 341.848 ms new 3.633 ms new
Core language and compiler benchmarks
Platform Benchmark ns/op vs main
Linux BenchmarkLookupPCRandom 13.410 ns/op new
Linux BenchmarkMergeCompilerFlags 150.200 ns/op new
Linux BenchmarkMergeLinkerFlags 97.740 ns/op new
Linux BenchmarkChannelBuffered 34.250 ns/op new
Linux BenchmarkChannelHandoff 26391 ns/op new
Linux BenchmarkDefer 55.320 ns/op new
Linux BenchmarkDirectCall 1.556 ns/op new
Linux BenchmarkGlobalRead 1.557 ns/op new
Linux BenchmarkGlobalWrite 2.487 ns/op new
Linux BenchmarkGoroutine 44568 ns/op new
Linux BenchmarkInterfaceCall 7.798 ns/op new
Linux BenchmarkRuntimeGetG 4.984 ns/op new
macOS BenchmarkLookupPCRandom 11.220 ns/op new
macOS BenchmarkMergeCompilerFlags 118.200 ns/op new
macOS BenchmarkMergeLinkerFlags 66.050 ns/op new
macOS BenchmarkChannelBuffered 30.920 ns/op new
macOS BenchmarkChannelHandoff 6957 ns/op new
macOS BenchmarkDefer 43.320 ns/op new
macOS BenchmarkDirectCall 1.176 ns/op new
macOS BenchmarkGlobalRead 1.225 ns/op new
macOS BenchmarkGlobalWrite 1.098 ns/op new
macOS BenchmarkGoroutine 55035 ns/op new
macOS BenchmarkInterfaceCall 4.857 ns/op new
macOS BenchmarkRuntimeGetG 2.635 ns/op new

No main baseline exists yet; all metrics are marked new.

Warning

  • The rendered benchmark data could not be pushed.

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.

debug: preserve variable locations on Linux/amd64

1 participant