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