Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

all: use the new LLVM pass manager #3918

Merged
merged 1 commit into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 8 additions & 11 deletions builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ type packageAction struct {
FileHashes map[string]string // hash of every file that's part of the package
EmbeddedFiles map[string]string // hash of all the //go:embed files in the package
Imports map[string]string // map from imported package to action ID hash
OptLevel int // LLVM optimization level (0-3)
SizeLevel int // LLVM optimization for size level (0-2)
OptLevel string // LLVM optimization level (O0, O1, O2, Os, Oz)
UndefinedGlobals []string // globals that are left as external globals (no initializer)
}

Expand Down Expand Up @@ -158,7 +157,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
return BuildResult{}, fmt.Errorf("unknown libc: %s", config.Target.Libc)
}

optLevel, sizeLevel, _ := config.OptLevels()
optLevel, speedLevel, sizeLevel := config.OptLevel()
compilerConfig := &compiler.Config{
Triple: config.Triple(),
CPU: config.CPU(),
Expand Down Expand Up @@ -321,7 +320,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
EmbeddedFiles: make(map[string]string, len(allFiles)),
Imports: make(map[string]string, len(pkg.Pkg.Imports())),
OptLevel: optLevel,
SizeLevel: sizeLevel,
UndefinedGlobals: undefinedGlobals,
}
for filePath, hash := range pkg.FileHashes {
Expand Down Expand Up @@ -743,17 +741,17 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if config.GOOS() == "windows" {
// Options for the MinGW wrapper for the lld COFF linker.
ldflags = append(ldflags,
"-Xlink=/opt:lldlto="+strconv.Itoa(optLevel),
"-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
} else if config.GOOS() == "darwin" {
// Options for the ld64-compatible lld linker.
ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(optLevel),
"--lto-O"+strconv.Itoa(speedLevel),
"-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
} else {
// Options for the ELF linker.
ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(optLevel),
"--lto-O"+strconv.Itoa(speedLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
)
}
Expand Down Expand Up @@ -1066,10 +1064,9 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
return err
}

// Optimization levels here are roughly the same as Clang, but probably not
// exactly.
optLevel, sizeLevel, inlinerThreshold := config.OptLevels()
errs := transform.Optimize(mod, config, optLevel, sizeLevel, inlinerThreshold)
// Run most of the whole-program optimizations (including the whole
// O0/O1/O2/Os/Oz optimization pipeline).
errs := transform.Optimize(mod, config)
if len(errs) > 0 {
return newMultiError(errs)
}
Expand Down
6 changes: 3 additions & 3 deletions builder/sizes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test.
tests := []sizeTest{
// microcontrollers
{"hifive1b", "examples/echo", 4568, 280, 0, 2252},
{"microbit", "examples/serial", 2728, 388, 8, 2256},
{"wioterminal", "examples/pininterrupt", 5996, 1484, 116, 6816},
{"hifive1b", "examples/echo", 4484, 280, 0, 2252},
{"microbit", "examples/serial", 2724, 388, 8, 2256},
{"wioterminal", "examples/pininterrupt", 6000, 1484, 116, 6816},

// TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the
Expand Down
12 changes: 6 additions & 6 deletions compileopts/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,18 +145,18 @@ func (c *Config) Serial() string {

// OptLevels returns the optimization level (0-2), size level (0-2), and inliner
// threshold as used in the LLVM optimization pipeline.
func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
func (c *Config) OptLevel() (level string, speedLevel, sizeLevel int) {
switch c.Options.Opt {
case "none", "0":
return 0, 0, 0 // -O0
return "O0", 0, 0
case "1":
return 1, 0, 0 // -O1
return "O1", 1, 0
case "2":
return 2, 0, 225 // -O2
return "O2", 2, 0
case "s":
return 2, 1, 225 // -Os
return "Os", 2, 1
case "z":
return 2, 2, 5 // -Oz, default
return "Oz", 2, 2 // default
default:
// This is not shown to the user: valid choices are already checked as
// part of Options.Verify(). It is here as a sanity check.
Expand Down
12 changes: 5 additions & 7 deletions compiler/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,12 @@ func TestCompiler(t *testing.T) {
}

// Optimize IR a little.
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
defer funcPasses.Dispose()
funcPasses.AddInstructionCombiningPass()
funcPasses.InitializeFunc()
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
funcPasses.RunFunc(fn)
passOptions := llvm.NewPassBuilderOptions()
defer passOptions.Dispose()
err = mod.RunPasses("instcombine", llvm.TargetMachine{}, passOptions)
if err != nil {
t.Error(err)
}
funcPasses.FinalizeFunc()

outFilePrefix := tc.file[:len(tc.file)-3]
if tc.target != "" {
Expand Down
9 changes: 3 additions & 6 deletions interp/interp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,9 @@ func runTest(t *testing.T, pathPrefix string) {
}

// Run some cleanup passes to get easy-to-read outputs.
pm := llvm.NewPassManager()
defer pm.Dispose()
pm.AddGlobalOptimizerPass()
pm.AddDeadStoreEliminationPass()
pm.AddAggressiveDCEPass()
pm.Run(mod)
to := llvm.NewPassBuilderOptions()
defer to.Dispose()
mod.RunPasses("globalopt,dse,adce", llvm.TargetMachine{}, to)

// Read the expected output IR.
out, err := os.ReadFile(pathPrefix + ".out.ll")
Expand Down
11 changes: 6 additions & 5 deletions transform/allocs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ func TestAllocs2(t *testing.T) {
mod := compileGoFileForTesting(t, "./testdata/allocs2.go")

// Run functionattrs pass, which is necessary for escape analysis.
pm := llvm.NewPassManager()
defer pm.Dispose()
pm.AddInstructionCombiningPass()
pm.AddFunctionAttrsPass()
pm.Run(mod)
po := llvm.NewPassBuilderOptions()
defer po.Dispose()
err := mod.RunPasses("function(instcombine),function-attrs", llvm.TargetMachine{}, po)
if err != nil {
t.Error("failed to run passes:", err)
}

// Run heap to stack transform.
var testOutputs []allocsTestOutput
Expand Down
10 changes: 6 additions & 4 deletions transform/interface-lowering_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ func TestInterfaceLowering(t *testing.T) {
t.Error(err)
}

pm := llvm.NewPassManager()
defer pm.Dispose()
pm.AddGlobalDCEPass()
pm.Run(mod)
po := llvm.NewPassBuilderOptions()
defer po.Dispose()
err = mod.RunPasses("globaldce", llvm.TargetMachine{}, po)
if err != nil {
t.Error("failed to run passes:", err)
}
})
}
11 changes: 6 additions & 5 deletions transform/maps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ func TestOptimizeMaps(t *testing.T) {

// Run an optimization pass, to clean up the result.
// This shows that all code related to the map is really eliminated.
pm := llvm.NewPassManager()
defer pm.Dispose()
pm.AddDeadStoreEliminationPass()
pm.AddAggressiveDCEPass()
pm.Run(mod)
po := llvm.NewPassBuilderOptions()
defer po.Dispose()
err := mod.RunPasses("dse,adce", llvm.TargetMachine{}, po)
if err != nil {
t.Error("failed to run passes:", err)
}
})
}
102 changes: 32 additions & 70 deletions transform/optimizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,54 +14,22 @@ import (
// OptimizePackage runs optimization passes over the LLVM module for the given
// Go package.
func OptimizePackage(mod llvm.Module, config *compileopts.Config) {
optLevel, sizeLevel, _ := config.OptLevels()

// Run function passes for each function in the module.
// These passes are intended to be run on each function right
// after they're created to reduce IR size (and maybe also for
// cache locality to improve performance), but for now they're
// run here for each function in turn. Maybe this can be
// improved in the future.
builder := llvm.NewPassManagerBuilder()
defer builder.Dispose()
builder.SetOptLevel(optLevel)
builder.SetSizeLevel(sizeLevel)
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
defer funcPasses.Dispose()
builder.PopulateFunc(funcPasses)
funcPasses.InitializeFunc()
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
if fn.IsDeclaration() {
continue
}
funcPasses.RunFunc(fn)
}
funcPasses.FinalizeFunc()
_, speedLevel, _ := config.OptLevel()

// Run TinyGo-specific optimization passes.
if optLevel > 0 {
if speedLevel > 0 {
OptimizeMaps(mod)
}
}

// Optimize runs a number of optimization and transformation passes over the
// given module. Some passes are specific to TinyGo, others are generic LLVM
// passes. You can set a preferred performance (0-3) and size (0-2) level and
// control the limits of the inliner (higher numbers mean more inlining, set it
// to 0 to disable entirely).
// passes.
//
// Please note that some optimizations are not optional, thus Optimize must
// alwasy be run before emitting machine code. Set all controls (optLevel,
// sizeLevel, inlinerThreshold) to 0 to reduce the number of optimizations to a
// minimum.
func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel int, inlinerThreshold uint) []error {
builder := llvm.NewPassManagerBuilder()
defer builder.Dispose()
builder.SetOptLevel(optLevel)
builder.SetSizeLevel(sizeLevel)
if inlinerThreshold != 0 {
builder.UseInlinerWithThreshold(inlinerThreshold)
}
// alwasy be run before emitting machine code.
func Optimize(mod llvm.Module, config *compileopts.Config) []error {
optLevel, speedLevel, _ := config.OptLevel()

// Make sure these functions are kept in tact during TinyGo transformation passes.
for _, name := range functionsUsedInTransforms {
Expand All @@ -84,23 +52,20 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
}
}

if optLevel > 0 {
if speedLevel > 0 {
// Run some preparatory passes for the Go optimizer.
goPasses := llvm.NewPassManager()
defer goPasses.Dispose()
goPasses.AddGlobalDCEPass()
goPasses.AddGlobalOptimizerPass()
goPasses.AddIPSCCPPass()
goPasses.AddInstructionCombiningPass() // necessary for OptimizeReflectImplements
goPasses.AddAggressiveDCEPass()
goPasses.AddFunctionAttrsPass()
goPasses.Run(mod)
po := llvm.NewPassBuilderOptions()
defer po.Dispose()
err := mod.RunPasses("globaldce,globalopt,ipsccp,instcombine,adce,function-attrs", llvm.TargetMachine{}, po)
if err != nil {
return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
}

// Run TinyGo-specific optimization passes.
OptimizeStringToBytes(mod)
OptimizeReflectImplements(mod)
OptimizeAllocs(mod, nil, nil)
err := LowerInterfaces(mod, config)
err = LowerInterfaces(mod, config)
if err != nil {
return []error{err}
}
Expand All @@ -113,7 +78,10 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
// After interfaces are lowered, there are many more opportunities for
// interprocedural optimizations. To get them to work, function
// attributes have to be updated first.
goPasses.Run(mod)
err = mod.RunPasses("globaldce,globalopt,ipsccp,instcombine,adce,function-attrs", llvm.TargetMachine{}, po)
if err != nil {
return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
}

// Run TinyGo-specific interprocedural optimizations.
OptimizeAllocs(mod, config.Options.PrintAllocs, func(pos token.Position, msg string) {
Expand All @@ -134,10 +102,12 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
}

// Clean up some leftover symbols of the previous transformations.
goPasses := llvm.NewPassManager()
defer goPasses.Dispose()
goPasses.AddGlobalDCEPass()
goPasses.Run(mod)
po := llvm.NewPassBuilderOptions()
defer po.Dispose()
err = mod.RunPasses("globaldce", llvm.TargetMachine{}, po)
if err != nil {
return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
}
}

if config.Scheduler() == "none" {
Expand Down Expand Up @@ -169,23 +139,15 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
fn.SetLinkage(llvm.InternalLinkage)
}

// Run function passes again, because without it, llvm.coro.size.i32()
// doesn't get lowered.
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
defer funcPasses.Dispose()
builder.PopulateFunc(funcPasses)
funcPasses.InitializeFunc()
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
funcPasses.RunFunc(fn)
// Run the default pass pipeline.
// TODO: set the PrepareForThinLTO flag somehow.
po := llvm.NewPassBuilderOptions()
defer po.Dispose()
passes := fmt.Sprintf("default<%s>", optLevel)
err := mod.RunPasses(passes, llvm.TargetMachine{}, po)
if err != nil {
return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
}
funcPasses.FinalizeFunc()

// Run module passes.
// TODO: somehow set the PrepareForThinLTO flag in the pass manager builder.
modPasses := llvm.NewPassManager()
defer modPasses.Dispose()
builder.Populate(modPasses)
modPasses.Run(mod)

hasGCPass := MakeGCStackSlots(mod)
if hasGCPass {
Expand Down
2 changes: 1 addition & 1 deletion transform/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
// the -opt= compiler flag.
func AddStandardAttributes(fn llvm.Value, config *compileopts.Config) {
ctx := fn.Type().Context()
_, sizeLevel, _ := config.OptLevels()
_, _, sizeLevel := config.OptLevel()
if sizeLevel >= 1 {
fn.AddFunctionAttr(ctx.CreateEnumAttribute(llvm.AttributeKindID("optsize"), 0))
}
Expand Down
Loading