Skip to content

build: support optimization flags for in-memory compilation#1836

Merged
xushiwei merged 2 commits intoxgo-dev:mainfrom
zhouguangyuan0718:main-opt-428
May 2, 2026
Merged

build: support optimization flags for in-memory compilation#1836
xushiwei merged 2 commits intoxgo-dev:mainfrom
zhouguangyuan0718:main-opt-428

Conversation

@zhouguangyuan0718
Copy link
Copy Markdown
Contributor

@zhouguangyuan0718 zhouguangyuan0718 commented Apr 27, 2026

Summary

This adds explicit optimization level support to the current in-memory compilation flow.

It revives the intent of #1762, but adapts the implementation to the native LLVM codegen path instead of only forwarding -O* to a final clang invocation.

What changed

  • add shared parsing/validation for -O0/-O1/-O2/-O3/-Os/-Oz and -O=<level>
  • make optimization flags mutually exclusive in CLI parsing
  • store the selected optimization level in build.Config
  • wire the optimization level into the in-memory LLVM target machine setup
  • run the LLVM default optimization pipeline according to the selected level for in-memory codegen
  • keep explicit -O* handling separate from the legacy LLGO_OPTIMIZE / IsOptimizeEnabled path
  • carry the selected optimization level through target-based cross compilation as well
  • make llgo monitor accept the same -O* flags and default target lookup to Oz

Test updates

  • refresh cl FileCheck expectations for function define lines so inserted function attributes do not break matching
  • refresh the cl/_testgo/sigsegv lit fixture after the optimization changes

Testing

  • go test ./internal/optlevel ./cmd/internal/flags ./internal/build ./internal/crosscompile ./ssa -count=1
  • go test ./cmd/internal/flags ./cmd/internal/monitor -count=1
  • go test ./cl -run 'TestRunAndTestFromTestdata/(method|print|embedunexport|geometry1370)' -count=1
  • go test ./cl -run 'TestRunAndTestFromTestrt/(qsort|struct|vamethod|builtin)' -count=1
  • go test ./cl -run 'TestRunAndTestFromTestgo/(invoke|tpnamed|reader|closureall)' -count=1

Copy link
Copy Markdown
Contributor

@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 comprehensive optimization level system, adding a dedicated optlevel package and command-line flags (-O0 to -Oz). These levels are integrated into the build configuration, cross-compilation flags, and the LLVM optimization pipeline. Review feedback identifies critical compilation errors in the test suite due to the use of an undefined function UseWithOptLevel. Additionally, suggestions were made to prevent potential errors when an empty optimization pipeline is returned and to centralize duplicated logic used to determine default optimization levels.

func TestUseWithTarget(t *testing.T) {
// Test target-based configuration takes precedence
export, err := Use("linux", "amd64", "esp32", false, true)
export, err := UseWithOptLevel("linux", "amd64", "esp32", false, true, optlevel.Oz)
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.

critical

The function UseWithOptLevel is not defined in the crosscompile package. The Use function was updated to accept an optimization level as its final argument, so it should be used here instead. This will cause a compilation error in the tests.

Suggested change
export, err := UseWithOptLevel("linux", "amd64", "esp32", false, true, optlevel.Oz)
export, err := Use("linux", "amd64", "esp32", false, true, optlevel.Oz)


// Test fallback to goos/goarch when no target specified
export, err = Use(runtime.GOOS, runtime.GOARCH, "", false, false)
export, err = UseWithOptLevel(runtime.GOOS, runtime.GOARCH, "", false, false, optlevel.O2)
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.

critical

The function UseWithOptLevel is not defined. Use the updated Use function instead.

Suggested change
export, err = UseWithOptLevel(runtime.GOOS, runtime.GOARCH, "", false, false, optlevel.O2)
export, err = Use(runtime.GOOS, runtime.GOARCH, "", false, false, optlevel.O2)

t.Fatalf("target CCFLAGS = %v, want first flag -Oz", export.CCFLAGS)
}

export, err = UseWithOptLevel(runtime.GOOS, runtime.GOARCH, "", false, false, optlevel.O3)
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.

critical

The function UseWithOptLevel is not defined. Use the updated Use function instead.

Suggested change
export, err = UseWithOptLevel(runtime.GOOS, runtime.GOARCH, "", false, false, optlevel.O3)
export, err = Use(runtime.GOOS, runtime.GOARCH, "", false, false, optlevel.O3)

Comment thread internal/build/build.go
Comment on lines 1288 to 1297
if ctx.passOpt {
mod := ret.Module()
mod.SetDataLayout(ctx.prog.DataLayout())
mod.SetTarget(ctx.prog.Target().Spec().Triple)
pbo := gllvm.NewPassBuilderOptions()
defer pbo.Dispose()
if err := mod.RunPasses("memcpyopt", ctx.prog.TargetMachine(), pbo); err != nil {
if err := mod.RunPasses(llvmPassPipeline(ctx.buildConf.OptLevel), ctx.prog.TargetMachine(), pbo); err != nil {
return fmt.Errorf("run LLVM passes failed for %v: %v", pkgPath, err)
}
}
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.

medium

If ctx.buildConf.OptLevel is O0, llvmPassPipeline returns an empty string. Calling mod.RunPasses with an empty pipeline string may cause an error in the LLVM pass builder or result in a no-op. It is safer to skip the optimization passes entirely when the pipeline string is empty.

Suggested change
if ctx.passOpt {
mod := ret.Module()
mod.SetDataLayout(ctx.prog.DataLayout())
mod.SetTarget(ctx.prog.Target().Spec().Triple)
pbo := gllvm.NewPassBuilderOptions()
defer pbo.Dispose()
if err := mod.RunPasses("memcpyopt", ctx.prog.TargetMachine(), pbo); err != nil {
if err := mod.RunPasses(llvmPassPipeline(ctx.buildConf.OptLevel), ctx.prog.TargetMachine(), pbo); err != nil {
return fmt.Errorf("run LLVM passes failed for %v: %v", pkgPath, err)
}
}
if ctx.passOpt {
if pipeline := llvmPassPipeline(ctx.buildConf.OptLevel); pipeline != "" {
mod := ret.Module()
mod.SetDataLayout(ctx.prog.DataLayout())
mod.SetTarget(ctx.prog.Target().Spec().Triple)
pbo := gllvm.NewPassBuilderOptions()
defer pbo.Dispose()
if err := mod.RunPasses(pipeline, ctx.prog.TargetMachine(), pbo); err != nil {
return fmt.Errorf("run LLVM passes failed for %v: %v", pkgPath, err)
}
}
}

Comment thread ssa/target.go
Comment on lines +58 to +66
func (p *Target) effectiveOptLevel() optlevel.Level {
if p != nil && p.OptLevel.IsValid() {
return p.OptLevel
}
if p != nil && p.Target != "" {
return optlevel.Oz
}
return optlevel.O2
}
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.

medium

The logic for determining the default optimization level (defaulting to Oz for targets and O2 for host) is duplicated here and in internal/build/build.go. Consider centralizing this logic, perhaps within the optlevel package, to ensure consistency and simplify maintenance.

Copy link
Copy Markdown
Contributor

@fennoai fennoai Bot left a comment

Choose a reason for hiding this comment

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

Review Summary

Well-structured PR that cleanly introduces optimization level handling through a dedicated internal/optlevel package and wires it through the full build pipeline. The enum-based design with allowlist validation is solid from both a correctness and security standpoint. Good test coverage across the new and modified packages.

There are a few issues to address — one compilation-breaking bug in the tests and one likely runtime issue with the -O0 path.

Issues Found

Severity Count
Critical 2
Medium 2
Low 2

func TestUseWithTarget(t *testing.T) {
// Test target-based configuration takes precedence
export, err := Use("linux", "amd64", "esp32", false, true)
export, err := UseWithOptLevel("linux", "amd64", "esp32", false, true, optlevel.Oz)
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.

Bug (build-breaking): UseWithOptLevel is called here but does not exist. The renamed function is Use(goos, goarch, targetName, wasiThreads, forceEspClang, level). This (and the other two calls on lines 328 and 348) should use Use(...) instead.

Suggested change
export, err := UseWithOptLevel("linux", "amd64", "esp32", false, true, optlevel.Oz)
export, err := Use("linux", "amd64", "esp32", false, true, optlevel.Oz)


// Test fallback to goos/goarch when no target specified
export, err = Use(runtime.GOOS, runtime.GOARCH, "", false, false)
export, err = UseWithOptLevel(runtime.GOOS, runtime.GOARCH, "", false, false, optlevel.O2)
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.

Same issue — UseWithOptLevelUse.

Suggested change
export, err = UseWithOptLevel(runtime.GOOS, runtime.GOARCH, "", false, false, optlevel.O2)
export, err = Use(runtime.GOOS, runtime.GOARCH, "", false, false, optlevel.O2)

t.Fatalf("target CCFLAGS = %v, want first flag -Oz", export.CCFLAGS)
}

export, err = UseWithOptLevel(runtime.GOOS, runtime.GOARCH, "", false, false, optlevel.O3)
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.

Same issue — UseWithOptLevelUse.

Suggested change
export, err = UseWithOptLevel(runtime.GOOS, runtime.GOARCH, "", false, false, optlevel.O3)
export, err = UseWithOptLevel(runtime.GOOS, runtime.GOARCH, "", false, false, optlevel.O3)

Should be:

Suggested change
export, err = UseWithOptLevel(runtime.GOOS, runtime.GOARCH, "", false, false, optlevel.O3)
export, err = Use(runtime.GOOS, runtime.GOARCH, "", false, false, optlevel.O3)

Comment thread internal/build/build.go
pbo := gllvm.NewPassBuilderOptions()
defer pbo.Dispose()
if err := mod.RunPasses("memcpyopt", ctx.prog.TargetMachine(), pbo); err != nil {
if err := mod.RunPasses(llvmPassPipeline(ctx.buildConf.OptLevel), ctx.prog.TargetMachine(), pbo); err != nil {
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.

Potential runtime error at -O0: When OptLevel is O0, llvmPassPipeline returns "". Since ctx.passOpt is still true (it's only disabled for debug/gen mode, not for O0), this will call mod.RunPasses("", ...) with an empty pipeline string. Depending on the LLVM version, this may error out.

Consider guarding:

pipeline := llvmPassPipeline(ctx.buildConf.OptLevel)
if ctx.passOpt && pipeline != "" {

Or skip the unnecessary PassBuilderOptions allocation entirely when there's nothing to run.

Comment thread ssa/target.go
return machine.CreateTargetData(), machine
}

func (p *Target) effectiveOptLevel() optlevel.Level {
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.

Duplicated fallback logic: This effectiveOptLevel mirrors internal/build/build.go's version, but diverges: the build.go version also consults the LLGO_OPTIMIZE env var, while this one does not. Since build.Do() already resolves the effective level and sets Target.OptLevel before reaching this code, this function is largely redundant in the normal flow. Consider either:

  • Simplifying this to just return p.OptLevel (trusting the caller), or
  • Extracting the shared fallback into the optlevel package

At minimum, a doc comment explaining why the ssa version intentionally omits the env-var check would prevent future confusion.

Comment thread ssa/target.go
spec.CPU,
spec.Features,
llvm.CodeGenLevelNone,
p.codeGenOptLevel(),
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.

Behavioral change worth noting: Previously this was hardcoded to llvm.CodeGenLevelNone. Now the default (when no explicit level is set and there's no cross-compile target) resolves to O2CodeGenLevelDefault. This will change backend codegen behavior for all existing users who haven't explicitly set an optimization level — potentially increasing build times while improving generated code quality. Worth calling out in the PR description or changelog.

@zhouguangyuan0718 zhouguangyuan0718 force-pushed the main-opt-428 branch 3 times, most recently from 60c696d to b210c40 Compare April 28, 2026 14:47
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 28, 2026

Codecov Report

❌ Patch coverage is 96.15385% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.54%. Comparing base (1196550) to head (22f1ec7).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
ssa/target.go 89.47% 2 Missing ⚠️
internal/crosscompile/crosscompile.go 88.88% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1836      +/-   ##
==========================================
+ Coverage   88.50%   88.54%   +0.04%     
==========================================
  Files          50       51       +1     
  Lines       14426    14496      +70     
==========================================
+ Hits        12768    12836      +68     
- Misses       1438     1442       +4     
+ Partials      220      218       -2     

☔ 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.

- add reusable parsing for -O0/-O1/-O2/-O3/-Os/-Oz and -O=<level>, with mutual exclusion checks
- apply optimization levels to in-memory LLVM codegen, target-based cross compilation, and monitor target lookup
- keep legacy LLGO_OPTIMIZE handling separate from explicit -O* selection and extend related tests

Signed-off-by: ZhouGuangyuan <zhouguangyuan.xian@gmail.com>
- allow FileCheck function definition lines to match inserted function attributes
- update cl lit expectations across _testdata, _testgo, _testrt and related suites
- refresh the sigsegv lit fixture after the optimization-level changes

Signed-off-by: ZhouGuangyuan <zhouguangyuan.xian@gmail.com>
@xushiwei xushiwei merged commit 3ac9c14 into xgo-dev:main May 2, 2026
58 checks passed
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.

3 participants