diff options
author | Ayke van Laethem <[email protected]> | 2020-03-18 19:12:07 +0100 |
---|---|---|
committer | Ron Evans <[email protected]> | 2020-03-21 15:45:25 +0100 |
commit | 9d3de552291d848d985cd960a094f218e53574cd (patch) | |
tree | 857569da4698746e10b02055b44eef07a6bb3b7e /transform/optimizer.go | |
parent | 78bd7e094f2883ec322d7ef2db326ec3327a338b (diff) | |
download | tinygo-9d3de552291d848d985cd960a094f218e53574cd.tar.gz tinygo-9d3de552291d848d985cd960a094f218e53574cd.zip |
compiler: move Optimizer function to transform package
This refactor is a prerequisite to much larger refactors in the
compiler.
Diffstat (limited to 'transform/optimizer.go')
-rw-r--r-- | transform/optimizer.go | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/transform/optimizer.go b/transform/optimizer.go new file mode 100644 index 000000000..71bf4d617 --- /dev/null +++ b/transform/optimizer.go @@ -0,0 +1,242 @@ +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) + LowerInterfaces(mod) + + 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) + + // Lower runtime.isnil calls to regular nil comparisons. + isnil := mod.NamedFunction("runtime.isnil") + if !isnil.IsNil() { + builder := mod.Context().NewBuilder() + defer builder.Dispose() + for _, use := range getUses(isnil) { + builder.SetInsertPointBefore(use) + ptr := use.Operand(0) + if !ptr.IsABitCastInst().IsNil() { + ptr = ptr.Operand(0) + } + nilptr := llvm.ConstPointerNull(ptr.Type()) + icmp := builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "") + use.ReplaceAllUsesWith(icmp) + use.EraseFromParentAsInstruction() + } + } + + } else { + // Must be run at any optimization level. + LowerInterfaces(mod) + 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() { + 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 +} |