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