
This environment variable can be set to 5, 6, or 7 and controls which ARM version (ARMv5, ARMv6, ARMv7) is used when compiling for GOARCH=arm. I have picked the default value ARMv6, which I believe is supported on most common single board computers including all Raspberry Pis. The difference in code size is pretty big. We could even go further and support ARMv4 if anybody is interested. It should be pretty simple to add this if needed.
459 строки
15 КиБ
Go
459 строки
15 КиБ
Go
// Package compileopts contains the configuration for a single to-be-built
|
|
// binary.
|
|
package compileopts
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/tinygo-org/tinygo/goenv"
|
|
)
|
|
|
|
// Config keeps all configuration affecting the build in a single struct.
|
|
type Config struct {
|
|
Options *Options
|
|
Target *TargetSpec
|
|
GoMinorVersion int
|
|
ClangHeaders string // Clang built-in header include path
|
|
TestConfig TestConfig
|
|
}
|
|
|
|
// Triple returns the LLVM target triple, like armv6m-unknown-unknown-eabi.
|
|
func (c *Config) Triple() string {
|
|
return c.Target.Triple
|
|
}
|
|
|
|
// CPU returns the LLVM CPU name, like atmega328p or arm7tdmi. It may return an
|
|
// empty string if the CPU name is not known.
|
|
func (c *Config) CPU() string {
|
|
return c.Target.CPU
|
|
}
|
|
|
|
// Features returns a list of features this CPU supports. For example, for a
|
|
// RISC-V processor, that could be "+a,+c,+m". For many targets, an empty list
|
|
// will be returned.
|
|
func (c *Config) Features() string {
|
|
if c.Target.Features == "" {
|
|
return c.Options.LLVMFeatures
|
|
}
|
|
if c.Options.LLVMFeatures == "" {
|
|
return c.Target.Features
|
|
}
|
|
return c.Target.Features + "," + c.Options.LLVMFeatures
|
|
}
|
|
|
|
// GOOS returns the GOOS of the target. This might not always be the actual OS:
|
|
// for example, bare-metal targets will usually pretend to be linux to get the
|
|
// standard library to compile.
|
|
func (c *Config) GOOS() string {
|
|
return c.Target.GOOS
|
|
}
|
|
|
|
// GOARCH returns the GOARCH of the target. This might not always be the actual
|
|
// archtecture: for example, the AVR target is not supported by the Go standard
|
|
// library so such targets will usually pretend to be linux/arm.
|
|
func (c *Config) GOARCH() string {
|
|
return c.Target.GOARCH
|
|
}
|
|
|
|
// GOARM will return the GOARM environment variable given to the compiler when
|
|
// building a program.
|
|
func (c *Config) GOARM() string {
|
|
return c.Options.GOARM
|
|
}
|
|
|
|
// BuildTags returns the complete list of build tags used during this build.
|
|
func (c *Config) BuildTags() []string {
|
|
tags := append(c.Target.BuildTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...)
|
|
for i := 1; i <= c.GoMinorVersion; i++ {
|
|
tags = append(tags, fmt.Sprintf("go1.%d", i))
|
|
}
|
|
if extraTags := strings.Fields(c.Options.Tags); len(extraTags) != 0 {
|
|
tags = append(tags, extraTags...)
|
|
}
|
|
return tags
|
|
}
|
|
|
|
// CgoEnabled returns true if (and only if) CGo is enabled. It is true by
|
|
// default and false if CGO_ENABLED is set to "0".
|
|
func (c *Config) CgoEnabled() bool {
|
|
return goenv.Get("CGO_ENABLED") == "1"
|
|
}
|
|
|
|
// GC returns the garbage collection strategy in use on this platform. Valid
|
|
// values are "none", "leaking", "extalloc", and "conservative".
|
|
func (c *Config) GC() string {
|
|
if c.Options.GC != "" {
|
|
return c.Options.GC
|
|
}
|
|
if c.Target.GC != "" {
|
|
return c.Target.GC
|
|
}
|
|
return "conservative"
|
|
}
|
|
|
|
// NeedsStackObjects returns true if the compiler should insert stack objects
|
|
// that can be traced by the garbage collector.
|
|
func (c *Config) NeedsStackObjects() bool {
|
|
switch c.GC() {
|
|
case "conservative", "extalloc":
|
|
for _, tag := range c.BuildTags() {
|
|
if tag == "tinygo.wasm" {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Scheduler returns the scheduler implementation. Valid values are "none",
|
|
//"coroutines" and "tasks".
|
|
func (c *Config) Scheduler() string {
|
|
if c.Options.Scheduler != "" {
|
|
return c.Options.Scheduler
|
|
}
|
|
if c.Target.Scheduler != "" {
|
|
return c.Target.Scheduler
|
|
}
|
|
// Fall back to coroutines, which are supported everywhere.
|
|
return "coroutines"
|
|
}
|
|
|
|
// Serial returns the serial implementation for this build configuration: uart,
|
|
// usb (meaning USB-CDC), or none.
|
|
func (c *Config) Serial() string {
|
|
if c.Options.Serial != "" {
|
|
return c.Options.Serial
|
|
}
|
|
if c.Target.Serial != "" {
|
|
return c.Target.Serial
|
|
}
|
|
return "none"
|
|
}
|
|
|
|
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner
|
|
// threshold as used in the LLVM optimization pipeline.
|
|
func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
|
|
switch c.Options.Opt {
|
|
case "none", "0":
|
|
return 0, 0, 0 // -O0
|
|
case "1":
|
|
return 1, 0, 0 // -O1
|
|
case "2":
|
|
return 2, 0, 225 // -O2
|
|
case "s":
|
|
return 2, 1, 225 // -Os
|
|
case "z":
|
|
return 2, 2, 5 // -Oz, default
|
|
default:
|
|
// This is not shown to the user: valid choices are already checked as
|
|
// part of Options.Verify(). It is here as a sanity check.
|
|
panic("unknown optimization level: -opt=" + c.Options.Opt)
|
|
}
|
|
}
|
|
|
|
// FuncImplementation picks an appropriate func value implementation for the
|
|
// target.
|
|
func (c *Config) FuncImplementation() string {
|
|
switch c.Scheduler() {
|
|
case "tasks", "asyncify":
|
|
// A func value is implemented as a pair of pointers:
|
|
// {context, function pointer}
|
|
// where the context may be a pointer to a heap-allocated struct
|
|
// containing the free variables, or it may be undef if the function
|
|
// being pointed to doesn't need a context. The function pointer is a
|
|
// regular function pointer.
|
|
return "doubleword"
|
|
case "none", "coroutines":
|
|
// As "doubleword", but with the function pointer replaced by a unique
|
|
// ID per function signature. Function values are called by using a
|
|
// switch statement and choosing which function to call.
|
|
// Pick the switch implementation with the coroutines scheduler, as it
|
|
// allows the use of blocking inside a function that is used as a func
|
|
// value.
|
|
return "switch"
|
|
default:
|
|
panic("unknown scheduler type")
|
|
}
|
|
}
|
|
|
|
// PanicStrategy returns the panic strategy selected for this target. Valid
|
|
// values are "print" (print the panic value, then exit) or "trap" (issue a trap
|
|
// instruction).
|
|
func (c *Config) PanicStrategy() string {
|
|
return c.Options.PanicStrategy
|
|
}
|
|
|
|
// AutomaticStackSize returns whether goroutine stack sizes should be determined
|
|
// automatically at compile time, if possible. If it is false, no attempt is
|
|
// made.
|
|
func (c *Config) AutomaticStackSize() bool {
|
|
if c.Target.AutoStackSize != nil && c.Scheduler() == "tasks" {
|
|
return *c.Target.AutoStackSize
|
|
}
|
|
return false
|
|
}
|
|
|
|
// RP2040BootPatch returns whether the RP2040 boot patch should be applied that
|
|
// calculates and patches in the checksum for the 2nd stage bootloader.
|
|
func (c *Config) RP2040BootPatch() bool {
|
|
if c.Target.RP2040BootPatch != nil {
|
|
return *c.Target.RP2040BootPatch
|
|
}
|
|
return false
|
|
}
|
|
|
|
// MuslArchitecture returns the architecture name as used in musl libc. It is
|
|
// usually the same as the first part of the LLVM triple, but not always.
|
|
func MuslArchitecture(triple string) string {
|
|
arch := strings.Split(triple, "-")[0]
|
|
if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") {
|
|
arch = "arm"
|
|
}
|
|
return arch
|
|
}
|
|
|
|
// LibcPath returns the path to the libc directory. The libc path will be either
|
|
// a precompiled libc shipped with a TinyGo build, or a libc path in the cache
|
|
// directory (which might not yet be built).
|
|
func (c *Config) LibcPath(name string) (path string, precompiled bool) {
|
|
// Try to load a precompiled library.
|
|
precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", c.Triple(), name)
|
|
if _, err := os.Stat(precompiledDir); err == nil {
|
|
// Found a precompiled library for this OS/architecture. Return the path
|
|
// directly.
|
|
return precompiledDir, true
|
|
}
|
|
|
|
// No precompiled library found. Determine the path name that will be used
|
|
// in the build cache.
|
|
var outname string
|
|
if c.CPU() != "" {
|
|
outname = name + "-" + c.Triple() + "-" + c.CPU()
|
|
} else {
|
|
outname = name + "-" + c.Triple()
|
|
}
|
|
return filepath.Join(goenv.Get("GOCACHE"), outname), false
|
|
}
|
|
|
|
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
|
|
// preprocessing.
|
|
func (c *Config) CFlags() []string {
|
|
var cflags []string
|
|
for _, flag := range c.Target.CFlags {
|
|
cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT")))
|
|
}
|
|
switch c.Target.Libc {
|
|
case "picolibc":
|
|
root := goenv.Get("TINYGOROOT")
|
|
picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc")
|
|
path, _ := c.LibcPath("picolibc")
|
|
cflags = append(cflags,
|
|
"--sysroot="+path,
|
|
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(picolibcDir, "include"),
|
|
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(picolibcDir, "tinystdio"),
|
|
)
|
|
case "musl":
|
|
root := goenv.Get("TINYGOROOT")
|
|
path, _ := c.LibcPath("musl")
|
|
arch := MuslArchitecture(c.Triple())
|
|
cflags = append(cflags,
|
|
"--sysroot="+path,
|
|
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "musl", "arch", arch),
|
|
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "musl", "include"),
|
|
)
|
|
case "wasi-libc":
|
|
root := goenv.Get("TINYGOROOT")
|
|
cflags = append(cflags, "--sysroot="+root+"/lib/wasi-libc/sysroot")
|
|
case "":
|
|
// No libc specified, nothing to add.
|
|
default:
|
|
// Incorrect configuration. This could be handled in a better way, but
|
|
// usually this will be found by developers (not by TinyGo users).
|
|
panic("unknown libc: " + c.Target.Libc)
|
|
}
|
|
// Always emit debug information. It is optionally stripped at link time.
|
|
cflags = append(cflags, "-g")
|
|
// Use the same optimization level as TinyGo.
|
|
cflags = append(cflags, "-O"+c.Options.Opt)
|
|
// Set the LLVM target triple.
|
|
cflags = append(cflags, "--target="+c.Triple())
|
|
// Set the -mcpu (or similar) flag.
|
|
if c.Target.CPU != "" {
|
|
if c.GOARCH() == "amd64" || c.GOARCH() == "386" {
|
|
// x86 prefers the -march flag (-mcpu is deprecated there).
|
|
cflags = append(cflags, "-march="+c.Target.CPU)
|
|
} else if strings.HasPrefix(c.Triple(), "avr") {
|
|
// AVR MCUs use -mmcu instead of -mcpu.
|
|
cflags = append(cflags, "-mmcu="+c.Target.CPU)
|
|
} else {
|
|
// The rest just uses -mcpu.
|
|
cflags = append(cflags, "-mcpu="+c.Target.CPU)
|
|
}
|
|
}
|
|
return cflags
|
|
}
|
|
|
|
// LDFlags returns the flags to pass to the linker. A few more flags are needed
|
|
// (like the one for the compiler runtime), but this represents the majority of
|
|
// the flags.
|
|
func (c *Config) LDFlags() []string {
|
|
root := goenv.Get("TINYGOROOT")
|
|
// Merge and adjust LDFlags.
|
|
var ldflags []string
|
|
for _, flag := range c.Target.LDFlags {
|
|
ldflags = append(ldflags, strings.ReplaceAll(flag, "{root}", root))
|
|
}
|
|
ldflags = append(ldflags, "-L", root)
|
|
if c.Target.LinkerScript != "" {
|
|
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
|
|
}
|
|
return ldflags
|
|
}
|
|
|
|
// ExtraFiles returns the list of extra files to be built and linked with the
|
|
// executable. This can include extra C and assembly files.
|
|
func (c *Config) ExtraFiles() []string {
|
|
return c.Target.ExtraFiles
|
|
}
|
|
|
|
// DumpSSA returns whether to dump Go SSA while compiling (-dumpssa flag). Only
|
|
// enable this for debugging.
|
|
func (c *Config) DumpSSA() bool {
|
|
return c.Options.DumpSSA
|
|
}
|
|
|
|
// VerifyIR returns whether to run extra checks on the IR. This is normally
|
|
// disabled but enabled during testing.
|
|
func (c *Config) VerifyIR() bool {
|
|
return c.Options.VerifyIR
|
|
}
|
|
|
|
// Debug returns whether debug (DWARF) information should be retained by the
|
|
// linker. By default, debug information is retained but it can be removed with
|
|
// the -no-debug flag.
|
|
func (c *Config) Debug() bool {
|
|
return c.Options.Debug
|
|
}
|
|
|
|
// BinaryFormat returns an appropriate binary format, based on the file
|
|
// extension and the configured binary format in the target JSON file.
|
|
func (c *Config) BinaryFormat(ext string) string {
|
|
switch ext {
|
|
case ".bin", ".gba", ".nro":
|
|
// The simplest format possible: dump everything in a raw binary file.
|
|
if c.Target.BinaryFormat != "" {
|
|
return c.Target.BinaryFormat
|
|
}
|
|
return "bin"
|
|
case ".hex":
|
|
// Similar to bin, but includes the start address and is thus usually a
|
|
// better format.
|
|
return "hex"
|
|
case ".uf2":
|
|
// Special purpose firmware format, mainly used on Adafruit boards.
|
|
// More information:
|
|
// https://github.com/Microsoft/uf2
|
|
return "uf2"
|
|
case ".zip":
|
|
if c.Target.BinaryFormat != "" {
|
|
return c.Target.BinaryFormat
|
|
}
|
|
return "zip"
|
|
default:
|
|
// Use the ELF format for unrecognized file formats.
|
|
return "elf"
|
|
}
|
|
}
|
|
|
|
// Programmer returns the flash method and OpenOCD interface name given a
|
|
// particular configuration. It may either be all configured in the target JSON
|
|
// file or be modified using the -programmmer command-line option.
|
|
func (c *Config) Programmer() (method, openocdInterface string) {
|
|
switch c.Options.Programmer {
|
|
case "":
|
|
// No configuration supplied.
|
|
return c.Target.FlashMethod, c.Target.OpenOCDInterface
|
|
case "openocd", "msd", "command":
|
|
// The -programmer flag only specifies the flash method.
|
|
return c.Options.Programmer, c.Target.OpenOCDInterface
|
|
case "bmp":
|
|
// The -programmer flag only specifies the flash method.
|
|
return c.Options.Programmer, ""
|
|
default:
|
|
// The -programmer flag specifies something else, assume it specifies
|
|
// the OpenOCD interface name.
|
|
return "openocd", c.Options.Programmer
|
|
}
|
|
}
|
|
|
|
// OpenOCDConfiguration returns a list of command line arguments to OpenOCD.
|
|
// This list of command-line arguments is based on the various OpenOCD-related
|
|
// flags in the target specification.
|
|
func (c *Config) OpenOCDConfiguration() (args []string, err error) {
|
|
_, openocdInterface := c.Programmer()
|
|
if openocdInterface == "" {
|
|
return nil, errors.New("OpenOCD programmer not set")
|
|
}
|
|
if !regexp.MustCompile("^[\\p{L}0-9_-]+$").MatchString(openocdInterface) {
|
|
return nil, fmt.Errorf("OpenOCD programmer has an invalid name: %#v", openocdInterface)
|
|
}
|
|
if c.Target.OpenOCDTarget == "" {
|
|
return nil, errors.New("OpenOCD chip not set")
|
|
}
|
|
if !regexp.MustCompile("^[\\p{L}0-9_-]+$").MatchString(c.Target.OpenOCDTarget) {
|
|
return nil, fmt.Errorf("OpenOCD target has an invalid name: %#v", c.Target.OpenOCDTarget)
|
|
}
|
|
if c.Target.OpenOCDTransport != "" && c.Target.OpenOCDTransport != "swd" {
|
|
return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport)
|
|
}
|
|
args = []string{"-f", "interface/" + openocdInterface + ".cfg"}
|
|
for _, cmd := range c.Target.OpenOCDCommands {
|
|
args = append(args, "-c", cmd)
|
|
}
|
|
if c.Target.OpenOCDTransport != "" {
|
|
args = append(args, "-c", "transport select "+c.Target.OpenOCDTransport)
|
|
}
|
|
args = append(args, "-f", "target/"+c.Target.OpenOCDTarget+".cfg")
|
|
return args, nil
|
|
}
|
|
|
|
// CodeModel returns the code model used on this platform.
|
|
func (c *Config) CodeModel() string {
|
|
if c.Target.CodeModel != "" {
|
|
return c.Target.CodeModel
|
|
}
|
|
|
|
return "default"
|
|
}
|
|
|
|
// RelocationModel returns the relocation model in use on this platform. Valid
|
|
// values are "static", "pic", "dynamicnopic".
|
|
func (c *Config) RelocationModel() string {
|
|
if c.Target.RelocationModel != "" {
|
|
return c.Target.RelocationModel
|
|
}
|
|
|
|
return "static"
|
|
}
|
|
|
|
// WasmAbi returns the WASM ABI which is specified in the target JSON file, and
|
|
// the value is overridden by `-wasm-abi` flag if it is provided
|
|
func (c *Config) WasmAbi() string {
|
|
if c.Options.WasmAbi != "" {
|
|
return c.Options.WasmAbi
|
|
}
|
|
return c.Target.WasmAbi
|
|
}
|
|
|
|
type TestConfig struct {
|
|
CompileTestBinary bool
|
|
// TODO: Filter the test functions to run, include verbose flag, etc
|
|
}
|