Summary
LLGo should parse Go build/compiler/linker flags into typed compiler and linker options instead of merely registering selected flags and then silently dropping most of them.
This is an umbrella proposal. The first implementation step is Go-compatible handling of -ldflags -s and -w. Later PRs can add the remaining linker flags and the other Go build/tool flags without repeatedly changing command plumbing or inventing flag-specific forwarding paths.
Motivation and current behavior
Today the flag path is incomplete and inconsistent:
llgo build records the flags registered by PassBuildFlags, but run and cmptest currently register and discard them, while test and install do not register the full set.
-ldflags can be accepted by the command line and passed to package loading, but LLGo does not parse its linker options and they do not affect the final native link.
-gcflags only has partial handling for a few frontend flags and does not implement Go's package-pattern and last-match semantics.
- unsupported flags can therefore appear to succeed while producing a binary with different semantics from
go build.
That is particularly surprising for release builds such as:
llgo build -ldflags='-s -w' ./cmd/app
Go behavior to preserve
The Go build flags -asmflags, -gcflags, -gccgoflags, and -ldflags accept:
[package-pattern=]quoted argument list
They may be repeated. If multiple entries match a package, the last matching entry wins. An entry without a pattern applies to command-line packages. Quotes group an argument but are not shell expansion.
For Go 1.26 cmd/link:
-w omits DWARF.
-s omits the native symbol table and implies -w only when -w was not explicitly set.
- Therefore
-s -w=false requests no native symbol table but retains DWARF where the backend supports that combination.
-s does not remove Go build information, required dynamic symbols, or Go's pclntab. Runtime stack traces, runtime.Caller, and runtime.FuncForPC continue to use pclntab.
References:
Proposed architecture
1. Capture flags consistently
All commands that build or link code (build, install, run, test, and cmptest) should preserve every occurrence in command-line order and pass it to internal/build.Config. go/packages may still receive the subset it needs, but it must not be the storage or parsing layer for LLGo semantics.
2. Parse per-package argument lists once
Introduce a reusable parser with Go-compatible behavior for:
- optional package patterns;
- single and double quoted arguments;
- repeated flags and last-match-wins selection;
- boolean forms such as
-s, -s=true, -s=false, -w, -w=0, and -w=false;
- options whose value may be joined (
-X=pkg.name=value) or separate (-X pkg.name=value).
The final root link gets the options selected for its command-line package. The same representation can later be used for compile and assembly invocations.
3. Translate into typed intent
Do not forward Go linker arguments blindly to clang/lld. Parse them into an LLGo-owned structure, conceptually:
LinkOptions {
StripSymbols bool
DWARF unset | enabled | disabled
Rewrites []XDefinition
External ExternalLinkOptions
...
}
Resolve derived behavior only after parsing all arguments. In particular:
omitDWARF = explicitW if -w was specified
omitDWARF = stripSymbols otherwise
Each object format/backend then maps that intent to supported linker arguments and post-link operations. This avoids leaking ELF flags into Mach-O, PE, wasm, or embedded targets.
4. Report support accurately
Each recognized flag should be classified as:
- implemented with Go-compatible behavior;
- accepted as a documented no-op because Go also treats it as one;
- rejected with a clear
llgo: unsupported linker flag ... diagnostic;
- internal/driver-owned and rejected when supplied directly by a user.
No newly parsed flag should be silently ignored. During migration, flags that LLGo historically accepted but has not implemented may remain compatibility no-ops only when called out explicitly in the implementation issue/PR.
Complete linker-flag support plan
The current Go 1.26 cmd/link surface can be divided into implementation groups:
| Group |
Flags |
LLGo plan |
| Size/debug metadata |
-s, -w, -compressdwarf |
Implement first; preserve pclntab independently |
| Go program metadata |
-X, -buildid, -B |
Reuse LLGo global rewrite support; add reproducible build-ID/UUID mapping |
| Link mode/tool selection |
-linkmode, -extld, -extldflags, -extar, -libgcc, -linkshared, -pluginpath |
Validate against LLGo's clang/lld and build-mode model; translate per target |
| Layout/loader policy |
-D, -E, -H, -I, -L, -R, -T, -r, -funcalign, -randlayout, -bindnow, -aslr, -d |
Add explicit target-capability mappings; reject unsupported combinations |
| Instrumentation/security |
-asan, -msan, -race, -fipso |
Integrate only with matching LLGo runtime/compiler instrumentation |
| Validation/link semantics |
-checklinkname, -strictdups, -pruneweakmap, -k, -g, -f, -e, -h |
Implement at the LLGo IR/object boundary where meaningful; otherwise diagnose unsupported use |
| Diagnostics/profiling |
-V, -v, -c, -dumpdep, -debugnosplit, -debugtextsize, -debugtramp, -benchmark, -benchmarkprofile, -cpuprofile, -memprofile, -memprofilerate, -capturehostobjs |
Map to LLGo phases rather than passing to the native linker |
| Driver-owned/output |
-buildmode, -o, -tmpdir, -importcfg, -installsuffix |
Keep ownership in the LLGo build driver and detect conflicts |
| Go compatibility no-op/deprecated |
-a, -n |
Match Go's linker no-op behavior, distinct from top-level build -a/-n |
The list should be versioned against the Go release used by LLGo CI so a new upstream flag is detected rather than silently falling through.
Other Go flag families
The same parser/dispatch layer should be extended in separate, reviewable PRs:
-gcflags: package selection, -lang, optimization/inlining flags, diagnostics, experiments, and explicit unsupported diagnostics.
-asmflags: Plan 9 assembler options and package selection.
-gccgoflags: either implement for a future gccgo-compatible path or reject clearly; do not pass them to clang by name.
- Common
go build flags: -a, -n, -p, -race, -msan, -asan, -trimpath, -work, -x, -tags, -buildmode, -buildvcs, -compiler, -installsuffix, -pkgdir, -toolexec, module flags, overlay, and PGO. Each needs an LLGo semantic owner and a capability/error policy.
- Low-level
go tool compile compatibility should share the typed compiler options but remain a separate CLI contract from go build.
Initial implementation: -s and -w
The first PR should:
- make
-ldflags reach every build/link command path;
- parse ordered boolean
-s/-w values, including explicit false;
- make
-s imply -w only when -w is unset;
- suppress DWARF production when requested, including when
LLGO_DEBUG_SYMBOLS is enabled;
- preserve LLGo's embedded pclntab/funcinfo and verify that stack names and file/line data still work;
- introduce the backend/post-link hook where additional native symbols can be removed incrementally;
- test parser precedence and observable Mach-O/ELF output.
LLGO_DEBUG_SYMBOLS remains a developer escape hatch, but an explicit command-line -w must take precedence. Existing default output should not gain DWARF merely because -w=false is accepted; producing full default Go-style DWARF is a separate compatibility step.
Relationship to configurable pclntab placement
Pclntab placement is intentionally a separate LLGo option and proposal. -s must retain the default embedded pclntab to match Go. A future -pclntab=embedded|external|none controls whether LLGo runtime symbolization metadata is embedded, loaded from a sidecar, or omitted. The proposals are related in post-link ordering, but neither flag should implicitly rewrite the other.
Related proposal: #2112
Compatibility and rollout
- Preserve current output when none of the new flags is supplied.
- Start with native Mach-O and ELF; add PE/wasm/embedded mappings behind explicit capability checks.
- Perform pclntab post-link rewriting/export before any operation that removes the native symbol table.
- On Darwin, perform final code signing after all binary mutation.
- Include effective link options in build fingerprints/caches.
Acceptance criteria
- Parser unit tests cover quoting, repeated values, package patterns, joined/separate values, boolean spellings, invalid values, and last-match behavior.
-w, -s, -s -w=false, and later matching overrides have documented observable behavior.
-s does not regress panic stack traces, runtime.Caller, runtime.CallersFrames, or runtime.FuncForPC under the default pclntab mode.
- Mach-O and ELF tests inspect DWARF and symbol-table sections instead of relying only on file size.
- All build/link commands use one options path.
- Unsupported combinations fail with actionable diagnostics.
Summary
LLGo should parse Go build/compiler/linker flags into typed compiler and linker options instead of merely registering selected flags and then silently dropping most of them.
This is an umbrella proposal. The first implementation step is Go-compatible handling of
-ldflags-sand-w. Later PRs can add the remaining linker flags and the other Go build/tool flags without repeatedly changing command plumbing or inventing flag-specific forwarding paths.Motivation and current behavior
Today the flag path is incomplete and inconsistent:
llgo buildrecords the flags registered byPassBuildFlags, butrunandcmptestcurrently register and discard them, whiletestandinstalldo not register the full set.-ldflagscan be accepted by the command line and passed to package loading, but LLGo does not parse its linker options and they do not affect the final native link.-gcflagsonly has partial handling for a few frontend flags and does not implement Go's package-pattern and last-match semantics.go build.That is particularly surprising for release builds such as:
Go behavior to preserve
The Go build flags
-asmflags,-gcflags,-gccgoflags, and-ldflagsaccept:They may be repeated. If multiple entries match a package, the last matching entry wins. An entry without a pattern applies to command-line packages. Quotes group an argument but are not shell expansion.
For Go 1.26
cmd/link:-womits DWARF.-somits the native symbol table and implies-wonly when-wwas not explicitly set.-s -w=falserequests no native symbol table but retains DWARF where the backend supports that combination.-sdoes not remove Go build information, required dynamic symbols, or Go's pclntab. Runtime stack traces,runtime.Caller, andruntime.FuncForPCcontinue to use pclntab.References:
Proposed architecture
1. Capture flags consistently
All commands that build or link code (
build,install,run,test, andcmptest) should preserve every occurrence in command-line order and pass it tointernal/build.Config.go/packagesmay still receive the subset it needs, but it must not be the storage or parsing layer for LLGo semantics.2. Parse per-package argument lists once
Introduce a reusable parser with Go-compatible behavior for:
-s,-s=true,-s=false,-w,-w=0, and-w=false;-X=pkg.name=value) or separate (-X pkg.name=value).The final root link gets the options selected for its command-line package. The same representation can later be used for compile and assembly invocations.
3. Translate into typed intent
Do not forward Go linker arguments blindly to clang/lld. Parse them into an LLGo-owned structure, conceptually:
Resolve derived behavior only after parsing all arguments. In particular:
Each object format/backend then maps that intent to supported linker arguments and post-link operations. This avoids leaking ELF flags into Mach-O, PE, wasm, or embedded targets.
4. Report support accurately
Each recognized flag should be classified as:
llgo: unsupported linker flag ...diagnostic;No newly parsed flag should be silently ignored. During migration, flags that LLGo historically accepted but has not implemented may remain compatibility no-ops only when called out explicitly in the implementation issue/PR.
Complete linker-flag support plan
The current Go 1.26
cmd/linksurface can be divided into implementation groups:-s,-w,-compressdwarf-X,-buildid,-B-linkmode,-extld,-extldflags,-extar,-libgcc,-linkshared,-pluginpath-D,-E,-H,-I,-L,-R,-T,-r,-funcalign,-randlayout,-bindnow,-aslr,-d-asan,-msan,-race,-fipso-checklinkname,-strictdups,-pruneweakmap,-k,-g,-f,-e,-h-V,-v,-c,-dumpdep,-debugnosplit,-debugtextsize,-debugtramp,-benchmark,-benchmarkprofile,-cpuprofile,-memprofile,-memprofilerate,-capturehostobjs-buildmode,-o,-tmpdir,-importcfg,-installsuffix-a,-n-a/-nThe list should be versioned against the Go release used by LLGo CI so a new upstream flag is detected rather than silently falling through.
Other Go flag families
The same parser/dispatch layer should be extended in separate, reviewable PRs:
-gcflags: package selection,-lang, optimization/inlining flags, diagnostics, experiments, and explicit unsupported diagnostics.-asmflags: Plan 9 assembler options and package selection.-gccgoflags: either implement for a future gccgo-compatible path or reject clearly; do not pass them to clang by name.go buildflags:-a,-n,-p,-race,-msan,-asan,-trimpath,-work,-x,-tags,-buildmode,-buildvcs,-compiler,-installsuffix,-pkgdir,-toolexec, module flags, overlay, and PGO. Each needs an LLGo semantic owner and a capability/error policy.go tool compilecompatibility should share the typed compiler options but remain a separate CLI contract fromgo build.Initial implementation:
-sand-wThe first PR should:
-ldflagsreach every build/link command path;-s/-wvalues, including explicit false;-simply-wonly when-wis unset;LLGO_DEBUG_SYMBOLSis enabled;LLGO_DEBUG_SYMBOLSremains a developer escape hatch, but an explicit command-line-wmust take precedence. Existing default output should not gain DWARF merely because-w=falseis accepted; producing full default Go-style DWARF is a separate compatibility step.Relationship to configurable pclntab placement
Pclntab placement is intentionally a separate LLGo option and proposal.
-smust retain the default embedded pclntab to match Go. A future-pclntab=embedded|external|nonecontrols whether LLGo runtime symbolization metadata is embedded, loaded from a sidecar, or omitted. The proposals are related in post-link ordering, but neither flag should implicitly rewrite the other.Related proposal: #2112
Compatibility and rollout
Acceptance criteria
-w,-s,-s -w=false, and later matching overrides have documented observable behavior.-sdoes not regress panic stack traces,runtime.Caller,runtime.CallersFrames, orruntime.FuncForPCunder the default pclntab mode.