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

     1  // Package compileopts contains the configuration for a single to-be-built
     2  // binary.
     3  package compileopts
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  	"strings"
    12  
    13  	"github.com/google/shlex"
    14  	"github.com/tinygo-org/tinygo/goenv"
    15  )
    16  
    17  // Config keeps all configuration affecting the build in a single struct.
    18  type Config struct {
    19  	Options        *Options
    20  	Target         *TargetSpec
    21  	GoMinorVersion int
    22  	TestConfig     TestConfig
    23  }
    24  
    25  // Triple returns the LLVM target triple, like armv6m-unknown-unknown-eabi.
    26  func (c *Config) Triple() string {
    27  	return c.Target.Triple
    28  }
    29  
    30  // CPU returns the LLVM CPU name, like atmega328p or arm7tdmi. It may return an
    31  // empty string if the CPU name is not known.
    32  func (c *Config) CPU() string {
    33  	return c.Target.CPU
    34  }
    35  
    36  // Features returns a list of features this CPU supports. For example, for a
    37  // RISC-V processor, that could be "+a,+c,+m". For many targets, an empty list
    38  // will be returned.
    39  func (c *Config) Features() string {
    40  	if c.Target.Features == "" {
    41  		return c.Options.LLVMFeatures
    42  	}
    43  	if c.Options.LLVMFeatures == "" {
    44  		return c.Target.Features
    45  	}
    46  	return c.Target.Features + "," + c.Options.LLVMFeatures
    47  }
    48  
    49  // ABI returns the -mabi= flag for this target (like -mabi=lp64). A zero-length
    50  // string is returned if the target doesn't specify an ABI.
    51  func (c *Config) ABI() string {
    52  	return c.Target.ABI
    53  }
    54  
    55  // GOOS returns the GOOS of the target. This might not always be the actual OS:
    56  // for example, bare-metal targets will usually pretend to be linux to get the
    57  // standard library to compile.
    58  func (c *Config) GOOS() string {
    59  	return c.Target.GOOS
    60  }
    61  
    62  // GOARCH returns the GOARCH of the target. This might not always be the actual
    63  // archtecture: for example, the AVR target is not supported by the Go standard
    64  // library so such targets will usually pretend to be linux/arm.
    65  func (c *Config) GOARCH() string {
    66  	return c.Target.GOARCH
    67  }
    68  
    69  // GOARM will return the GOARM environment variable given to the compiler when
    70  // building a program.
    71  func (c *Config) GOARM() string {
    72  	return c.Options.GOARM
    73  }
    74  
    75  // BuildTags returns the complete list of build tags used during this build.
    76  func (c *Config) BuildTags() []string {
    77  	tags := append(c.Target.BuildTags, []string{
    78  		"tinygo",                                     // that's the compiler
    79  		"purego",                                     // to get various crypto packages to work
    80  		"math_big_pure_go",                           // to get math/big to work
    81  		"gc." + c.GC(), "scheduler." + c.Scheduler(), // used inside the runtime package
    82  		"serial." + c.Serial()}...) // used inside the machine package
    83  	for i := 1; i <= c.GoMinorVersion; i++ {
    84  		tags = append(tags, fmt.Sprintf("go1.%d", i))
    85  	}
    86  	tags = append(tags, c.Options.Tags...)
    87  	return tags
    88  }
    89  
    90  // GC returns the garbage collection strategy in use on this platform. Valid
    91  // values are "none", "leaking", "conservative" and "precise".
    92  func (c *Config) GC() string {
    93  	if c.Options.GC != "" {
    94  		return c.Options.GC
    95  	}
    96  	if c.Target.GC != "" {
    97  		return c.Target.GC
    98  	}
    99  	return "conservative"
   100  }
   101  
   102  // NeedsStackObjects returns true if the compiler should insert stack objects
   103  // that can be traced by the garbage collector.
   104  func (c *Config) NeedsStackObjects() bool {
   105  	switch c.GC() {
   106  	case "conservative", "custom", "precise":
   107  		for _, tag := range c.BuildTags() {
   108  			if tag == "tinygo.wasm" {
   109  				return true
   110  			}
   111  		}
   112  
   113  		return false
   114  	default:
   115  		return false
   116  	}
   117  }
   118  
   119  // Scheduler returns the scheduler implementation. Valid values are "none",
   120  // "asyncify" and "tasks".
   121  func (c *Config) Scheduler() string {
   122  	if c.Options.Scheduler != "" {
   123  		return c.Options.Scheduler
   124  	}
   125  	if c.Target.Scheduler != "" {
   126  		return c.Target.Scheduler
   127  	}
   128  	// Fall back to none.
   129  	return "none"
   130  }
   131  
   132  // Serial returns the serial implementation for this build configuration: uart,
   133  // usb (meaning USB-CDC), or none.
   134  func (c *Config) Serial() string {
   135  	if c.Options.Serial != "" {
   136  		return c.Options.Serial
   137  	}
   138  	if c.Target.Serial != "" {
   139  		return c.Target.Serial
   140  	}
   141  	return "none"
   142  }
   143  
   144  // OptLevels returns the optimization level (0-2), size level (0-2), and inliner
   145  // threshold as used in the LLVM optimization pipeline.
   146  func (c *Config) OptLevel() (level string, speedLevel, sizeLevel int) {
   147  	switch c.Options.Opt {
   148  	case "none", "0":
   149  		return "O0", 0, 0
   150  	case "1":
   151  		return "O1", 1, 0
   152  	case "2":
   153  		return "O2", 2, 0
   154  	case "s":
   155  		return "Os", 2, 1
   156  	case "z":
   157  		return "Oz", 2, 2 // default
   158  	default:
   159  		// This is not shown to the user: valid choices are already checked as
   160  		// part of Options.Verify(). It is here as a sanity check.
   161  		panic("unknown optimization level: -opt=" + c.Options.Opt)
   162  	}
   163  }
   164  
   165  // PanicStrategy returns the panic strategy selected for this target. Valid
   166  // values are "print" (print the panic value, then exit) or "trap" (issue a trap
   167  // instruction).
   168  func (c *Config) PanicStrategy() string {
   169  	return c.Options.PanicStrategy
   170  }
   171  
   172  // AutomaticStackSize returns whether goroutine stack sizes should be determined
   173  // automatically at compile time, if possible. If it is false, no attempt is
   174  // made.
   175  func (c *Config) AutomaticStackSize() bool {
   176  	if c.Target.AutoStackSize != nil && c.Scheduler() == "tasks" {
   177  		return *c.Target.AutoStackSize
   178  	}
   179  	return false
   180  }
   181  
   182  // StackSize returns the default stack size to be used for goroutines, if the
   183  // stack size could not be determined automatically at compile time.
   184  func (c *Config) StackSize() uint64 {
   185  	if c.Options.StackSize != 0 {
   186  		return c.Options.StackSize
   187  	}
   188  	return c.Target.DefaultStackSize
   189  }
   190  
   191  // MaxStackAlloc returns the size of the maximum allocation to put on the stack vs heap.
   192  func (c *Config) MaxStackAlloc() uint64 {
   193  	if c.StackSize() > 32*1024 {
   194  		return 1024
   195  	}
   196  
   197  	return 256
   198  }
   199  
   200  // RP2040BootPatch returns whether the RP2040 boot patch should be applied that
   201  // calculates and patches in the checksum for the 2nd stage bootloader.
   202  func (c *Config) RP2040BootPatch() bool {
   203  	if c.Target.RP2040BootPatch != nil {
   204  		return *c.Target.RP2040BootPatch
   205  	}
   206  	return false
   207  }
   208  
   209  // MuslArchitecture returns the architecture name as used in musl libc. It is
   210  // usually the same as the first part of the LLVM triple, but not always.
   211  func MuslArchitecture(triple string) string {
   212  	arch := strings.Split(triple, "-")[0]
   213  	if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") {
   214  		arch = "arm"
   215  	}
   216  	return arch
   217  }
   218  
   219  // LibcPath returns the path to the libc directory. The libc path will be either
   220  // a precompiled libc shipped with a TinyGo build, or a libc path in the cache
   221  // directory (which might not yet be built).
   222  func (c *Config) LibcPath(name string) (path string, precompiled bool) {
   223  	archname := c.Triple()
   224  	if c.CPU() != "" {
   225  		archname += "-" + c.CPU()
   226  	}
   227  	if c.ABI() != "" {
   228  		archname += "-" + c.ABI()
   229  	}
   230  
   231  	// Try to load a precompiled library.
   232  	precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", archname, name)
   233  	if _, err := os.Stat(precompiledDir); err == nil {
   234  		// Found a precompiled library for this OS/architecture. Return the path
   235  		// directly.
   236  		return precompiledDir, true
   237  	}
   238  
   239  	// No precompiled library found. Determine the path name that will be used
   240  	// in the build cache.
   241  	return filepath.Join(goenv.Get("GOCACHE"), name+"-"+archname), false
   242  }
   243  
   244  // DefaultBinaryExtension returns the default extension for binaries, such as
   245  // .exe, .wasm, or no extension (depending on the target).
   246  func (c *Config) DefaultBinaryExtension() string {
   247  	parts := strings.Split(c.Triple(), "-")
   248  	if parts[0] == "wasm32" {
   249  		// WebAssembly files always have the .wasm file extension.
   250  		return ".wasm"
   251  	}
   252  	if len(parts) >= 3 && parts[2] == "windows" {
   253  		// Windows uses .exe.
   254  		return ".exe"
   255  	}
   256  	if len(parts) >= 3 && parts[2] == "unknown" {
   257  		// There appears to be a convention to use the .elf file extension for
   258  		// ELF files intended for microcontrollers. I'm not aware of the origin
   259  		// of this, it's just something that is used by many projects.
   260  		// I think it's a good tradition, so let's keep it.
   261  		return ".elf"
   262  	}
   263  	// Linux, MacOS, etc, don't use a file extension. Use it as a fallback.
   264  	return ""
   265  }
   266  
   267  // CFlags returns the flags to pass to the C compiler. This is necessary for CGo
   268  // preprocessing.
   269  func (c *Config) CFlags(libclang bool) []string {
   270  	var cflags []string
   271  	for _, flag := range c.Target.CFlags {
   272  		cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT")))
   273  	}
   274  	resourceDir := goenv.ClangResourceDir(libclang)
   275  	if resourceDir != "" {
   276  		// The resource directory contains the built-in clang headers like
   277  		// stdbool.h, stdint.h, float.h, etc.
   278  		// It is left empty if we're using an external compiler (that already
   279  		// knows these headers).
   280  		cflags = append(cflags,
   281  			"-resource-dir="+resourceDir,
   282  		)
   283  	}
   284  	switch c.Target.Libc {
   285  	case "darwin-libSystem":
   286  		root := goenv.Get("TINYGOROOT")
   287  		cflags = append(cflags,
   288  			"-nostdlibinc",
   289  			"-isystem", filepath.Join(root, "lib/macos-minimal-sdk/src/usr/include"),
   290  		)
   291  	case "picolibc":
   292  		root := goenv.Get("TINYGOROOT")
   293  		picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc")
   294  		path, _ := c.LibcPath("picolibc")
   295  		cflags = append(cflags,
   296  			"-nostdlibinc",
   297  			"-isystem", filepath.Join(path, "include"),
   298  			"-isystem", filepath.Join(picolibcDir, "include"),
   299  			"-isystem", filepath.Join(picolibcDir, "tinystdio"),
   300  		)
   301  	case "musl":
   302  		root := goenv.Get("TINYGOROOT")
   303  		path, _ := c.LibcPath("musl")
   304  		arch := MuslArchitecture(c.Triple())
   305  		cflags = append(cflags,
   306  			"-nostdlibinc",
   307  			"-isystem", filepath.Join(path, "include"),
   308  			"-isystem", filepath.Join(root, "lib", "musl", "arch", arch),
   309  			"-isystem", filepath.Join(root, "lib", "musl", "include"),
   310  		)
   311  	case "wasi-libc":
   312  		root := goenv.Get("TINYGOROOT")
   313  		cflags = append(cflags,
   314  			"-nostdlibinc",
   315  			"-isystem", root+"/lib/wasi-libc/sysroot/include")
   316  	case "wasmbuiltins":
   317  		// nothing to add (library is purely for builtins)
   318  	case "mingw-w64":
   319  		root := goenv.Get("TINYGOROOT")
   320  		path, _ := c.LibcPath("mingw-w64")
   321  		cflags = append(cflags,
   322  			"-nostdlibinc",
   323  			"-isystem", filepath.Join(path, "include"),
   324  			"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"),
   325  			"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"),
   326  			"-D_UCRT",
   327  		)
   328  	case "":
   329  		// No libc specified, nothing to add.
   330  	default:
   331  		// Incorrect configuration. This could be handled in a better way, but
   332  		// usually this will be found by developers (not by TinyGo users).
   333  		panic("unknown libc: " + c.Target.Libc)
   334  	}
   335  	// Always emit debug information. It is optionally stripped at link time.
   336  	cflags = append(cflags, "-gdwarf-4")
   337  	// Use the same optimization level as TinyGo.
   338  	cflags = append(cflags, "-O"+c.Options.Opt)
   339  	// Set the LLVM target triple.
   340  	cflags = append(cflags, "--target="+c.Triple())
   341  	// Set the -mcpu (or similar) flag.
   342  	if c.Target.CPU != "" {
   343  		if c.GOARCH() == "amd64" || c.GOARCH() == "386" {
   344  			// x86 prefers the -march flag (-mcpu is deprecated there).
   345  			cflags = append(cflags, "-march="+c.Target.CPU)
   346  		} else if strings.HasPrefix(c.Triple(), "avr") {
   347  			// AVR MCUs use -mmcu instead of -mcpu.
   348  			cflags = append(cflags, "-mmcu="+c.Target.CPU)
   349  		} else {
   350  			// The rest just uses -mcpu.
   351  			cflags = append(cflags, "-mcpu="+c.Target.CPU)
   352  		}
   353  	}
   354  	// Set the -mabi flag, if needed.
   355  	if c.ABI() != "" {
   356  		cflags = append(cflags, "-mabi="+c.ABI())
   357  	}
   358  	return cflags
   359  }
   360  
   361  // LDFlags returns the flags to pass to the linker. A few more flags are needed
   362  // (like the one for the compiler runtime), but this represents the majority of
   363  // the flags.
   364  func (c *Config) LDFlags() []string {
   365  	root := goenv.Get("TINYGOROOT")
   366  	// Merge and adjust LDFlags.
   367  	var ldflags []string
   368  	for _, flag := range c.Target.LDFlags {
   369  		ldflags = append(ldflags, strings.ReplaceAll(flag, "{root}", root))
   370  	}
   371  	ldflags = append(ldflags, "-L", root)
   372  	if c.Target.LinkerScript != "" {
   373  		ldflags = append(ldflags, "-T", c.Target.LinkerScript)
   374  	}
   375  	return ldflags
   376  }
   377  
   378  // ExtraFiles returns the list of extra files to be built and linked with the
   379  // executable. This can include extra C and assembly files.
   380  func (c *Config) ExtraFiles() []string {
   381  	return c.Target.ExtraFiles
   382  }
   383  
   384  // DumpSSA returns whether to dump Go SSA while compiling (-dumpssa flag). Only
   385  // enable this for debugging.
   386  func (c *Config) DumpSSA() bool {
   387  	return c.Options.DumpSSA
   388  }
   389  
   390  // VerifyIR returns whether to run extra checks on the IR. This is normally
   391  // disabled but enabled during testing.
   392  func (c *Config) VerifyIR() bool {
   393  	return c.Options.VerifyIR
   394  }
   395  
   396  // Debug returns whether debug (DWARF) information should be retained by the
   397  // linker. By default, debug information is retained, but it can be removed
   398  // with the -no-debug flag.
   399  func (c *Config) Debug() bool {
   400  	return c.Options.Debug
   401  }
   402  
   403  // BinaryFormat returns an appropriate binary format, based on the file
   404  // extension and the configured binary format in the target JSON file.
   405  func (c *Config) BinaryFormat(ext string) string {
   406  	switch ext {
   407  	case ".bin", ".gba", ".nro":
   408  		// The simplest format possible: dump everything in a raw binary file.
   409  		if c.Target.BinaryFormat != "" {
   410  			return c.Target.BinaryFormat
   411  		}
   412  		return "bin"
   413  	case ".img":
   414  		// Image file. Only defined for the ESP32 at the moment, where it is a
   415  		// full (runnable) image that can be used in the Espressif QEMU fork.
   416  		if c.Target.BinaryFormat != "" {
   417  			return c.Target.BinaryFormat + "-img"
   418  		}
   419  		return "bin"
   420  	case ".hex":
   421  		// Similar to bin, but includes the start address and is thus usually a
   422  		// better format.
   423  		return "hex"
   424  	case ".uf2":
   425  		// Special purpose firmware format, mainly used on Adafruit boards.
   426  		// More information:
   427  		// https://github.com/Microsoft/uf2
   428  		return "uf2"
   429  	case ".zip":
   430  		if c.Target.BinaryFormat != "" {
   431  			return c.Target.BinaryFormat
   432  		}
   433  		return "zip"
   434  	default:
   435  		// Use the ELF format for unrecognized file formats.
   436  		return "elf"
   437  	}
   438  }
   439  
   440  // Programmer returns the flash method and OpenOCD interface name given a
   441  // particular configuration. It may either be all configured in the target JSON
   442  // file or be modified using the -programmmer command-line option.
   443  func (c *Config) Programmer() (method, openocdInterface string) {
   444  	switch c.Options.Programmer {
   445  	case "":
   446  		// No configuration supplied.
   447  		return c.Target.FlashMethod, c.Target.OpenOCDInterface
   448  	case "openocd", "msd", "command":
   449  		// The -programmer flag only specifies the flash method.
   450  		return c.Options.Programmer, c.Target.OpenOCDInterface
   451  	case "bmp":
   452  		// The -programmer flag only specifies the flash method.
   453  		return c.Options.Programmer, ""
   454  	default:
   455  		// The -programmer flag specifies something else, assume it specifies
   456  		// the OpenOCD interface name.
   457  		return "openocd", c.Options.Programmer
   458  	}
   459  }
   460  
   461  // OpenOCDConfiguration returns a list of command line arguments to OpenOCD.
   462  // This list of command-line arguments is based on the various OpenOCD-related
   463  // flags in the target specification.
   464  func (c *Config) OpenOCDConfiguration() (args []string, err error) {
   465  	_, openocdInterface := c.Programmer()
   466  	if openocdInterface == "" {
   467  		return nil, errors.New("OpenOCD programmer not set")
   468  	}
   469  	if !regexp.MustCompile(`^[\p{L}0-9_-]+$`).MatchString(openocdInterface) {
   470  		return nil, fmt.Errorf("OpenOCD programmer has an invalid name: %#v", openocdInterface)
   471  	}
   472  	if c.Target.OpenOCDTarget == "" {
   473  		return nil, errors.New("OpenOCD chip not set")
   474  	}
   475  	if !regexp.MustCompile(`^[\p{L}0-9_-]+$`).MatchString(c.Target.OpenOCDTarget) {
   476  		return nil, fmt.Errorf("OpenOCD target has an invalid name: %#v", c.Target.OpenOCDTarget)
   477  	}
   478  	if c.Target.OpenOCDTransport != "" && c.Target.OpenOCDTransport != "swd" {
   479  		return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport)
   480  	}
   481  	args = []string{"-f", "interface/" + openocdInterface + ".cfg"}
   482  	for _, cmd := range c.Target.OpenOCDCommands {
   483  		args = append(args, "-c", cmd)
   484  	}
   485  	if c.Target.OpenOCDTransport != "" {
   486  		transport := c.Target.OpenOCDTransport
   487  		if transport == "swd" {
   488  			switch openocdInterface {
   489  			case "stlink-dap":
   490  				transport = "dapdirect_swd"
   491  			}
   492  		}
   493  		args = append(args, "-c", "transport select "+transport)
   494  	}
   495  	args = append(args, "-f", "target/"+c.Target.OpenOCDTarget+".cfg")
   496  	return args, nil
   497  }
   498  
   499  // CodeModel returns the code model used on this platform.
   500  func (c *Config) CodeModel() string {
   501  	if c.Target.CodeModel != "" {
   502  		return c.Target.CodeModel
   503  	}
   504  
   505  	return "default"
   506  }
   507  
   508  // RelocationModel returns the relocation model in use on this platform. Valid
   509  // values are "static", "pic", "dynamicnopic".
   510  func (c *Config) RelocationModel() string {
   511  	if c.Target.RelocationModel != "" {
   512  		return c.Target.RelocationModel
   513  	}
   514  
   515  	return "static"
   516  }
   517  
   518  // EmulatorName is a shorthand to get the command for this emulator, something
   519  // like qemu-system-arm or simavr.
   520  func (c *Config) EmulatorName() string {
   521  	parts := strings.SplitN(c.Target.Emulator, " ", 2)
   522  	if len(parts) > 1 {
   523  		return parts[0]
   524  	}
   525  	return ""
   526  }
   527  
   528  // EmulatorFormat returns the binary format for the emulator and the associated
   529  // file extension. An empty string means to pass directly whatever the linker
   530  // produces directly without conversion (usually ELF format).
   531  func (c *Config) EmulatorFormat() (format, fileExt string) {
   532  	switch {
   533  	case strings.Contains(c.Target.Emulator, "{img}"):
   534  		return "img", ".img"
   535  	default:
   536  		return "", ""
   537  	}
   538  }
   539  
   540  // Emulator returns a ready-to-run command to run the given binary in an
   541  // emulator. Give it the format (returned by EmulatorFormat()) and the path to
   542  // the compiled binary.
   543  func (c *Config) Emulator(format, binary string) ([]string, error) {
   544  	parts, err := shlex.Split(c.Target.Emulator)
   545  	if err != nil {
   546  		return nil, fmt.Errorf("could not parse emulator command: %w", err)
   547  	}
   548  	var emulator []string
   549  	for _, s := range parts {
   550  		s = strings.ReplaceAll(s, "{root}", goenv.Get("TINYGOROOT"))
   551  		// Allow replacement of what's usually /tmp except notably Windows.
   552  		s = strings.ReplaceAll(s, "{tmpDir}", os.TempDir())
   553  		s = strings.ReplaceAll(s, "{"+format+"}", binary)
   554  		emulator = append(emulator, s)
   555  	}
   556  	return emulator, nil
   557  }
   558  
   559  type TestConfig struct {
   560  	CompileTestBinary bool
   561  	CompileOnly       bool
   562  	Verbose           bool
   563  	Short             bool
   564  	RunRegexp         string
   565  	SkipRegexp        string
   566  	Count             *int
   567  	BenchRegexp       string
   568  	BenchTime         string
   569  	BenchMem          bool
   570  	Shuffle           string
   571  }