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.

231 lines
6.6 KiB

package transform
import (
"errors"
"fmt"
"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)
}
builder.AddCoroutinePassesToExtensionPoints()
// Make sure these functions are kept in tact during TinyGo transformation passes.
for _, name := range getFunctionsUsedInTransforms(config) {
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
}
}
// Run function passes for each function.
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()
if optLevel > 0 {
// Run some preparatory passes for the Go optimizer.
goPasses := llvm.NewPassManager()
defer goPasses.Dispose()
goPasses.AddGlobalDCEPass()
goPasses.AddGlobalOptimizerPass()
goPasses.AddConstantPropagationPass()
goPasses.AddAggressiveDCEPass()
goPasses.AddFunctionAttrsPass()
goPasses.Run(mod)
// Run Go-specific optimization passes.
OptimizeMaps(mod)
OptimizeStringToBytes(mod)
OptimizeAllocs(mod)
err := LowerInterfaces(mod)
if err != nil {
return []error{err}
}
errs := LowerInterrupts(mod)
if len(errs) > 0 {
return errs
}
if config.FuncImplementation() == compileopts.FuncValueSwitch {
LowerFuncValues(mod)
}
// 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)
OptimizeStringToBytes(mod)
} else {
// Must be run at any optimization level.
err := LowerInterfaces(mod)
if err != nil {
return []error{err}
}
if config.FuncImplementation() == compileopts.FuncValueSwitch {
LowerFuncValues(mod)
}
errs := LowerInterrupts(mod)
if len(errs) > 0 {
return errs
}
}
// Lower async implementations.
switch config.Scheduler() {
case "coroutines":
// Lower async as coroutines.
err := LowerCoroutines(mod, config.NeedsStackObjects())
if err != nil {
return []error{err}
}
case "tasks":
// No transformations necessary.
case "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
}
default:
return []error{errors.New("invalid scheduler")}
}
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")}
}
if sizeLevel >= 2 {
// Set the "optsize" attribute to make slightly smaller binaries at the
// cost of some performance.
kind := llvm.AttributeKindID("optsize")
attr := mod.Context().CreateEnumAttribute(kind, 0)
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
fn.AddFunctionAttr(attr)
}
}
// After TinyGo-specific transforms have finished, undo exporting these functions.
for _, name := range getFunctionsUsedInTransforms(config) {
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.
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
funcPasses.RunFunc(fn)
}
funcPasses.FinalizeFunc()
// Run module passes.
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",
}
var taskFunctionsUsedInTransforms = []string{}
// These functions need to be preserved in the IR until after the coroutines
// pass has run.
var coroFunctionsUsedInTransforms = []string{
"internal/task.start",
"internal/task.Pause",
"internal/task.fake",
"internal/task.Current",
"internal/task.createTask",
"(*internal/task.Task).setState",
"(*internal/task.Task).returnTo",
"(*internal/task.Task).returnCurrent",
"(*internal/task.Task).setReturnPtr",
"(*internal/task.Task).getReturnPtr",
}
// getFunctionsUsedInTransforms gets a list of all special functions that should be preserved during transforms and optimization.
func getFunctionsUsedInTransforms(config *compileopts.Config) []string {
fnused := functionsUsedInTransforms
switch config.Scheduler() {
case "none":
case "coroutines":
fnused = append(append([]string{}, fnused...), coroFunctionsUsedInTransforms...)
case "tasks":
fnused = append(append([]string{}, fnused...), taskFunctionsUsedInTransforms...)
default:
panic(fmt.Errorf("invalid scheduler %q", config.Scheduler()))
}
return fnused
}