mirror of https://github.com/tinygo-org/tinygo.git
wasmstm32webassemblymicrocontrollerarmavrspiwasiadafruitarduinocircuitplayground-expressgpioi2cllvmmicrobitnrf51nrf52nrf52840samd21tinygo
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
207 lines
6.1 KiB
207 lines
6.1 KiB
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"
|
|
)
|
|
|
|
// 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()
|
|
|
|
// Run TinyGo-specific optimization passes.
|
|
if optLevel > 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).
|
|
//
|
|
// 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.
|
|
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.
|
|
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}
|
|
}
|
|
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 := MakeGCStackSlots(mod)
|
|
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",
|
|
}
|
|
|