github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/transform/optimizer.go (about) 1 package transform 2 3 import ( 4 "errors" 5 "fmt" 6 "go/token" 7 "os" 8 9 "github.com/tinygo-org/tinygo/compileopts" 10 "github.com/tinygo-org/tinygo/compiler/ircheck" 11 "tinygo.org/x/go-llvm" 12 ) 13 14 // OptimizePackage runs optimization passes over the LLVM module for the given 15 // Go package. 16 func OptimizePackage(mod llvm.Module, config *compileopts.Config) { 17 _, speedLevel, _ := config.OptLevel() 18 19 // Run TinyGo-specific optimization passes. 20 if speedLevel > 0 { 21 OptimizeMaps(mod) 22 } 23 } 24 25 // Optimize runs a number of optimization and transformation passes over the 26 // given module. Some passes are specific to TinyGo, others are generic LLVM 27 // passes. 28 // 29 // Please note that some optimizations are not optional, thus Optimize must 30 // alwasy be run before emitting machine code. 31 func Optimize(mod llvm.Module, config *compileopts.Config) []error { 32 optLevel, speedLevel, _ := config.OptLevel() 33 34 // Make sure these functions are kept in tact during TinyGo transformation passes. 35 for _, name := range functionsUsedInTransforms { 36 fn := mod.NamedFunction(name) 37 if fn.IsNil() { 38 panic(fmt.Errorf("missing core function %q", name)) 39 } 40 fn.SetLinkage(llvm.ExternalLinkage) 41 } 42 43 // run a check of all of our code 44 if config.VerifyIR() { 45 errs := ircheck.Module(mod) 46 if errs != nil { 47 return errs 48 } 49 } 50 51 if speedLevel > 0 { 52 // Run some preparatory passes for the Go optimizer. 53 po := llvm.NewPassBuilderOptions() 54 defer po.Dispose() 55 err := mod.RunPasses("globaldce,globalopt,ipsccp,instcombine,adce,function-attrs", llvm.TargetMachine{}, po) 56 if err != nil { 57 return []error{fmt.Errorf("could not build pass pipeline: %w", err)} 58 } 59 60 // Run TinyGo-specific optimization passes. 61 OptimizeStringToBytes(mod) 62 OptimizeReflectImplements(mod) 63 maxStackSize := config.MaxStackAlloc() 64 OptimizeAllocs(mod, nil, maxStackSize, nil) 65 err = LowerInterfaces(mod, config) 66 if err != nil { 67 return []error{err} 68 } 69 70 errs := LowerInterrupts(mod) 71 if len(errs) > 0 { 72 return errs 73 } 74 75 // After interfaces are lowered, there are many more opportunities for 76 // interprocedural optimizations. To get them to work, function 77 // attributes have to be updated first. 78 err = mod.RunPasses("globaldce,globalopt,ipsccp,instcombine,adce,function-attrs", llvm.TargetMachine{}, po) 79 if err != nil { 80 return []error{fmt.Errorf("could not build pass pipeline: %w", err)} 81 } 82 83 // Run TinyGo-specific interprocedural optimizations. 84 OptimizeAllocs(mod, config.Options.PrintAllocs, maxStackSize, func(pos token.Position, msg string) { 85 fmt.Fprintln(os.Stderr, pos.String()+": "+msg) 86 }) 87 OptimizeStringToBytes(mod) 88 OptimizeStringEqual(mod) 89 90 } else { 91 // Must be run at any optimization level. 92 err := LowerInterfaces(mod, config) 93 if err != nil { 94 return []error{err} 95 } 96 errs := LowerInterrupts(mod) 97 if len(errs) > 0 { 98 return errs 99 } 100 101 // Clean up some leftover symbols of the previous transformations. 102 po := llvm.NewPassBuilderOptions() 103 defer po.Dispose() 104 err = mod.RunPasses("globaldce", llvm.TargetMachine{}, po) 105 if err != nil { 106 return []error{fmt.Errorf("could not build pass pipeline: %w", err)} 107 } 108 } 109 110 if config.Scheduler() == "none" { 111 // Check for any goroutine starts. 112 if start := mod.NamedFunction("internal/task.start"); !start.IsNil() && len(getUses(start)) > 0 { 113 errs := []error{} 114 for _, call := range getUses(start) { 115 errs = append(errs, errorAt(call, "attempted to start a goroutine without a scheduler")) 116 } 117 return errs 118 } 119 } 120 121 if config.VerifyIR() { 122 if errs := ircheck.Module(mod); errs != nil { 123 return errs 124 } 125 } 126 if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { 127 return []error{errors.New("optimizations caused a verification failure")} 128 } 129 130 // After TinyGo-specific transforms have finished, undo exporting these functions. 131 for _, name := range functionsUsedInTransforms { 132 fn := mod.NamedFunction(name) 133 if fn.IsNil() || fn.IsDeclaration() { 134 continue 135 } 136 fn.SetLinkage(llvm.InternalLinkage) 137 } 138 139 // Run the default pass pipeline. 140 // TODO: set the PrepareForThinLTO flag somehow. 141 po := llvm.NewPassBuilderOptions() 142 defer po.Dispose() 143 passes := fmt.Sprintf("default<%s>", optLevel) 144 err := mod.RunPasses(passes, llvm.TargetMachine{}, po) 145 if err != nil { 146 return []error{fmt.Errorf("could not build pass pipeline: %w", err)} 147 } 148 149 hasGCPass := MakeGCStackSlots(mod) 150 if hasGCPass { 151 if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { 152 return []error{errors.New("GC pass caused a verification failure")} 153 } 154 } 155 156 return nil 157 } 158 159 // functionsUsedInTransform is a list of function symbols that may be used 160 // during TinyGo optimization passes so they have to be marked as external 161 // linkage until all TinyGo passes have finished. 162 var functionsUsedInTransforms = []string{ 163 "runtime.alloc", 164 "runtime.free", 165 "runtime.nilPanic", 166 }