github.com/aykevl/tinygo@v0.5.0/main.go (about) 1 package main 2 3 import ( 4 "errors" 5 "flag" 6 "fmt" 7 "go/types" 8 "io" 9 "io/ioutil" 10 "os" 11 "os/exec" 12 "os/signal" 13 "path/filepath" 14 "runtime" 15 "strconv" 16 "strings" 17 "syscall" 18 19 "github.com/tinygo-org/tinygo/compiler" 20 "github.com/tinygo-org/tinygo/interp" 21 "github.com/tinygo-org/tinygo/loader" 22 ) 23 24 // commandError is an error type to wrap os/exec.Command errors. This provides 25 // some more information regarding what went wrong while running a command. 26 type commandError struct { 27 Msg string 28 File string 29 Err error 30 } 31 32 func (e *commandError) Error() string { 33 return e.Msg + " " + e.File + ": " + e.Err.Error() 34 } 35 36 type BuildConfig struct { 37 opt string 38 gc string 39 printIR bool 40 dumpSSA bool 41 debug bool 42 printSizes string 43 cFlags []string 44 ldFlags []string 45 wasmAbi string 46 } 47 48 // Helper function for Compiler object. 49 func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, action func(string) error) error { 50 if config.gc == "" && spec.GC != "" { 51 config.gc = spec.GC 52 } 53 54 // Append command line passed CFlags and LDFlags 55 spec.CFlags = append(spec.CFlags, config.cFlags...) 56 spec.LDFlags = append(spec.LDFlags, config.ldFlags...) 57 58 compilerConfig := compiler.Config{ 59 Triple: spec.Triple, 60 CPU: spec.CPU, 61 GOOS: spec.GOOS, 62 GOARCH: spec.GOARCH, 63 GC: config.gc, 64 CFlags: spec.CFlags, 65 LDFlags: spec.LDFlags, 66 Debug: config.debug, 67 DumpSSA: config.dumpSSA, 68 RootDir: sourceDir(), 69 GOPATH: getGopath(), 70 BuildTags: spec.BuildTags, 71 } 72 c, err := compiler.NewCompiler(pkgName, compilerConfig) 73 if err != nil { 74 return err 75 } 76 77 // Compile Go code to IR. 78 err = c.Compile(pkgName) 79 if err != nil { 80 return err 81 } 82 if config.printIR { 83 fmt.Println("Generated LLVM IR:") 84 fmt.Println(c.IR()) 85 } 86 if err := c.Verify(); err != nil { 87 return errors.New("verification error after IR construction") 88 } 89 90 err = interp.Run(c.Module(), c.TargetData(), config.dumpSSA) 91 if err != nil { 92 return err 93 } 94 if err := c.Verify(); err != nil { 95 return errors.New("verification error after interpreting runtime.initAll") 96 } 97 98 if spec.GOOS != "darwin" { 99 c.ApplyFunctionSections() // -ffunction-sections 100 } 101 if err := c.Verify(); err != nil { 102 return errors.New("verification error after applying function sections") 103 } 104 105 // Browsers cannot handle external functions that have type i64 because it 106 // cannot be represented exactly in JavaScript (JS only has doubles). To 107 // keep functions interoperable, pass int64 types as pointers to 108 // stack-allocated values. 109 // Use -wasm-abi=generic to disable this behaviour. 110 if config.wasmAbi == "js" && strings.HasPrefix(spec.Triple, "wasm") { 111 err := c.ExternalInt64AsPtr() 112 if err != nil { 113 return err 114 } 115 if err := c.Verify(); err != nil { 116 return errors.New("verification error after running the wasm i64 hack") 117 } 118 } 119 120 // Optimization levels here are roughly the same as Clang, but probably not 121 // exactly. 122 switch config.opt { 123 case "none:", "0": 124 err = c.Optimize(0, 0, 0) // -O0 125 case "1": 126 err = c.Optimize(1, 0, 0) // -O1 127 case "2": 128 err = c.Optimize(2, 0, 225) // -O2 129 case "s": 130 err = c.Optimize(2, 1, 225) // -Os 131 case "z": 132 err = c.Optimize(2, 2, 5) // -Oz, default 133 default: 134 err = errors.New("unknown optimization level: -opt=" + config.opt) 135 } 136 if err != nil { 137 return err 138 } 139 if err := c.Verify(); err != nil { 140 return errors.New("verification failure after LLVM optimization passes") 141 } 142 143 // On the AVR, pointers can point either to flash or to RAM, but we don't 144 // know. As a temporary fix, load all global variables in RAM. 145 // In the future, there should be a compiler pass that determines which 146 // pointers are flash and which are in RAM so that pointers can have a 147 // correct address space parameter (address space 1 is for flash). 148 if strings.HasPrefix(spec.Triple, "avr") { 149 c.NonConstGlobals() 150 if err := c.Verify(); err != nil { 151 return errors.New("verification error after making all globals non-constant on AVR") 152 } 153 } 154 155 // Generate output. 156 outext := filepath.Ext(outpath) 157 switch outext { 158 case ".o": 159 return c.EmitObject(outpath) 160 case ".bc": 161 return c.EmitBitcode(outpath) 162 case ".ll": 163 return c.EmitText(outpath) 164 default: 165 // Act as a compiler driver. 166 167 // Create a temporary directory for intermediary files. 168 dir, err := ioutil.TempDir("", "tinygo") 169 if err != nil { 170 return err 171 } 172 defer os.RemoveAll(dir) 173 174 // Write the object file. 175 objfile := filepath.Join(dir, "main.o") 176 err = c.EmitObject(objfile) 177 if err != nil { 178 return err 179 } 180 181 // Load builtins library from the cache, possibly compiling it on the 182 // fly. 183 var librt string 184 if spec.RTLib == "compiler-rt" { 185 librt, err = loadBuiltins(spec.Triple) 186 if err != nil { 187 return err 188 } 189 } 190 191 // Prepare link command. 192 executable := filepath.Join(dir, "main") 193 tmppath := executable // final file 194 ldflags := append(spec.LDFlags, "-o", executable, objfile, "-L", sourceDir()) 195 if spec.RTLib == "compiler-rt" { 196 ldflags = append(ldflags, librt) 197 } 198 199 // Compile extra files. 200 for i, path := range spec.ExtraFiles { 201 outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o") 202 cmd := exec.Command(spec.Compiler, append(spec.CFlags, "-c", "-o", outpath, path)...) 203 cmd.Stdout = os.Stdout 204 cmd.Stderr = os.Stderr 205 cmd.Dir = sourceDir() 206 err := cmd.Run() 207 if err != nil { 208 return &commandError{"failed to build", path, err} 209 } 210 ldflags = append(ldflags, outpath) 211 } 212 213 // Compile C files in packages. 214 for i, pkg := range c.Packages() { 215 for _, file := range pkg.CFiles { 216 path := filepath.Join(pkg.Package.Dir, file) 217 outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o") 218 cmd := exec.Command(spec.Compiler, append(spec.CFlags, "-c", "-o", outpath, path)...) 219 cmd.Stdout = os.Stdout 220 cmd.Stderr = os.Stderr 221 cmd.Dir = sourceDir() 222 err := cmd.Run() 223 if err != nil { 224 return &commandError{"failed to build", path, err} 225 } 226 ldflags = append(ldflags, outpath) 227 } 228 } 229 230 // Link the object files together. 231 if linker, ok := commands[spec.Linker]; ok { 232 err = Link(linker, ldflags...) 233 } else { 234 err = Link(spec.Linker, ldflags...) 235 } 236 if err != nil { 237 return &commandError{"failed to link", executable, err} 238 } 239 240 if config.printSizes == "short" || config.printSizes == "full" { 241 sizes, err := Sizes(executable) 242 if err != nil { 243 return err 244 } 245 if config.printSizes == "short" { 246 fmt.Printf(" code data bss | flash ram\n") 247 fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS) 248 } else { 249 fmt.Printf(" code rodata data bss | flash ram | package\n") 250 for _, name := range sizes.SortedPackageNames() { 251 pkgSize := sizes.Packages[name] 252 fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name) 253 } 254 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()) 255 fmt.Printf("%7d - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS) 256 } 257 } 258 259 // Get an Intel .hex file or .bin file from the .elf file. 260 if outext == ".hex" || outext == ".bin" { 261 tmppath = filepath.Join(dir, "main"+outext) 262 err := Objcopy(executable, tmppath) 263 if err != nil { 264 return err 265 } 266 } else if outext == ".uf2" { 267 // Get UF2 from the .elf file. 268 tmppath = filepath.Join(dir, "main"+outext) 269 err := ConvertELFFileToUF2File(executable, tmppath) 270 if err != nil { 271 return err 272 } 273 } 274 return action(tmppath) 275 } 276 } 277 278 func Build(pkgName, outpath, target string, config *BuildConfig) error { 279 spec, err := LoadTarget(target) 280 if err != nil { 281 return err 282 } 283 284 return Compile(pkgName, outpath, spec, config, func(tmppath string) error { 285 if err := os.Rename(tmppath, outpath); err != nil { 286 // Moving failed. Do a file copy. 287 inf, err := os.Open(tmppath) 288 if err != nil { 289 return err 290 } 291 defer inf.Close() 292 outf, err := os.OpenFile(outpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777) 293 if err != nil { 294 return err 295 } 296 297 // Copy data to output file. 298 _, err = io.Copy(outf, inf) 299 if err != nil { 300 return err 301 } 302 303 // Check whether file writing was successful. 304 return outf.Close() 305 } else { 306 // Move was successful. 307 return nil 308 } 309 }) 310 } 311 312 func Flash(pkgName, target, port string, config *BuildConfig) error { 313 spec, err := LoadTarget(target) 314 if err != nil { 315 return err 316 } 317 318 // determine the type of file to compile 319 var fileExt string 320 321 switch { 322 case strings.Contains(spec.Flasher, "{hex}"): 323 fileExt = ".hex" 324 case strings.Contains(spec.Flasher, "{elf}"): 325 fileExt = ".elf" 326 case strings.Contains(spec.Flasher, "{bin}"): 327 fileExt = ".bin" 328 case strings.Contains(spec.Flasher, "{uf2}"): 329 fileExt = ".uf2" 330 default: 331 return errors.New("invalid target file - did you forget the {hex} token in the 'flash' section?") 332 } 333 334 return Compile(pkgName, fileExt, spec, config, func(tmppath string) error { 335 if spec.Flasher == "" { 336 return errors.New("no flash command specified - did you miss a -target flag?") 337 } 338 339 // Create the command. 340 flashCmd := spec.Flasher 341 fileToken := "{" + fileExt[1:] + "}" 342 flashCmd = strings.Replace(flashCmd, fileToken, tmppath, -1) 343 flashCmd = strings.Replace(flashCmd, "{port}", port, -1) 344 345 // Execute the command. 346 cmd := exec.Command("/bin/sh", "-c", flashCmd) 347 cmd.Stdout = os.Stdout 348 cmd.Stderr = os.Stderr 349 cmd.Dir = sourceDir() 350 err := cmd.Run() 351 if err != nil { 352 return &commandError{"failed to flash", tmppath, err} 353 } 354 return nil 355 }) 356 } 357 358 // Flash a program on a microcontroller and drop into a GDB shell. 359 // 360 // Note: this command is expected to execute just before exiting, as it 361 // modifies global state. 362 func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig) error { 363 spec, err := LoadTarget(target) 364 if err != nil { 365 return err 366 } 367 368 if spec.GDB == "" { 369 return errors.New("gdb not configured in the target specification") 370 } 371 372 return Compile(pkgName, "", spec, config, func(tmppath string) error { 373 if len(spec.OCDDaemon) != 0 { 374 // We need a separate debugging daemon for on-chip debugging. 375 daemon := exec.Command(spec.OCDDaemon[0], spec.OCDDaemon[1:]...) 376 if ocdOutput { 377 // Make it clear which output is from the daemon. 378 w := &ColorWriter{ 379 Out: os.Stderr, 380 Prefix: spec.OCDDaemon[0] + ": ", 381 Color: TermColorYellow, 382 } 383 daemon.Stdout = w 384 daemon.Stderr = w 385 } 386 // Make sure the daemon doesn't receive Ctrl-C that is intended for 387 // GDB (to break the currently executing program). 388 // https://stackoverflow.com/a/35435038/559350 389 daemon.SysProcAttr = &syscall.SysProcAttr{ 390 Setpgid: true, 391 Pgid: 0, 392 } 393 // Start now, and kill it on exit. 394 daemon.Start() 395 defer func() { 396 daemon.Process.Signal(os.Interrupt) 397 // Maybe we should send a .Kill() after x seconds? 398 daemon.Wait() 399 }() 400 } 401 402 // Ignore Ctrl-C, it must be passed on to GDB. 403 c := make(chan os.Signal, 1) 404 signal.Notify(c, os.Interrupt) 405 go func() { 406 for range c { 407 } 408 }() 409 410 // Construct and execute a gdb command. 411 // By default: gdb -ex run <binary> 412 // Exit GDB with Ctrl-D. 413 params := []string{tmppath} 414 for _, cmd := range spec.GDBCmds { 415 params = append(params, "-ex", cmd) 416 } 417 cmd := exec.Command(spec.GDB, params...) 418 cmd.Stdin = os.Stdin 419 cmd.Stdout = os.Stdout 420 cmd.Stderr = os.Stderr 421 err := cmd.Run() 422 if err != nil { 423 return &commandError{"failed to run gdb with", tmppath, err} 424 } 425 return nil 426 }) 427 } 428 429 // Compile and run the given program, directly or in an emulator. 430 func Run(pkgName, target string, config *BuildConfig) error { 431 spec, err := LoadTarget(target) 432 if err != nil { 433 return err 434 } 435 436 return Compile(pkgName, ".elf", spec, config, func(tmppath string) error { 437 if len(spec.Emulator) == 0 { 438 // Run directly. 439 cmd := exec.Command(tmppath) 440 cmd.Stdout = os.Stdout 441 cmd.Stderr = os.Stderr 442 err := cmd.Run() 443 if err != nil { 444 if err, ok := err.(*exec.ExitError); ok && err.Exited() { 445 // Workaround for QEMU which always exits with an error. 446 return nil 447 } 448 return &commandError{"failed to run compiled binary", tmppath, err} 449 } 450 return nil 451 } else { 452 // Run in an emulator. 453 args := append(spec.Emulator[1:], tmppath) 454 cmd := exec.Command(spec.Emulator[0], args...) 455 cmd.Stdout = os.Stdout 456 cmd.Stderr = os.Stderr 457 err := cmd.Run() 458 if err != nil { 459 if err, ok := err.(*exec.ExitError); ok && err.Exited() { 460 // Workaround for QEMU which always exits with an error. 461 return nil 462 } 463 return &commandError{"failed to run emulator with", tmppath, err} 464 } 465 return nil 466 } 467 }) 468 } 469 470 func usage() { 471 fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.") 472 fmt.Fprintln(os.Stderr, "version:", version) 473 fmt.Fprintf(os.Stderr, "usage: %s command [-printir] [-target=<target>] -o <output> <input>\n", os.Args[0]) 474 fmt.Fprintln(os.Stderr, "\ncommands:") 475 fmt.Fprintln(os.Stderr, " build: compile packages and dependencies") 476 fmt.Fprintln(os.Stderr, " run: compile and run immediately") 477 fmt.Fprintln(os.Stderr, " flash: compile and flash to the device") 478 fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB") 479 fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+cacheDir()+")") 480 fmt.Fprintln(os.Stderr, " help: print this help text") 481 fmt.Fprintln(os.Stderr, "\nflags:") 482 flag.PrintDefaults() 483 } 484 485 func handleCompilerError(err error) { 486 if err != nil { 487 if errUnsupported, ok := err.(*interp.Unsupported); ok { 488 // hit an unknown/unsupported instruction 489 fmt.Fprintln(os.Stderr, "unsupported instruction during init evaluation:") 490 errUnsupported.Inst.Dump() 491 fmt.Fprintln(os.Stderr) 492 } else if errCompiler, ok := err.(types.Error); ok { 493 fmt.Fprintln(os.Stderr, errCompiler) 494 } else if errLoader, ok := err.(loader.Errors); ok { 495 fmt.Fprintln(os.Stderr, "#", errLoader.Pkg.ImportPath) 496 for _, err := range errLoader.Errs { 497 fmt.Fprintln(os.Stderr, err) 498 } 499 } else { 500 fmt.Fprintln(os.Stderr, "error:", err) 501 } 502 os.Exit(1) 503 } 504 } 505 506 func main() { 507 outpath := flag.String("o", "", "output filename") 508 opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z") 509 gc := flag.String("gc", "", "garbage collector to use (none, dumb, marksweep)") 510 printIR := flag.Bool("printir", false, "print LLVM IR") 511 dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA") 512 target := flag.String("target", "", "LLVM target") 513 printSize := flag.String("size", "", "print sizes (none, short, full)") 514 nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation") 515 ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug") 516 port := flag.String("port", "/dev/ttyACM0", "flash port") 517 cFlags := flag.String("cflags", "", "additional cflags for compiler") 518 ldFlags := flag.String("ldflags", "", "additional ldflags for linker") 519 wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic") 520 521 if len(os.Args) < 2 { 522 fmt.Fprintln(os.Stderr, "No command-line arguments supplied.") 523 usage() 524 os.Exit(1) 525 } 526 command := os.Args[1] 527 528 flag.CommandLine.Parse(os.Args[2:]) 529 config := &BuildConfig{ 530 opt: *opt, 531 gc: *gc, 532 printIR: *printIR, 533 dumpSSA: *dumpSSA, 534 debug: !*nodebug, 535 printSizes: *printSize, 536 wasmAbi: *wasmAbi, 537 } 538 539 if *cFlags != "" { 540 config.cFlags = strings.Split(*cFlags, " ") 541 } 542 543 if *ldFlags != "" { 544 config.ldFlags = strings.Split(*ldFlags, " ") 545 } 546 547 os.Setenv("CC", "clang -target="+*target) 548 549 switch command { 550 case "build": 551 if *outpath == "" { 552 fmt.Fprintln(os.Stderr, "No output filename supplied (-o).") 553 usage() 554 os.Exit(1) 555 } 556 if flag.NArg() != 1 { 557 fmt.Fprintln(os.Stderr, "No package specified.") 558 usage() 559 os.Exit(1) 560 } 561 target := *target 562 if target == "" && filepath.Ext(*outpath) == ".wasm" { 563 target = "wasm" 564 } 565 err := Build(flag.Arg(0), *outpath, target, config) 566 handleCompilerError(err) 567 case "build-builtins": 568 // Note: this command is only meant to be used while making a release! 569 if *outpath == "" { 570 fmt.Fprintln(os.Stderr, "No output filename supplied (-o).") 571 usage() 572 os.Exit(1) 573 } 574 if *target == "" { 575 fmt.Fprintln(os.Stderr, "No target (-target).") 576 } 577 err := compileBuiltins(*target, func(path string) error { 578 return moveFile(path, *outpath) 579 }) 580 handleCompilerError(err) 581 case "flash", "gdb": 582 if *outpath != "" { 583 fmt.Fprintln(os.Stderr, "Output cannot be specified with the flash command.") 584 usage() 585 os.Exit(1) 586 } 587 if command == "flash" { 588 err := Flash(flag.Arg(0), *target, *port, config) 589 handleCompilerError(err) 590 } else { 591 if !config.debug { 592 fmt.Fprintln(os.Stderr, "Debug disabled while running gdb?") 593 usage() 594 os.Exit(1) 595 } 596 err := FlashGDB(flag.Arg(0), *target, *port, *ocdOutput, config) 597 handleCompilerError(err) 598 } 599 case "run": 600 if flag.NArg() != 1 { 601 fmt.Fprintln(os.Stderr, "No package specified.") 602 usage() 603 os.Exit(1) 604 } 605 err := Run(flag.Arg(0), *target, config) 606 handleCompilerError(err) 607 case "clean": 608 // remove cache directory 609 dir := cacheDir() 610 err := os.RemoveAll(dir) 611 if err != nil { 612 fmt.Fprintln(os.Stderr, "cannot clean cache:", err) 613 os.Exit(1) 614 } 615 case "help": 616 usage() 617 case "version": 618 fmt.Printf("tinygo version %s %s/%s\n", version, runtime.GOOS, runtime.GOARCH) 619 default: 620 fmt.Fprintln(os.Stderr, "Unknown command:", command) 621 usage() 622 os.Exit(1) 623 } 624 }