github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/compileopts/target.go (about) 1 package compileopts 2 3 // This file loads a target specification from a JSON file. 4 5 import ( 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "reflect" 14 "runtime" 15 "strings" 16 17 "github.com/tinygo-org/tinygo/goenv" 18 ) 19 20 // Target specification for a given target. Used for bare metal targets. 21 // 22 // The target specification is mostly inspired by Rust: 23 // https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/struct.TargetOptions.html 24 // https://github.com/shepmaster/rust-arduino-blink-led-no-core-with-cargo/blob/master/blink/arduino.json 25 type TargetSpec struct { 26 Inherits []string `json:"inherits,omitempty"` 27 Triple string `json:"llvm-target,omitempty"` 28 CPU string `json:"cpu,omitempty"` 29 ABI string `json:"target-abi,omitempty"` // rougly equivalent to -mabi= flag 30 Features string `json:"features,omitempty"` 31 GOOS string `json:"goos,omitempty"` 32 GOARCH string `json:"goarch,omitempty"` 33 BuildTags []string `json:"build-tags,omitempty"` 34 GC string `json:"gc,omitempty"` 35 Scheduler string `json:"scheduler,omitempty"` 36 Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none) 37 Linker string `json:"linker,omitempty"` 38 RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt) 39 Libc string `json:"libc,omitempty"` 40 AutoStackSize *bool `json:"automatic-stack-size,omitempty"` // Determine stack size automatically at compile time. 41 DefaultStackSize uint64 `json:"default-stack-size,omitempty"` // Default stack size if the size couldn't be determined at compile time. 42 CFlags []string `json:"cflags,omitempty"` 43 LDFlags []string `json:"ldflags,omitempty"` 44 LinkerScript string `json:"linkerscript,omitempty"` 45 ExtraFiles []string `json:"extra-files,omitempty"` 46 RP2040BootPatch *bool `json:"rp2040-boot-patch,omitempty"` // Patch RP2040 2nd stage bootloader checksum 47 Emulator string `json:"emulator,omitempty"` 48 FlashCommand string `json:"flash-command,omitempty"` 49 GDB []string `json:"gdb,omitempty"` 50 PortReset string `json:"flash-1200-bps-reset,omitempty"` 51 SerialPort []string `json:"serial-port,omitempty"` // serial port IDs in the form "vid:pid" 52 FlashMethod string `json:"flash-method,omitempty"` 53 FlashVolume []string `json:"msd-volume-name,omitempty"` 54 FlashFilename string `json:"msd-firmware-name,omitempty"` 55 UF2FamilyID string `json:"uf2-family-id,omitempty"` 56 BinaryFormat string `json:"binary-format,omitempty"` 57 OpenOCDInterface string `json:"openocd-interface,omitempty"` 58 OpenOCDTarget string `json:"openocd-target,omitempty"` 59 OpenOCDTransport string `json:"openocd-transport,omitempty"` 60 OpenOCDCommands []string `json:"openocd-commands,omitempty"` 61 OpenOCDVerify *bool `json:"openocd-verify,omitempty"` // enable verify when flashing with openocd 62 JLinkDevice string `json:"jlink-device,omitempty"` 63 CodeModel string `json:"code-model,omitempty"` 64 RelocationModel string `json:"relocation-model,omitempty"` 65 } 66 67 // overrideProperties overrides all properties that are set in child into itself using reflection. 68 func (spec *TargetSpec) overrideProperties(child *TargetSpec) error { 69 specType := reflect.TypeOf(spec).Elem() 70 specValue := reflect.ValueOf(spec).Elem() 71 childValue := reflect.ValueOf(child).Elem() 72 73 for i := 0; i < specType.NumField(); i++ { 74 field := specType.Field(i) 75 src := childValue.Field(i) 76 dst := specValue.Field(i) 77 78 switch kind := field.Type.Kind(); kind { 79 case reflect.String: // for strings, just copy the field of child to spec if not empty 80 if src.Len() > 0 { 81 dst.Set(src) 82 } 83 case reflect.Uint, reflect.Uint32, reflect.Uint64: // for Uint, copy if not zero 84 if src.Uint() != 0 { 85 dst.Set(src) 86 } 87 case reflect.Ptr: // for pointers, copy if not nil 88 if !src.IsNil() { 89 dst.Set(src) 90 } 91 case reflect.Slice: // for slices, append the field and check for duplicates 92 dst.Set(reflect.AppendSlice(dst, src)) 93 for i := 0; i < dst.Len(); i++ { 94 v := dst.Index(i).String() 95 for j := i + 1; j < dst.Len(); j++ { 96 w := dst.Index(j).String() 97 if v == w { 98 return fmt.Errorf("duplicate value '%s' in field %s", v, field.Name) 99 } 100 } 101 } 102 default: 103 return fmt.Errorf("unknown field type: %s", kind) 104 } 105 } 106 return nil 107 } 108 109 // load reads a target specification from the JSON in the given io.Reader. It 110 // may load more targets specified using the "inherits" property. 111 func (spec *TargetSpec) load(r io.Reader) error { 112 err := json.NewDecoder(r).Decode(spec) 113 if err != nil { 114 return err 115 } 116 117 return nil 118 } 119 120 // loadFromGivenStr loads the TargetSpec from the given string that could be: 121 // - targets/ directory inside the compiler sources 122 // - a relative or absolute path to custom (project specific) target specification .json file; 123 // the Inherits[] could contain the files from target folder (ex. stm32f4disco) 124 // as well as path to custom files (ex. myAwesomeProject.json) 125 func (spec *TargetSpec) loadFromGivenStr(str string) error { 126 path := "" 127 if strings.HasSuffix(str, ".json") { 128 path, _ = filepath.Abs(str) 129 } else { 130 path = filepath.Join(goenv.Get("TINYGOROOT"), "targets", strings.ToLower(str)+".json") 131 } 132 fp, err := os.Open(path) 133 if err != nil { 134 return err 135 } 136 defer fp.Close() 137 return spec.load(fp) 138 } 139 140 // resolveInherits loads inherited targets, recursively. 141 func (spec *TargetSpec) resolveInherits() error { 142 // First create a new spec with all the inherited properties. 143 newSpec := &TargetSpec{} 144 for _, name := range spec.Inherits { 145 subtarget := &TargetSpec{} 146 err := subtarget.loadFromGivenStr(name) 147 if err != nil { 148 return err 149 } 150 err = subtarget.resolveInherits() 151 if err != nil { 152 return err 153 } 154 err = newSpec.overrideProperties(subtarget) 155 if err != nil { 156 return err 157 } 158 } 159 160 // When all properties are loaded, make sure they are properly inherited. 161 err := newSpec.overrideProperties(spec) 162 if err != nil { 163 return err 164 } 165 *spec = *newSpec 166 167 return nil 168 } 169 170 // Load a target specification. 171 func LoadTarget(options *Options) (*TargetSpec, error) { 172 if options.Target == "" { 173 // Configure based on GOOS/GOARCH environment variables (falling back to 174 // runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it. 175 var llvmarch string 176 switch options.GOARCH { 177 case "386": 178 llvmarch = "i386" 179 case "amd64": 180 llvmarch = "x86_64" 181 case "arm64": 182 llvmarch = "aarch64" 183 case "arm": 184 switch options.GOARM { 185 case "5": 186 llvmarch = "armv5" 187 case "6": 188 llvmarch = "armv6" 189 case "7": 190 llvmarch = "armv7" 191 default: 192 return nil, fmt.Errorf("invalid GOARM=%s, must be 5, 6, or 7", options.GOARM) 193 } 194 case "wasm": 195 llvmarch = "wasm32" 196 default: 197 llvmarch = options.GOARCH 198 } 199 llvmvendor := "unknown" 200 llvmos := options.GOOS 201 switch llvmos { 202 case "darwin": 203 // Use macosx* instead of darwin, otherwise darwin/arm64 will refer 204 // to iOS! 205 llvmos = "macosx10.12.0" 206 if llvmarch == "aarch64" { 207 // Looks like Apple prefers to call this architecture ARM64 208 // instead of AArch64. 209 llvmarch = "arm64" 210 llvmos = "macosx11.0.0" 211 } 212 llvmvendor = "apple" 213 case "wasip1": 214 llvmos = "wasi" 215 } 216 // Target triples (which actually have four components, but are called 217 // triples for historical reasons) have the form: 218 // arch-vendor-os-environment 219 target := llvmarch + "-" + llvmvendor + "-" + llvmos 220 if options.GOOS == "windows" { 221 target += "-gnu" 222 } else if options.GOARCH == "arm" { 223 target += "-gnueabihf" 224 } 225 return defaultTarget(options.GOOS, options.GOARCH, target) 226 } 227 228 // See whether there is a target specification for this target (e.g. 229 // Arduino). 230 spec := &TargetSpec{} 231 err := spec.loadFromGivenStr(options.Target) 232 if err != nil { 233 return nil, err 234 } 235 // Successfully loaded this target from a built-in .json file. Make sure 236 // it includes all parents as specified in the "inherits" key. 237 err = spec.resolveInherits() 238 if err != nil { 239 return nil, fmt.Errorf("%s : %w", options.Target, err) 240 } 241 242 if spec.Scheduler == "asyncify" { 243 spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_asyncify_wasm.S") 244 } 245 246 return spec, nil 247 } 248 249 // GetTargetSpecs retrieves target specifications from the TINYGOROOT targets 250 // directory. Only valid target JSON files are considered, and the function 251 // returns a map of target names to their respective TargetSpec. 252 func GetTargetSpecs() (map[string]*TargetSpec, error) { 253 dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets") 254 entries, err := os.ReadDir(dir) 255 if err != nil { 256 return nil, fmt.Errorf("could not list targets: %w", err) 257 } 258 259 maps := map[string]*TargetSpec{} 260 for _, entry := range entries { 261 entryInfo, err := entry.Info() 262 if err != nil { 263 return nil, fmt.Errorf("could not get entry info: %w", err) 264 } 265 if !entryInfo.Mode().IsRegular() || !strings.HasSuffix(entry.Name(), ".json") { 266 // Only inspect JSON files. 267 continue 268 } 269 path := filepath.Join(dir, entry.Name()) 270 spec, err := LoadTarget(&Options{Target: path}) 271 if err != nil { 272 return nil, fmt.Errorf("could not list target: %w", err) 273 } 274 if spec.FlashMethod == "" && spec.FlashCommand == "" && spec.Emulator == "" { 275 // This doesn't look like a regular target file, but rather like 276 // a parent target (such as targets/cortex-m.json). 277 continue 278 } 279 name := entry.Name() 280 name = name[:len(name)-5] 281 maps[name] = spec 282 } 283 return maps, nil 284 } 285 286 func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { 287 // No target spec available. Use the default one, useful on most systems 288 // with a regular OS. 289 spec := TargetSpec{ 290 Triple: triple, 291 GOOS: goos, 292 GOARCH: goarch, 293 BuildTags: []string{goos, goarch}, 294 GC: "precise", 295 Scheduler: "tasks", 296 Linker: "cc", 297 DefaultStackSize: 1024 * 64, // 64kB 298 GDB: []string{"gdb"}, 299 PortReset: "false", 300 } 301 switch goarch { 302 case "386": 303 spec.CPU = "pentium4" 304 spec.Features = "+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" 305 case "amd64": 306 spec.CPU = "x86-64" 307 spec.Features = "+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" 308 case "arm": 309 spec.CPU = "generic" 310 spec.CFlags = append(spec.CFlags, "-fno-unwind-tables", "-fno-asynchronous-unwind-tables") 311 switch strings.Split(triple, "-")[0] { 312 case "armv5": 313 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" 314 case "armv6": 315 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" 316 case "armv7": 317 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" 318 } 319 case "arm64": 320 spec.CPU = "generic" 321 if goos == "darwin" { 322 spec.Features = "+neon" 323 } else if goos == "windows" { 324 spec.Features = "+neon,-fmv" 325 } else { // linux 326 spec.Features = "+neon,-fmv,-outline-atomics" 327 } 328 case "wasm": 329 spec.CPU = "generic" 330 spec.Features = "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" 331 spec.BuildTags = append(spec.BuildTags, "tinygo.wasm") 332 spec.CFlags = append(spec.CFlags, 333 "-mbulk-memory", 334 "-mnontrapping-fptoint", 335 "-msign-ext", 336 ) 337 } 338 if goos == "darwin" { 339 spec.Linker = "ld.lld" 340 spec.Libc = "darwin-libSystem" 341 arch := strings.Split(triple, "-")[0] 342 platformVersion := strings.TrimPrefix(strings.Split(triple, "-")[2], "macosx") 343 spec.LDFlags = append(spec.LDFlags, 344 "-flavor", "darwin", 345 "-dead_strip", 346 "-arch", arch, 347 "-platform_version", "macos", platformVersion, platformVersion, 348 ) 349 } else if goos == "linux" { 350 spec.Linker = "ld.lld" 351 spec.RTLib = "compiler-rt" 352 spec.Libc = "musl" 353 spec.LDFlags = append(spec.LDFlags, "--gc-sections") 354 if goarch == "arm64" { 355 // Disable outline atomics. For details, see: 356 // https://cpufun.substack.com/p/atomics-in-aarch64 357 // A better way would be to fully support outline atomics, which 358 // makes atomics slightly more efficient on systems with many cores. 359 // But the instructions are only supported on newer aarch64 CPUs, so 360 // this feature is normally put in a system library which does 361 // feature detection for you. 362 // We take the lazy way out and simply disable this feature, instead 363 // of enabling it in compiler-rt (which is a bit more complicated). 364 // We don't really need this feature anyway as we don't even support 365 // proper threading. 366 spec.CFlags = append(spec.CFlags, "-mno-outline-atomics") 367 } 368 } else if goos == "windows" { 369 spec.Linker = "ld.lld" 370 spec.Libc = "mingw-w64" 371 // Note: using a medium code model, low image base and no ASLR 372 // because Go doesn't really need those features. ASLR patches 373 // around issues for unsafe languages like C/C++ that are not 374 // normally present in Go (without explicitly opting in). 375 // For more discussion: 376 // https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1 377 switch goarch { 378 case "amd64": 379 spec.LDFlags = append(spec.LDFlags, 380 "-m", "i386pep", 381 "--image-base", "0x400000", 382 ) 383 case "arm64": 384 spec.LDFlags = append(spec.LDFlags, 385 "-m", "arm64pe", 386 ) 387 } 388 spec.LDFlags = append(spec.LDFlags, 389 "-Bdynamic", 390 "--gc-sections", 391 "--no-insert-timestamp", 392 "--no-dynamicbase", 393 ) 394 } else if goos == "wasip1" { 395 spec.GC = "" // use default GC 396 spec.Scheduler = "asyncify" 397 spec.Linker = "wasm-ld" 398 spec.RTLib = "compiler-rt" 399 spec.Libc = "wasi-libc" 400 spec.DefaultStackSize = 1024 * 64 // 64kB 401 spec.LDFlags = append(spec.LDFlags, 402 "--stack-first", 403 "--no-demangle", 404 ) 405 spec.Emulator = "wasmtime --dir={tmpDir}::/tmp {}" 406 spec.ExtraFiles = append(spec.ExtraFiles, 407 "src/runtime/asm_tinygowasm.S", 408 "src/internal/task/task_asyncify_wasm.S", 409 ) 410 } else { 411 spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie 412 } 413 if goarch != "wasm" { 414 suffix := "" 415 if goos == "windows" && goarch == "amd64" { 416 // Windows uses a different calling convention on amd64 from other 417 // operating systems so we need separate assembly files. 418 suffix = "_windows" 419 } 420 spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+goarch+suffix+".S") 421 spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+suffix+".S") 422 } 423 if goarch != runtime.GOARCH { 424 // Some educated guesses as to how to invoke helper programs. 425 spec.GDB = []string{"gdb-multiarch"} 426 if goos == "linux" { 427 switch goarch { 428 case "386": 429 // amd64 can _usually_ run 32-bit programs, so skip the emulator in that case. 430 if runtime.GOARCH != "amd64" { 431 spec.Emulator = "qemu-i386 {}" 432 } 433 case "amd64": 434 spec.Emulator = "qemu-x86_64 {}" 435 case "arm": 436 spec.Emulator = "qemu-arm {}" 437 case "arm64": 438 spec.Emulator = "qemu-aarch64 {}" 439 } 440 } 441 } 442 if goos != runtime.GOOS { 443 if goos == "windows" { 444 spec.Emulator = "wine {}" 445 } 446 } 447 return &spec, nil 448 } 449 450 // LookupGDB looks up a gdb executable. 451 func (spec *TargetSpec) LookupGDB() (string, error) { 452 if len(spec.GDB) == 0 { 453 return "", errors.New("gdb not configured in the target specification") 454 } 455 for _, d := range spec.GDB { 456 _, err := exec.LookPath(d) 457 if err == nil { 458 return d, nil 459 } 460 } 461 return "", errors.New("no gdb found configured in the target specification (" + strings.Join(spec.GDB, ", ") + ")") 462 }