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