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