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  }