aboutsummaryrefslogtreecommitdiffhomepage
path: root/transform/optimizer.go
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2020-03-18 19:12:07 +0100
committerRon Evans <[email protected]>2020-03-21 15:45:25 +0100
commit9d3de552291d848d985cd960a094f218e53574cd (patch)
tree857569da4698746e10b02055b44eef07a6bb3b7e /transform/optimizer.go
parent78bd7e094f2883ec322d7ef2db326ec3327a338b (diff)
downloadtinygo-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.go242
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
+}