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 _, 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 if config.IsStandaloneRuntime() {
137 // Standalone runtime: keep all symbols external for cross-module linking.
138 // Skip internalization and globaldce entirely.
139 return nil
140 }
141
142 // After Moxie-specific transforms have finished, undo exporting these functions.
143 for _, name := range functionsUsedInTransforms {
144 fn := mod.NamedFunction(name)
145 if fn.IsNil() || fn.IsDeclaration() {
146 continue
147 }
148 fn.SetLinkage(llvm.InternalLinkage)
149 }
150
151 // No ThinLTO pre-link passes - we emit native objects and link without LTO.
152 // Run a basic cleanup pass instead of the full thinlto-pre-link pipeline.
153 po := llvm.NewPassBuilderOptions()
154 defer po.Dispose()
155 if speedLevel > 0 {
156 err := mod.RunPasses("globaldce", llvm.TargetMachine{}, po)
157 if err != nil {
158 return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
159 }
160 }
161
162 return nil
163 }
164
165 // functionsUsedInTransform is a list of function symbols that may be used
166 // during Moxie optimization passes so they have to be marked as external
167 // linkage until all Moxie passes have finished.
168 var functionsUsedInTransforms = []string{
169 "runtime.alloc",
170 "runtime.nilPanic",
171 }
172