github.com/aykevl/tinygo@v0.5.0/compiler/optimizer.go (about) 1 package compiler 2 3 import ( 4 "errors" 5 6 "tinygo.org/x/go-llvm" 7 ) 8 9 // Run the LLVM optimizer over the module. 10 // The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold. 11 func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) error { 12 builder := llvm.NewPassManagerBuilder() 13 defer builder.Dispose() 14 builder.SetOptLevel(optLevel) 15 builder.SetSizeLevel(sizeLevel) 16 if inlinerThreshold != 0 { 17 builder.UseInlinerWithThreshold(inlinerThreshold) 18 } 19 builder.AddCoroutinePassesToExtensionPoints() 20 21 // Run function passes for each function. 22 funcPasses := llvm.NewFunctionPassManagerForModule(c.mod) 23 defer funcPasses.Dispose() 24 builder.PopulateFunc(funcPasses) 25 funcPasses.InitializeFunc() 26 for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { 27 funcPasses.RunFunc(fn) 28 } 29 funcPasses.FinalizeFunc() 30 31 if optLevel > 0 { 32 // Run some preparatory passes for the Go optimizer. 33 goPasses := llvm.NewPassManager() 34 defer goPasses.Dispose() 35 goPasses.AddGlobalOptimizerPass() 36 goPasses.AddConstantPropagationPass() 37 goPasses.AddAggressiveDCEPass() 38 goPasses.AddFunctionAttrsPass() 39 goPasses.Run(c.mod) 40 41 // Run Go-specific optimization passes. 42 c.OptimizeMaps() 43 c.OptimizeStringToBytes() 44 c.OptimizeAllocs() 45 c.LowerInterfaces() 46 c.LowerFuncValues() 47 48 // After interfaces are lowered, there are many more opportunities for 49 // interprocedural optimizations. To get them to work, function 50 // attributes have to be updated first. 51 goPasses.Run(c.mod) 52 53 // Run TinyGo-specific interprocedural optimizations. 54 c.OptimizeAllocs() 55 c.OptimizeStringToBytes() 56 57 // Lower runtime.isnil calls to regular nil comparisons. 58 isnil := c.mod.NamedFunction("runtime.isnil") 59 if !isnil.IsNil() { 60 for _, use := range getUses(isnil) { 61 c.builder.SetInsertPointBefore(use) 62 ptr := use.Operand(0) 63 if !ptr.IsABitCastInst().IsNil() { 64 ptr = ptr.Operand(0) 65 } 66 nilptr := llvm.ConstPointerNull(ptr.Type()) 67 icmp := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "") 68 use.ReplaceAllUsesWith(icmp) 69 use.EraseFromParentAsInstruction() 70 } 71 } 72 73 err := c.LowerGoroutines() 74 if err != nil { 75 return err 76 } 77 } else { 78 // Must be run at any optimization level. 79 c.LowerInterfaces() 80 c.LowerFuncValues() 81 err := c.LowerGoroutines() 82 if err != nil { 83 return err 84 } 85 } 86 if err := c.Verify(); err != nil { 87 return errors.New("optimizations caused a verification failure") 88 } 89 90 if sizeLevel >= 2 { 91 // Set the "optsize" attribute to make slightly smaller binaries at the 92 // cost of some performance. 93 kind := llvm.AttributeKindID("optsize") 94 attr := c.ctx.CreateEnumAttribute(kind, 0) 95 for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { 96 fn.AddFunctionAttr(attr) 97 } 98 } 99 100 // Run function passes again, because without it, llvm.coro.size.i32() 101 // doesn't get lowered. 102 for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { 103 funcPasses.RunFunc(fn) 104 } 105 funcPasses.FinalizeFunc() 106 107 // Run module passes. 108 modPasses := llvm.NewPassManager() 109 defer modPasses.Dispose() 110 builder.Populate(modPasses) 111 modPasses.Run(c.mod) 112 113 return nil 114 } 115 116 // Eliminate created but not used maps. 117 // 118 // In the future, this should statically allocate created but never modified 119 // maps. This has not yet been implemented, however. 120 func (c *Compiler) OptimizeMaps() { 121 hashmapMake := c.mod.NamedFunction("runtime.hashmapMake") 122 if hashmapMake.IsNil() { 123 // nothing to optimize 124 return 125 } 126 127 hashmapBinarySet := c.mod.NamedFunction("runtime.hashmapBinarySet") 128 hashmapStringSet := c.mod.NamedFunction("runtime.hashmapStringSet") 129 130 for _, makeInst := range getUses(hashmapMake) { 131 updateInsts := []llvm.Value{} 132 unknownUses := false // are there any uses other than setting a value? 133 134 for _, use := range getUses(makeInst) { 135 if use := use.IsACallInst(); !use.IsNil() { 136 switch use.CalledValue() { 137 case hashmapBinarySet, hashmapStringSet: 138 updateInsts = append(updateInsts, use) 139 default: 140 unknownUses = true 141 } 142 } else { 143 unknownUses = true 144 } 145 } 146 147 if !unknownUses { 148 // This map can be entirely removed, as it is only created but never 149 // used. 150 for _, inst := range updateInsts { 151 inst.EraseFromParentAsInstruction() 152 } 153 makeInst.EraseFromParentAsInstruction() 154 } 155 } 156 } 157 158 // Transform runtime.stringToBytes(...) calls into const []byte slices whenever 159 // possible. This optimizes the following pattern: 160 // w.Write([]byte("foo")) 161 // where Write does not store to the slice. 162 func (c *Compiler) OptimizeStringToBytes() { 163 stringToBytes := c.mod.NamedFunction("runtime.stringToBytes") 164 if stringToBytes.IsNil() { 165 // nothing to optimize 166 return 167 } 168 169 for _, call := range getUses(stringToBytes) { 170 strptr := call.Operand(0) 171 strlen := call.Operand(1) 172 173 // strptr is always constant because strings are always constant. 174 175 convertedAllUses := true 176 for _, use := range getUses(call) { 177 nilValue := llvm.Value{} 178 if use.IsAExtractValueInst() == nilValue { 179 convertedAllUses = false 180 continue 181 } 182 switch use.Type().TypeKind() { 183 case llvm.IntegerTypeKind: 184 // A length (len or cap). Propagate the length value. 185 use.ReplaceAllUsesWith(strlen) 186 use.EraseFromParentAsInstruction() 187 case llvm.PointerTypeKind: 188 // The string pointer itself. 189 if !c.isReadOnly(use) { 190 convertedAllUses = false 191 continue 192 } 193 use.ReplaceAllUsesWith(strptr) 194 use.EraseFromParentAsInstruction() 195 default: 196 // should not happen 197 panic("unknown return type of runtime.stringToBytes: " + use.Type().String()) 198 } 199 } 200 if convertedAllUses { 201 // Call to runtime.stringToBytes can be eliminated: both the input 202 // and the output is constant. 203 call.EraseFromParentAsInstruction() 204 } 205 } 206 } 207 208 // Basic escape analysis: translate runtime.alloc calls into alloca 209 // instructions. 210 func (c *Compiler) OptimizeAllocs() { 211 allocator := c.mod.NamedFunction("runtime.alloc") 212 if allocator.IsNil() { 213 // nothing to optimize 214 return 215 } 216 217 heapallocs := getUses(allocator) 218 for _, heapalloc := range heapallocs { 219 nilValue := llvm.Value{} 220 if heapalloc.Operand(0).IsAConstant() == nilValue { 221 // Do not allocate variable length arrays on the stack. 222 continue 223 } 224 size := heapalloc.Operand(0).ZExtValue() 225 if size > 256 { 226 // The maximum value for a stack allocation. 227 // TODO: tune this, this is just a random value. 228 continue 229 } 230 231 // In general the pattern is: 232 // %0 = call i8* @runtime.alloc(i32 %size) 233 // %1 = bitcast i8* %0 to type* 234 // (use %1 only) 235 // But the bitcast might sometimes be dropped when allocating an *i8. 236 // The 'bitcast' variable below is thus usually a bitcast of the 237 // heapalloc but not always. 238 bitcast := heapalloc // instruction that creates the value 239 if uses := getUses(heapalloc); len(uses) == 1 && uses[0].IsABitCastInst() != nilValue { 240 // getting only bitcast use 241 bitcast = uses[0] 242 } 243 if !c.doesEscape(bitcast) { 244 // Insert alloca in the entry block. Do it here so that mem2reg can 245 // promote it to a SSA value. 246 fn := bitcast.InstructionParent().Parent() 247 c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction()) 248 alignment := c.targetData.ABITypeAlignment(c.i8ptrType) 249 sizeInWords := (size + uint64(alignment) - 1) / uint64(alignment) 250 allocaType := llvm.ArrayType(c.ctx.IntType(alignment*8), int(sizeInWords)) 251 alloca := c.builder.CreateAlloca(allocaType, "stackalloc.alloca") 252 zero, _ := c.getZeroValue(alloca.Type().ElementType()) 253 c.builder.CreateStore(zero, alloca) 254 stackalloc := c.builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc") 255 bitcast.ReplaceAllUsesWith(stackalloc) 256 if heapalloc != bitcast { 257 bitcast.EraseFromParentAsInstruction() 258 } 259 heapalloc.EraseFromParentAsInstruction() 260 } 261 } 262 } 263 264 // Very basic escape analysis. 265 func (c *Compiler) doesEscape(value llvm.Value) bool { 266 uses := getUses(value) 267 for _, use := range uses { 268 nilValue := llvm.Value{} 269 if use.IsAGetElementPtrInst() != nilValue { 270 if c.doesEscape(use) { 271 return true 272 } 273 } else if use.IsABitCastInst() != nilValue { 274 // A bitcast escapes if the casted-to value escapes. 275 if c.doesEscape(use) { 276 return true 277 } 278 } else if use.IsALoadInst() != nilValue { 279 // Load does not escape. 280 } else if use.IsAStoreInst() != nilValue { 281 // Store only escapes when the value is stored to, not when the 282 // value is stored into another value. 283 if use.Operand(0) == value { 284 return true 285 } 286 } else if use.IsACallInst() != nilValue { 287 // Call only escapes when the (pointer) parameter is not marked 288 // "nocapture". This flag means that the parameter does not escape 289 // the give function. 290 if use.CalledValue().IsAFunction() != nilValue { 291 if use.CalledValue().IsDeclaration() { 292 // Kind of dirty: assume external functions don't let 293 // pointers escape. 294 // TODO: introduce //go:noescape that sets the 'nocapture' 295 // flag on each input parameter. 296 continue 297 } 298 } 299 if !c.hasFlag(use, value, "nocapture") { 300 return true 301 } 302 } else if use.IsAICmpInst() != nilValue { 303 // Comparing pointers don't let the pointer escape. 304 // This is often a compiler-inserted nil check. 305 } else { 306 // Unknown instruction, might escape. 307 return true 308 } 309 } 310 311 // does not escape 312 return false 313 } 314 315 // Check whether the given value (which is of pointer type) is never stored to. 316 func (c *Compiler) isReadOnly(value llvm.Value) bool { 317 uses := getUses(value) 318 for _, use := range uses { 319 nilValue := llvm.Value{} 320 if use.IsAGetElementPtrInst() != nilValue { 321 if !c.isReadOnly(use) { 322 return false 323 } 324 } else if use.IsACallInst() != nilValue { 325 if !c.hasFlag(use, value, "readonly") { 326 return false 327 } 328 } else { 329 // Unknown instruction, might not be readonly. 330 return false 331 } 332 } 333 return true 334 } 335 336 // Check whether all uses of this param as parameter to the call have the given 337 // flag. In most cases, there will only be one use but a function could take the 338 // same parameter twice, in which case both must have the flag. 339 // A flag can be any enum flag, like "readonly". 340 func (c *Compiler) hasFlag(call, param llvm.Value, kind string) bool { 341 fn := call.CalledValue() 342 nilValue := llvm.Value{} 343 if fn.IsAFunction() == nilValue { 344 // This is not a function but something else, like a function pointer. 345 return false 346 } 347 kindID := llvm.AttributeKindID(kind) 348 for i := 0; i < fn.ParamsCount(); i++ { 349 if call.Operand(i) != param { 350 // This is not the parameter we're checking. 351 continue 352 } 353 index := i + 1 // param attributes start at 1 354 attr := fn.GetEnumAttributeAtIndex(index, kindID) 355 nilAttribute := llvm.Attribute{} 356 if attr == nilAttribute { 357 // At least one parameter doesn't have the flag (there may be 358 // multiple). 359 return false 360 } 361 } 362 return true 363 }