github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/builder/build.go (about) 1 // Package builder is the compiler driver of TinyGo. It takes in a package name 2 // and an output path, and outputs an executable. It manages the entire 3 // compilation pipeline in between. 4 package builder 5 6 import ( 7 "crypto/sha256" 8 "crypto/sha512" 9 "debug/elf" 10 "encoding/binary" 11 "encoding/hex" 12 "encoding/json" 13 "errors" 14 "fmt" 15 "go/types" 16 "hash/crc32" 17 "io/fs" 18 "math/bits" 19 "os" 20 "os/exec" 21 "path/filepath" 22 "runtime" 23 "sort" 24 "strconv" 25 "strings" 26 27 "github.com/gofrs/flock" 28 "github.com/tinygo-org/tinygo/compileopts" 29 "github.com/tinygo-org/tinygo/compiler" 30 "github.com/tinygo-org/tinygo/goenv" 31 "github.com/tinygo-org/tinygo/interp" 32 "github.com/tinygo-org/tinygo/loader" 33 "github.com/tinygo-org/tinygo/stacksize" 34 "github.com/tinygo-org/tinygo/transform" 35 "tinygo.org/x/go-llvm" 36 ) 37 38 // BuildResult is the output of a build. This includes the binary itself and 39 // some other metadata that is obtained while building the binary. 40 type BuildResult struct { 41 // The executable directly from the linker, usually including debug 42 // information. Used for GDB for example. 43 Executable string 44 45 // A path to the output binary. It is stored in the tmpdir directory of the 46 // Build function, so if it should be kept it must be copied or moved away. 47 // It is often the same as Executable, but differs if the output format is 48 // .hex for example (instead of the usual ELF). 49 Binary string 50 51 // The directory of the main package. This is useful for testing as the test 52 // binary must be run in the directory of the tested package. 53 MainDir string 54 55 // The root of the Go module tree. This is used for running tests in emulator 56 // that restrict file system access to allow them to grant access to the entire 57 // source tree they're likely to need to read testdata from. 58 ModuleRoot string 59 60 // ImportPath is the import path of the main package. This is useful for 61 // correctly printing test results: the import path isn't always the same as 62 // the path listed on the command line. 63 ImportPath string 64 } 65 66 // packageAction is the struct that is serialized to JSON and hashed, to work as 67 // a cache key of compiled packages. It should contain all the information that 68 // goes into a compiled package to avoid using stale data. 69 // 70 // Right now it's still important to include a hash of every import, because a 71 // dependency might have a public constant that this package uses and thus this 72 // package will need to be recompiled if that constant changes. In the future, 73 // the type data should be serialized to disk which can then be used as cache 74 // key, avoiding the need for recompiling all dependencies when only the 75 // implementation of an imported package changes. 76 type packageAction struct { 77 ImportPath string 78 CompilerBuildID string 79 TinyGoVersion string 80 LLVMVersion string 81 Config *compiler.Config 82 CFlags []string 83 FileHashes map[string]string // hash of every file that's part of the package 84 EmbeddedFiles map[string]string // hash of all the //go:embed files in the package 85 Imports map[string]string // map from imported package to action ID hash 86 OptLevel string // LLVM optimization level (O0, O1, O2, Os, Oz) 87 UndefinedGlobals []string // globals that are left as external globals (no initializer) 88 } 89 90 // Build performs a single package to executable Go build. It takes in a package 91 // name, an output path, and set of compile options and from that it manages the 92 // whole compilation process. 93 // 94 // The error value may be of type *MultiError. Callers will likely want to check 95 // for this case and print such errors individually. 96 func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildResult, error) { 97 // Read the build ID of the tinygo binary. 98 // Used as a cache key for package builds. 99 compilerBuildID, err := ReadBuildID() 100 if err != nil { 101 return BuildResult{}, err 102 } 103 104 if config.Options.Work { 105 fmt.Printf("WORK=%s\n", tmpdir) 106 } 107 108 // Look up the build cache directory, which is used to speed up incremental 109 // builds. 110 cacheDir := goenv.Get("GOCACHE") 111 if cacheDir == "off" { 112 // Use temporary build directory instead, effectively disabling the 113 // build cache. 114 cacheDir = tmpdir 115 } 116 117 // Create default global values. 118 globalValues := map[string]map[string]string{ 119 "runtime": { 120 "buildVersion": goenv.Version(), 121 }, 122 "testing": {}, 123 } 124 if config.TestConfig.CompileTestBinary { 125 // The testing.testBinary is set to "1" when in a test. 126 // This is needed for testing.Testing() to work correctly. 127 globalValues["testing"]["testBinary"] = "1" 128 } 129 130 // Copy over explicitly set global values, like 131 // -ldflags="-X main.Version="1.0" 132 for pkgPath, vals := range config.Options.GlobalValues { 133 if _, ok := globalValues[pkgPath]; !ok { 134 globalValues[pkgPath] = map[string]string{} 135 } 136 for k, v := range vals { 137 globalValues[pkgPath][k] = v 138 } 139 } 140 141 // Check for a libc dependency. 142 // As a side effect, this also creates the headers for the given libc, if 143 // the libc needs them. 144 root := goenv.Get("TINYGOROOT") 145 var libcDependencies []*compileJob 146 switch config.Target.Libc { 147 case "darwin-libSystem": 148 job := makeDarwinLibSystemJob(config, tmpdir) 149 libcDependencies = append(libcDependencies, job) 150 case "musl": 151 job, unlock, err := Musl.load(config, tmpdir) 152 if err != nil { 153 return BuildResult{}, err 154 } 155 defer unlock() 156 libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(job.result), "crt1.o"))) 157 libcDependencies = append(libcDependencies, job) 158 case "picolibc": 159 libcJob, unlock, err := Picolibc.load(config, tmpdir) 160 if err != nil { 161 return BuildResult{}, err 162 } 163 defer unlock() 164 libcDependencies = append(libcDependencies, libcJob) 165 case "wasi-libc": 166 path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a") 167 if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { 168 return BuildResult{}, errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?") 169 } 170 libcDependencies = append(libcDependencies, dummyCompileJob(path)) 171 case "wasmbuiltins": 172 libcJob, unlock, err := WasmBuiltins.load(config, tmpdir) 173 if err != nil { 174 return BuildResult{}, err 175 } 176 defer unlock() 177 libcDependencies = append(libcDependencies, libcJob) 178 case "mingw-w64": 179 _, unlock, err := MinGW.load(config, tmpdir) 180 if err != nil { 181 return BuildResult{}, err 182 } 183 unlock() 184 libcDependencies = append(libcDependencies, makeMinGWExtraLibs(tmpdir, config.GOARCH())...) 185 case "": 186 // no library specified, so nothing to do 187 default: 188 return BuildResult{}, fmt.Errorf("unknown libc: %s", config.Target.Libc) 189 } 190 191 optLevel, speedLevel, sizeLevel := config.OptLevel() 192 compilerConfig := &compiler.Config{ 193 Triple: config.Triple(), 194 CPU: config.CPU(), 195 Features: config.Features(), 196 ABI: config.ABI(), 197 GOOS: config.GOOS(), 198 GOARCH: config.GOARCH(), 199 CodeModel: config.CodeModel(), 200 RelocationModel: config.RelocationModel(), 201 SizeLevel: sizeLevel, 202 TinyGoVersion: goenv.Version(), 203 204 Scheduler: config.Scheduler(), 205 AutomaticStackSize: config.AutomaticStackSize(), 206 DefaultStackSize: config.StackSize(), 207 MaxStackAlloc: config.MaxStackAlloc(), 208 NeedsStackObjects: config.NeedsStackObjects(), 209 Debug: !config.Options.SkipDWARF, // emit DWARF except when -internal-nodwarf is passed 210 PanicStrategy: config.PanicStrategy(), 211 } 212 213 // Load the target machine, which is the LLVM object that contains all 214 // details of a target (alignment restrictions, pointer size, default 215 // address spaces, etc). 216 machine, err := compiler.NewTargetMachine(compilerConfig) 217 if err != nil { 218 return BuildResult{}, err 219 } 220 defer machine.Dispose() 221 222 // Load entire program AST into memory. 223 lprogram, err := loader.Load(config, pkgName, types.Config{ 224 Sizes: compiler.Sizes(machine), 225 }) 226 if err != nil { 227 return BuildResult{}, err 228 } 229 result := BuildResult{ 230 ModuleRoot: lprogram.MainPkg().Module.Dir, 231 MainDir: lprogram.MainPkg().Dir, 232 ImportPath: lprogram.MainPkg().ImportPath, 233 } 234 if result.ModuleRoot == "" { 235 // If there is no module root, just the regular root. 236 result.ModuleRoot = lprogram.MainPkg().Root 237 } 238 err = lprogram.Parse() 239 if err != nil { 240 return result, err 241 } 242 243 // Create the *ssa.Program. This does not yet build the entire SSA of the 244 // program so it's pretty fast and doesn't need to be parallelized. 245 program := lprogram.LoadSSA() 246 247 // Add jobs to compile each package. 248 // Packages that have a cache hit will not be compiled again. 249 var packageJobs []*compileJob 250 packageActionIDJobs := make(map[string]*compileJob) 251 252 var embedFileObjects []*compileJob 253 for _, pkg := range lprogram.Sorted() { 254 pkg := pkg // necessary to avoid a race condition 255 256 var undefinedGlobals []string 257 for name := range globalValues[pkg.Pkg.Path()] { 258 undefinedGlobals = append(undefinedGlobals, name) 259 } 260 sort.Strings(undefinedGlobals) 261 262 // Make compile jobs to load files to be embedded in the output binary. 263 var actionIDDependencies []*compileJob 264 allFiles := map[string][]*loader.EmbedFile{} 265 for _, files := range pkg.EmbedGlobals { 266 for _, file := range files { 267 allFiles[file.Name] = append(allFiles[file.Name], file) 268 } 269 } 270 for name, files := range allFiles { 271 name := name 272 files := files 273 job := &compileJob{ 274 description: "make object file for " + name, 275 run: func(job *compileJob) error { 276 // Read the file contents in memory. 277 path := filepath.Join(pkg.Dir, name) 278 data, err := os.ReadFile(path) 279 if err != nil { 280 return err 281 } 282 283 // Hash the file. 284 sum := sha256.Sum256(data) 285 hexSum := hex.EncodeToString(sum[:16]) 286 287 for _, file := range files { 288 file.Size = uint64(len(data)) 289 file.Hash = hexSum 290 if file.NeedsData { 291 file.Data = data 292 } 293 } 294 295 job.result, err = createEmbedObjectFile(string(data), hexSum, name, pkg.OriginalDir(), tmpdir, compilerConfig) 296 return err 297 }, 298 } 299 actionIDDependencies = append(actionIDDependencies, job) 300 embedFileObjects = append(embedFileObjects, job) 301 } 302 303 // Action ID jobs need to know the action ID of all the jobs the package 304 // imports. 305 var importedPackages []*compileJob 306 for _, imported := range pkg.Pkg.Imports() { 307 job, ok := packageActionIDJobs[imported.Path()] 308 if !ok { 309 return result, fmt.Errorf("package %s imports %s but couldn't find dependency", pkg.ImportPath, imported.Path()) 310 } 311 importedPackages = append(importedPackages, job) 312 actionIDDependencies = append(actionIDDependencies, job) 313 } 314 315 // Create a job that will calculate the action ID for a package compile 316 // job. The action ID is the cache key that is used for caching this 317 // package. 318 packageActionIDJob := &compileJob{ 319 description: "calculate cache key for package " + pkg.ImportPath, 320 dependencies: actionIDDependencies, 321 run: func(job *compileJob) error { 322 // Create a cache key: a hash from the action ID below that contains all 323 // the parameters for the build. 324 actionID := packageAction{ 325 ImportPath: pkg.ImportPath, 326 CompilerBuildID: string(compilerBuildID), 327 LLVMVersion: llvm.Version, 328 Config: compilerConfig, 329 CFlags: pkg.CFlags, 330 FileHashes: make(map[string]string, len(pkg.FileHashes)), 331 EmbeddedFiles: make(map[string]string, len(allFiles)), 332 Imports: make(map[string]string, len(pkg.Pkg.Imports())), 333 OptLevel: optLevel, 334 UndefinedGlobals: undefinedGlobals, 335 } 336 for filePath, hash := range pkg.FileHashes { 337 actionID.FileHashes[filePath] = hex.EncodeToString(hash) 338 } 339 for name, files := range allFiles { 340 actionID.EmbeddedFiles[name] = files[0].Hash 341 } 342 for i, imported := range pkg.Pkg.Imports() { 343 actionID.Imports[imported.Path()] = importedPackages[i].result 344 } 345 buf, err := json.Marshal(actionID) 346 if err != nil { 347 return err // shouldn't happen 348 } 349 hash := sha512.Sum512_224(buf) 350 job.result = hex.EncodeToString(hash[:]) 351 return nil 352 }, 353 } 354 packageActionIDJobs[pkg.ImportPath] = packageActionIDJob 355 356 // Build the SSA for the given package. 357 ssaPkg := program.Package(pkg.Pkg) 358 ssaPkg.Build() 359 360 // Now create the job to actually build the package. It will exit early 361 // if the package is already compiled. 362 job := &compileJob{ 363 description: "compile package " + pkg.ImportPath, 364 dependencies: []*compileJob{packageActionIDJob}, 365 run: func(job *compileJob) error { 366 job.result = filepath.Join(cacheDir, "pkg-"+packageActionIDJob.result+".bc") 367 // Acquire a lock (if supported). 368 unlock := lock(job.result + ".lock") 369 defer unlock() 370 371 if _, err := os.Stat(job.result); err == nil { 372 // Already cached, don't recreate this package. 373 return nil 374 } 375 376 // Compile AST to IR. The compiler.CompilePackage function will 377 // build the SSA as needed. 378 mod, errs := compiler.CompilePackage(pkg.ImportPath, pkg, program.Package(pkg.Pkg), machine, compilerConfig, config.DumpSSA()) 379 defer mod.Context().Dispose() 380 defer mod.Dispose() 381 if errs != nil { 382 return newMultiError(errs) 383 } 384 if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { 385 return errors.New("verification error after compiling package " + pkg.ImportPath) 386 } 387 388 // Load bitcode of CGo headers and join the modules together. 389 // This may seem vulnerable to cache problems, but this is not 390 // the case: the Go code that was just compiled already tracks 391 // all C files that are read and hashes them. 392 // These headers could be compiled in parallel but the benefit 393 // is so small that it's probably not worth parallelizing. 394 // Packages are compiled independently anyway. 395 for _, cgoHeader := range pkg.CGoHeaders { 396 // Store the header text in a temporary file. 397 f, err := os.CreateTemp(tmpdir, "cgosnippet-*.c") 398 if err != nil { 399 return err 400 } 401 _, err = f.Write([]byte(cgoHeader)) 402 if err != nil { 403 return err 404 } 405 f.Close() 406 407 // Compile the code (if there is any) to bitcode. 408 flags := append([]string{"-c", "-emit-llvm", "-o", f.Name() + ".bc", f.Name()}, pkg.CFlags...) 409 if config.Options.PrintCommands != nil { 410 config.Options.PrintCommands("clang", flags...) 411 } 412 err = runCCompiler(flags...) 413 if err != nil { 414 return &commandError{"failed to build CGo header", "", err} 415 } 416 417 // Load and link the bitcode. 418 // This makes it possible to optimize the functions defined 419 // in the header together with the Go code. In particular, 420 // this allows inlining. It also ensures there is only one 421 // file per package to cache. 422 headerMod, err := mod.Context().ParseBitcodeFile(f.Name() + ".bc") 423 if err != nil { 424 return fmt.Errorf("failed to load bitcode file: %w", err) 425 } 426 err = llvm.LinkModules(mod, headerMod) 427 if err != nil { 428 return fmt.Errorf("failed to link module: %w", err) 429 } 430 } 431 432 // Erase all globals that are part of the undefinedGlobals list. 433 // This list comes from the -ldflags="-X pkg.foo=val" option. 434 // Instead of setting the value directly in the AST (which would 435 // mean the value, which may be a secret, is stored in the build 436 // cache), the global itself is left external (undefined) and is 437 // only set at the end of the compilation. 438 for _, name := range undefinedGlobals { 439 globalName := pkg.Pkg.Path() + "." + name 440 global := mod.NamedGlobal(globalName) 441 if global.IsNil() { 442 return errors.New("global not found: " + globalName) 443 } 444 name := global.Name() 445 newGlobal := llvm.AddGlobal(mod, global.GlobalValueType(), name+".tmp") 446 global.ReplaceAllUsesWith(newGlobal) 447 global.EraseFromParentAsGlobal() 448 newGlobal.SetName(name) 449 } 450 451 // Try to interpret package initializers at compile time. 452 // It may only be possible to do this partially, in which case 453 // it is completed after all IR files are linked. 454 pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init") 455 if pkgInit.IsNil() { 456 panic("init not found for " + pkg.Pkg.Path()) 457 } 458 err := interp.RunFunc(pkgInit, config.Options.InterpTimeout, config.DumpSSA()) 459 if err != nil { 460 return err 461 } 462 if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { 463 return errors.New("verification error after interpreting " + pkgInit.Name()) 464 } 465 466 transform.OptimizePackage(mod, config) 467 468 // Serialize the LLVM module as a bitcode file. 469 // Write to a temporary path that is renamed to the destination 470 // file to avoid race conditions with other TinyGo invocatiosn 471 // that might also be compiling this package at the same time. 472 f, err := os.CreateTemp(filepath.Dir(job.result), filepath.Base(job.result)) 473 if err != nil { 474 return err 475 } 476 if runtime.GOOS == "windows" { 477 // Work around a problem on Windows. 478 // For some reason, WriteBitcodeToFile causes TinyGo to 479 // exit with the following message: 480 // LLVM ERROR: IO failure on output stream: Bad file descriptor 481 buf := llvm.WriteBitcodeToMemoryBuffer(mod) 482 defer buf.Dispose() 483 _, err = f.Write(buf.Bytes()) 484 } else { 485 // Otherwise, write bitcode directly to the file (probably 486 // faster). 487 err = llvm.WriteBitcodeToFile(mod, f) 488 } 489 if err != nil { 490 // WriteBitcodeToFile doesn't produce a useful error on its 491 // own, so create a somewhat useful error message here. 492 return fmt.Errorf("failed to write bitcode for package %s to file %s", pkg.ImportPath, job.result) 493 } 494 err = f.Close() 495 if err != nil { 496 return err 497 } 498 return os.Rename(f.Name(), job.result) 499 }, 500 } 501 packageJobs = append(packageJobs, job) 502 } 503 504 // Add job that links and optimizes all packages together. 505 var mod llvm.Module 506 defer func() { 507 if !mod.IsNil() { 508 ctx := mod.Context() 509 mod.Dispose() 510 ctx.Dispose() 511 } 512 }() 513 var stackSizeLoads []string 514 programJob := &compileJob{ 515 description: "link+optimize packages (LTO)", 516 dependencies: packageJobs, 517 run: func(*compileJob) error { 518 // Load and link all the bitcode files. This does not yet optimize 519 // anything, it only links the bitcode files together. 520 ctx := llvm.NewContext() 521 mod = ctx.NewModule("main") 522 for _, pkgJob := range packageJobs { 523 pkgMod, err := ctx.ParseBitcodeFile(pkgJob.result) 524 if err != nil { 525 return fmt.Errorf("failed to load bitcode file: %w", err) 526 } 527 err = llvm.LinkModules(mod, pkgMod) 528 if err != nil { 529 return fmt.Errorf("failed to link module: %w", err) 530 } 531 } 532 533 // Create runtime.initAll function that calls the runtime 534 // initializer of each package. 535 llvmInitFn := mod.NamedFunction("runtime.initAll") 536 llvmInitFn.SetLinkage(llvm.InternalLinkage) 537 llvmInitFn.SetUnnamedAddr(true) 538 transform.AddStandardAttributes(llvmInitFn, config) 539 llvmInitFn.Param(0).SetName("context") 540 block := mod.Context().AddBasicBlock(llvmInitFn, "entry") 541 irbuilder := mod.Context().NewBuilder() 542 defer irbuilder.Dispose() 543 irbuilder.SetInsertPointAtEnd(block) 544 ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) 545 for _, pkg := range lprogram.Sorted() { 546 pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init") 547 if pkgInit.IsNil() { 548 panic("init not found for " + pkg.Pkg.Path()) 549 } 550 irbuilder.CreateCall(pkgInit.GlobalValueType(), pkgInit, []llvm.Value{llvm.Undef(ptrType)}, "") 551 } 552 irbuilder.CreateRetVoid() 553 554 // After linking, functions should (as far as possible) be set to 555 // private linkage or internal linkage. The compiler package marks 556 // non-exported functions by setting the visibility to hidden or 557 // (for thunks) to linkonce_odr linkage. Change the linkage here to 558 // internal to benefit much more from interprocedural optimizations. 559 for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { 560 if fn.Visibility() == llvm.HiddenVisibility { 561 fn.SetVisibility(llvm.DefaultVisibility) 562 fn.SetLinkage(llvm.InternalLinkage) 563 } else if fn.Linkage() == llvm.LinkOnceODRLinkage { 564 fn.SetLinkage(llvm.InternalLinkage) 565 } 566 } 567 568 // Do the same for globals. 569 for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) { 570 if global.Visibility() == llvm.HiddenVisibility { 571 global.SetVisibility(llvm.DefaultVisibility) 572 global.SetLinkage(llvm.InternalLinkage) 573 } else if global.Linkage() == llvm.LinkOnceODRLinkage { 574 global.SetLinkage(llvm.InternalLinkage) 575 } 576 } 577 578 if config.Options.PrintIR { 579 fmt.Println("; Generated LLVM IR:") 580 fmt.Println(mod.String()) 581 } 582 583 // Run all optimization passes, which are much more effective now 584 // that the optimizer can see the whole program at once. 585 err := optimizeProgram(mod, config, globalValues) 586 if err != nil { 587 return err 588 } 589 590 // Make sure stack sizes are loaded from a separate section so they can be 591 // modified after linking. 592 if config.AutomaticStackSize() { 593 stackSizeLoads = transform.CreateStackSizeLoads(mod, config) 594 } 595 return nil 596 }, 597 } 598 599 // Check whether we only need to create an object file. 600 // If so, we don't need to link anything and will be finished quickly. 601 outext := filepath.Ext(outpath) 602 if outext == ".o" || outext == ".bc" || outext == ".ll" { 603 // Run jobs to produce the LLVM module. 604 err := runJobs(programJob, config.Options.Semaphore) 605 if err != nil { 606 return result, err 607 } 608 // Generate output. 609 switch outext { 610 case ".o": 611 llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile) 612 if err != nil { 613 return result, err 614 } 615 defer llvmBuf.Dispose() 616 return result, os.WriteFile(outpath, llvmBuf.Bytes(), 0666) 617 case ".bc": 618 buf := llvm.WriteThinLTOBitcodeToMemoryBuffer(mod) 619 defer buf.Dispose() 620 return result, os.WriteFile(outpath, buf.Bytes(), 0666) 621 case ".ll": 622 data := []byte(mod.String()) 623 return result, os.WriteFile(outpath, data, 0666) 624 default: 625 panic("unreachable") 626 } 627 } 628 629 // Act as a compiler driver, as we need to produce a complete executable. 630 // First add all jobs necessary to build this object file, then afterwards 631 // run all jobs in parallel as far as possible. 632 633 // Add job to write the output object file. 634 objfile := filepath.Join(tmpdir, "main.o") 635 outputObjectFileJob := &compileJob{ 636 description: "generate output file", 637 dependencies: []*compileJob{programJob}, 638 result: objfile, 639 run: func(*compileJob) error { 640 llvmBuf := llvm.WriteThinLTOBitcodeToMemoryBuffer(mod) 641 defer llvmBuf.Dispose() 642 return os.WriteFile(objfile, llvmBuf.Bytes(), 0666) 643 }, 644 } 645 646 // Prepare link command. 647 linkerDependencies := []*compileJob{outputObjectFileJob} 648 result.Executable = filepath.Join(tmpdir, "main") 649 if config.GOOS() == "windows" { 650 result.Executable += ".exe" 651 } 652 result.Binary = result.Executable // final file 653 ldflags := append(config.LDFlags(), "-o", result.Executable) 654 655 // Add compiler-rt dependency if needed. Usually this is a simple load from 656 // a cache. 657 if config.Target.RTLib == "compiler-rt" { 658 job, unlock, err := CompilerRT.load(config, tmpdir) 659 if err != nil { 660 return result, err 661 } 662 defer unlock() 663 linkerDependencies = append(linkerDependencies, job) 664 } 665 666 // Add jobs to compile extra files. These files are in C or assembly and 667 // contain things like the interrupt vector table and low level operations 668 // such as stack switching. 669 for _, path := range config.ExtraFiles() { 670 abspath := filepath.Join(root, path) 671 job := &compileJob{ 672 description: "compile extra file " + path, 673 run: func(job *compileJob) error { 674 result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(false), config.Options.PrintCommands) 675 job.result = result 676 return err 677 }, 678 } 679 linkerDependencies = append(linkerDependencies, job) 680 } 681 682 // Add jobs to compile C files in all packages. This is part of CGo. 683 // TODO: do this as part of building the package to be able to link the 684 // bitcode files together. 685 for _, pkg := range lprogram.Sorted() { 686 pkg := pkg 687 for _, filename := range pkg.CFiles { 688 abspath := filepath.Join(pkg.Dir, filename) 689 job := &compileJob{ 690 description: "compile CGo file " + abspath, 691 run: func(job *compileJob) error { 692 result, err := compileAndCacheCFile(abspath, tmpdir, pkg.CFlags, config.Options.PrintCommands) 693 job.result = result 694 return err 695 }, 696 } 697 linkerDependencies = append(linkerDependencies, job) 698 } 699 } 700 701 // Linker flags from CGo lines: 702 // #cgo LDFLAGS: foo 703 if len(lprogram.LDFlags) > 0 { 704 ldflags = append(ldflags, lprogram.LDFlags...) 705 } 706 707 // Add libc dependencies, if they exist. 708 linkerDependencies = append(linkerDependencies, libcDependencies...) 709 710 // Add embedded files. 711 linkerDependencies = append(linkerDependencies, embedFileObjects...) 712 713 // Determine whether the compilation configuration would result in debug 714 // (DWARF) information in the object files. 715 var hasDebug = true 716 if config.GOOS() == "darwin" { 717 // Debug information isn't stored in the binary itself on MacOS but 718 // is left in the object files by default. The binary does store the 719 // path to these object files though. 720 hasDebug = false 721 } 722 723 // Strip debug information with -no-debug. 724 if hasDebug && !config.Debug() { 725 if config.Target.Linker == "wasm-ld" { 726 // Don't just strip debug information, also compress relocations 727 // while we're at it. Relocations can only be compressed when debug 728 // information is stripped. 729 ldflags = append(ldflags, "--strip-debug", "--compress-relocations") 730 } else if config.Target.Linker == "ld.lld" { 731 // ld.lld is also used on Linux. 732 ldflags = append(ldflags, "--strip-debug") 733 } else { 734 // Other linkers may have different flags. 735 return result, errors.New("cannot remove debug information: unknown linker: " + config.Target.Linker) 736 } 737 } 738 739 // Create a linker job, which links all object files together and does some 740 // extra stuff that can only be done after linking. 741 linkJob := &compileJob{ 742 description: "link", 743 dependencies: linkerDependencies, 744 run: func(job *compileJob) error { 745 for _, dependency := range job.dependencies { 746 if dependency.result == "" { 747 return errors.New("dependency without result: " + dependency.description) 748 } 749 ldflags = append(ldflags, dependency.result) 750 } 751 ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU()) 752 if config.GOOS() == "windows" { 753 // Options for the MinGW wrapper for the lld COFF linker. 754 ldflags = append(ldflags, 755 "-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel), 756 "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto")) 757 } else if config.GOOS() == "darwin" { 758 // Options for the ld64-compatible lld linker. 759 ldflags = append(ldflags, 760 "--lto-O"+strconv.Itoa(speedLevel), 761 "-cache_path_lto", filepath.Join(cacheDir, "thinlto")) 762 } else { 763 // Options for the ELF linker. 764 ldflags = append(ldflags, 765 "--lto-O"+strconv.Itoa(speedLevel), 766 "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"), 767 ) 768 } 769 if config.CodeModel() != "default" { 770 ldflags = append(ldflags, 771 "-mllvm", "-code-model="+config.CodeModel()) 772 } 773 if sizeLevel >= 2 { 774 // Workaround with roughly the same effect as 775 // https://reviews.llvm.org/D119342. 776 // Can hopefully be removed in LLVM 18. 777 ldflags = append(ldflags, 778 "-mllvm", "--rotation-max-header-size=0") 779 } 780 if config.Options.PrintCommands != nil { 781 config.Options.PrintCommands(config.Target.Linker, ldflags...) 782 } 783 err = link(config.Target.Linker, ldflags...) 784 if err != nil { 785 return &commandError{"failed to link", result.Executable, err} 786 } 787 788 var calculatedStacks []string 789 var stackSizes map[string]functionStackSize 790 if config.Options.PrintStacks || config.AutomaticStackSize() { 791 // Try to determine stack sizes at compile time. 792 // Don't do this by default as it usually doesn't work on 793 // unsupported architectures. 794 calculatedStacks, stackSizes, err = determineStackSizes(mod, result.Executable) 795 if err != nil { 796 return err 797 } 798 } 799 800 // Apply ELF patches 801 if config.AutomaticStackSize() { 802 // Modify the .tinygo_stacksizes section that contains a stack size 803 // for each goroutine. 804 err = modifyStackSizes(result.Executable, stackSizeLoads, stackSizes) 805 if err != nil { 806 return fmt.Errorf("could not modify stack sizes: %w", err) 807 } 808 } 809 if config.RP2040BootPatch() { 810 // Patch the second stage bootloader CRC into the .boot2 section 811 err = patchRP2040BootCRC(result.Executable) 812 if err != nil { 813 return fmt.Errorf("could not patch RP2040 second stage boot loader: %w", err) 814 } 815 } 816 817 // Run wasm-opt for wasm binaries 818 if arch := strings.Split(config.Triple(), "-")[0]; arch == "wasm32" { 819 optLevel, _, _ := config.OptLevel() 820 opt := "-" + optLevel 821 822 var args []string 823 824 if config.Scheduler() == "asyncify" { 825 args = append(args, "--asyncify") 826 } 827 828 args = append(args, 829 opt, 830 "-g", 831 result.Executable, 832 "--output", result.Executable, 833 ) 834 835 cmd := exec.Command(goenv.Get("WASMOPT"), args...) 836 cmd.Stdout = os.Stdout 837 cmd.Stderr = os.Stderr 838 839 err := cmd.Run() 840 if err != nil { 841 return fmt.Errorf("wasm-opt failed: %w", err) 842 } 843 } 844 845 // Print code size if requested. 846 if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" { 847 packagePathMap := make(map[string]string, len(lprogram.Packages)) 848 for _, pkg := range lprogram.Sorted() { 849 packagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path() 850 } 851 sizes, err := loadProgramSize(result.Executable, packagePathMap) 852 if err != nil { 853 return err 854 } 855 if config.Options.PrintSizes == "short" { 856 fmt.Printf(" code data bss | flash ram\n") 857 fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code+sizes.ROData, sizes.Data, sizes.BSS, sizes.Flash(), sizes.RAM()) 858 } else { 859 if !config.Debug() { 860 fmt.Println("warning: data incomplete, remove the -no-debug flag for more detail") 861 } 862 fmt.Printf(" code rodata data bss | flash ram | package\n") 863 fmt.Printf("------------------------------- | --------------- | -------\n") 864 for _, name := range sizes.sortedPackageNames() { 865 pkgSize := sizes.Packages[name] 866 fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name) 867 } 868 fmt.Printf("------------------------------- | --------------- | -------\n") 869 fmt.Printf("%7d %7d %7d %7d | %7d %7d | total\n", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS, sizes.Code+sizes.ROData+sizes.Data, sizes.Data+sizes.BSS) 870 } 871 } 872 873 // Print goroutine stack sizes, as far as possible. 874 if config.Options.PrintStacks { 875 printStacks(calculatedStacks, stackSizes) 876 } 877 878 return nil 879 }, 880 } 881 882 // Run all jobs to compile and link the program. 883 // Do this now (instead of after elf-to-hex and similar conversions) as it 884 // is simpler and cannot be parallelized. 885 err = runJobs(linkJob, config.Options.Semaphore) 886 if err != nil { 887 return result, err 888 } 889 890 // Get an Intel .hex file or .bin file from the .elf file. 891 outputBinaryFormat := config.BinaryFormat(outext) 892 switch outputBinaryFormat { 893 case "elf": 894 // do nothing, file is already in ELF format 895 case "hex", "bin": 896 // Extract raw binary, either encoding it as a hex file or as a raw 897 // firmware file. 898 result.Binary = filepath.Join(tmpdir, "main"+outext) 899 err := objcopy(result.Executable, result.Binary, outputBinaryFormat) 900 if err != nil { 901 return result, err 902 } 903 case "uf2": 904 // Get UF2 from the .elf file. 905 result.Binary = filepath.Join(tmpdir, "main"+outext) 906 err := convertELFFileToUF2File(result.Executable, result.Binary, config.Target.UF2FamilyID) 907 if err != nil { 908 return result, err 909 } 910 case "esp32", "esp32-img", "esp32c3", "esp8266": 911 // Special format for the ESP family of chips (parsed by the ROM 912 // bootloader). 913 result.Binary = filepath.Join(tmpdir, "main"+outext) 914 err := makeESPFirmareImage(result.Executable, result.Binary, outputBinaryFormat) 915 if err != nil { 916 return result, err 917 } 918 case "nrf-dfu": 919 // special format for nrfutil for Nordic chips 920 result.Binary = filepath.Join(tmpdir, "main"+outext) 921 err = makeDFUFirmwareImage(config.Options, result.Executable, result.Binary) 922 if err != nil { 923 return result, err 924 } 925 default: 926 return result, fmt.Errorf("unknown output binary format: %s", outputBinaryFormat) 927 } 928 929 return result, nil 930 } 931 932 // createEmbedObjectFile creates a new object file with the given contents, for 933 // the embed package. 934 func createEmbedObjectFile(data, hexSum, sourceFile, sourceDir, tmpdir string, compilerConfig *compiler.Config) (string, error) { 935 // TODO: this works for small files, but can be a problem for larger files. 936 // For larger files, it seems more appropriate to generate the object file 937 // manually without going through LLVM. 938 // On the other hand, generating DWARF like we do here can be difficult 939 // without assistance from LLVM. 940 941 // Create new LLVM module just for this file. 942 ctx := llvm.NewContext() 943 defer ctx.Dispose() 944 mod := ctx.NewModule("data") 945 defer mod.Dispose() 946 947 // Create data global. 948 value := ctx.ConstString(data, false) 949 globalName := "embed/file_" + hexSum 950 global := llvm.AddGlobal(mod, value.Type(), globalName) 951 global.SetInitializer(value) 952 global.SetLinkage(llvm.LinkOnceODRLinkage) 953 global.SetGlobalConstant(true) 954 global.SetUnnamedAddr(true) 955 global.SetAlignment(1) 956 if compilerConfig.GOOS != "darwin" { 957 // MachO doesn't support COMDATs, while COFF requires it (to avoid 958 // "duplicate symbol" errors). ELF works either way. 959 // Therefore, only use a COMDAT on non-MachO systems (aka non-MacOS). 960 global.SetComdat(mod.Comdat(globalName)) 961 } 962 963 // Add DWARF debug information to this global, so that it is 964 // correctly counted when compiling with the -size= flag. 965 dibuilder := llvm.NewDIBuilder(mod) 966 dibuilder.CreateCompileUnit(llvm.DICompileUnit{ 967 Language: 0xb, // DW_LANG_C99 (0xc, off-by-one?) 968 File: sourceFile, 969 Dir: sourceDir, 970 Producer: "TinyGo", 971 Optimized: false, 972 }) 973 ditype := dibuilder.CreateArrayType(llvm.DIArrayType{ 974 SizeInBits: uint64(len(data)) * 8, 975 AlignInBits: 8, 976 ElementType: dibuilder.CreateBasicType(llvm.DIBasicType{ 977 Name: "byte", 978 SizeInBits: 8, 979 Encoding: llvm.DW_ATE_unsigned_char, 980 }), 981 Subscripts: []llvm.DISubrange{ 982 { 983 Lo: 0, 984 Count: int64(len(data)), 985 }, 986 }, 987 }) 988 difile := dibuilder.CreateFile(sourceFile, sourceDir) 989 diglobalexpr := dibuilder.CreateGlobalVariableExpression(difile, llvm.DIGlobalVariableExpression{ 990 Name: globalName, 991 File: difile, 992 Line: 1, 993 Type: ditype, 994 Expr: dibuilder.CreateExpression(nil), 995 AlignInBits: 8, 996 }) 997 global.AddMetadata(0, diglobalexpr) 998 mod.AddNamedMetadataOperand("llvm.module.flags", 999 ctx.MDNode([]llvm.Metadata{ 1000 llvm.ConstInt(ctx.Int32Type(), 2, false).ConstantAsMetadata(), // Warning on mismatch 1001 ctx.MDString("Debug Info Version"), 1002 llvm.ConstInt(ctx.Int32Type(), 3, false).ConstantAsMetadata(), 1003 }), 1004 ) 1005 mod.AddNamedMetadataOperand("llvm.module.flags", 1006 ctx.MDNode([]llvm.Metadata{ 1007 llvm.ConstInt(ctx.Int32Type(), 7, false).ConstantAsMetadata(), // Max on mismatch 1008 ctx.MDString("Dwarf Version"), 1009 llvm.ConstInt(ctx.Int32Type(), 4, false).ConstantAsMetadata(), 1010 }), 1011 ) 1012 dibuilder.Finalize() 1013 dibuilder.Destroy() 1014 1015 // Write this LLVM module out as an object file. 1016 machine, err := compiler.NewTargetMachine(compilerConfig) 1017 if err != nil { 1018 return "", err 1019 } 1020 defer machine.Dispose() 1021 outfile, err := os.CreateTemp(tmpdir, "embed-"+hexSum+"-*.o") 1022 if err != nil { 1023 return "", err 1024 } 1025 defer outfile.Close() 1026 buf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile) 1027 if err != nil { 1028 return "", err 1029 } 1030 defer buf.Dispose() 1031 _, err = outfile.Write(buf.Bytes()) 1032 if err != nil { 1033 return "", err 1034 } 1035 return outfile.Name(), outfile.Close() 1036 } 1037 1038 // optimizeProgram runs a series of optimizations and transformations that are 1039 // needed to convert a program to its final form. Some transformations are not 1040 // optional and must be run as the compiler expects them to run. 1041 func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues map[string]map[string]string) error { 1042 err := interp.Run(mod, config.Options.InterpTimeout, config.DumpSSA()) 1043 if err != nil { 1044 return err 1045 } 1046 if config.VerifyIR() { 1047 // Only verify if we really need it. 1048 // The IR has already been verified before writing the bitcode to disk 1049 // and the interp function above doesn't need to do a lot as most of the 1050 // package initializers have already run. Additionally, verifying this 1051 // linked IR is _expensive_ because dead code hasn't been removed yet, 1052 // easily costing a few hundred milliseconds. Therefore, only do it when 1053 // specifically requested. 1054 if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { 1055 return errors.New("verification error after interpreting runtime.initAll") 1056 } 1057 } 1058 1059 // Insert values from -ldflags="-X ..." into the IR. 1060 err = setGlobalValues(mod, globalValues) 1061 if err != nil { 1062 return err 1063 } 1064 1065 // Run most of the whole-program optimizations (including the whole 1066 // O0/O1/O2/Os/Oz optimization pipeline). 1067 errs := transform.Optimize(mod, config) 1068 if len(errs) > 0 { 1069 return newMultiError(errs) 1070 } 1071 if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { 1072 return errors.New("verification failure after LLVM optimization passes") 1073 } 1074 1075 return nil 1076 } 1077 1078 // setGlobalValues sets the global values from the -ldflags="-X ..." compiler 1079 // option in the given module. An error may be returned if the global is not of 1080 // the expected type. 1081 func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) error { 1082 var pkgPaths []string 1083 for pkgPath := range globals { 1084 pkgPaths = append(pkgPaths, pkgPath) 1085 } 1086 sort.Strings(pkgPaths) 1087 for _, pkgPath := range pkgPaths { 1088 pkg := globals[pkgPath] 1089 var names []string 1090 for name := range pkg { 1091 names = append(names, name) 1092 } 1093 sort.Strings(names) 1094 for _, name := range names { 1095 value := pkg[name] 1096 globalName := pkgPath + "." + name 1097 global := mod.NamedGlobal(globalName) 1098 if global.IsNil() || !global.Initializer().IsNil() { 1099 // The global either does not exist (optimized away?) or has 1100 // some value, in which case it has already been initialized at 1101 // package init time. 1102 continue 1103 } 1104 1105 // A strin is a {ptr, len} pair. We need these types to build the 1106 // initializer. 1107 initializerType := global.GlobalValueType() 1108 if initializerType.TypeKind() != llvm.StructTypeKind || initializerType.StructName() == "" { 1109 return fmt.Errorf("%s: not a string", globalName) 1110 } 1111 elementTypes := initializerType.StructElementTypes() 1112 if len(elementTypes) != 2 { 1113 return fmt.Errorf("%s: not a string", globalName) 1114 } 1115 1116 // Create a buffer for the string contents. 1117 bufInitializer := mod.Context().ConstString(value, false) 1118 buf := llvm.AddGlobal(mod, bufInitializer.Type(), ".string") 1119 buf.SetInitializer(bufInitializer) 1120 buf.SetAlignment(1) 1121 buf.SetUnnamedAddr(true) 1122 buf.SetLinkage(llvm.PrivateLinkage) 1123 1124 // Create the string value, which is a {ptr, len} pair. 1125 zero := llvm.ConstInt(mod.Context().Int32Type(), 0, false) 1126 ptr := llvm.ConstGEP(bufInitializer.Type(), buf, []llvm.Value{zero, zero}) 1127 if ptr.Type() != elementTypes[0] { 1128 return fmt.Errorf("%s: not a string", globalName) 1129 } 1130 length := llvm.ConstInt(elementTypes[1], uint64(len(value)), false) 1131 initializer := llvm.ConstNamedStruct(initializerType, []llvm.Value{ 1132 ptr, 1133 length, 1134 }) 1135 1136 // Set the initializer. No initializer should be set at this point. 1137 global.SetInitializer(initializer) 1138 } 1139 } 1140 return nil 1141 } 1142 1143 // functionStackSizes keeps stack size information about a single function 1144 // (usually a goroutine). 1145 type functionStackSize struct { 1146 humanName string 1147 stackSize uint64 1148 stackSizeType stacksize.SizeType 1149 missingStackSize *stacksize.CallNode 1150 } 1151 1152 // determineStackSizes tries to determine the stack sizes of all started 1153 // goroutines and of the reset vector. The LLVM module is necessary to find 1154 // functions that call a function pointer. 1155 func determineStackSizes(mod llvm.Module, executable string) ([]string, map[string]functionStackSize, error) { 1156 var callsIndirectFunction []string 1157 gowrappers := []string{} 1158 gowrapperNames := make(map[string]string) 1159 for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { 1160 // Determine which functions call a function pointer. 1161 for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) { 1162 for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { 1163 if inst.IsACallInst().IsNil() { 1164 continue 1165 } 1166 if callee := inst.CalledValue(); callee.IsAFunction().IsNil() && callee.IsAInlineAsm().IsNil() { 1167 callsIndirectFunction = append(callsIndirectFunction, fn.Name()) 1168 } 1169 } 1170 } 1171 1172 // Get a list of "go wrappers", small wrapper functions that decode 1173 // parameters when starting a new goroutine. 1174 attr := fn.GetStringAttributeAtIndex(-1, "tinygo-gowrapper") 1175 if !attr.IsNil() { 1176 gowrappers = append(gowrappers, fn.Name()) 1177 gowrapperNames[fn.Name()] = attr.GetStringValue() 1178 } 1179 } 1180 sort.Strings(gowrappers) 1181 1182 // Load the ELF binary. 1183 f, err := elf.Open(executable) 1184 if err != nil { 1185 return nil, nil, fmt.Errorf("could not load executable for stack size analysis: %w", err) 1186 } 1187 defer f.Close() 1188 1189 // Determine the frame size of each function (if available) and the callgraph. 1190 functions, err := stacksize.CallGraph(f, callsIndirectFunction) 1191 if err != nil { 1192 return nil, nil, fmt.Errorf("could not parse executable for stack size analysis: %w", err) 1193 } 1194 1195 // Goroutines need to be started and finished and take up some stack space 1196 // that way. This can be measured by measuing the stack size of 1197 // tinygo_startTask. 1198 if numFuncs := len(functions["tinygo_startTask"]); numFuncs != 1 { 1199 return nil, nil, fmt.Errorf("expected exactly one definition of tinygo_startTask, got %d", numFuncs) 1200 } 1201 baseStackSize, baseStackSizeType, baseStackSizeFailedAt := functions["tinygo_startTask"][0].StackSize() 1202 1203 sizes := make(map[string]functionStackSize) 1204 1205 // Add the reset handler function, for convenience. The reset handler runs 1206 // startup code and the scheduler. The listed stack size is not the full 1207 // stack size: interrupts are not counted. 1208 var resetFunction string 1209 switch f.Machine { 1210 case elf.EM_ARM: 1211 // Note: all interrupts happen on this stack so the real size is bigger. 1212 resetFunction = "Reset_Handler" 1213 } 1214 if resetFunction != "" { 1215 funcs := functions[resetFunction] 1216 if len(funcs) != 1 { 1217 return nil, nil, fmt.Errorf("expected exactly one definition of %s in the callgraph, found %d", resetFunction, len(funcs)) 1218 } 1219 stackSize, stackSizeType, missingStackSize := funcs[0].StackSize() 1220 sizes[resetFunction] = functionStackSize{ 1221 stackSize: stackSize, 1222 stackSizeType: stackSizeType, 1223 missingStackSize: missingStackSize, 1224 humanName: resetFunction, 1225 } 1226 } 1227 1228 // Add all goroutine wrapper functions. 1229 for _, name := range gowrappers { 1230 funcs := functions[name] 1231 if len(funcs) != 1 { 1232 return nil, nil, fmt.Errorf("expected exactly one definition of %s in the callgraph, found %d", name, len(funcs)) 1233 } 1234 humanName := gowrapperNames[name] 1235 if humanName == "" { 1236 humanName = name // fallback 1237 } 1238 stackSize, stackSizeType, missingStackSize := funcs[0].StackSize() 1239 if baseStackSizeType != stacksize.Bounded { 1240 // It was not possible to determine the stack size at compile time 1241 // because tinygo_startTask does not have a fixed stack size. This 1242 // can happen when using -opt=1. 1243 stackSizeType = baseStackSizeType 1244 missingStackSize = baseStackSizeFailedAt 1245 } else if stackSize < baseStackSize { 1246 // This goroutine has a very small stack, but still needs to fit all 1247 // registers to start and suspend the goroutine. Otherwise a stack 1248 // overflow will occur even before the goroutine is started. 1249 stackSize = baseStackSize 1250 } 1251 sizes[name] = functionStackSize{ 1252 stackSize: stackSize, 1253 stackSizeType: stackSizeType, 1254 missingStackSize: missingStackSize, 1255 humanName: humanName, 1256 } 1257 } 1258 1259 if resetFunction != "" { 1260 return append([]string{resetFunction}, gowrappers...), sizes, nil 1261 } 1262 return gowrappers, sizes, nil 1263 } 1264 1265 // modifyStackSizes modifies the .tinygo_stacksizes section with the updated 1266 // stack size information. Before this modification, all stack sizes in the 1267 // section assume the default stack size (which is relatively big). 1268 func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map[string]functionStackSize) error { 1269 data, fileHeader, err := getElfSectionData(executable, ".tinygo_stacksizes") 1270 if err != nil { 1271 return err 1272 } 1273 1274 if len(stackSizeLoads)*4 != len(data) { 1275 // Note: while AVR should use 2 byte stack sizes, even 64-bit platforms 1276 // should probably stick to 4 byte stack sizes as a larger than 4GB 1277 // stack doesn't make much sense. 1278 return errors.New("expected 4 byte stack sizes") 1279 } 1280 1281 // Modify goroutine stack sizes with a compile-time known worst case stack 1282 // size. 1283 for i, name := range stackSizeLoads { 1284 fn, ok := stackSizes[name] 1285 if !ok { 1286 return fmt.Errorf("could not find symbol %s in ELF file", name) 1287 } 1288 if fn.stackSizeType == stacksize.Bounded { 1289 stackSize := uint32(fn.stackSize) 1290 1291 // Add stack size used by interrupts. 1292 switch fileHeader.Machine { 1293 case elf.EM_ARM: 1294 if stackSize%8 != 0 { 1295 // If the stack isn't a multiple of 8, it means the leaf 1296 // function with the biggest stack depth doesn't have an aligned 1297 // stack. If the STKALIGN flag is set (which it is by default) 1298 // the interrupt controller will forcibly align the stack before 1299 // storing in-use registers. This will thus overwrite one word 1300 // past the end of the stack (off-by-one). 1301 stackSize += 4 1302 } 1303 1304 // On Cortex-M (assumed here), this stack size is 8 words or 32 1305 // bytes. This is only to store the registers that the interrupt 1306 // may modify, the interrupt will switch to the interrupt stack 1307 // (MSP). 1308 // Some background: 1309 // https://interrupt.memfault.com/blog/cortex-m-rtos-context-switching 1310 stackSize += 32 1311 1312 // Adding 4 for the stack canary, and another 4 to keep the 1313 // stack aligned. Even though the size may be automatically 1314 // determined, stack overflow checking is still important as the 1315 // stack size cannot be determined for all goroutines. 1316 stackSize += 8 1317 default: 1318 return fmt.Errorf("unknown architecture: %s", fileHeader.Machine.String()) 1319 } 1320 1321 // Finally write the stack size to the binary. 1322 binary.LittleEndian.PutUint32(data[i*4:], stackSize) 1323 } 1324 } 1325 1326 return replaceElfSection(executable, ".tinygo_stacksizes", data) 1327 } 1328 1329 // printStacks prints the maximum stack depth for functions that are started as 1330 // goroutines. Stack sizes cannot always be determined statically, in particular 1331 // recursive functions and functions that call interface methods or function 1332 // pointers may have an unknown stack depth (depending on what the optimizer 1333 // manages to optimize away). 1334 // 1335 // It might print something like the following: 1336 // 1337 // function stack usage (in bytes) 1338 // Reset_Handler 316 1339 // examples/blinky2.led1 92 1340 // runtime.run$1 300 1341 func printStacks(calculatedStacks []string, stackSizes map[string]functionStackSize) { 1342 // Print the sizes of all stacks. 1343 fmt.Printf("%-32s %s\n", "function", "stack usage (in bytes)") 1344 for _, name := range calculatedStacks { 1345 fn := stackSizes[name] 1346 switch fn.stackSizeType { 1347 case stacksize.Bounded: 1348 fmt.Printf("%-32s %d\n", fn.humanName, fn.stackSize) 1349 case stacksize.Unknown: 1350 fmt.Printf("%-32s unknown, %s does not have stack frame information\n", fn.humanName, fn.missingStackSize) 1351 case stacksize.Recursive: 1352 fmt.Printf("%-32s recursive, %s may call itself\n", fn.humanName, fn.missingStackSize) 1353 case stacksize.IndirectCall: 1354 fmt.Printf("%-32s unknown, %s calls a function pointer\n", fn.humanName, fn.missingStackSize) 1355 } 1356 } 1357 } 1358 1359 // RP2040 second stage bootloader CRC32 calculation 1360 // 1361 // Spec: https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf 1362 // Section: 2.8.1.3.1. Checksum 1363 func patchRP2040BootCRC(executable string) error { 1364 bytes, _, err := getElfSectionData(executable, ".boot2") 1365 if err != nil { 1366 return err 1367 } 1368 1369 if len(bytes) != 256 { 1370 return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes") 1371 } 1372 1373 // From the 'official' RP2040 checksum script: 1374 // 1375 // Our bootrom CRC32 is slightly bass-ackward but it's 1376 // best to work around for now (FIXME) 1377 // 100% worth it to save two Thumb instructions 1378 revBytes := make([]byte, len(bytes)) 1379 for i := range bytes { 1380 revBytes[i] = bits.Reverse8(bytes[i]) 1381 } 1382 1383 // crc32.Update does an initial negate and negates the 1384 // result, so to meet RP2040 spec, pass 0x0 as initial 1385 // hash and negate returned value. 1386 // 1387 // Note: checksum is over 252 bytes (256 - 4) 1388 hash := bits.Reverse32(crc32.Update(0x0, crc32.IEEETable, revBytes[:252]) ^ 0xFFFFFFFF) 1389 1390 // Write the CRC to the end of the bootloader. 1391 binary.LittleEndian.PutUint32(bytes[252:], hash) 1392 1393 // Update the .boot2 section to included the CRC 1394 return replaceElfSection(executable, ".boot2", bytes) 1395 } 1396 1397 // lock may acquire a lock at the specified path. 1398 // It returns a function to release the lock. 1399 // If flock is not supported, it does nothing. 1400 func lock(path string) func() { 1401 flock := flock.New(path) 1402 err := flock.Lock() 1403 if err != nil { 1404 return func() {} 1405 } 1406 1407 return func() { flock.Close() } 1408 }