diff --git a/BUILDING.md b/BUILDING.md index cd15d77d..55b9f74d 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -1,16 +1,17 @@ # Building TinyGo -TinyGo depends on LLVM and libclang, which are both big C++ libraries. There are -two ways these can be linked: dynamically and statically. The default is dynamic -linking because it is fast and works almost out of the box on Debian-based -systems with the right libraries installed. +TinyGo depends on LLVM and libclang, which are both big C++ libraries. It can +also optionally use a built-in lld to ease cross compiling. There are two ways +these can be linked: dynamically and statically. The default is dynamic linking +because it is fast and works almost out of the box on Debian-based systems with +the right libraries installed. -This guide describes how to statically link TinyGo against LLVM and libclang so -that the binary can be easily moved between systems. +This guide describes how to statically link TinyGo against LLVM, libclang and +lld so that the binary can be easily moved between systems. ## Dependencies -LLVM and Clang are both quite light on dependencies, requiring only standard +LLVM, Clang and LLD are quite light on dependencies, requiring only standard build tools to be built. Go is of course necessary to build TinyGo itself. * Go (1.11+) @@ -30,14 +31,15 @@ The first step is to get the source code. Place it in some directory, assuming git clone -b release_70 https://github.com/llvm-mirror/llvm.git $HOME/src/llvm git clone -b release_70 https://github.com/llvm-mirror/clang.git $HOME/src/llvm/tools/clang + git clone -b release_70 https://github.com/llvm-mirror/lld.git $HOME/src/llvm/tools/lld go get -d github.com/tinygo-org/tinygo cd $HOME/go/src/github.com/tinygo-org/tinygo dep ensure -vendor-only # download dependencies -Note that Clang must be placed inside the tools subdirectory of LLVM to be -automatically built with the rest of the system. +Note that Clang and LLD must be placed inside the tools subdirectory of LLVM to +be automatically built with the rest of the system. -## Build LLVM and Clang +## Build LLVM, Clang, LLD Building LLVM is quite easy compared to some other software packages. However, the default configuration is _not_ optimized for distribution. It is optimized @@ -86,10 +88,10 @@ This can take over an hour depending on the speed of your system. ## Build TinyGo Now that you have a working version of LLVM, build TinyGo using it. You need to -specify the directories to the LLVM build directory and to the Clang source. +specify the directories to the LLVM build directory and to the Clang and LLD source. cd $HOME/go/src/github.com/tinygo-org/tinygo - make static LLVM_BUILDDIR=$HOME/src/llvm-build CLANG_SRC=$HOME/src/llvm/tools/clang + make static LLVM_BUILDDIR=$HOME/src/llvm-build CLANG_SRC=$HOME/src/llvm/tools/clang LLD_SRC=$HOME/src/llvm/tools/lld ## Verify TinyGo @@ -98,7 +100,7 @@ Try running TinyGo: ./build/tinygo help Also, make sure the `tinygo` binary really is statically linked. Check this -using `ldd`: +using `ldd` (not to be confused with `lld`): ldd ./build/tinygo diff --git a/Makefile b/Makefile index c913c92d..c42eb805 100644 --- a/Makefile +++ b/Makefile @@ -40,14 +40,17 @@ $(error Unknown target) endif -LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines debuginfodwarf executionengine instrumentation interpreter ipo irreader linker mc mcjit objcarcopts option profiledata scalaropts support target +LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target CLANG_LIBS = -Wl,--start-group $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions -lclangToolingRefactor -Wl,--end-group -lstdc++ +LLD_LIBS = -Wl,--start-group -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML -Wl,--end-group + + # For static linking. -CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include +CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include CGO_CXXFLAGS=-std=c++11 -CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) +CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) diff --git a/linker-builtin.go b/linker-builtin.go new file mode 100644 index 00000000..ba63f827 --- /dev/null +++ b/linker-builtin.go @@ -0,0 +1,66 @@ +// +build byollvm + +package main + +// This file provides a Link() function that uses the bundled lld if possible. + +import ( + "errors" + "os" + "os/exec" + "unsafe" +) + +/* +#include +#include +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(dir, linker string, flags ...string) error { + switch linker { + case "ld.lld", commands["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", commands["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. + cmd := exec.Command(linker, flags...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = dir + return cmd.Run() + } +} diff --git a/linker-external.go b/linker-external.go new file mode 100644 index 00000000..d0c8126a --- /dev/null +++ b/linker-external.go @@ -0,0 +1,22 @@ +// +build !byollvm + +package main + +// 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" +) + +// Link invokes a linker with the given name and arguments. +// +// This version always runs the linker as an external command. +func Link(dir, linker string, flags ...string) error { + cmd := exec.Command(linker, flags...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = dir + return cmd.Run() +} diff --git a/lld.cpp b/lld.cpp new file mode 100644 index 00000000..2373edee --- /dev/null +++ b/lld.cpp @@ -0,0 +1,19 @@ +// +build byollvm + +// This file provides C wrappers for liblld. + +#include + +extern "C" { + +bool tinygo_link_elf(int argc, char **argv) { + std::vector args(argv, argv + argc); + return lld::elf::link(args, false); +} + +bool tinygo_link_wasm(int argc, char **argv) { + std::vector args(argv, argv + argc); + return lld::wasm::link(args, false); +} + +} // external "C" diff --git a/main.go b/main.go index 3f61b83c..65f2effb 100644 --- a/main.go +++ b/main.go @@ -21,8 +21,10 @@ import ( ) var commands = map[string]string{ - "ar": "ar", - "clang": "clang-7", + "ar": "ar", + "clang": "clang-7", + "ld.lld": "ld.lld-7", + "wasm-ld": "wasm-ld-7", } // commandError is an error type to wrap os/exec.Command errors. This provides @@ -234,11 +236,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act } // Link the object files together. - cmd := exec.Command(spec.Linker, ldflags...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Dir = sourceDir() - err = cmd.Run() + err = Link(sourceDir(), spec.Linker, ldflags...) if err != nil { return &commandError{"failed to link", executable, err} } diff --git a/targets/wasm.json b/targets/wasm.json index 7deb29a3..d7eab5a5 100644 --- a/targets/wasm.json +++ b/targets/wasm.json @@ -4,13 +4,12 @@ "goos": "js", "goarch": "wasm", "compiler": "clang-7", - "linker": "ld.lld-7", + "linker": "wasm-ld-7", "cflags": [ "--target=wasm32", "-Oz" ], "ldflags": [ - "-flavor", "wasm", "-allow-undefined" ], "emulator": ["cwa"]