github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/transform/optimizer.go (about)

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