diff options
author | Ayke van Laethem <[email protected]> | 2020-04-02 21:14:07 +0200 |
---|---|---|
committer | Ron Evans <[email protected]> | 2020-04-03 12:41:44 +0200 |
commit | f06d7d1bd6cf5d5231dde4de8ef4ae5ecd908074 (patch) | |
tree | 75a607804bb0323a9d782e662bf3c96a3a962dcf | |
parent | 407149e323d8235cf8bce153201195ddbe2eaa65 (diff) | |
download | tinygo-f06d7d1bd6cf5d5231dde4de8ef4ae5ecd908074.tar.gz tinygo-f06d7d1bd6cf5d5231dde4de8ef4ae5ecd908074.zip |
builder: run tools (clang, ...) as separate processes
This is necessary because LLVM defines many options in global variables
that are modified when invoking Clang. In particular, LLVM 10 seems to
have a bug in which it always sets the -pgo-warn-misexpect flag. Setting
it multiple times (over various cc1 invocations) results in an error:
clang (LLVM option parsing): for the --pgo-warn-misexpect option: may only occur zero or one times!
This is fixed by running the Clang invocation in a new `tinygo`
invocation.
Because we've had issues with lld in the past, also run lld in a
separate process so similar issues won't happen with lld in the future.
-rw-r--r-- | builder/compiler-builtin.go | 60 | ||||
-rw-r--r-- | builder/compiler-external.go | 24 | ||||
-rw-r--r-- | builder/linker-builtin.go | 71 | ||||
-rw-r--r-- | builder/linker-external.go | 27 | ||||
-rw-r--r-- | builder/tools-builtin.go | 54 | ||||
-rw-r--r-- | builder/tools-external.go | 15 | ||||
-rw-r--r-- | builder/tools.go | 59 | ||||
-rw-r--r-- | main.go | 12 | ||||
-rw-r--r-- | main_test.go | 23 |
9 files changed, 163 insertions, 182 deletions
diff --git a/builder/compiler-builtin.go b/builder/compiler-builtin.go deleted file mode 100644 index 8fd37c903..000000000 --- a/builder/compiler-builtin.go +++ /dev/null @@ -1,60 +0,0 @@ -// +build byollvm - -package builder - -import ( - "errors" - "os" - "os/exec" - "unsafe" - - "github.com/tinygo-org/tinygo/goenv" -) - -/* -#cgo CXXFLAGS: -fno-rtti -#include <stdbool.h> -#include <stdlib.h> -bool tinygo_clang_driver(int argc, char **argv); -*/ -import "C" - -// runCCompiler invokes a C compiler with the given arguments. -// -// This version invokes the built-in Clang when trying to run the Clang compiler. -func runCCompiler(command string, flags ...string) error { - switch command { - case "clang": - // Compile this with the internal Clang compiler. - headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT")) - if headerPath == "" { - return errors.New("could not locate Clang headers") - } - flags = append(flags, "-I"+headerPath) - flags = append([]string{"tinygo:" + command}, flags...) - var cflag *C.char - buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag))) - cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)] - for i, flag := range flags { - cflag := C.CString(flag) - cflags[i] = cflag - defer C.free(unsafe.Pointer(cflag)) - } - ok := C.tinygo_clang_driver(C.int(len(flags)), (**C.char)(buf)) - if !ok { - return errors.New("failed to compile using built-in clang") - } - return nil - default: - // Running some other compiler. Maybe it has been defined in the - // commands map (unlikely). - if cmdNames, ok := commands[command]; ok { - return execCommand(cmdNames, flags...) - } - // Alternatively, run the compiler directly. - cmd := exec.Command(command, flags...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() - } -} diff --git a/builder/compiler-external.go b/builder/compiler-external.go deleted file mode 100644 index b8bc688ab..000000000 --- a/builder/compiler-external.go +++ /dev/null @@ -1,24 +0,0 @@ -// +build !byollvm - -package builder - -// This file provides a way to a C compiler as an external command. See also: -// clang-external.go - -import ( - "os" - "os/exec" -) - -// runCCompiler invokes a C compiler with the given arguments. -// -// This version always runs the compiler as an external command. -func runCCompiler(command string, flags ...string) error { - if cmdNames, ok := commands[command]; ok { - return execCommand(cmdNames, flags...) - } - cmd := exec.Command(command, flags...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() -} diff --git a/builder/linker-builtin.go b/builder/linker-builtin.go deleted file mode 100644 index ace0d2ebf..000000000 --- a/builder/linker-builtin.go +++ /dev/null @@ -1,71 +0,0 @@ -// +build byollvm - -package builder - -// This file provides a Link() function that uses the bundled lld if possible. - -import ( - "errors" - "os" - "os/exec" - "unsafe" - - "github.com/tinygo-org/tinygo/goenv" -) - -/* -#include <stdbool.h> -#include <stdlib.h> -bool tinygo_link_elf(int argc, char **argv); -bool tinygo_link_wasm(int argc, char **argv); -*/ -import "C" - -// 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 { - switch linker { - case "ld.lld": - flags = append([]string{"tinygo:" + linker}, flags...) - var cflag *C.char - buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag))) - cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)] - for i, flag := range flags { - cflag := C.CString(flag) - cflags[i] = cflag - defer C.free(unsafe.Pointer(cflag)) - } - ok := C.tinygo_link_elf(C.int(len(flags)), (**C.char)(buf)) - if !ok { - return errors.New("failed to link using built-in ld.lld") - } - return nil - case "wasm-ld": - flags = append([]string{"tinygo:" + linker}, flags...) - var cflag *C.char - buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag))) - defer C.free(buf) - cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)] - for i, flag := range flags { - cflag := C.CString(flag) - cflags[i] = cflag - defer C.free(unsafe.Pointer(cflag)) - } - ok := C.tinygo_link_wasm(C.int(len(flags)), (**C.char)(buf)) - if !ok { - return errors.New("failed to link using built-in wasm-ld") - } - return nil - default: - // Fall back to external command. - if cmdNames, ok := commands[linker]; ok { - return execCommand(cmdNames, flags...) - } - cmd := exec.Command(linker, flags...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Dir = goenv.Get("TINYGOROOT") - return cmd.Run() - } -} diff --git a/builder/linker-external.go b/builder/linker-external.go deleted file mode 100644 index 472645eb7..000000000 --- a/builder/linker-external.go +++ /dev/null @@ -1,27 +0,0 @@ -// +build !byollvm - -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. - -import ( - "os" - "os/exec" - - "github.com/tinygo-org/tinygo/goenv" -) - -// 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 { - if cmdNames, ok := commands[linker]; ok { - return execCommand(cmdNames, flags...) - } - cmd := exec.Command(linker, flags...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Dir = goenv.Get("TINYGOROOT") - return cmd.Run() -} diff --git a/builder/tools-builtin.go b/builder/tools-builtin.go new file mode 100644 index 000000000..61e7a325b --- /dev/null +++ b/builder/tools-builtin.go @@ -0,0 +1,54 @@ +// +build byollvm + +package builder + +import ( + "errors" + "unsafe" +) + +/* +#cgo CXXFLAGS: -fno-rtti +#include <stdbool.h> +#include <stdlib.h> +bool tinygo_clang_driver(int argc, char **argv); +bool tinygo_link_elf(int argc, char **argv); +bool tinygo_link_wasm(int argc, char **argv); +*/ +import "C" + +const hasBuiltinTools = true + +// RunTool runs the given tool (such as clang). +// +// This version actually runs the tools because TinyGo was compiled while +// linking statically with LLVM (with the byollvm build tag). +func RunTool(tool string, args ...string) error { + args = append([]string{"tinygo:" + tool}, args...) + + var cflag *C.char + buf := C.calloc(C.size_t(len(args)), C.size_t(unsafe.Sizeof(cflag))) + defer C.free(buf) + cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(args):len(args)] + for i, flag := range args { + cflag := C.CString(flag) + cflags[i] = cflag + defer C.free(unsafe.Pointer(cflag)) + } + + var ok C.bool + switch tool { + case "clang": + ok = C.tinygo_clang_driver(C.int(len(args)), (**C.char)(buf)) + case "ld.lld": + ok = C.tinygo_link_elf(C.int(len(args)), (**C.char)(buf)) + case "wasm-ld": + ok = C.tinygo_link_wasm(C.int(len(args)), (**C.char)(buf)) + default: + return errors.New("unknown tool: " + tool) + } + if !ok { + return errors.New("failed to run tool: " + tool) + } + return nil +} diff --git a/builder/tools-external.go b/builder/tools-external.go new file mode 100644 index 000000000..4955bd2c7 --- /dev/null +++ b/builder/tools-external.go @@ -0,0 +1,15 @@ +// +build !byollvm + +package builder + +import "errors" + +const hasBuiltinTools = false + +// RunTool runs the given tool (such as clang). +// +// This version doesn't actually run the tool: TinyGo has not been compiled by +// statically linking to LLVM. +func RunTool(tool string, args ...string) error { + return errors.New("cannot run tool: " + tool) +} diff --git a/builder/tools.go b/builder/tools.go new file mode 100644 index 000000000..bc0f97824 --- /dev/null +++ b/builder/tools.go @@ -0,0 +1,59 @@ +package builder + +import ( + "errors" + "os" + "os/exec" + + "github.com/tinygo-org/tinygo/goenv" +) + +// runCCompiler invokes a C compiler with the given arguments. +func runCCompiler(command string, flags ...string) error { + if hasBuiltinTools && command == "clang" { + // Compile this with the internal Clang compiler. + headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT")) + if headerPath == "" { + return errors.New("could not locate Clang headers") + } + flags = append(flags, "-I"+headerPath) + cmd := exec.Command(os.Args[0], append([]string{"clang"}, flags...)...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() + } + + // Running some other compiler. Maybe it has been defined in the + // commands map (unlikely). + if cmdNames, ok := commands[command]; ok { + return execCommand(cmdNames, flags...) + } + + // Alternatively, run the compiler directly. + cmd := exec.Command(command, flags...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +// link invokes a linker with the given name and flags. +func link(linker string, flags ...string) error { + if hasBuiltinTools && (linker == "ld.lld" || linker == "wasm-ld") { + // Run command with internal linker. + cmd := exec.Command(os.Args[0], append([]string{linker}, flags...)...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() + } + + // Fall back to external command. + if cmdNames, ok := commands[linker]; ok { + return execCommand(cmdNames, flags...) + } + + cmd := exec.Command(linker, flags...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = goenv.Get("TINYGOROOT") + return cmd.Run() +} @@ -726,6 +726,18 @@ func main() { } command := os.Args[1] + // Early command processing, before commands are interpreted by the Go flag + // library. + switch command { + case "clang", "ld.lld", "wasm-ld": + err := builder.RunTool(command, os.Args[2:]...) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + os.Exit(0) + } + flag.CommandLine.Parse(os.Args[2:]) options := &compileopts.Options{ Target: *target, diff --git a/main_test.go b/main_test.go index 3031f6b55..c8f46915e 100644 --- a/main_test.go +++ b/main_test.go @@ -6,6 +6,7 @@ package main import ( "bufio" "bytes" + "fmt" "io/ioutil" "os" "os/exec" @@ -16,6 +17,7 @@ import ( "testing" "time" + "github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/compileopts" ) @@ -218,3 +220,24 @@ func runTest(path, target string, t *testing.T) { t.Fail() } } + +// This TestMain is necessary because TinyGo may also be invoked to run certain +// LLVM tools in a separate process. Not capturing these invocations would lead +// to recursive tests. +func TestMain(m *testing.M) { + if len(os.Args) >= 2 { + switch os.Args[1] { + case "clang", "ld.lld", "wasm-ld": + // Invoke a specific tool. + err := builder.RunTool(os.Args[1], os.Args[2:]...) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + os.Exit(0) + } + } + + // Run normal tests. + os.Exit(m.Run()) +} |