Skip to content

builder: add -trimpath support - #5711

Merged
deadprogram merged 6 commits into
tinygo-org:devfrom
jakebailey:trimpath
Sep 30, 2026
Merged

deadprogram merged 6 commits into
tinygo-org:devfrom
jakebailey:trimpath

Conversation

@jakebailey

Copy link
Copy Markdown
Member

Fixes #5275

With -no-debug, this doesn't really matter, but otherwise it does make binaries smaller and more reproducible.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

CGo headers outside the package directory can still expose local paths and prevent reproducible cache keys.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Adds -trimpath support to remove local paths and improve binary reproducibility.

Changes:

  • Adds and propagates the -trimpath option.
  • Rewrites Go, CGo, library, and embedded-file paths.
  • Adds path-removal and reproducibility tests.
File Description
main.go Adds the CLI flag.
main_test.go Tests the flag and output reproducibility.
compileopts/​options.go Stores the option.
compileopts/​config.go Configures path mapping and cache isolation.
compiler/​compiler.go Records mapped debug paths.
loader/​loader.go Maps package paths and flags.
loader/​loader_test.go Tests module path mapping.
cgo/​cgo.go Maps CGo source locations.
cgo/​cgo_test.go Updates the CGo call signature.
builder/​build.go Applies mapping during builds.
builder/​library.go Applies mapping to C libraries.
testdata/​trimpath/​main.go Adds the integration test program.
testdata/​trimpath/​main.c Adds test C code.
testdata/​trimpath/​go.mod Defines the test module.
testdata/​trimpath/​dependency/​go.mod Defines the test dependency.
testdata/​trimpath/​dependency/​dependency.go Adds dependency code.
testdata/​trimpath/​data.txt Adds embedded test data.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread loader/loader.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Darwin cross-builds can fail on Windows because the linker preparation requires symbolic-link privileges.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (1)

Comment thread builder/build.go

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Module-less GOPATH packages can still retain absolute paths for shared C headers.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (1)

Comment thread loader/loader.go

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Synthetic GOROOT paths can still expose an absolute GOCACHE path.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (1)

Comment thread loader/loader.go
@deadprogram

Copy link
Copy Markdown
Member

I know that this feature is wanted, for example #5275

Any update here?

@jakebailey

Copy link
Copy Markdown
Member Author

Just dealing with copilot back and forth, will undraft when I feel like it's not missing any weird cases.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Cross-platform CGo, DWARF, cache, and linker behavior warrants final human validation.

Review effort: Balanced
Findings: None

Resolved since last review (1)

@jakebailey
jakebailey marked this pull request as ready for review September 27, 2026 20:12
@deadprogram

Copy link
Copy Markdown
Member

@jakebailey please rebase and resolve merge conflicts. Thank you.

@jakebailey
jakebailey force-pushed the trimpath branch 2 times, most recently from 7080803 to 2f14a87 Compare September 28, 2026 19:28
@deadprogram

Copy link
Copy Markdown
Member

Thanks for all the work on this @jakebailey. I ran the tests on my local machine and took a good look. The following is edited from an automated review:

  1. On targets that use automatic stack sizing, such as -target=pico2, a -trimpath build still contains the local TINYGOROOT/src path. It comes from the DWARF info for internal/task.stackSizes in transform/stacksize.go:86, which calls CreateFile("internal/task/task_stack.go", filepath.Join(goenv.Get("TINYGOROOT"), "src")) and is not changed in this PR. TestTrimPath runs on native, wasm and desktop targets, so it does not cover this path. Perhaps record it as internal/task/task_stack.go with an empty dir when trimming, and add a baremetal target to the test?

  2. Some changes also apply when -trimpath is not set. C and assembly files are now compiled with -working-directory and passed by base name (builder/cc.go:140), CGo snippets get -working-directory=<tmpdir> (builder/build.go:477), CGo parsing always gets -Xclang -working-directory <pkgdir> (loader/loader.go:686), and the cgosnippet and embed object files now have fixed names instead of CreateTemp names. Is that intended for all builds? If so, should the PR description mention it?

  3. Package.RecordedPath (loader/loader.go:435) loops over every package, and then every module dir, for each filename. It runs for each FileHashes entry, each CFlags path and each new DI file. Maybe build the dir to recorded dir table once per program? Seems like it could help on large programs maybe?

What do you think of these?

@jakebailey

Copy link
Copy Markdown
Member Author
  1. Yeah, I think this is true, will look.
  2. That's intentional to match upstream Go and the change isn't possible without it.
  3. I'll see if this can be improved.

Generated CGo globals can share a source position. Break these ties by
name so independent compilations produce the same bitcode and ThinLTO
symbol names instead of depending on Go map iteration order.
Local source and cache paths make binaries depend on the build directory
and expose the builder filesystem layout. Trim these paths from debug
information and C __FILE__ strings, including with -no-debug. Leave the
compiled-in runtime.GOROOT empty in trimmed builds, matching Go.

Match Go's C compilation directories in both modes so trimpath does not
change header lookup. Relative C flags now resolve from the source
directory instead of the command's working directory.

Keep source paths usable for debugger substitution and package-size
reports. Compare independent builds with separate caches so cache reuse
does not hide nondeterministic output.

Fixes 5275
LLD 15 records ThinLTO cache paths and object timestamps in Mach-O
outputs. These make trimmed binaries depend on cache location and build
time even when their source paths have been removed.

LLD 16 changed cached-object handling in https://reviews.llvm.org/D131624.
Disable ThinLTO caching only for trimmed Darwin builds on LLVM 15, and
suppress object timestamps. Other compilation caches remain enabled.
Exercise Darwin trimpath builds in the oldest-toolchain compatibility job.
Some Clang builds record compiler arguments in debug information by
default. Those arguments contain source, cache, and temporary paths that
prefix maps do not rewrite. Even when the final binary omits that debug
information, the paths affect ThinLTO symbol hashes.

Disable command-line recording for trimmed C sources and libraries.
The LLVM 18.1.8 Darwin regression reproduces this with independent builds.
Compiler-provided headers can live outside the TinyGo source tree when
using a system LLVM installation. Their absolute paths remain in DWARF,
as seen with __stddef_size_t.h in WASI test binaries on LLVM 18 and 19.

Use the known Clang resource directory for trimmed builds so its headers
can be recorded under a stable clang path. Apply this to both package
compilation and cached libraries.
Trimmed path lookups repeatedly resolve every package and module root.
This repeats filesystem work for synthetic GOROOT paths and visits the
same module once per package.

Build immutable directory mappings after loading the package graph and
deduplicate shared roots. Keep package mappings ahead of module mappings,
with the same longest-directory and lexical tie-breaking rules.

Add lookup benchmarks for 10, 100, and 1000 packages and regression
coverage for mapping precedence and path boundaries.

@deadprogram deadprogram left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you very much for everything on this @jakebailey now merging!

@deadprogram
deadprogram merged commit 2119e25 into tinygo-org:dev Sep 30, 2026
33 checks passed
@jakebailey
jakebailey deleted the trimpath branch September 30, 2026 17:26
@pjebs

pjebs commented Oct 3, 2026

Copy link
Copy Markdown

@jakebailey Was this PR built with AI assistance?

@jakebailey

Copy link
Copy Markdown
Member Author

To some extent, yes

@pjebs

pjebs commented Oct 3, 2026

Copy link
Copy Markdown

@jakebailey Which model did you use?

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.

-trimpath build flag not working

4 participants