tinygo/main.go
Ayke van Laethem 4e4f91bea7
main: cleanup printing of IR
Panics don't usually happen nowadays, instead the compiler package
returns errors while compiling. If it still panics, this is usually from
within LLVM from where deferred functions are not run.
2018-10-31 10:10:49 +01:00

494 строки
13 КиБ
Go

package main
import (
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strings"
"syscall"
"github.com/aykevl/go-llvm"
"github.com/aykevl/tinygo/compiler"
)
var commands = map[string]string{
"ar": "ar",
"clang": "clang-7",
}
type BuildConfig struct {
opt string
printIR bool
dumpSSA bool
debug bool
printSizes string
}
// Helper function for Compiler object.
func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, action func(string) error) error {
compilerConfig := compiler.Config{
Triple: spec.Triple,
Debug: config.debug,
DumpSSA: config.dumpSSA,
RootDir: sourceDir(),
GOPATH: getGopath(),
BuildTags: append(spec.BuildTags, "tinygo"),
}
c, err := compiler.NewCompiler(pkgName, compilerConfig)
if err != nil {
return err
}
// Compile Go code to IR.
err = c.Compile(pkgName)
if err != nil {
return err
}
if err := c.Verify(); err != nil {
return err
}
if config.printIR {
fmt.Println("Generated LLVM IR:")
fmt.Println(c.IR())
}
c.ApplyFunctionSections() // -ffunction-sections
if err := c.Verify(); err != nil {
return err
}
// Optimization levels here are roughly the same as Clang, but probably not
// exactly.
switch config.opt {
case "none:", "0":
c.Optimize(0, 0, 0) // -O0
case "1":
c.Optimize(1, 0, 0) // -O1
case "2":
c.Optimize(2, 0, 225) // -O2
case "s":
c.Optimize(2, 1, 225) // -Os
case "z":
c.Optimize(2, 2, 5) // -Oz, default
default:
return errors.New("unknown optimization level: -opt=" + config.opt)
}
if err := c.Verify(); err != nil {
return err
}
// 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 err
}
}
// 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 cachePath string
if spec.CompilerRT {
librt, err := loadBuiltins(spec.Triple)
if err != nil {
return err
}
cachePath, _ = filepath.Split(librt)
}
// Link the object file with the system compiler.
executable := filepath.Join(dir, "main")
tmppath := executable // final file
args := append(spec.PreLinkArgs, "-o", executable, objfile)
if spec.CompilerRT {
args = append(args, "-L", cachePath, "-lrt-"+spec.Triple)
}
cmd := exec.Command(spec.Linker, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
err = cmd.Run()
if err != nil {
return err
}
if config.printSizes == "short" || config.printSizes == "full" {
sizes, err := Sizes(executable)
if err != nil {
return err
}
if config.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)
}
}
if outext == ".hex" || outext == ".bin" {
// Get an Intel .hex file or .bin file from the .elf file.
tmppath = filepath.Join(dir, "main"+outext)
format := map[string]string{
".hex": "ihex",
".bin": "binary",
}[outext]
cmd := exec.Command(spec.Objcopy, "-O", format, executable, tmppath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return err
}
}
return action(tmppath)
}
}
func Build(pkgName, outpath, target string, config *BuildConfig) error {
spec, err := LoadTarget(target)
if err != nil {
return err
}
return Compile(pkgName, outpath, spec, config, func(tmppath string) error {
if err := os.Rename(tmppath, outpath); err != nil {
// Moving failed. Do a file copy.
inf, err := os.Open(tmppath)
if err != nil {
return err
}
defer inf.Close()
outf, err := os.OpenFile(outpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
return err
}
// Copy data to output file.
_, err = io.Copy(outf, inf)
if err != nil {
return err
}
// Check whether file writing was successful.
return outf.Close()
} else {
// Move was successful.
return nil
}
})
}
func Flash(pkgName, target, port string, config *BuildConfig) error {
spec, err := LoadTarget(target)
if err != nil {
return err
}
return Compile(pkgName, ".hex", spec, config, func(tmppath string) error {
if spec.Flasher == "" {
return errors.New("no flash command specified - did you miss a -target flag?")
}
// Create the command.
flashCmd := spec.Flasher
flashCmd = strings.Replace(flashCmd, "{hex}", tmppath, -1)
flashCmd = strings.Replace(flashCmd, "{port}", port, -1)
// Execute the command.
cmd := exec.Command("/bin/sh", "-c", flashCmd)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
return cmd.Run()
})
}
// Flash a program on a microcontroller and drop into a GDB shell.
//
// Note: this command is expected to execute just before exiting, as it
// modifies global state.
func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig) error {
spec, err := LoadTarget(target)
if err != nil {
return err
}
if spec.GDB == "" {
return errors.New("gdb not configured in the target specification")
}
return Compile(pkgName, "", spec, config, func(tmppath string) error {
if len(spec.OCDDaemon) != 0 {
// We need a separate debugging daemon for on-chip debugging.
daemon := exec.Command(spec.OCDDaemon[0], spec.OCDDaemon[1:]...)
if ocdOutput {
// Make it clear which output is from the daemon.
w := &ColorWriter{
Out: os.Stderr,
Prefix: spec.OCDDaemon[0] + ": ",
Color: TermColorYellow,
}
daemon.Stdout = w
daemon.Stderr = w
}
// Make sure the daemon doesn't receive Ctrl-C that is intended for
// GDB (to break the currently executing program).
// https://stackoverflow.com/a/35435038/559350
daemon.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
Pgid: 0,
}
// Start now, and kill it on exit.
daemon.Start()
defer func() {
daemon.Process.Signal(os.Interrupt)
// Maybe we should send a .Kill() after x seconds?
daemon.Wait()
}()
}
// Ignore Ctrl-C, it must be passed on to GDB.
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for range c {
}
}()
// Construct and execute a gdb command.
// By default: gdb -ex run <binary>
// Exit GDB with Ctrl-D.
params := []string{tmppath}
for _, cmd := range spec.GDBCmds {
params = append(params, "-ex", cmd)
}
cmd := exec.Command(spec.GDB, params...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
})
}
// Run the specified package directly (using JIT or interpretation).
func Run(pkgName string) error {
config := compiler.Config{
RootDir: sourceDir(),
GOPATH: getGopath(),
}
c, err := compiler.NewCompiler(pkgName, config)
if err != nil {
return errors.New("compiler: " + err.Error())
}
err = c.Compile(pkgName)
if err != nil {
return errors.New("compiler: " + err.Error())
}
if err := c.Verify(); err != nil {
return errors.New("compiler error: failed to verify module: " + err.Error())
}
// -Oz, which is the fastest optimization level (faster than -O0, -O1, -O2
// and -Os). Turn off the inliner, as the inliner increases optimization
// time.
c.Optimize(2, 2, 0)
engine, err := llvm.NewExecutionEngine(c.Module())
if err != nil {
return errors.New("interpreter setup: " + err.Error())
}
defer engine.Dispose()
main := engine.FindFunction("main")
if main.IsNil() {
return errors.New("could not find main function")
}
engine.RunFunction(main, nil)
return nil
}
// Compile and run the given program in an emulator.
func Emulate(pkgName, target string, config *BuildConfig) error {
spec, err := LoadTarget(target)
if err != nil {
return err
}
if len(spec.Emulator) == 0 {
return errors.New("no emulator configured for this target")
}
return Compile(pkgName, ".elf", spec, config, func(tmppath string) error {
args := append(spec.Emulator[1:], tmppath)
cmd := exec.Command(spec.Emulator[0], args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
if err, ok := err.(*exec.ExitError); ok && err.Exited() {
// Workaround for QEMU which always exits with an error.
return nil
}
}
return err
})
}
func usage() {
fmt.Fprintf(os.Stderr, "usage: %s command [-printir] [-target=<target>] -o <output> <input>\n", os.Args[0])
fmt.Fprintln(os.Stderr, "\ncommands:")
fmt.Fprintln(os.Stderr, " build: compile packages and dependencies")
fmt.Fprintln(os.Stderr, " run: compile and run immediately")
fmt.Fprintln(os.Stderr, " flash: compile and flash to the device")
fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB")
fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+cacheDir()+")")
fmt.Fprintln(os.Stderr, " help: print this help text")
fmt.Fprintln(os.Stderr, "\nflags:")
flag.PrintDefaults()
}
func main() {
outpath := flag.String("o", "", "output filename")
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
printIR := flag.Bool("printir", false, "print LLVM IR")
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
target := flag.String("target", "", "LLVM target")
printSize := flag.String("size", "", "print sizes (none, short, full)")
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
port := flag.String("port", "/dev/ttyACM0", "flash port")
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
usage()
os.Exit(1)
}
command := os.Args[1]
flag.CommandLine.Parse(os.Args[2:])
config := &BuildConfig{
opt: *opt,
printIR: *printIR,
dumpSSA: *dumpSSA,
debug: !*nodebug,
printSizes: *printSize,
}
os.Setenv("CC", "clang -target="+*target)
switch command {
case "build":
if *outpath == "" {
fmt.Fprintln(os.Stderr, "No output filename supplied (-o).")
usage()
os.Exit(1)
}
if flag.NArg() != 1 {
fmt.Fprintln(os.Stderr, "No package specified.")
usage()
os.Exit(1)
}
target := *target
if target == "" && filepath.Ext(*outpath) == ".wasm" {
target = "wasm"
}
err := Build(flag.Arg(0), *outpath, target, config)
if err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
case "flash", "gdb":
if *outpath != "" {
fmt.Fprintln(os.Stderr, "Output cannot be specified with the flash command.")
usage()
os.Exit(1)
}
var err error
if command == "flash" {
err = Flash(flag.Arg(0), *target, *port, config)
} else {
if !config.debug {
fmt.Fprintln(os.Stderr, "Debug disabled while running gdb?")
usage()
os.Exit(1)
}
err = FlashGDB(flag.Arg(0), *target, *port, *ocdOutput, config)
}
if err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
case "run":
if flag.NArg() != 1 {
fmt.Fprintln(os.Stderr, "No package specified.")
usage()
os.Exit(1)
}
var err error
if *target == "" {
err = Run(flag.Arg(0))
} else {
err = Emulate(flag.Arg(0), *target, config)
}
if err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
case "clean":
// remove cache directory
dir := cacheDir()
err := os.RemoveAll(dir)
if err != nil {
fmt.Fprintln(os.Stderr, "cannot clean cache:", err)
os.Exit(1)
}
case "help":
usage()
default:
fmt.Fprintln(os.Stderr, "Unknown command:", command)
usage()
os.Exit(1)
}
}