build.go raw

   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