
ThinLTO optimizes across LLVM modules at link time. This means that optimizations (such as inlining and const-propagation) are possible between C and Go. This makes this change especially useful for CGo, but not just for CGo. By doing some optimizations at link time, the linker can discard some unused functions and this leads to a size reduction on average. It does increase code size in some cases, but that's true for most optimizations. I've excluded a number of targets for now (wasm, avr, xtensa, windows, macos). They can probably be supported with some more work, but that should be done in separate PRs. Overall, this change results in an average 3.24% size reduction over all the tinygo.org/x/drivers smoke tests. TODO: this commit runs part of the pass pipeline twice. We should set the PrepareForThinLTO flag in the PassManagerBuilder for even further reduced code size (0.7%) and improved compilation speed.
178 строки
5,1 КиБ
Go
178 строки
5,1 КиБ
Go
package transform
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"go/token"
|
|
"os"
|
|
|
|
"github.com/tinygo-org/tinygo/compileopts"
|
|
"github.com/tinygo-org/tinygo/compiler/ircheck"
|
|
"tinygo.org/x/go-llvm"
|
|
)
|
|
|
|
// 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).
|
|
//
|
|
// 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)
|
|
}
|
|
|
|
// Make sure these functions are kept in tact during TinyGo transformation passes.
|
|
for _, name := range functionsUsedInTransforms {
|
|
fn := mod.NamedFunction(name)
|
|
if fn.IsNil() {
|
|
panic(fmt.Errorf("missing core function %q", name))
|
|
}
|
|
fn.SetLinkage(llvm.ExternalLinkage)
|
|
}
|
|
|
|
if config.PanicStrategy() == "trap" {
|
|
ReplacePanicsWithTrap(mod) // -panic=trap
|
|
}
|
|
|
|
// run a check of all of our code
|
|
if config.VerifyIR() {
|
|
errs := ircheck.Module(mod)
|
|
if errs != nil {
|
|
return errs
|
|
}
|
|
}
|
|
|
|
if optLevel > 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)
|
|
|
|
// Run TinyGo-specific optimization passes.
|
|
OptimizeMaps(mod)
|
|
OptimizeStringToBytes(mod)
|
|
OptimizeReflectImplements(mod)
|
|
OptimizeAllocs(mod, nil, nil)
|
|
err := LowerInterfaces(mod, config)
|
|
if err != nil {
|
|
return []error{err}
|
|
}
|
|
|
|
errs := LowerInterrupts(mod)
|
|
if len(errs) > 0 {
|
|
return errs
|
|
}
|
|
|
|
// 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)
|
|
|
|
// Run TinyGo-specific interprocedural optimizations.
|
|
LowerReflect(mod)
|
|
OptimizeAllocs(mod, config.Options.PrintAllocs, func(pos token.Position, msg string) {
|
|
fmt.Fprintln(os.Stderr, pos.String()+": "+msg)
|
|
})
|
|
OptimizeStringToBytes(mod)
|
|
OptimizeStringEqual(mod)
|
|
|
|
} else {
|
|
// Must be run at any optimization level.
|
|
err := LowerInterfaces(mod, config)
|
|
if err != nil {
|
|
return []error{err}
|
|
}
|
|
LowerReflect(mod)
|
|
errs := LowerInterrupts(mod)
|
|
if len(errs) > 0 {
|
|
return errs
|
|
}
|
|
|
|
// Clean up some leftover symbols of the previous transformations.
|
|
goPasses := llvm.NewPassManager()
|
|
defer goPasses.Dispose()
|
|
goPasses.AddGlobalDCEPass()
|
|
goPasses.Run(mod)
|
|
}
|
|
|
|
if config.Scheduler() == "none" {
|
|
// Check for any goroutine starts.
|
|
if start := mod.NamedFunction("internal/task.start"); !start.IsNil() && len(getUses(start)) > 0 {
|
|
errs := []error{}
|
|
for _, call := range getUses(start) {
|
|
errs = append(errs, errorAt(call, "attempted to start a goroutine without a scheduler"))
|
|
}
|
|
return errs
|
|
}
|
|
}
|
|
|
|
if config.VerifyIR() {
|
|
if errs := ircheck.Module(mod); errs != nil {
|
|
return errs
|
|
}
|
|
}
|
|
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
|
|
return []error{errors.New("optimizations caused a verification failure")}
|
|
}
|
|
|
|
// After TinyGo-specific transforms have finished, undo exporting these functions.
|
|
for _, name := range functionsUsedInTransforms {
|
|
fn := mod.NamedFunction(name)
|
|
if fn.IsNil() || fn.IsDeclaration() {
|
|
continue
|
|
}
|
|
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)
|
|
}
|
|
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 := AddGlobalsBitmap(mod)
|
|
hasGCPass = MakeGCStackSlots(mod) || hasGCPass
|
|
if hasGCPass {
|
|
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
|
|
return []error{errors.New("GC pass caused a verification failure")}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// functionsUsedInTransform is a list of function symbols that may be used
|
|
// during TinyGo optimization passes so they have to be marked as external
|
|
// linkage until all TinyGo passes have finished.
|
|
var functionsUsedInTransforms = []string{
|
|
"runtime.alloc",
|
|
"runtime.free",
|
|
"runtime.nilPanic",
|
|
}
|