Skip to content

build: introduce new LTO CLI control with target-aware defaults#1694

Open
zhouguangyuan0718 wants to merge 1 commit intogoplus:mainfrom
zhouguangyuan0718:main_lto_embed
Open

build: introduce new LTO CLI control with target-aware defaults#1694
zhouguangyuan0718 wants to merge 1 commit intogoplus:mainfrom
zhouguangyuan0718:main_lto_embed

Conversation

@zhouguangyuan0718
Copy link
Contributor

@zhouguangyuan0718 zhouguangyuan0718 commented Mar 9, 2026

Summary

  • introduce a new tri-state -lto option (auto/true/false)
  • default LTO to ON for -target builds and OFF for non-target builds
  • apply LTO decision when generating crosscompile flags (instead of post-filtering)
  • make llgo monitor follow the same LTO rule path

Details

  • add shared LTO flag parsing helpers in cmd/internal/flags
  • thread LTO decision through build config into internal/crosscompile
  • gate -flto=thin and --lto-* emission on the resolved LTO switch
  • update crosscompile/build tests for default and explicit override behavior

Validation

  • go test ./internal/crosscompile -run TestUseLTOFlagsControlledByOption
  • go test ./cmd/internal/monitor ./cmd/internal/flags ./internal/crosscompile -run TestUseLTOFlagsControlledByOption
  • go test ./cmd/internal/flags ./cmd/internal/build ./cmd/internal/run ./cmd/internal/test ./cmd/internal/install ./cmd/internal/monitor ./internal/build -run TestLTOEnabled

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the build system by introducing a sophisticated and flexible mechanism for managing Link-Time Optimization (LTO). It establishes intelligent, target-aware defaults for LTO, enabling it automatically for embedded and cross-compiled targets while disabling it for standard host builds. This change not only provides granular control over LTO through a new command-line flag but also integrates the LTO decision-making process earlier into the flag generation, leading to a more streamlined and optimized build experience.

Highlights

  • New LTO CLI Control: Introduced a new tri-state option (auto/true/false) for Link-Time Optimization (LTO) control, allowing for more flexible configuration.
  • Target-Aware LTO Defaults: Configured LTO to be ON by default for target builds and OFF for non-target builds, optimizing build performance and size based on the build context.
  • Integrated LTO Decision Making: The LTO decision is now applied when generating cross-compile flags, ensuring consistent application of LTO settings across the build process.
  • Monitor Command LTO Alignment: The monitor command now adheres to the same LTO rule path, ensuring its behavior is consistent with the new LTO configuration.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • _demo/embed/targetsbuild/build.sh
    • Added 'nintendoswitch' to the list of test directories, with a comment indicating it should not work under LTO.
  • cmd/internal/flags/flags.go
    • Imported fmt and strconv packages.
    • Defined a new optionalBool type to represent a tri-state boolean flag (auto/true/false) and its flag.Value interface methods.
    • Declared a global LTO variable of type optionalBool.
    • Added AddLTOFlag function to register the -lto command-line flag with a description of its default behavior.
    • Implemented ResolveLTO function to determine the effective LTO setting based on user input or a provided default.
    • Called AddLTOFlag within AddBuildFlags to make the LTO flag available for build commands.
    • Modified UpdateConfig to propagate the explicitly specified LTO value from the command-line flag to the build configuration.
  • cmd/internal/monitor/monitor.go
    • Added the LTO flag to the monitor command's flag set.
    • Updated the call to crosscompile.UseTarget to pass the resolved LTO setting, ensuring monitor respects LTO configuration.
  • internal/build/build.go
    • Added a LTO *bool field to the Config struct to store the LTO setting, with nil indicating auto-detection.
    • Implemented the ltoEnabled() method to determine if LTO should be enabled, defaulting to true for target builds and false otherwise, unless explicitly overridden.
    • Modified the call to crosscompile.Use to pass the result of conf.ltoEnabled(), integrating the LTO decision into the cross-compilation setup.
  • internal/build/build_test.go
    • Added TestLTOEnabledDefault to verify that LTO is enabled by default for target builds and disabled for non-target builds.
    • Added TestLTOEnabledExplicitOverride to confirm that explicit LTO=true or LTO=false overrides the default behavior for both host and target builds.
  • internal/crosscompile/crosscompile.go
    • Modified the use function signature to accept an enableLTO boolean parameter.
    • Conditionally appended ThinLTO linker flags (-Wl,--lto-O0) and compiler flags (-flto=thin) to LDFLAGS and CCFLAGS respectively, based on the enableLTO parameter.
    • Modified the UseTarget function signature to accept an enableLTO boolean parameter.
    • Conditionally appended ThinLTO linker and compiler flags within UseTarget when the linker is ld.lld and enableLTO is true.
    • Added specific ldflags for riscv64 architecture to include codegen options for LTO.
    • Added ldflags for CodeModel and TargetABI configurations to ensure codegen options are passed to the linker during LTO.
    • Modified the Use function signature to accept an enableLTO boolean parameter and passed it down to UseTarget or use accordingly.
  • internal/crosscompile/crosscompile_test.go
    • Imported the strings package.
    • Updated existing test calls to use, UseTarget, and Use to include the new enableLTO parameter, typically setting it to true to maintain previous test behavior.
    • Added TestUseLTOFlagsControlledByOption to specifically verify that LTO-related flags are correctly omitted when enableLTO is set to false.
Activity
  • No specific activity (comments, reviews, progress) has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new command-line option to control Link-Time Optimization (LTO) with target-aware defaults. The implementation of the tri-state flag and its integration into the build configuration and cross-compilation logic is well-structured. I've identified a bug in the flag parsing logic that doesn't handle the 'auto' value, and also noted that some LTO-specific linker flags are being added unconditionally, which could potentially break non-LTO builds. I have provided suggestions to address these issues. Additionally, there's a minor point about a confusing comment in a test script.

Comment on lines +60 to +72
func (o *optionalBool) SetValue(v string) error {
val, err := strconv.ParseBool(v)
if err != nil {
return fmt.Errorf("invalid bool value %q", v)
}
o.Specified = true
o.Value = val
return nil
}

func (o *optionalBool) Set(v string) error {
return o.SetValue(v)
}

Choose a reason for hiding this comment

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

high

The Set method for optionalBool doesn't handle the value "auto". According to the flag's usage string, "auto" is a valid option. If a user provides -lto=auto, strconv.ParseBool("auto") will fail, causing an error. The Set method should be updated to handle "auto" gracefully, likely by resetting the optionalBool to its "unspecified" state. Additionally, the SetValue helper function can be removed for clarity by incorporating its logic directly into Set.

Suggested change
func (o *optionalBool) SetValue(v string) error {
val, err := strconv.ParseBool(v)
if err != nil {
return fmt.Errorf("invalid bool value %q", v)
}
o.Specified = true
o.Value = val
return nil
}
func (o *optionalBool) Set(v string) error {
return o.SetValue(v)
}
func (o *optionalBool) Set(v string) error {
if v == "auto" {
o.Specified = false
o.Value = false
return nil
}
val, err := strconv.ParseBool(v)
if err != nil {
return fmt.Errorf("invalid value %q for lto flag; must be true, false, or auto", v)
}
o.Specified = true
o.Value = val
return nil
}

Comment on lines 560 to +562
ccflags = append(ccflags, "-march=rv64gc")
// codegen option should be added to ldflags for lto
ldflags = append(ldflags, "-mllvm", "-march=rv64gc")

Choose a reason for hiding this comment

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

high

The linker flag -mllvm -march=rv64gc is added unconditionally for the riscv64 architecture. The comment indicates this is for LTO. Passing LLVM-specific options to the linker might cause issues for non-LTO builds. This flag should only be added when enableLTO is true.

Suggested change
ccflags = append(ccflags, "-march=rv64gc")
// codegen option should be added to ldflags for lto
ldflags = append(ldflags, "-mllvm", "-march=rv64gc")
ccflags = append(ccflags, "-march=rv64gc")
if enableLTO {
// codegen option should be added to ldflags for lto
ldflags = append(ldflags, "-mllvm", "-march=rv64gc")
}

Comment on lines 590 to 599
if config.CodeModel != "" {
ccflags = append(ccflags, "-mcmodel="+config.CodeModel)
// codegen option should be added to ldflags for lto
ldflags = append(ldflags, "-mllvm", "-code-model="+config.CodeModel)
}
if config.TargetABI != "" {
ccflags = append(ccflags, "-mabi="+config.TargetABI)
// codegen option should be added to ldflags for lto
ldflags = append(ldflags, "-mllvm", "-target-abi="+config.TargetABI)
}

Choose a reason for hiding this comment

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

high

The linker flags for code-model and target-abi are added unconditionally. As the comments suggest, these are needed for LTO. To avoid potential issues in non-LTO builds, these flags should be added to ldflags only when enableLTO is true.

	if config.CodeModel != "" {
		ccflags = append(ccflags, "-mcmodel="+config.CodeModel)
		if enableLTO {
			// codegen option should be added to ldflags for lto
			ldflags = append(ldflags, "-mllvm", "-code-model="+config.CodeModel)
		}
	}
	if config.TargetABI != "" {
		ccflags = append(ccflags, "-mabi="+config.TargetABI)
		if enableLTO {
			// codegen option should be added to ldflags for lto
			ldflags = append(ldflags, "-mllvm", "-target-abi="+config.TargetABI)
		}
	}

"riscv32"
"riscv64"
"rp2040"
"nintendoswitch" # undefined symbol under lto, should not work when no-lto

Choose a reason for hiding this comment

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

medium

The comment here is a bit confusing. "undefined symbol under lto" suggests it fails with LTO enabled, which makes sense for adding it to the ignore list given LTO is now on by default for targets. However, "should not work when no-lto" seems to suggest it's also expected to fail without LTO. Could you clarify this comment for future reference? Perhaps something like "Fails with LTO due to undefined symbols, and has separate known issues without LTO." would be clearer.

@xgopilot
Copy link
Contributor

xgopilot bot commented Mar 9, 2026

Good approach overall — the tri-state flag design with target-aware defaults is clean, and the Config.ltoEnabled() abstraction centralizes the resolution logic well. Tests cover the key default/override matrix. A few items need attention, mostly around the LTO optimization level choice and some flags being unconditionally appended despite being LTO-specific. See inline comments.

// Enable ThinLTO, using default lto kind(thinlto).
export.LDFLAGS = append(export.LDFLAGS, "-Wl,--lto-O0")
}
if clangRoot != "" {
Copy link
Contributor

Choose a reason for hiding this comment

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

--lto-O0 tells the linker to skip all optimization passes during the LTO step. This pays the full cost of ThinLTO (bitcode serialization, cross-module summary analysis, importing) while delivering zero optimization benefit — no cross-module inlining, dead global elimination, or interprocedural constant propagation.

Code compiled with -flto=thin may defer certain optimization decisions to link time; with O0 at link time those deferred optimizations never happen, potentially producing worse output than a non-LTO build.

Consider --lto-O2 (or --lto-O1 for a link-time vs optimization tradeoff). The same applies to line 510 in UseTarget().

Comment on lines 558 to +562
ccflags = append(ccflags, "-fforce-enable-int128")
case "riscv64":
ccflags = append(ccflags, "-march=rv64gc")
// codegen option should be added to ldflags for lto
ldflags = append(ldflags, "-mllvm", "-march=rv64gc")
Copy link
Contributor

Choose a reason for hiding this comment

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

The comments say "codegen option should be added to ldflags for lto", but these -mllvm flags are appended unconditionally regardless of whether enableLTO is true. The same pattern appears for CodeModel (line 593) and TargetABI (line 598).

-mllvm flags are only meaningful when the linker runs the LLVM backend during LTO codegen. When LTO is off, the linker doesn't invoke codegen, so these flags are inert at best or may trigger warnings depending on the LLD version. These blocks should be gated on enableLTO, matching the guard on lines 508–513.

Comment on lines +257 to 259
export.CCFLAGS = append(export.CCFLAGS, "-flto=thin")
}

Copy link
Contributor

Choose a reason for hiding this comment

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

In this non-target use() path, -flto=thin is added to CCFLAGS but not to CFLAGS. In the UseTarget() path (lines 511–512), it's correctly added to both cflags and ccflags. If any compilation steps use only CFLAGS, those object files won't contain LLVM bitcode and will pass through the LTO link step as opaque machine code, creating a partially-LTO build. Consider adding -flto=thin to export.CFLAGS as well for consistency.

"riscv32"
"riscv64"
"rp2040"
"nintendoswitch" # undefined symbol under lto, should not work when no-lto
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment is contradictory: "undefined symbol under lto, should not work when no-lto." The first clause says it fails with LTO; the second says it should not work without LTO. Based on context (LTO is now on by default for target builds), the likely intent is:

nintendoswitch: has undefined symbol errors under LTO; added to ignore list since LTO is now enabled by default for target builds

var serialPort []string
if flags.Target != "" {
conf, err := crosscompile.UseTarget(flags.Target)
conf, err := crosscompile.UseTarget(flags.Target, flags.ResolveLTO(true))
Copy link
Contributor

Choose a reason for hiding this comment

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

This hardcodes true as the default, bypassing the centralized Config.ltoEnabled() logic. The build path uses Config.ltoEnabled() which derives the default from c.Target != "". Today the results are equivalent (monitor only calls UseTarget when flags.Target != ""), but if the resolution logic changes later, this code path would need a separate manual update. Consider routing through Config.ltoEnabled() for consistency, or at minimum adding a comment explaining why the hardcoded default is correct here.

@codecov
Copy link

codecov bot commented Mar 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.19%. Comparing base (8301bc3) to head (9945d01).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1694      +/-   ##
==========================================
+ Coverage   93.15%   93.19%   +0.03%     
==========================================
  Files          48       48              
  Lines       13331    13343      +12     
==========================================
+ Hits        12419    12435      +16     
+ Misses        725      722       -3     
+ Partials      187      186       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@zhouguangyuan0718 zhouguangyuan0718 force-pushed the main_lto_embed branch 2 times, most recently from af5b84e to 6e34d25 Compare March 9, 2026 15:20
This change newly introduces end-to-end LTO control in llgo.

- add a new tri-state -lto option (auto/true/false)

- default LTO ON for embedded target builds and OFF for non-target builds

- allow explicit user override in both paths

- apply LTO decision at crosscompile flag generation time

- make monitor follow the same LTO rule path

- add/update tests for default and override behavior

Signed-off-by: ZhouGuangyuan <zhouguangyuan.xian@gmail.com>
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