diff --git a/Makefile b/Makefile index 740aeb0d..e4d12b87 100644 --- a/Makefile +++ b/Makefile @@ -87,7 +87,7 @@ endif clean: @rm -rf build -FMT_PATHS = ./*.go cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform +FMT_PATHS = ./*.go builder cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform fmt: @gofmt -l -w $(FMT_PATHS) fmt-check: diff --git a/builder/build.go b/builder/build.go new file mode 100644 index 00000000..ceb4c0f9 --- /dev/null +++ b/builder/build.go @@ -0,0 +1,227 @@ +// Package builder is the compiler driver of TinyGo. It takes in a package name +// and an output path, and outputs an executable. It manages the entire +// compilation pipeline in between. +package builder + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/compiler" + "github.com/tinygo-org/tinygo/goenv" + "github.com/tinygo-org/tinygo/interp" +) + +// Build performs a single package to executable Go build. It takes in a package +// name, an output path, and set of compile options and from that it manages the +// whole compilation process. +// +// The error value may be of type *MultiError. Callers will likely want to check +// for this case and print such errors individually. +func Build(pkgName, outpath string, config *compileopts.Config, action func(string) error) error { + c, err := compiler.NewCompiler(pkgName, config) + if err != nil { + return err + } + + // Compile Go code to IR. + errs := c.Compile(pkgName) + if len(errs) != 0 { + if len(errs) == 1 { + return errs[0] + } + return &MultiError{errs} + } + if config.Options.PrintIR { + fmt.Println("; Generated LLVM IR:") + fmt.Println(c.IR()) + } + if err := c.Verify(); err != nil { + return errors.New("verification error after IR construction") + } + + err = interp.Run(c.Module(), config.DumpSSA()) + if err != nil { + return err + } + if err := c.Verify(); err != nil { + return errors.New("verification error after interpreting runtime.initAll") + } + + if config.GOOS() != "darwin" { + c.ApplyFunctionSections() // -ffunction-sections + } + + // Browsers cannot handle external functions that have type i64 because it + // cannot be represented exactly in JavaScript (JS only has doubles). To + // keep functions interoperable, pass int64 types as pointers to + // stack-allocated values. + // Use -wasm-abi=generic to disable this behaviour. + if config.Options.WasmAbi == "js" && strings.HasPrefix(config.Triple(), "wasm") { + err := c.ExternalInt64AsPtr() + if err != nil { + return err + } + } + + // Optimization levels here are roughly the same as Clang, but probably not + // exactly. + switch config.Options.Opt { + case "none:", "0": + err = c.Optimize(0, 0, 0) // -O0 + case "1": + err = c.Optimize(1, 0, 0) // -O1 + case "2": + err = c.Optimize(2, 0, 225) // -O2 + case "s": + err = c.Optimize(2, 1, 225) // -Os + case "z": + err = c.Optimize(2, 2, 5) // -Oz, default + default: + err = errors.New("unknown optimization level: -opt=" + config.Options.Opt) + } + if err != nil { + return err + } + if err := c.Verify(); err != nil { + return errors.New("verification failure after LLVM optimization passes") + } + + // On the AVR, pointers can point either to flash or to RAM, but we don't + // know. As a temporary fix, load all global variables in RAM. + // In the future, there should be a compiler pass that determines which + // pointers are flash and which are in RAM so that pointers can have a + // correct address space parameter (address space 1 is for flash). + if strings.HasPrefix(config.Triple(), "avr") { + c.NonConstGlobals() + if err := c.Verify(); err != nil { + return errors.New("verification error after making all globals non-constant on AVR") + } + } + + // Generate output. + outext := filepath.Ext(outpath) + switch outext { + case ".o": + return c.EmitObject(outpath) + case ".bc": + return c.EmitBitcode(outpath) + case ".ll": + return c.EmitText(outpath) + default: + // Act as a compiler driver. + + // Create a temporary directory for intermediary files. + dir, err := ioutil.TempDir("", "tinygo") + if err != nil { + return err + } + defer os.RemoveAll(dir) + + // Write the object file. + objfile := filepath.Join(dir, "main.o") + err = c.EmitObject(objfile) + if err != nil { + return err + } + + // Load builtins library from the cache, possibly compiling it on the + // fly. + var librt string + if config.Target.RTLib == "compiler-rt" { + librt, err = loadBuiltins(config.Triple()) + if err != nil { + return err + } + } + + // Prepare link command. + executable := filepath.Join(dir, "main") + tmppath := executable // final file + ldflags := append(config.LDFlags(), "-o", executable, objfile) + if config.Target.RTLib == "compiler-rt" { + ldflags = append(ldflags, librt) + } + + // Compile extra files. + root := goenv.Get("TINYGOROOT") + for i, path := range config.ExtraFiles() { + abspath := filepath.Join(root, path) + outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o") + cmdNames := []string{config.Target.Compiler} + if names, ok := commands[config.Target.Compiler]; ok { + cmdNames = names + } + err := execCommand(cmdNames, append(config.CFlags(), "-c", "-o", outpath, abspath)...) + if err != nil { + return &commandError{"failed to build", path, err} + } + ldflags = append(ldflags, outpath) + } + + // Compile C files in packages. + for i, pkg := range c.Packages() { + for _, file := range pkg.CFiles { + path := filepath.Join(pkg.Package.Dir, file) + outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o") + cmdNames := []string{config.Target.Compiler} + if names, ok := commands[config.Target.Compiler]; ok { + cmdNames = names + } + err := execCommand(cmdNames, append(config.CFlags(), "-c", "-o", outpath, path)...) + if err != nil { + return &commandError{"failed to build", path, err} + } + ldflags = append(ldflags, outpath) + } + } + + // Link the object files together. + err = link(config.Target.Linker, ldflags...) + if err != nil { + return &commandError{"failed to link", executable, err} + } + + if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" { + sizes, err := loadProgramSize(executable) + if err != nil { + return err + } + if config.Options.PrintSizes == "short" { + fmt.Printf(" code data bss | flash ram\n") + fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS) + } else { + fmt.Printf(" code rodata data bss | flash ram | package\n") + for _, name := range sizes.sortedPackageNames() { + pkgSize := sizes.Packages[name] + fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name) + } + fmt.Printf("%7d %7d %7d %7d | %7d %7d | (sum)\n", sizes.Sum.Code, sizes.Sum.ROData, sizes.Sum.Data, sizes.Sum.BSS, sizes.Sum.Flash(), sizes.Sum.RAM()) + fmt.Printf("%7d - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS) + } + } + + // Get an Intel .hex file or .bin file from the .elf file. + if outext == ".hex" || outext == ".bin" || outext == ".gba" { + tmppath = filepath.Join(dir, "main"+outext) + err := objcopy(executable, tmppath) + if err != nil { + return err + } + } else if outext == ".uf2" { + // Get UF2 from the .elf file. + tmppath = filepath.Join(dir, "main"+outext) + err := convertELFFileToUF2File(executable, tmppath) + if err != nil { + return err + } + } + return action(tmppath) + } +} diff --git a/buildcache.go b/builder/buildcache.go similarity index 99% rename from buildcache.go rename to builder/buildcache.go index 0628d288..912cc8e0 100644 --- a/buildcache.go +++ b/builder/buildcache.go @@ -1,4 +1,4 @@ -package main +package builder import ( "io" diff --git a/builtins.go b/builder/builtins.go similarity index 97% rename from builtins.go rename to builder/builtins.go index 15b94b66..21e0a7ae 100644 --- a/builtins.go +++ b/builder/builtins.go @@ -1,4 +1,4 @@ -package main +package builder import ( "errors" @@ -197,7 +197,7 @@ func loadBuiltins(target string) (path string, err error) { } var cachepath string - err = compileBuiltins(target, func(path string) error { + err = CompileBuiltins(target, func(path string) error { path, err := cacheStore(path, outfile, commands["clang"][0], srcs) cachepath = path return err @@ -205,11 +205,11 @@ func loadBuiltins(target string) (path string, err error) { return cachepath, err } -// compileBuiltins compiles builtins from compiler-rt into a static library. +// CompileBuiltins compiles builtins from compiler-rt into a static library. // When it succeeds, it will call the callback with the resulting path. The path // will be removed after callback returns. If callback returns an error, this is // passed through to the return value of this function. -func compileBuiltins(target string, callback func(path string) error) error { +func CompileBuiltins(target string, callback func(path string) error) error { builtinsDir := builtinsDir() builtins := builtinFiles(target) diff --git a/commands.go b/builder/commands.go similarity index 86% rename from commands.go rename to builder/commands.go index d7c6d57f..9ee43538 100644 --- a/commands.go +++ b/builder/commands.go @@ -1,4 +1,4 @@ -package main +package builder import ( "errors" @@ -8,8 +8,10 @@ import ( "strings" ) -// Commands used by the compilation process might have different file names -// across operating systems and distributions. +// Commands lists command alternatives for various operating systems. These +// commands may have a slightly different name across operating systems and +// distributions or may not even exist in $PATH, in which case absolute paths +// may be used. var commands = map[string][]string{ "clang": {"clang-8"}, "ld.lld": {"ld.lld-8", "ld.lld"}, diff --git a/builder/config.go b/builder/config.go new file mode 100644 index 00000000..8bb3a116 --- /dev/null +++ b/builder/config.go @@ -0,0 +1,39 @@ +package builder + +import ( + "errors" + "fmt" + + "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/goenv" +) + +// NewConfig builds a new Config object from a set of compiler options. It also +// loads some information from the environment while doing that. For example, it +// uses the currently active GOPATH (from the goenv package) to determine the Go +// version to use. +func NewConfig(options *compileopts.Options) (*compileopts.Config, error) { + spec, err := compileopts.LoadTarget(options.Target) + if err != nil { + return nil, err + } + goroot := goenv.Get("GOROOT") + if goroot == "" { + return nil, errors.New("cannot locate $GOROOT, please set it manually") + } + major, minor, err := getGorootVersion(goroot) + if err != nil { + return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err) + } + if major != 1 || (minor != 11 && minor != 12 && minor != 13) { + return nil, fmt.Errorf("requires go version 1.11, 1.12, or 1.13, got go%d.%d", major, minor) + } + clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT")) + return &compileopts.Config{ + Options: options, + Target: spec, + GoMinorVersion: minor, + ClangHeaders: clangHeaderPath, + TestConfig: options.TestConfig, + }, nil +} diff --git a/paths.go b/builder/env.go similarity index 94% rename from paths.go rename to builder/env.go index 70b06768..370de728 100644 --- a/paths.go +++ b/builder/env.go @@ -1,4 +1,4 @@ -package main +package builder import ( "errors" @@ -15,7 +15,7 @@ import ( // getGorootVersion returns the major and minor version for a given GOROOT path. // If the goroot cannot be determined, (0, 0) is returned. func getGorootVersion(goroot string) (major, minor int, err error) { - s, err := getGorootVersionString(goroot) + s, err := GorootVersionString(goroot) if err != nil { return 0, 0, err } @@ -42,10 +42,10 @@ func getGorootVersion(goroot string) (major, minor int, err error) { return } -// getGorootVersionString returns the version string as reported by the Go +// GorootVersionString returns the version string as reported by the Go // toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but // can have some variations (for beta releases, for example). -func getGorootVersionString(goroot string) (string, error) { +func GorootVersionString(goroot string) (string, error) { if data, err := ioutil.ReadFile(filepath.Join( goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil { diff --git a/builder/error.go b/builder/error.go new file mode 100644 index 00000000..a6f59eb0 --- /dev/null +++ b/builder/error.go @@ -0,0 +1,25 @@ +package builder + +// MultiError is a list of multiple errors (actually: diagnostics) returned +// during LLVM IR generation. +type MultiError struct { + Errs []error +} + +func (e *MultiError) Error() string { + // Return the first error, to conform to the error interface. Clients should + // really do a type-assertion on *MultiError. + return e.Errs[0].Error() +} + +// commandError is an error type to wrap os/exec.Command errors. This provides +// some more information regarding what went wrong while running a command. +type commandError struct { + Msg string + File string + Err error +} + +func (e *commandError) Error() string { + return e.Msg + " " + e.File + ": " + e.Err.Error() +} diff --git a/linker-builtin.go b/builder/linker-builtin.go similarity index 93% rename from linker-builtin.go rename to builder/linker-builtin.go index a9872e4b..ace0d2eb 100644 --- a/linker-builtin.go +++ b/builder/linker-builtin.go @@ -1,6 +1,6 @@ // +build byollvm -package main +package builder // This file provides a Link() function that uses the bundled lld if possible. @@ -21,10 +21,10 @@ bool tinygo_link_wasm(int argc, char **argv); */ import "C" -// Link invokes a linker with the given name and flags. +// link invokes a linker with the given name and flags. // // This version uses the built-in linker when trying to use lld. -func Link(linker string, flags ...string) error { +func link(linker string, flags ...string) error { switch linker { case "ld.lld": flags = append([]string{"tinygo:" + linker}, flags...) diff --git a/linker-external.go b/builder/linker-external.go similarity index 80% rename from linker-external.go rename to builder/linker-external.go index 082b3e54..472645eb 100644 --- a/linker-external.go +++ b/builder/linker-external.go @@ -1,6 +1,6 @@ // +build !byollvm -package main +package builder // This file provides a Link() function that always runs an external command. It // is provided for when tinygo is built without linking to liblld. @@ -12,10 +12,10 @@ import ( "github.com/tinygo-org/tinygo/goenv" ) -// Link invokes a linker with the given name and arguments. +// link invokes a linker with the given name and arguments. // // This version always runs the linker as an external command. -func Link(linker string, flags ...string) error { +func link(linker string, flags ...string) error { if cmdNames, ok := commands[linker]; ok { return execCommand(cmdNames, flags...) } diff --git a/lld.cpp b/builder/lld.cpp similarity index 100% rename from lld.cpp rename to builder/lld.cpp diff --git a/objcopy.go b/builder/objcopy.go similarity index 73% rename from objcopy.go rename to builder/objcopy.go index 71744c02..3ab0eaef 100644 --- a/objcopy.go +++ b/builder/objcopy.go @@ -1,4 +1,4 @@ -package main +package builder import ( "debug/elf" @@ -10,31 +10,31 @@ import ( "github.com/marcinbor85/gohex" ) -// ObjcopyError is an error returned by functions that act like objcopy. -type ObjcopyError struct { +// objcopyError is an error returned by functions that act like objcopy. +type objcopyError struct { Op string Err error } -func (e ObjcopyError) Error() string { +func (e objcopyError) Error() string { if e.Err == nil { return e.Op } return e.Op + ": " + e.Err.Error() } -type ProgSlice []*elf.Prog +type progSlice []*elf.Prog -func (s ProgSlice) Len() int { return len(s) } -func (s ProgSlice) Less(i, j int) bool { return s[i].Paddr < s[j].Paddr } -func (s ProgSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s progSlice) Len() int { return len(s) } +func (s progSlice) Less(i, j int) bool { return s[i].Paddr < s[j].Paddr } +func (s progSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -// ExtractROM extracts a firmware image and the first load address from the +// extractROM extracts a firmware image and the first load address from the // given ELF file. It tries to emulate the behavior of objcopy. -func ExtractROM(path string) (uint64, []byte, error) { +func extractROM(path string) (uint64, []byte, error) { f, err := elf.Open(path) if err != nil { - return 0, nil, ObjcopyError{"failed to open ELF file to extract text segment", err} + return 0, nil, objcopyError{"failed to open ELF file to extract text segment", err} } defer f.Close() @@ -56,7 +56,7 @@ func ExtractROM(path string) (uint64, []byte, error) { } } - progs := make(ProgSlice, 0, 2) + progs := make(progSlice, 0, 2) for _, prog := range f.Progs { if prog.Type != elf.PT_LOAD || prog.Filesz == 0 { continue @@ -64,18 +64,18 @@ func ExtractROM(path string) (uint64, []byte, error) { progs = append(progs, prog) } if len(progs) == 0 { - return 0, nil, ObjcopyError{"file does not contain ROM segments: " + path, nil} + return 0, nil, objcopyError{"file does not contain ROM segments: " + path, nil} } sort.Sort(progs) var rom []byte for _, prog := range progs { if prog.Paddr != progs[0].Paddr+uint64(len(rom)) { - return 0, nil, ObjcopyError{"ROM segments are non-contiguous: " + path, nil} + return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil} } data, err := ioutil.ReadAll(prog.Open()) if err != nil { - return 0, nil, ObjcopyError{"failed to extract segment from ELF file: " + path, err} + return 0, nil, objcopyError{"failed to extract segment from ELF file: " + path, err} } rom = append(rom, data...) } @@ -91,9 +91,9 @@ func ExtractROM(path string) (uint64, []byte, error) { } } -// Objcopy converts an ELF file to a different (simpler) output file format: +// objcopy converts an ELF file to a different (simpler) output file format: // .bin or .hex. It extracts only the .text section. -func Objcopy(infile, outfile string) error { +func objcopy(infile, outfile string) error { f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { return err @@ -101,7 +101,7 @@ func Objcopy(infile, outfile string) error { defer f.Close() // Read the .text segment. - addr, data, err := ExtractROM(infile) + addr, data, err := extractROM(infile) if err != nil { return err } @@ -121,7 +121,7 @@ func Objcopy(infile, outfile string) error { mem := gohex.NewMemory() err := mem.AddBinary(uint32(addr), data) if err != nil { - return ObjcopyError{"failed to create .hex file", err} + return objcopyError{"failed to create .hex file", err} } mem.DumpIntelHex(f, 16) // TODO: handle error return nil diff --git a/binutils.go b/builder/sizes.go similarity index 78% rename from binutils.go rename to builder/sizes.go index bb83c872..c88c9b7b 100644 --- a/binutils.go +++ b/builder/sizes.go @@ -1,4 +1,4 @@ -package main +package builder import ( "debug/elf" @@ -6,18 +6,18 @@ import ( "strings" ) -// Statistics about code size in a program. -type ProgramSize struct { - Packages map[string]*PackageSize - Sum *PackageSize +// programSize contains size statistics per package of a compiled program. +type programSize struct { + Packages map[string]*packageSize + Sum *packageSize Code uint64 Data uint64 BSS uint64 } -// Return the list of package names (ProgramSize.Packages) sorted -// alphabetically. -func (ps *ProgramSize) SortedPackageNames() []string { +// sortedPackageNames returns the list of package names (ProgramSize.Packages) +// sorted alphabetically. +func (ps *programSize) sortedPackageNames() []string { names := make([]string, 0, len(ps.Packages)) for name := range ps.Packages { names = append(names, name) @@ -26,8 +26,9 @@ func (ps *ProgramSize) SortedPackageNames() []string { return names } -// The size of a package, calculated from the linked object file. -type PackageSize struct { +// packageSize contains the size of a package, calculated from the linked object +// file. +type packageSize struct { Code uint64 ROData uint64 Data uint64 @@ -35,12 +36,12 @@ type PackageSize struct { } // Flash usage in regular microcontrollers. -func (ps *PackageSize) Flash() uint64 { +func (ps *packageSize) Flash() uint64 { return ps.Code + ps.ROData + ps.Data } // Static RAM usage in regular microcontrollers. -func (ps *PackageSize) RAM() uint64 { +func (ps *packageSize) RAM() uint64 { return ps.Data + ps.BSS } @@ -64,8 +65,9 @@ func (l symbolList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } -// Calculate program/data size breakdown of each package for a given ELF file. -func Sizes(path string) (*ProgramSize, error) { +// loadProgramSize calculate a program/data size breakdown of each package for a +// given ELF file. +func loadProgramSize(path string) (*programSize, error) { file, err := elf.Open(path) if err != nil { return nil, err @@ -115,7 +117,7 @@ func Sizes(path string) (*ProgramSize, error) { } sort.Sort(symbolList(symbols)) - sizes := map[string]*PackageSize{} + sizes := map[string]*packageSize{} var lastSymbolValue uint64 for _, symbol := range symbols { symType := elf.ST_TYPE(symbol.Info) @@ -129,7 +131,7 @@ func Sizes(path string) (*ProgramSize, error) { } pkgSize := sizes[pkgName] if pkgSize == nil { - pkgSize = &PackageSize{} + pkgSize = &packageSize{} sizes[pkgName] = pkgSize } if lastSymbolValue != symbol.Value || lastSymbolValue == 0 { @@ -148,7 +150,7 @@ func Sizes(path string) (*ProgramSize, error) { lastSymbolValue = symbol.Value } - sum := &PackageSize{} + sum := &packageSize{} for _, pkg := range sizes { sum.Code += pkg.Code sum.ROData += pkg.ROData @@ -156,5 +158,5 @@ func Sizes(path string) (*ProgramSize, error) { sum.BSS += pkg.BSS } - return &ProgramSize{Packages: sizes, Code: sumCode, Data: sumData, BSS: sumBSS, Sum: sum}, nil + return &programSize{Packages: sizes, Code: sumCode, Data: sumData, BSS: sumBSS, Sum: sum}, nil } diff --git a/uf2.go b/builder/uf2.go similarity index 71% rename from uf2.go rename to builder/uf2.go index ef257ffc..3ff0a045 100644 --- a/uf2.go +++ b/builder/uf2.go @@ -1,10 +1,11 @@ -// Converts firmware files from BIN to UF2 format before flashing. +package builder + +// This file converts firmware files from BIN to UF2 format before flashing. // // For more information about the UF2 firmware file format, please see: // https://github.com/Microsoft/uf2 // // -package main import ( "bytes" @@ -12,24 +13,24 @@ import ( "io/ioutil" ) -// ConvertELFFileToUF2File converts an ELF file to a UF2 file. -func ConvertELFFileToUF2File(infile, outfile string) error { +// convertELFFileToUF2File converts an ELF file to a UF2 file. +func convertELFFileToUF2File(infile, outfile string) error { // Read the .text segment. - targetAddress, data, err := ExtractROM(infile) + targetAddress, data, err := extractROM(infile) if err != nil { return err } - output, _ := ConvertBinToUF2(data, uint32(targetAddress)) + output, _ := convertBinToUF2(data, uint32(targetAddress)) return ioutil.WriteFile(outfile, output, 0644) } -// ConvertBinToUF2 converts the binary bytes in input to UF2 formatted data. -func ConvertBinToUF2(input []byte, targetAddr uint32) ([]byte, int) { +// convertBinToUF2 converts the binary bytes in input to UF2 formatted data. +func convertBinToUF2(input []byte, targetAddr uint32) ([]byte, int) { blocks := split(input, 256) output := make([]byte, 0) - bl := NewUF2Block(targetAddr) + bl := newUF2Block(targetAddr) bl.SetNumBlocks(len(blocks)) for i := 0; i < len(blocks); i++ { @@ -49,8 +50,8 @@ const ( uf2MagicEnd = 0x0AB16F30 // Ditto ) -// UF2Block is the structure used for each UF2 code block sent to device. -type UF2Block struct { +// uf2Block is the structure used for each UF2 code block sent to device. +type uf2Block struct { magicStart0 uint32 magicStart1 uint32 flags uint32 @@ -63,9 +64,9 @@ type UF2Block struct { magicEnd uint32 } -// NewUF2Block returns a new UF2Block struct that has been correctly populated -func NewUF2Block(targetAddr uint32) *UF2Block { - return &UF2Block{magicStart0: uf2MagicStart0, +// newUF2Block returns a new uf2Block struct that has been correctly populated +func newUF2Block(targetAddr uint32) *uf2Block { + return &uf2Block{magicStart0: uf2MagicStart0, magicStart1: uf2MagicStart1, magicEnd: uf2MagicEnd, targetAddr: targetAddr, @@ -76,8 +77,8 @@ func NewUF2Block(targetAddr uint32) *UF2Block { } } -// Bytes converts the UF2Block to a slice of bytes that can be written to file. -func (b *UF2Block) Bytes() []byte { +// Bytes converts the uf2Block to a slice of bytes that can be written to file. +func (b *uf2Block) Bytes() []byte { buf := bytes.NewBuffer(make([]byte, 0, 512)) binary.Write(buf, binary.LittleEndian, b.magicStart0) binary.Write(buf, binary.LittleEndian, b.magicStart1) @@ -94,23 +95,23 @@ func (b *UF2Block) Bytes() []byte { } // IncrementAddress moves the target address pointer forward by count bytes. -func (b *UF2Block) IncrementAddress(count uint32) { +func (b *uf2Block) IncrementAddress(count uint32) { b.targetAddr += b.payloadSize } // SetData sets the data to be used for the current block. -func (b *UF2Block) SetData(d []byte) { +func (b *uf2Block) SetData(d []byte) { b.data = make([]byte, 476) copy(b.data[:], d) } // SetBlockNo sets the current block number to be used. -func (b *UF2Block) SetBlockNo(bn int) { +func (b *uf2Block) SetBlockNo(bn int) { b.blockNo = uint32(bn) } // SetNumBlocks sets the total number of blocks for this UF2 file. -func (b *UF2Block) SetNumBlocks(total int) { +func (b *uf2Block) SetNumBlocks(total int) { b.numBlocks = uint32(total) } diff --git a/compileopts/config.go b/compileopts/config.go index 8eacddf8..47c63b64 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -128,6 +128,12 @@ func (c *Config) LDFlags() []string { return ldflags } +// ExtraFiles returns the list of extra files to be built and linked with the +// executable. This can include extra C and assembly files. +func (c *Config) ExtraFiles() []string { + return c.Target.ExtraFiles +} + // DumpSSA returns whether to dump Go SSA while compiling (-dumpssa flag). Only // enable this for debugging. func (c *Config) DumpSSA() bool { diff --git a/compileopts/options.go b/compileopts/options.go index eb52bdc6..891f2172 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -3,6 +3,7 @@ package compileopts // Options contains extra options to give to the compiler. These options are // usually passed from the command line. type Options struct { + Target string Opt string GC string PanicStrategy string diff --git a/main.go b/main.go index b448f6fd..44e8b5a8 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( "fmt" "go/types" "io" - "io/ioutil" "os" "os/exec" "os/signal" @@ -17,8 +16,8 @@ import ( "syscall" "time" + "github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/compileopts" - "github.com/tinygo-org/tinygo/compiler" "github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/interp" "github.com/tinygo-org/tinygo/loader" @@ -38,246 +37,50 @@ func (e *commandError) Error() string { return e.Msg + " " + e.File + ": " + e.Err.Error() } -// multiError is a list of multiple errors (actually: diagnostics) returned -// during LLVM IR generation. -type multiError struct { - Errs []error +// moveFile renames the file from src to dst. If renaming doesn't work (for +// example, the rename crosses a filesystem boundary), the file is copied and +// the old file is removed. +func moveFile(src, dst string) error { + err := os.Rename(src, dst) + if err == nil { + // Success! + return nil + } + // Failed to move, probably a different filesystem. + // Do a copy + remove. + inf, err := os.Open(src) + if err != nil { + return err + } + defer inf.Close() + outpath := dst + ".tmp" + outf, err := os.Create(outpath) + if err != nil { + return err + } + + _, err = io.Copy(outf, inf) + if err != nil { + os.Remove(outpath) + return err + } + + err = outf.Close() + if err != nil { + return err + } + + return os.Rename(dst+".tmp", dst) } -func (e *multiError) Error() string { - return e.Errs[0].Error() -} - -// Helper function for Compiler object. -func Compile(pkgName, outpath string, spec *compileopts.TargetSpec, options *compileopts.Options, action func(string) error) error { - - root := goenv.Get("TINYGOROOT") - - goroot := goenv.Get("GOROOT") - if goroot == "" { - return errors.New("cannot locate $GOROOT, please set it manually") - } - major, minor, err := getGorootVersion(goroot) - if err != nil { - return fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err) - } - if major != 1 || (minor != 11 && minor != 12 && minor != 13) { - return fmt.Errorf("requires go version 1.11, 1.12, or 1.13, got go%d.%d", major, minor) - } - compilerConfig := &compileopts.Config{ - Options: options, - Target: spec, - GoMinorVersion: minor, - ClangHeaders: getClangHeaderPath(root), - TestConfig: options.TestConfig, - } - c, err := compiler.NewCompiler(pkgName, compilerConfig) +// Build compiles and links the given package and writes it to outpath. +func Build(pkgName, outpath string, options *compileopts.Options) error { + config, err := builder.NewConfig(options) if err != nil { return err } - // Compile Go code to IR. - errs := c.Compile(pkgName) - if len(errs) != 0 { - if len(errs) == 1 { - return errs[0] - } - return &multiError{errs} - } - if options.PrintIR { - fmt.Println("; Generated LLVM IR:") - fmt.Println(c.IR()) - } - if err := c.Verify(); err != nil { - return errors.New("verification error after IR construction") - } - - err = interp.Run(c.Module(), options.DumpSSA) - if err != nil { - return err - } - if err := c.Verify(); err != nil { - return errors.New("verification error after interpreting runtime.initAll") - } - - if spec.GOOS != "darwin" { - c.ApplyFunctionSections() // -ffunction-sections - } - - // Browsers cannot handle external functions that have type i64 because it - // cannot be represented exactly in JavaScript (JS only has doubles). To - // keep functions interoperable, pass int64 types as pointers to - // stack-allocated values. - // Use -wasm-abi=generic to disable this behaviour. - if options.WasmAbi == "js" && strings.HasPrefix(spec.Triple, "wasm") { - err := c.ExternalInt64AsPtr() - if err != nil { - return err - } - } - - // Optimization levels here are roughly the same as Clang, but probably not - // exactly. - switch options.Opt { - case "none:", "0": - err = c.Optimize(0, 0, 0) // -O0 - case "1": - err = c.Optimize(1, 0, 0) // -O1 - case "2": - err = c.Optimize(2, 0, 225) // -O2 - case "s": - err = c.Optimize(2, 1, 225) // -Os - case "z": - err = c.Optimize(2, 2, 5) // -Oz, default - default: - err = errors.New("unknown optimization level: -opt=" + options.Opt) - } - if err != nil { - return err - } - if err := c.Verify(); err != nil { - return errors.New("verification failure after LLVM optimization passes") - } - - // On the AVR, pointers can point either to flash or to RAM, but we don't - // know. As a temporary fix, load all global variables in RAM. - // In the future, there should be a compiler pass that determines which - // pointers are flash and which are in RAM so that pointers can have a - // correct address space parameter (address space 1 is for flash). - if strings.HasPrefix(spec.Triple, "avr") { - c.NonConstGlobals() - if err := c.Verify(); err != nil { - return errors.New("verification error after making all globals non-constant on AVR") - } - } - - // Generate output. - outext := filepath.Ext(outpath) - switch outext { - case ".o": - return c.EmitObject(outpath) - case ".bc": - return c.EmitBitcode(outpath) - case ".ll": - return c.EmitText(outpath) - default: - // Act as a compiler driver. - - // Create a temporary directory for intermediary files. - dir, err := ioutil.TempDir("", "tinygo") - if err != nil { - return err - } - defer os.RemoveAll(dir) - - // Write the object file. - objfile := filepath.Join(dir, "main.o") - err = c.EmitObject(objfile) - if err != nil { - return err - } - - // Load builtins library from the cache, possibly compiling it on the - // fly. - var librt string - if spec.RTLib == "compiler-rt" { - librt, err = loadBuiltins(spec.Triple) - if err != nil { - return err - } - } - - // Prepare link command. - executable := filepath.Join(dir, "main") - tmppath := executable // final file - ldflags := append(compilerConfig.LDFlags(), "-o", executable, objfile) - if spec.RTLib == "compiler-rt" { - ldflags = append(ldflags, librt) - } - - // Compile extra files. - for i, path := range spec.ExtraFiles { - abspath := filepath.Join(root, path) - outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o") - cmdNames := []string{spec.Compiler} - if names, ok := commands[spec.Compiler]; ok { - cmdNames = names - } - err := execCommand(cmdNames, append(compilerConfig.CFlags(), "-c", "-o", outpath, abspath)...) - if err != nil { - return &commandError{"failed to build", path, err} - } - ldflags = append(ldflags, outpath) - } - - // Compile C files in packages. - for i, pkg := range c.Packages() { - for _, file := range pkg.CFiles { - path := filepath.Join(pkg.Package.Dir, file) - outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o") - cmdNames := []string{spec.Compiler} - if names, ok := commands[spec.Compiler]; ok { - cmdNames = names - } - err := execCommand(cmdNames, append(compilerConfig.CFlags(), "-c", "-o", outpath, path)...) - if err != nil { - return &commandError{"failed to build", path, err} - } - ldflags = append(ldflags, outpath) - } - } - - // Link the object files together. - err = Link(spec.Linker, ldflags...) - if err != nil { - return &commandError{"failed to link", executable, err} - } - - if options.PrintSizes == "short" || options.PrintSizes == "full" { - sizes, err := Sizes(executable) - if err != nil { - return err - } - if options.PrintSizes == "short" { - fmt.Printf(" code data bss | flash ram\n") - fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS) - } else { - fmt.Printf(" code rodata data bss | flash ram | package\n") - for _, name := range sizes.SortedPackageNames() { - pkgSize := sizes.Packages[name] - fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name) - } - fmt.Printf("%7d %7d %7d %7d | %7d %7d | (sum)\n", sizes.Sum.Code, sizes.Sum.ROData, sizes.Sum.Data, sizes.Sum.BSS, sizes.Sum.Flash(), sizes.Sum.RAM()) - fmt.Printf("%7d - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS) - } - } - - // Get an Intel .hex file or .bin file from the .elf file. - if outext == ".hex" || outext == ".bin" || outext == ".gba" { - tmppath = filepath.Join(dir, "main"+outext) - err := Objcopy(executable, tmppath) - if err != nil { - return err - } - } else if outext == ".uf2" { - // Get UF2 from the .elf file. - tmppath = filepath.Join(dir, "main"+outext) - err := ConvertELFFileToUF2File(executable, tmppath) - if err != nil { - return err - } - } - return action(tmppath) - } -} - -func Build(pkgName, outpath, target string, options *compileopts.Options) error { - spec, err := compileopts.LoadTarget(target) - if err != nil { - return err - } - - return Compile(pkgName, outpath, spec, options, func(tmppath string) error { + return builder.Build(pkgName, outpath, config, func(tmppath string) error { if err := os.Rename(tmppath, outpath); err != nil { // Moving failed. Do a file copy. inf, err := os.Open(tmppath) @@ -305,15 +108,21 @@ func Build(pkgName, outpath, target string, options *compileopts.Options) error }) } -func Test(pkgName, target string, options *compileopts.Options) error { - spec, err := compileopts.LoadTarget(target) +// Test runs the tests in the given package. +func Test(pkgName string, options *compileopts.Options) error { + config, err := builder.NewConfig(options) if err != nil { return err } - spec.BuildTags = append(spec.BuildTags, "test") + // Add test build tag. This is incorrect: `go test` only looks at the + // _test.go file suffix but does not add the test build tag in the process. + // However, it's a simple fix right now. + // For details: https://github.com/golang/go/issues/21360 + config.Target.BuildTags = append(config.Target.BuildTags, "test") + options.TestConfig.CompileTestBinary = true - return Compile(pkgName, ".elf", spec, options, func(tmppath string) error { + return builder.Build(pkgName, ".elf", config, func(tmppath string) error { cmd := exec.Command(tmppath) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -332,8 +141,9 @@ func Test(pkgName, target string, options *compileopts.Options) error { }) } -func Flash(pkgName, target, port string, options *compileopts.Options) error { - spec, err := compileopts.LoadTarget(target) +// Flash builds and flashes the built binary to the given serial port. +func Flash(pkgName, port string, options *compileopts.Options) error { + config, err := builder.NewConfig(options) if err != nil { return err } @@ -341,36 +151,36 @@ func Flash(pkgName, target, port string, options *compileopts.Options) error { // determine the type of file to compile var fileExt string - switch spec.FlashMethod { + switch config.Target.FlashMethod { case "command", "": switch { - case strings.Contains(spec.FlashCommand, "{hex}"): + case strings.Contains(config.Target.FlashCommand, "{hex}"): fileExt = ".hex" - case strings.Contains(spec.FlashCommand, "{elf}"): + case strings.Contains(config.Target.FlashCommand, "{elf}"): fileExt = ".elf" - case strings.Contains(spec.FlashCommand, "{bin}"): + case strings.Contains(config.Target.FlashCommand, "{bin}"): fileExt = ".bin" - case strings.Contains(spec.FlashCommand, "{uf2}"): + case strings.Contains(config.Target.FlashCommand, "{uf2}"): fileExt = ".uf2" default: return errors.New("invalid target file - did you forget the {hex} token in the 'flash-command' section?") } case "msd": - if spec.FlashFilename == "" { + if config.Target.FlashFilename == "" { return errors.New("invalid target file: flash-method was set to \"msd\" but no msd-firmware-name was set") } - fileExt = filepath.Ext(spec.FlashFilename) + fileExt = filepath.Ext(config.Target.FlashFilename) case "openocd": fileExt = ".hex" case "native": return errors.New("unknown flash method \"native\" - did you miss a -target flag?") default: - return errors.New("unknown flash method: " + spec.FlashMethod) + return errors.New("unknown flash method: " + config.Target.FlashMethod) } - return Compile(pkgName, fileExt, spec, options, func(tmppath string) error { + return builder.Build(pkgName, fileExt, config, func(tmppath string) error { // do we need port reset to put MCU into bootloader mode? - if spec.PortReset == "true" { + if config.Target.PortReset == "true" { err := touchSerialPortAt1200bps(port) if err != nil { return &commandError{"failed to reset port", tmppath, err} @@ -380,10 +190,10 @@ func Flash(pkgName, target, port string, options *compileopts.Options) error { } // this flashing method copies the binary data to a Mass Storage Device (msd) - switch spec.FlashMethod { + switch config.Target.FlashMethod { case "", "command": // Create the command. - flashCmd := spec.FlashCommand + flashCmd := config.Target.FlashCommand fileToken := "{" + fileExt[1:] + "}" flashCmd = strings.Replace(flashCmd, fileToken, tmppath, -1) flashCmd = strings.Replace(flashCmd, "{port}", port, -1) @@ -401,13 +211,13 @@ func Flash(pkgName, target, port string, options *compileopts.Options) error { case "msd": switch fileExt { case ".uf2": - err := flashUF2UsingMSD(spec.FlashVolume, tmppath) + err := flashUF2UsingMSD(config.Target.FlashVolume, tmppath) if err != nil { return &commandError{"failed to flash", tmppath, err} } return nil case ".hex": - err := flashHexUsingMSD(spec.FlashVolume, tmppath) + err := flashHexUsingMSD(config.Target.FlashVolume, tmppath) if err != nil { return &commandError{"failed to flash", tmppath, err} } @@ -416,7 +226,7 @@ func Flash(pkgName, target, port string, options *compileopts.Options) error { return errors.New("mass storage device flashing currently only supports uf2 and hex") } case "openocd": - args, err := spec.OpenOCDConfiguration() + args, err := config.Target.OpenOCDConfiguration() if err != nil { return err } @@ -430,34 +240,36 @@ func Flash(pkgName, target, port string, options *compileopts.Options) error { } return nil default: - return fmt.Errorf("unknown flash method: %s", spec.FlashMethod) + return fmt.Errorf("unknown flash method: %s", config.Target.FlashMethod) } }) } -// Flash a program on a microcontroller and drop into a GDB shell. +// FlashGDB compiles and flashes a program to a microcontroller (just like +// Flash) but instead of resetting the target, it will drop into a GDB shell. +// You can then set breakpoints, run the GDB `continue` command to start, hit +// Ctrl+C to break the running program, etc. // // Note: this command is expected to execute just before exiting, as it // modifies global state. -func FlashGDB(pkgName, target, port string, ocdOutput bool, options *compileopts.Options) error { - spec, err := compileopts.LoadTarget(target) +func FlashGDB(pkgName, port string, ocdOutput bool, options *compileopts.Options) error { + config, err := builder.NewConfig(options) if err != nil { return err } - - if spec.GDB == "" { + if config.Target.GDB == "" { return errors.New("gdb not configured in the target specification") } - return Compile(pkgName, "", spec, options, func(tmppath string) error { + return builder.Build(pkgName, "", config, func(tmppath string) error { // Find a good way to run GDB. - gdbInterface := spec.FlashMethod + gdbInterface := config.Target.FlashMethod switch gdbInterface { case "msd", "command", "": if gdbInterface == "" { gdbInterface = "command" } - if spec.OpenOCDInterface != "" && spec.OpenOCDTarget != "" { + if config.Target.OpenOCDInterface != "" && config.Target.OpenOCDTarget != "" { gdbInterface = "openocd" } } @@ -471,7 +283,7 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, options *compileopts gdbCommands = append(gdbCommands, "target remote :3333", "monitor halt", "load", "monitor reset halt") // We need a separate debugging daemon for on-chip debugging. - args, err := spec.OpenOCDConfiguration() + args, err := config.Target.OpenOCDConfiguration() if err != nil { return err } @@ -517,7 +329,7 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, options *compileopts for _, cmd := range gdbCommands { params = append(params, "-ex", cmd) } - cmd := exec.Command(spec.GDB, params...) + cmd := exec.Command(config.Target.GDB, params...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -529,15 +341,18 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, options *compileopts }) } -// Compile and run the given program, directly or in an emulator. -func Run(pkgName, target string, options *compileopts.Options) error { - spec, err := compileopts.LoadTarget(target) +// Run compiles and runs the given program. Depending on the target provided in +// the options, it will run the program directly on the host or will run it in +// an emulator. For example, -target=wasm will cause the binary to be run inside +// of a WebAssembly VM. +func Run(pkgName string, options *compileopts.Options) error { + config, err := builder.NewConfig(options) if err != nil { return err } - return Compile(pkgName, ".elf", spec, options, func(tmppath string) error { - if len(spec.Emulator) == 0 { + return builder.Build(pkgName, ".elf", config, func(tmppath string) error { + if len(config.Target.Emulator) == 0 { // Run directly. cmd := exec.Command(tmppath) cmd.Stdout = os.Stdout @@ -553,8 +368,8 @@ func Run(pkgName, target string, options *compileopts.Options) error { return nil } else { // Run in an emulator. - args := append(spec.Emulator[1:], tmppath) - cmd := exec.Command(spec.Emulator[0], args...) + args := append(config.Target.Emulator[1:], tmppath) + cmd := exec.Command(config.Target.Emulator[0], args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() @@ -674,7 +489,7 @@ func handleCompilerError(err error) { for _, err := range err.Errs { fmt.Fprintln(os.Stderr, err) } - case *multiError: + case *builder.MultiError: for _, err := range err.Errs { fmt.Fprintln(os.Stderr, err) } @@ -714,6 +529,7 @@ func main() { flag.CommandLine.Parse(os.Args[2:]) options := &compileopts.Options{ + Target: *target, Opt: *opt, GC: *gc, PanicStrategy: *panicStrategy, @@ -765,11 +581,10 @@ func main() { usage() os.Exit(1) } - target := *target - if target == "" && filepath.Ext(*outpath) == ".wasm" { - target = "wasm" + if options.Target == "" && filepath.Ext(*outpath) == ".wasm" { + options.Target = "wasm" } - err := Build(pkgName, *outpath, target, options) + err := Build(pkgName, *outpath, options) handleCompilerError(err) case "build-builtins": // Note: this command is only meant to be used while making a release! @@ -781,7 +596,7 @@ func main() { if *target == "" { fmt.Fprintln(os.Stderr, "No target (-target).") } - err := compileBuiltins(*target, func(path string) error { + err := builder.CompileBuiltins(*target, func(path string) error { return moveFile(path, *outpath) }) handleCompilerError(err) @@ -792,7 +607,7 @@ func main() { os.Exit(1) } if command == "flash" { - err := Flash(flag.Arg(0), *target, *port, options) + err := Flash(flag.Arg(0), *port, options) handleCompilerError(err) } else { if !options.Debug { @@ -800,7 +615,7 @@ func main() { usage() os.Exit(1) } - err := FlashGDB(flag.Arg(0), *target, *port, *ocdOutput, options) + err := FlashGDB(flag.Arg(0), *port, *ocdOutput, options) handleCompilerError(err) } case "run": @@ -809,7 +624,7 @@ func main() { usage() os.Exit(1) } - err := Run(flag.Arg(0), *target, options) + err := Run(flag.Arg(0), options) handleCompilerError(err) case "test": pkgName := "." @@ -820,25 +635,23 @@ func main() { usage() os.Exit(1) } - err := Test(pkgName, *target, options) + err := Test(pkgName, options) handleCompilerError(err) case "info": - target := *target if flag.NArg() == 1 { - target = flag.Arg(0) + options.Target = flag.Arg(0) } else if flag.NArg() > 1 { fmt.Fprintln(os.Stderr, "only one target name is accepted") usage() os.Exit(1) } - spec, err := compileopts.LoadTarget(target) - config := &compileopts.Config{ - Options: options, - Target: spec, - GoMinorVersion: 0, // this avoids creating the list of Go1.x build tags. - ClangHeaders: getClangHeaderPath(goenv.Get("TINYGOROOT")), - TestConfig: options.TestConfig, + config, err := builder.NewConfig(options) + if err != nil { + fmt.Fprintln(os.Stderr, err) + usage() + os.Exit(1) } + config.GoMinorVersion = 0 // this avoids creating the list of Go1.x build tags. if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -860,7 +673,7 @@ func main() { usage() case "version": goversion := "" - if s, err := getGorootVersionString(goenv.Get("GOROOT")); err == nil { + if s, err := builder.GorootVersionString(goenv.Get("GOROOT")); err == nil { goversion = s } fmt.Printf("tinygo version %s %s/%s (using go version %s)\n", version, runtime.GOOS, runtime.GOARCH, goversion) diff --git a/main_test.go b/main_test.go index ffa2e47b..03dac09f 100644 --- a/main_test.go +++ b/main_test.go @@ -120,6 +120,7 @@ func runTest(path, target string, t *testing.T) { // Build the test binary. config := &compileopts.Options{ + Target: target, Opt: "z", PrintIR: false, DumpSSA: false, @@ -129,7 +130,7 @@ func runTest(path, target string, t *testing.T) { WasmAbi: "js", } binary := filepath.Join(tmpdir, "test") - err = Build("./"+path, binary, target, config) + err = Build("./"+path, binary, config) if err != nil { if errLoader, ok := err.(loader.Errors); ok { for _, err := range errLoader.Errs {