diff --git a/builder/build.go b/builder/build.go index 1bc06af4d2..c9642e0cff 100644 --- a/builder/build.go +++ b/builder/build.go @@ -72,16 +72,16 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri // exactly. errs = nil switch config.Options.Opt { - case "none:", "0": - errs = c.Optimize(0, 0, 0) // -O0 + case "none", "0": + errs = transform.Optimize(c.Module(), config, 0, 0, 0) // -O0 case "1": - errs = c.Optimize(1, 0, 0) // -O1 + errs = transform.Optimize(c.Module(), config, 1, 0, 0) // -O1 case "2": - errs = c.Optimize(2, 0, 225) // -O2 + errs = transform.Optimize(c.Module(), config, 2, 0, 225) // -O2 case "s": - errs = c.Optimize(2, 1, 225) // -Os + errs = transform.Optimize(c.Module(), config, 2, 1, 225) // -Os case "z": - errs = c.Optimize(2, 2, 5) // -Oz, default + errs = transform.Optimize(c.Module(), config, 2, 2, 5) // -Oz, default default: errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)} } diff --git a/compileopts/config.go b/compileopts/config.go index cbd0bd1d58..d3f4bd1d00 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -21,6 +21,29 @@ type Config struct { TestConfig TestConfig } +// FuncValueImplementation is an enum for the particular implementations of Go +// func values. +type FuncValueImplementation int + +// These constants describe the various possible implementations of Go func +// values. +const ( + FuncValueNone FuncValueImplementation = iota + + // A func value is implemented as a pair of pointers: + // {context, function pointer} + // where the context may be a pointer to a heap-allocated struct containing + // the free variables, or it may be undef if the function being pointed to + // doesn't need a context. The function pointer is a regular function + // pointer. + FuncValueDoubleword + + // As funcValueDoubleword, but with the function pointer replaced by a + // unique ID per function signature. Function values are called by using a + // switch statement and choosing which function to call. + FuncValueSwitch +) + // Triple returns the LLVM target triple, like armv6m-none-eabi. func (c *Config) Triple() string { return c.Target.Triple @@ -111,6 +134,21 @@ func (c *Config) Scheduler() string { return "coroutines" } +// FuncImplementation picks an appropriate func value implementation for the +// target. +func (c *Config) FuncImplementation() FuncValueImplementation { + // Always pick the switch implementation, as it allows the use of blocking + // inside a function that is used as a func value. + switch c.Scheduler() { + case "none", "coroutines": + return FuncValueSwitch + case "tasks": + return FuncValueDoubleword + default: + panic("unknown scheduler type") + } +} + // PanicStrategy returns the panic strategy selected for this target. Valid // values are "print" (print the panic value, then exit) or "trap" (issue a trap // instruction). diff --git a/compiler/compiler.go b/compiler/compiler.go index 0e459be375..e5057179b3 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -34,30 +34,6 @@ func init() { // The TinyGo import path. const tinygoPath = "github.com/tinygo-org/tinygo" -// 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{} - -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", -} - type Compiler struct { *compileopts.Config mod llvm.Module @@ -156,21 +132,6 @@ func (c *Compiler) Module() llvm.Module { return c.mod } -// getFunctionsUsedInTransforms gets a list of all special functions that should be preserved during transforms and optimization. -func (c *Compiler) getFunctionsUsedInTransforms() []string { - fnused := functionsUsedInTransforms - switch c.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", c.Scheduler())) - } - return fnused -} - // Compile the given package path or .go file path. Return an error when this // fails (in any stage). func (c *Compiler) Compile(mainPath string) []error { @@ -338,14 +299,8 @@ func (c *Compiler) Compile(mainPath string) []error { realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main") realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering - // Make sure these functions are kept in tact during TinyGo transformation passes. - for _, name := range c.getFunctionsUsedInTransforms() { - fn := c.mod.NamedFunction(name) - if fn.IsNil() { - panic(fmt.Errorf("missing core function %q", name)) - } - fn.SetLinkage(llvm.ExternalLinkage) - } + // Replace callMain placeholder with actual main function. + c.mod.NamedFunction("runtime.callMain").ReplaceAllUsesWith(realMain) // Load some attributes getAttr := func(attrName string) llvm.Attribute { diff --git a/compiler/func.go b/compiler/func.go index c7c9f9ffab..b79b17f822 100644 --- a/compiler/func.go +++ b/compiler/func.go @@ -6,53 +6,20 @@ package compiler import ( "go/types" + "github.com/tinygo-org/tinygo/compileopts" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) -type funcValueImplementation int - -const ( - funcValueNone funcValueImplementation = iota - - // A func value is implemented as a pair of pointers: - // {context, function pointer} - // where the context may be a pointer to a heap-allocated struct containing - // the free variables, or it may be undef if the function being pointed to - // doesn't need a context. The function pointer is a regular function - // pointer. - funcValueDoubleword - - // As funcValueDoubleword, but with the function pointer replaced by a - // unique ID per function signature. Function values are called by using a - // switch statement and choosing which function to call. - funcValueSwitch -) - -// funcImplementation picks an appropriate func value implementation for the -// target. -func (c *Compiler) funcImplementation() funcValueImplementation { - // Always pick the switch implementation, as it allows the use of blocking - // inside a function that is used as a func value. - switch c.Scheduler() { - case "none", "coroutines": - return funcValueSwitch - case "tasks": - return funcValueDoubleword - default: - panic("unknown scheduler type") - } -} - // createFuncValue creates a function value from a raw function pointer with no // context. func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value { var funcValueScalar llvm.Value - switch c.funcImplementation() { - case funcValueDoubleword: + switch c.FuncImplementation() { + case compileopts.FuncValueDoubleword: // Closure is: {context, function pointer} funcValueScalar = funcPtr - case funcValueSwitch: + case compileopts.FuncValueSwitch: sigGlobal := c.getTypeCode(sig) funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature" funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName) @@ -94,10 +61,10 @@ func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value { // value. This may be an expensive operation. func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) { context = c.builder.CreateExtractValue(funcValue, 0, "") - switch c.funcImplementation() { - case funcValueDoubleword: + switch c.FuncImplementation() { + case compileopts.FuncValueDoubleword: funcPtr = c.builder.CreateExtractValue(funcValue, 1, "") - case funcValueSwitch: + case compileopts.FuncValueSwitch: llvmSig := c.getRawFuncType(sig) sigGlobal := c.getTypeCode(sig) funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "") @@ -110,11 +77,11 @@ func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) ( // getFuncType returns the type of a func value given a signature. func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type { - switch c.funcImplementation() { - case funcValueDoubleword: + switch c.FuncImplementation() { + case compileopts.FuncValueDoubleword: rawPtr := c.getRawFuncType(typ) return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false) - case funcValueSwitch: + case compileopts.FuncValueSwitch: return c.getLLVMRuntimeType("funcValue") default: panic("unimplemented func value variant") diff --git a/compiler/check.go b/compiler/ircheck/check.go similarity index 81% rename from compiler/check.go rename to compiler/ircheck/check.go index 5372b1efcf..bbedc6273a 100644 --- a/compiler/check.go +++ b/compiler/ircheck/check.go @@ -1,7 +1,8 @@ -package compiler - -// This file implements a set of sanity checks for the IR that is generated. -// It can catch some mistakes that LLVM's verifier cannot. +// Package ircheck implements a checker for LLVM IR, that goes a bit further +// than the regular LLVM IR verifier. Note that it checks different things, so +// this is not a replacement for the LLVM verifier but does catch things that +// the LLVM verifier doesn't catch. +package ircheck import ( "errors" @@ -10,7 +11,11 @@ import ( "tinygo.org/x/go-llvm" ) -func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error { +type checker struct { + ctx llvm.Context +} + +func (c *checker) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error { // prevent infinite recursion for self-referential types if _, ok := checked[t]; ok { return nil @@ -81,7 +86,7 @@ func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specia return nil } -func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error { +func (c *checker) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error { // check type if err := c.checkType(v.Type(), types, specials); err != nil { return fmt.Errorf("failed to verify type of value: %s", err.Error()) @@ -95,7 +100,7 @@ func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specia return nil } -func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error { +func (c *checker) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error { // check value properties if err := c.checkValue(inst, types, specials); err != nil { return errorAt(inst, err.Error()) @@ -129,7 +134,7 @@ func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{ return nil } -func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error { +func (c *checker) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error { // check basic block value and type var errs []error if err := c.checkValue(bb.AsValue(), types, specials); err != nil { @@ -146,7 +151,7 @@ func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struc return errs } -func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error { +func (c *checker) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error { // check function value and type var errs []error if err := c.checkValue(fn, types, specials); err != nil { @@ -161,26 +166,25 @@ func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, sp return errs } -func (c *Compiler) checkModule() []error { +// Module checks the given module and returns a slice of error, if there are +// any. +func Module(mod llvm.Module) []error { // check for any context mismatches var errs []error - switch { - case c.mod.Context() == c.ctx: - // this is correct - case c.mod.Context() == llvm.GlobalContext(): + c := checker{ + ctx: mod.Context(), + } + if c.ctx == llvm.GlobalContext() { // somewhere we accidentally used the global context instead of a real context errs = append(errs, errors.New("module uses global context")) - default: - // we used some other context by accident - errs = append(errs, fmt.Errorf("module uses context %v instead of the main context %v", c.mod.Context(), c.ctx)) } types := map[llvm.Type]struct{}{} specials := map[llvm.TypeKind]llvm.Type{} - for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { + for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { errs = append(errs, c.checkFunction(fn, types, specials)...) } - for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) { + for g := mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) { if err := c.checkValue(g, types, specials); err != nil { errs = append(errs, fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error())) } diff --git a/compiler/ircheck/errors.go b/compiler/ircheck/errors.go new file mode 100644 index 0000000000..d2a013933e --- /dev/null +++ b/compiler/ircheck/errors.go @@ -0,0 +1,48 @@ +package ircheck + +import ( + "go/scanner" + "go/token" + "path/filepath" + + "tinygo.org/x/go-llvm" +) + +// errorAt returns an error value at the location of the instruction. +// The location information may not be complete as it depends on debug +// information in the IR. +func errorAt(inst llvm.Value, msg string) scanner.Error { + return scanner.Error{ + Pos: getPosition(inst), + Msg: msg, + } +} + +// getPosition returns the position information for the given value, as far as +// it is available. +func getPosition(val llvm.Value) token.Position { + if !val.IsAInstruction().IsNil() { + loc := val.InstructionDebugLoc() + if loc.IsNil() { + return token.Position{} + } + file := loc.LocationScope().ScopeFile() + return token.Position{ + Filename: filepath.Join(file.FileDirectory(), file.FileFilename()), + Line: int(loc.LocationLine()), + Column: int(loc.LocationColumn()), + } + } else if !val.IsAFunction().IsNil() { + loc := val.Subprogram() + if loc.IsNil() { + return token.Position{} + } + file := loc.ScopeFile() + return token.Position{ + Filename: filepath.Join(file.FileDirectory(), file.FileFilename()), + Line: int(loc.SubprogramLine()), + } + } else { + return token.Position{} + } +} diff --git a/compiler/optimizer.go b/compiler/optimizer.go deleted file mode 100644 index 5402fa18d1..0000000000 --- a/compiler/optimizer.go +++ /dev/null @@ -1,183 +0,0 @@ -package compiler - -import ( - "errors" - - "github.com/tinygo-org/tinygo/transform" - "tinygo.org/x/go-llvm" -) - -// Run the LLVM optimizer over the module. -// The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold. -func (c *Compiler) Optimize(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() - - if c.PanicStrategy() == "trap" { - transform.ReplacePanicsWithTrap(c.mod) // -panic=trap - } - - // run a check of all of our code - if c.VerifyIR() { - errs := c.checkModule() - if errs != nil { - return errs - } - } - - // Replace callMain placeholder with actual main function. - c.mod.NamedFunction("runtime.callMain").ReplaceAllUsesWith(c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")) - - // Run function passes for each function. - funcPasses := llvm.NewFunctionPassManagerForModule(c.mod) - defer funcPasses.Dispose() - builder.PopulateFunc(funcPasses) - funcPasses.InitializeFunc() - for fn := c.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(c.mod) - - // Run Go-specific optimization passes. - transform.OptimizeMaps(c.mod) - transform.OptimizeStringToBytes(c.mod) - transform.OptimizeAllocs(c.mod) - transform.LowerInterfaces(c.mod) - - errs := transform.LowerInterrupts(c.mod) - if len(errs) > 0 { - return errs - } - - if c.funcImplementation() == funcValueSwitch { - transform.LowerFuncValues(c.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(c.mod) - - // Run TinyGo-specific interprocedural optimizations. - transform.OptimizeAllocs(c.mod) - transform.OptimizeStringToBytes(c.mod) - - // Lower runtime.isnil calls to regular nil comparisons. - isnil := c.mod.NamedFunction("runtime.isnil") - if !isnil.IsNil() { - for _, use := range getUses(isnil) { - c.builder.SetInsertPointBefore(use) - ptr := use.Operand(0) - if !ptr.IsABitCastInst().IsNil() { - ptr = ptr.Operand(0) - } - nilptr := llvm.ConstPointerNull(ptr.Type()) - icmp := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "") - use.ReplaceAllUsesWith(icmp) - use.EraseFromParentAsInstruction() - } - } - - } else { - // Must be run at any optimization level. - transform.LowerInterfaces(c.mod) - if c.funcImplementation() == funcValueSwitch { - transform.LowerFuncValues(c.mod) - } - errs := transform.LowerInterrupts(c.mod) - if len(errs) > 0 { - return errs - } - } - - // Lower async implementations. - switch c.Scheduler() { - case "coroutines": - // Lower async as coroutines. - err := transform.LowerCoroutines(c.mod, c.NeedsStackObjects()) - if err != nil { - return []error{err} - } - case "tasks": - // No transformations necessary. - case "none": - // Check for any goroutine starts. - if start := c.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 c.VerifyIR() { - if errs := c.checkModule(); errs != nil { - return errs - } - } - if err := c.Verify(); 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 := c.ctx.CreateEnumAttribute(kind, 0) - for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { - fn.AddFunctionAttr(attr) - } - } - - // After TinyGo-specific transforms have finished, undo exporting these functions. - for _, name := range c.getFunctionsUsedInTransforms() { - fn := c.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 := c.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(c.mod) - - hasGCPass := transform.AddGlobalsBitmap(c.mod) - hasGCPass = transform.MakeGCStackSlots(c.mod) || hasGCPass - if hasGCPass { - if err := c.Verify(); err != nil { - return []error{errors.New("GC pass caused a verification failure")} - } - } - - return nil -} diff --git a/transform/coroutines.go b/transform/coroutines.go index 5df2d74609..d1609b6516 100644 --- a/transform/coroutines.go +++ b/transform/coroutines.go @@ -5,8 +5,9 @@ package transform import ( "errors" - "github.com/tinygo-org/tinygo/compiler/llvmutil" "strconv" + + "github.com/tinygo-org/tinygo/compiler/llvmutil" "tinygo.org/x/go-llvm" ) diff --git a/transform/optimizer.go b/transform/optimizer.go new file mode 100644 index 0000000000..71bf4d6176 --- /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 +}