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