mirror of https://github.com/tinygo-org/tinygo.git
wasmstm32webassemblymicrocontrollerarmavrspiwasiadafruitarduinocircuitplayground-expressgpioi2cllvmmicrobitnrf51nrf52nrf52840samd21tinygo
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
364 lines
12 KiB
364 lines
12 KiB
package compileopts
|
|
|
|
// This file loads a target specification from a JSON file.
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/tinygo-org/tinygo/goenv"
|
|
"tinygo.org/x/go-llvm"
|
|
)
|
|
|
|
// Target specification for a given target. Used for bare metal targets.
|
|
//
|
|
// The target specification is mostly inspired by Rust:
|
|
// https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/struct.TargetOptions.html
|
|
// https://github.com/shepmaster/rust-arduino-blink-led-no-core-with-cargo/blob/master/blink/arduino.json
|
|
type TargetSpec struct {
|
|
Inherits []string `json:"inherits"`
|
|
Triple string `json:"llvm-target"`
|
|
CPU string `json:"cpu"`
|
|
Features string `json:"features"`
|
|
GOOS string `json:"goos"`
|
|
GOARCH string `json:"goarch"`
|
|
BuildTags []string `json:"build-tags"`
|
|
GC string `json:"gc"`
|
|
Scheduler string `json:"scheduler"`
|
|
Serial string `json:"serial"` // which serial output to use (uart, usb, none)
|
|
Linker string `json:"linker"`
|
|
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
|
|
Libc string `json:"libc"`
|
|
AutoStackSize *bool `json:"automatic-stack-size"` // Determine stack size automatically at compile time.
|
|
DefaultStackSize uint64 `json:"default-stack-size"` // Default stack size if the size couldn't be determined at compile time.
|
|
CFlags []string `json:"cflags"`
|
|
LDFlags []string `json:"ldflags"`
|
|
LinkerScript string `json:"linkerscript"`
|
|
ExtraFiles []string `json:"extra-files"`
|
|
RP2040BootPatch *bool `json:"rp2040-boot-patch"` // Patch RP2040 2nd stage bootloader checksum
|
|
Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append
|
|
FlashCommand string `json:"flash-command"`
|
|
GDB []string `json:"gdb"`
|
|
PortReset string `json:"flash-1200-bps-reset"`
|
|
SerialPort []string `json:"serial-port"` // serial port IDs in the form "acm:vid:pid" or "usb:vid:pid"
|
|
FlashMethod string `json:"flash-method"`
|
|
FlashVolume string `json:"msd-volume-name"`
|
|
FlashFilename string `json:"msd-firmware-name"`
|
|
UF2FamilyID string `json:"uf2-family-id"`
|
|
BinaryFormat string `json:"binary-format"`
|
|
OpenOCDInterface string `json:"openocd-interface"`
|
|
OpenOCDTarget string `json:"openocd-target"`
|
|
OpenOCDTransport string `json:"openocd-transport"`
|
|
OpenOCDCommands []string `json:"openocd-commands"`
|
|
JLinkDevice string `json:"jlink-device"`
|
|
CodeModel string `json:"code-model"`
|
|
RelocationModel string `json:"relocation-model"`
|
|
WasmAbi string `json:"wasm-abi"`
|
|
}
|
|
|
|
// overrideProperties overrides all properties that are set in child into itself using reflection.
|
|
func (spec *TargetSpec) overrideProperties(child *TargetSpec) {
|
|
specType := reflect.TypeOf(spec).Elem()
|
|
specValue := reflect.ValueOf(spec).Elem()
|
|
childValue := reflect.ValueOf(child).Elem()
|
|
|
|
for i := 0; i < specType.NumField(); i++ {
|
|
field := specType.Field(i)
|
|
src := childValue.Field(i)
|
|
dst := specValue.Field(i)
|
|
|
|
switch kind := field.Type.Kind(); kind {
|
|
case reflect.String: // for strings, just copy the field of child to spec if not empty
|
|
if src.Len() > 0 {
|
|
dst.Set(src)
|
|
}
|
|
case reflect.Uint, reflect.Uint32, reflect.Uint64: // for Uint, copy if not zero
|
|
if src.Uint() != 0 {
|
|
dst.Set(src)
|
|
}
|
|
case reflect.Ptr: // for pointers, copy if not nil
|
|
if !src.IsNil() {
|
|
dst.Set(src)
|
|
}
|
|
case reflect.Slice: // for slices...
|
|
if src.Len() > 0 { // ... if not empty ...
|
|
switch tag := field.Tag.Get("override"); tag {
|
|
case "copy":
|
|
// copy the field of child to spec
|
|
dst.Set(src)
|
|
case "append", "":
|
|
// or append the field of child to spec
|
|
dst.Set(reflect.AppendSlice(dst, src))
|
|
default:
|
|
panic("override mode must be 'copy' or 'append' (default). I don't know how to '" + tag + "'.")
|
|
}
|
|
}
|
|
default:
|
|
panic("unknown field type : " + kind.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
// load reads a target specification from the JSON in the given io.Reader. It
|
|
// may load more targets specified using the "inherits" property.
|
|
func (spec *TargetSpec) load(r io.Reader) error {
|
|
err := json.NewDecoder(r).Decode(spec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// loadFromGivenStr loads the TargetSpec from the given string that could be:
|
|
// - targets/ directory inside the compiler sources
|
|
// - a relative or absolute path to custom (project specific) target specification .json file;
|
|
// the Inherits[] could contain the files from target folder (ex. stm32f4disco)
|
|
// as well as path to custom files (ex. myAwesomeProject.json)
|
|
func (spec *TargetSpec) loadFromGivenStr(str string) error {
|
|
path := ""
|
|
if strings.HasSuffix(str, ".json") {
|
|
path, _ = filepath.Abs(str)
|
|
} else {
|
|
path = filepath.Join(goenv.Get("TINYGOROOT"), "targets", strings.ToLower(str)+".json")
|
|
}
|
|
fp, err := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer fp.Close()
|
|
return spec.load(fp)
|
|
}
|
|
|
|
// resolveInherits loads inherited targets, recursively.
|
|
func (spec *TargetSpec) resolveInherits() error {
|
|
// First create a new spec with all the inherited properties.
|
|
newSpec := &TargetSpec{}
|
|
for _, name := range spec.Inherits {
|
|
subtarget := &TargetSpec{}
|
|
err := subtarget.loadFromGivenStr(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = subtarget.resolveInherits()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newSpec.overrideProperties(subtarget)
|
|
}
|
|
|
|
// When all properties are loaded, make sure they are properly inherited.
|
|
newSpec.overrideProperties(spec)
|
|
*spec = *newSpec
|
|
|
|
return nil
|
|
}
|
|
|
|
// Load a target specification.
|
|
func LoadTarget(options *Options) (*TargetSpec, error) {
|
|
if options.Target == "" {
|
|
// Configure based on GOOS/GOARCH environment variables (falling back to
|
|
// runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it.
|
|
var llvmarch string
|
|
switch options.GOARCH {
|
|
case "386":
|
|
llvmarch = "i386"
|
|
case "amd64":
|
|
llvmarch = "x86_64"
|
|
case "arm64":
|
|
llvmarch = "aarch64"
|
|
case "arm":
|
|
switch options.GOARM {
|
|
case "5":
|
|
llvmarch = "armv5"
|
|
case "6":
|
|
llvmarch = "armv6"
|
|
case "7":
|
|
llvmarch = "armv7"
|
|
default:
|
|
return nil, fmt.Errorf("invalid GOARM=%s, must be 5, 6, or 7", options.GOARM)
|
|
}
|
|
default:
|
|
llvmarch = options.GOARCH
|
|
}
|
|
llvmos := options.GOOS
|
|
if llvmos == "darwin" {
|
|
// Use macosx* instead of darwin, otherwise darwin/arm64 will refer
|
|
// to iOS!
|
|
llvmos = "macosx10.12.0"
|
|
if llvmarch == "aarch64" {
|
|
// Looks like Apple prefers to call this architecture ARM64
|
|
// instead of AArch64.
|
|
llvmarch = "arm64"
|
|
}
|
|
}
|
|
// Target triples (which actually have four components, but are called
|
|
// triples for historical reasons) have the form:
|
|
// arch-vendor-os-environment
|
|
target := llvmarch + "-unknown-" + llvmos
|
|
if options.GOARCH == "arm" {
|
|
target += "-gnueabihf"
|
|
}
|
|
if options.GOOS == "windows" {
|
|
target += "-gnu"
|
|
}
|
|
return defaultTarget(options.GOOS, options.GOARCH, target)
|
|
}
|
|
|
|
// See whether there is a target specification for this target (e.g.
|
|
// Arduino).
|
|
spec := &TargetSpec{}
|
|
err := spec.loadFromGivenStr(options.Target)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Successfully loaded this target from a built-in .json file. Make sure
|
|
// it includes all parents as specified in the "inherits" key.
|
|
err = spec.resolveInherits()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if spec.Scheduler == "asyncify" {
|
|
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_asyncify_wasm.S")
|
|
}
|
|
|
|
return spec, nil
|
|
}
|
|
|
|
func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
|
// No target spec available. Use the default one, useful on most systems
|
|
// with a regular OS.
|
|
spec := TargetSpec{
|
|
Triple: triple,
|
|
GOOS: goos,
|
|
GOARCH: goarch,
|
|
BuildTags: []string{goos, goarch},
|
|
Scheduler: "tasks",
|
|
Linker: "cc",
|
|
DefaultStackSize: 1024 * 64, // 64kB
|
|
GDB: []string{"gdb"},
|
|
PortReset: "false",
|
|
}
|
|
switch goarch {
|
|
case "386":
|
|
spec.CPU = "pentium4"
|
|
spec.Features = "+cx8,+fxsr,+mmx,+sse,+sse2,+x87"
|
|
case "amd64":
|
|
spec.CPU = "x86-64"
|
|
spec.Features = "+cx8,+fxsr,+mmx,+sse,+sse2,+x87"
|
|
case "arm":
|
|
spec.CPU = "generic"
|
|
spec.CFlags = append(spec.CFlags, "-fno-unwind-tables")
|
|
switch strings.Split(triple, "-")[0] {
|
|
case "armv5":
|
|
spec.Features = "+armv5t,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
|
|
case "armv6":
|
|
spec.Features = "+armv6,+dsp,+fp64,+strict-align,+vfp2,+vfp2sp,-aes,-d32,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-neon,-sha2,-thumb-mode,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
|
|
case "armv7":
|
|
spec.Features = "+armv7-a,+d32,+dsp,+fp64,+neon,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-thumb-mode,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
|
|
}
|
|
case "arm64":
|
|
spec.CPU = "generic"
|
|
spec.Features = "+neon"
|
|
}
|
|
if goos == "darwin" {
|
|
spec.Linker = "ld.lld"
|
|
spec.Libc = "darwin-libSystem"
|
|
arch := strings.Split(triple, "-")[0]
|
|
platformVersion := strings.TrimPrefix(strings.Split(triple, "-")[2], "macosx")
|
|
spec.LDFlags = append(spec.LDFlags,
|
|
"-flavor", "darwinnew",
|
|
"-dead_strip",
|
|
"-arch", arch,
|
|
"-platform_version", "macos", platformVersion, platformVersion,
|
|
)
|
|
} else if goos == "linux" {
|
|
spec.Linker = "ld.lld"
|
|
spec.RTLib = "compiler-rt"
|
|
spec.Libc = "musl"
|
|
spec.LDFlags = append(spec.LDFlags, "--gc-sections")
|
|
} else if goos == "windows" {
|
|
spec.Linker = "ld.lld"
|
|
spec.Libc = "mingw-w64"
|
|
// Note: using a medium code model, low image base and no ASLR
|
|
// because Go doesn't really need those features. ASLR patches
|
|
// around issues for unsafe languages like C/C++ that are not
|
|
// normally present in Go (without explicitly opting in).
|
|
// For more discussion:
|
|
// https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1
|
|
spec.LDFlags = append(spec.LDFlags,
|
|
"-m", "i386pep",
|
|
"-Bdynamic",
|
|
"--image-base", "0x400000",
|
|
"--gc-sections",
|
|
"--no-insert-timestamp",
|
|
)
|
|
llvmMajor, _ := strconv.Atoi(strings.Split(llvm.Version, ".")[0])
|
|
if llvmMajor >= 12 {
|
|
// This flag was added in LLVM 12. At the same time, LLVM 12
|
|
// switched the default from --dynamicbase to --no-dynamicbase.
|
|
spec.LDFlags = append(spec.LDFlags, "--no-dynamicbase")
|
|
}
|
|
} else {
|
|
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
|
|
}
|
|
if goarch != "wasm" {
|
|
suffix := ""
|
|
if goos == "windows" {
|
|
// Windows uses a different calling convention from other operating
|
|
// systems so we need separate assembly files.
|
|
suffix = "_windows"
|
|
}
|
|
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+suffix+".S")
|
|
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+suffix+".S")
|
|
}
|
|
if goarch != runtime.GOARCH {
|
|
// Some educated guesses as to how to invoke helper programs.
|
|
spec.GDB = []string{"gdb-multiarch"}
|
|
if goos == "linux" {
|
|
switch goarch {
|
|
case "386":
|
|
// amd64 can _usually_ run 32-bit programs, so skip the emulator in that case.
|
|
if runtime.GOARCH != "amd64" {
|
|
spec.Emulator = []string{"qemu-i386"}
|
|
}
|
|
case "amd64":
|
|
spec.Emulator = []string{"qemu-x86_64"}
|
|
case "arm":
|
|
spec.Emulator = []string{"qemu-arm"}
|
|
case "arm64":
|
|
spec.Emulator = []string{"qemu-aarch64"}
|
|
}
|
|
}
|
|
}
|
|
if goos != runtime.GOOS {
|
|
if goos == "windows" {
|
|
spec.Emulator = []string{"wine"}
|
|
}
|
|
}
|
|
return &spec, nil
|
|
}
|
|
|
|
// LookupGDB looks up a gdb executable.
|
|
func (spec *TargetSpec) LookupGDB() (string, error) {
|
|
if len(spec.GDB) == 0 {
|
|
return "", errors.New("gdb not configured in the target specification")
|
|
}
|
|
for _, d := range spec.GDB {
|
|
_, err := exec.LookPath(d)
|
|
if err == nil {
|
|
return d, nil
|
|
}
|
|
}
|
|
return "", errors.New("no gdb found configured in the target specification (" + strings.Join(spec.GDB, ", ") + ")")
|
|
}
|
|
|