From 403d93560b78a7eb6cc3bc2853d80cb7dfadc239 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 1 Apr 2020 23:13:16 +0200 Subject: [PATCH] builder: build static binaries using musl on Linux This commit adds support for musl-libc and uses it by default on Linux. The main benefit of it is that binaries are always statically linked instead of depending on the host libc, even when using CGo. Advantages: - The resulting binaries are always statically linked. - No need for any tools on the host OS, like a compiler, linker, or libc in a release build of TinyGo. - This also simplifies cross compilation as no cross compiler is needed (it's all built into the TinyGo release build). Disadvantages: - Binary size increases by 5-6 kilobytes if -no-debug is used. Binary size increases by a much larger margin when debugging symbols are included (the default behavior) because musl is built with debugging symbols enabled. - Musl does things a bit differently than glibc, and some CGo code might rely on the glibc behavior. - The first build takes a bit longer because musl needs to be built. As an additional bonus, time is now obtained from the system in a way that fixes the Y2038 problem because musl has been a bit more agressive in switching to 64-bit time_t. --- .circleci/config.yml | 19 +---- .gitmodules | 3 + Makefile | 25 ++++++ builder/build.go | 12 ++- builder/builtins.go | 4 +- builder/library.go | 74 ++++++++++------ builder/musl.go | 162 ++++++++++++++++++++++++++++++++++++ builder/picolibc.go | 6 +- compileopts/config.go | 19 +++++ compileopts/target.go | 17 ++-- lib/musl | 1 + src/runtime/runtime_unix.go | 40 +++++++-- 12 files changed, 319 insertions(+), 63 deletions(-) create mode 100644 builder/musl.go create mode 160000 lib/musl diff --git a/.circleci/config.yml b/.circleci/config.yml index dbdb6b54..de75739b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,18 +17,15 @@ commands: echo 'deb https://apt.llvm.org/buster/ llvm-toolchain-buster-<> main' | sudo tee /etc/apt/sources.list.d/llvm.list wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - sudo apt-get update - sudo apt-get install \ + sudo apt-get install --no-install-recommends \ llvm-<>-dev \ clang-<> \ libclang-<>-dev \ lld-<> \ - gcc-arm-linux-gnueabihf \ - gcc-aarch64-linux-gnu \ qemu-system-arm \ qemu-user \ gcc-avr \ avr-libc - sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-8-dev install-node: steps: - run: @@ -139,17 +136,12 @@ commands: name: "Install apt dependencies" command: | sudo apt-get update - sudo apt-get install \ + sudo apt-get install --no-install-recommends \ libgnutls30 libssl1.0.2 \ - gcc-arm-linux-gnueabihf \ - libc6-dev-armel-cross \ - gcc-aarch64-linux-gnu \ - libc6-dev-arm64-cross \ qemu-system-arm \ qemu-user \ gcc-avr \ avr-libc - sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev - install-node - install-wasmtime - install-xtensa-toolchain: @@ -209,17 +201,12 @@ commands: name: "Install apt dependencies" command: | sudo apt-get update - sudo apt-get install \ + sudo apt-get install --no-install-recommends \ libgnutls30 libssl1.0.2 \ - gcc-arm-linux-gnueabihf \ - libc6-dev-armel-cross \ - gcc-aarch64-linux-gnu \ - libc6-dev-arm64-cross \ qemu-system-arm \ qemu-user \ gcc-avr \ avr-libc - sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev - install-node - install-wasmtime - install-xtensa-toolchain: diff --git a/.gitmodules b/.gitmodules index f4138be5..4df14b5e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -23,3 +23,6 @@ [submodule "lib/stm32-svd"] path = lib/stm32-svd url = https://github.com/tinygo-org/stm32-svd +[submodule "lib/musl"] + path = lib/musl + url = git://git.musl-libc.org/musl diff --git a/Makefile b/Makefile index 645b6fad..7fbfddcb 100644 --- a/Makefile +++ b/Makefile @@ -465,6 +465,7 @@ endif @$(MD5SUM) test.nro $(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go @$(MD5SUM) test.hex + GOOS=linux GOARCH=arm $(TINYGO) build -size short -o test.elf ./testdata/cgo ifneq ($(OS),Windows_NT) $(TINYGO) build -o test.elf -gc=leaking -scheduler=none examples/serial endif @@ -478,6 +479,9 @@ build/release: tinygo gen-device wasi-libc @mkdir -p build/release/tinygo/lib/clang/include @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS @mkdir -p build/release/tinygo/lib/compiler-rt/lib + @mkdir -p build/release/tinygo/lib/musl/arch + @mkdir -p build/release/tinygo/lib/musl/crt + @mkdir -p build/release/tinygo/lib/musl/src @mkdir -p build/release/tinygo/lib/nrfx @mkdir -p build/release/tinygo/lib/picolibc/newlib/libc @mkdir -p build/release/tinygo/lib/picolibc/newlib/libm @@ -493,6 +497,27 @@ build/release: tinygo gen-device wasi-libc @cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib @cp -rp lib/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt @cp -rp lib/compiler-rt/README.txt build/release/tinygo/lib/compiler-rt + @cp -rp lib/musl/arch/aarch64 build/release/tinygo/lib/musl/arch + @cp -rp lib/musl/arch/arm build/release/tinygo/lib/musl/arch + @cp -rp lib/musl/arch/generic build/release/tinygo/lib/musl/arch + @cp -rp lib/musl/arch/i386 build/release/tinygo/lib/musl/arch + @cp -rp lib/musl/arch/x86_64 build/release/tinygo/lib/musl/arch + @cp -rp lib/musl/crt/crt1.c build/release/tinygo/lib/musl/crt + @cp -rp lib/musl/COPYRIGHT build/release/tinygo/lib/musl + @cp -rp lib/musl/include build/release/tinygo/lib/musl + @cp -rp lib/musl/src/env build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/errno build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/exit build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/include build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/internal build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/malloc build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/mman build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/string build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/unistd build/release/tinygo/lib/musl/src @cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx @cp -rp lib/picolibc/newlib/libc/ctype build/release/tinygo/lib/picolibc/newlib/libc @cp -rp lib/picolibc/newlib/libc/include build/release/tinygo/lib/picolibc/newlib/libc diff --git a/builder/build.go b/builder/build.go index 73a0f257..e8ae7bc1 100644 --- a/builder/build.go +++ b/builder/build.go @@ -92,6 +92,13 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil root := goenv.Get("TINYGOROOT") var libcDependencies []*compileJob switch config.Target.Libc { + case "musl": + job, err := Musl.load(config, dir) + if err != nil { + return err + } + libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(job.result), "crt1.o"))) + libcDependencies = append(libcDependencies, job) case "picolibc": libcJob, err := Picolibc.load(config, dir) if err != nil { @@ -578,12 +585,15 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil // while we're at it. Relocations can only be compressed when debug // information is stripped. ldflags = append(ldflags, "--strip-debug", "--compress-relocations") + } else if config.Target.Linker == "ld.lld" { + // ld.lld is also used on Linux. + ldflags = append(ldflags, "--strip-debug") } else { switch config.GOOS() { case "linux": // Either real linux or an embedded system (like AVR) that // pretends to be Linux. It's a ELF linker wrapped by GCC in any - // case. + // case (not ld.lld - that case is handled above). ldflags = append(ldflags, "-Wl,--strip-debug") case "darwin": // MacOS (darwin) doesn't have a linker flag to strip debug diff --git a/builder/builtins.go b/builder/builtins.go index 33bcc99a..76ef3975 100644 --- a/builder/builtins.go +++ b/builder/builtins.go @@ -158,11 +158,11 @@ var aeabiBuiltins = []string{ // For more information, see: https://compiler-rt.llvm.org/ var CompilerRT = Library{ name: "compiler-rt", - cflags: func(headerPath string) []string { + cflags: func(target, headerPath string) []string { return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"} }, sourceDir: "lib/compiler-rt/lib/builtins", - sources: func(target string) []string { + librarySources: func(target string) []string { builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") { builtins = append(builtins, aeabiBuiltins...) diff --git a/builder/library.go b/builder/library.go index 511348c4..73079785 100644 --- a/builder/library.go +++ b/builder/library.go @@ -17,31 +17,19 @@ type Library struct { name string // makeHeaders creates a header include dir for the library - makeHeaders func(includeDir string) error + makeHeaders func(target, includeDir string) error // cflags returns the C flags specific to this library - cflags func(headerPath string) []string + cflags func(target, headerPath string) []string // The source directory, relative to TINYGOROOT. sourceDir string // The source files, relative to sourceDir. - sources func(target string) []string -} + librarySources func(target string) []string -// fullPath returns the full path to the source directory. -func (l *Library) fullPath() string { - return filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir) -} - -// sourcePaths returns a slice with the full paths to the source files. -func (l *Library) sourcePaths(target string) []string { - sources := l.sources(target) - paths := make([]string, len(sources)) - for i, name := range sources { - paths[i] = filepath.Join(l.fullPath(), name) - } - return paths + // The source code for the crt1.o file, relative to sourceDir. + crt1Source string } // Load the library archive, possibly generating and caching it if needed. @@ -90,6 +78,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ // Make headers if needed. headerPath := filepath.Join(outdir, "include") + target := config.Triple() if l.makeHeaders != nil { if _, err = os.Stat(headerPath); err != nil { temporaryHeaderPath, err := ioutil.TempDir(outdir, "include.tmp*") @@ -97,7 +86,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ return nil, err } defer os.RemoveAll(temporaryHeaderPath) - err = l.makeHeaders(temporaryHeaderPath) + err = l.makeHeaders(target, temporaryHeaderPath) if err != nil { return nil, err } @@ -119,11 +108,16 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ // Note: -fdebug-prefix-map is necessary to make the output archive // reproducible. Otherwise the temporary directory is stored in the archive // itself, which varies each run. - target := config.Triple() - args := append(l.cflags(headerPath), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir) + args := append(l.cflags(target, headerPath), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir) cpu := config.CPU() if cpu != "" { - args = append(args, "-mcpu="+cpu) + // X86 has deprecated the -mcpu flag, so we need to use -march instead. + // However, ARM has not done this. + if strings.HasPrefix(target, "i386") || strings.HasPrefix(target, "x86_64") { + args = append(args, "-march="+cpu) + } else { + args = append(args, "-mcpu="+cpu) + } } if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") { args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft") @@ -162,9 +156,15 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ // Create jobs to compile all sources. These jobs are depended upon by the // archive job above, so must be run first. - for _, srcpath := range l.sourcePaths(target) { - srcpath := srcpath // avoid concurrency issues by redefining inside the loop - objpath := filepath.Join(dir, filepath.Base(srcpath)+".o") + for _, path := range l.librarySources(target) { + // Strip leading "../" parts off the path. + cleanpath := path + for strings.HasPrefix(cleanpath, "../") { + cleanpath = cleanpath[3:] + } + srcpath := filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir, path) + objpath := filepath.Join(dir, cleanpath+".o") + os.MkdirAll(filepath.Dir(objpath), 0o777) objs = append(objs, objpath) job.dependencies = append(job.dependencies, &compileJob{ description: "compile " + srcpath, @@ -181,5 +181,31 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ }) } + // Create crt1.o job, if needed. + // Add this as a (fake) dependency to the ar file so it gets compiled. + // (It could be done in parallel with creating the ar file, but it probably + // won't make much of a difference in speed). + if l.crt1Source != "" { + srcpath := filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir, l.crt1Source) + job.dependencies = append(job.dependencies, &compileJob{ + description: "compile " + srcpath, + run: func(*compileJob) error { + var compileArgs []string + compileArgs = append(compileArgs, args...) + tmpfile, err := ioutil.TempFile(outdir, "crt1.o.tmp*") + if err != nil { + return err + } + tmpfile.Close() + compileArgs = append(compileArgs, "-o", tmpfile.Name(), srcpath) + err = runCCompiler(compileArgs...) + if err != nil { + return &commandError{"failed to build", srcpath, err} + } + return os.Rename(tmpfile.Name(), filepath.Join(outdir, "crt1.o")) + }, + }) + } + return job, nil } diff --git a/builder/musl.go b/builder/musl.go new file mode 100644 index 00000000..a54c092a --- /dev/null +++ b/builder/musl.go @@ -0,0 +1,162 @@ +package builder + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/goenv" +) + +var Musl = Library{ + name: "musl", + makeHeaders: func(target, includeDir string) error { + bits := filepath.Join(includeDir, "bits") + err := os.Mkdir(bits, 0777) + if err != nil { + return err + } + + arch := compileopts.MuslArchitecture(target) + muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "musl") + + // Create the file alltypes.h. + f, err := os.Create(filepath.Join(bits, "alltypes.h")) + if err != nil { + return err + } + infiles := []string{ + filepath.Join(muslDir, "arch", arch, "bits", "alltypes.h.in"), + filepath.Join(muslDir, "include", "alltypes.h.in"), + } + for _, infile := range infiles { + data, err := ioutil.ReadFile(infile) + if err != nil { + return err + } + lines := strings.Split(string(data), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "TYPEDEF ") { + matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line) + value := matches[1] + name := matches[2] + line = fmt.Sprintf("#if defined(__NEED_%s) && !defined(__DEFINED_%s)\ntypedef %s %s;\n#define __DEFINED_%s\n#endif\n", name, name, value, name, name) + } + if strings.HasPrefix(line, "STRUCT ") { + matches := regexp.MustCompile(`STRUCT * ([^ ]*) (.*);`).FindStringSubmatch(line) + name := matches[1] + value := matches[2] + line = fmt.Sprintf("#if defined(__NEED_struct_%s) && !defined(__DEFINED_struct_%s)\nstruct %s %s;\n#define __DEFINED_struct_%s\n#endif\n", name, name, name, value, name) + } + f.WriteString(line + "\n") + } + } + f.Close() + + // Create the file syscall.h. + f, err = os.Create(filepath.Join(bits, "syscall.h")) + if err != nil { + return err + } + data, err := ioutil.ReadFile(filepath.Join(muslDir, "arch", arch, "bits", "syscall.h.in")) + if err != nil { + return err + } + _, err = f.Write(bytes.ReplaceAll(data, []byte("__NR_"), []byte("SYS_"))) + if err != nil { + return err + } + f.Close() + + return nil + }, + cflags: func(target, headerPath string) []string { + arch := compileopts.MuslArchitecture(target) + muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl") + return []string{ + "-std=c99", // same as in musl + "-D_XOPEN_SOURCE=700", // same as in musl + // Musl triggers some warnings and we don't want to show any + // warnings while compiling (only errors or silence), so disable + // specific warnings that are triggered in musl. + "-Werror", + "-Wno-logical-op-parentheses", + "-Wno-bitwise-op-parentheses", + "-Wno-shift-op-parentheses", + "-Wno-ignored-attributes", + "-Wno-string-plus-int", + "-Qunused-arguments", + // Select include dirs. Don't include standard library includes + // (that would introduce host dependencies and other complications), + // but do include all the include directories expected by musl. + "-nostdlibinc", + "-I" + muslDir + "/arch/" + arch, + "-I" + muslDir + "/arch/generic", + "-I" + muslDir + "/src/include", + "-I" + muslDir + "/src/internal", + "-I" + headerPath, + "-I" + muslDir + "/include", + } + }, + sourceDir: "lib/musl/src", + librarySources: func(target string) []string { + arch := compileopts.MuslArchitecture(target) + globs := []string{ + "env/*.c", + "errno/*.c", + "exit/*.c", + "internal/defsysinfo.c", + "internal/libc.c", + "internal/syscall_ret.c", + "internal/vdso.c", + "malloc/*.c", + "mman/*.c", + "signal/*.c", + "stdio/*.c", + "string/*.c", + "thread/" + arch + "/*.s", + "thread/" + arch + "/*.c", + "thread/*.c", + "time/*.c", + "unistd/*.c", + } + + var sources []string + seenSources := map[string]struct{}{} + basepath := goenv.Get("TINYGOROOT") + "/lib/musl/src/" + for _, pattern := range globs { + matches, err := filepath.Glob(basepath + pattern) + if err != nil { + // From the documentation: + // > Glob ignores file system errors such as I/O errors reading + // > directories. The only possible returned error is + // > ErrBadPattern, when pattern is malformed. + // So the only possible error is when the (statically defined) + // pattern is wrong. In other words, a programming bug. + panic("could not glob source dirs: " + err.Error()) + } + for _, match := range matches { + relpath, err := filepath.Rel(basepath, match) + if err != nil { + // Not sure if this is even possible. + panic(err) + } + // Make sure architecture specific files override generic files. + id := strings.ReplaceAll(relpath, "/"+arch+"/", "/") + if _, ok := seenSources[id]; ok { + // Already seen this file, skipping this (generic) file. + continue + } + seenSources[id] = struct{}{} + sources = append(sources, relpath) + } + } + return sources + }, + crt1Source: "../crt/crt1.c", // lib/musl/crt/crt1.c +} diff --git a/builder/picolibc.go b/builder/picolibc.go index 273c1360..75c93e91 100644 --- a/builder/picolibc.go +++ b/builder/picolibc.go @@ -11,14 +11,14 @@ import ( // based on newlib. var Picolibc = Library{ name: "picolibc", - makeHeaders: func(includeDir string) error { + makeHeaders: func(target, includeDir string) error { f, err := os.Create(filepath.Join(includeDir, "picolibc.h")) if err != nil { return err } return f.Close() }, - cflags: func(headerPath string) []string { + cflags: func(target, headerPath string) []string { picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib/libc") return []string{ "-Werror", @@ -34,7 +34,7 @@ var Picolibc = Library{ } }, sourceDir: "lib/picolibc/newlib/libc", - sources: func(target string) []string { + librarySources: func(target string) []string { return picolibcSources }, } diff --git a/compileopts/config.go b/compileopts/config.go index 9664d277..bdbdd0d2 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -198,6 +198,16 @@ func (c *Config) RP2040BootPatch() bool { return false } +// MuslArchitecture returns the architecture name as used in musl libc. It is +// usually the same as the first part of the LLVM triple, but not always. +func MuslArchitecture(triple string) string { + arch := strings.Split(triple, "-")[0] + if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") { + arch = "arm" + } + return arch +} + // LibcPath returns the path to the libc directory. The libc path will be either // a precompiled libc shipped with a TinyGo build, or a libc path in the cache // directory (which might not yet be built). @@ -238,6 +248,15 @@ func (c *Config) CFlags() []string { "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(picolibcDir, "include"), "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(picolibcDir, "tinystdio"), ) + case "musl": + root := goenv.Get("TINYGOROOT") + path, _ := c.LibcPath("musl") + arch := MuslArchitecture(c.Triple()) + cflags = append(cflags, + "--sysroot="+path, + "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "musl", "arch", arch), + "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "musl", "include"), + ) case "wasi-libc": root := goenv.Get("TINYGOROOT") cflags = append(cflags, "--sysroot="+root+"/lib/wasi-libc/sysroot") diff --git a/compileopts/target.go b/compileopts/target.go index 88beb7a0..0293f2b4 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -234,6 +234,11 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { if goos == "darwin" { spec.CFlags = append(spec.CFlags, "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk") spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip") + } else if goos == "linux" { + spec.Linker = "ld.lld" + spec.RTLib = "compiler-rt" + spec.Libc = "musl" + spec.LDFlags = append(spec.LDFlags, "--gc-sections") } else { spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie } @@ -245,18 +250,10 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { // Some educated guesses as to how to invoke helper programs. spec.GDB = []string{"gdb-multiarch"} if goarch == "arm" && goos == "linux" { - spec.CFlags = append(spec.CFlags, "--sysroot=/usr/arm-linux-gnueabihf") - spec.Linker = "arm-linux-gnueabihf-gcc" - spec.Emulator = []string{"qemu-arm", "-L", "/usr/arm-linux-gnueabihf"} + spec.Emulator = []string{"qemu-arm"} } if goarch == "arm64" && goos == "linux" { - spec.CFlags = append(spec.CFlags, "--sysroot=/usr/aarch64-linux-gnu") - spec.Linker = "aarch64-linux-gnu-gcc" - spec.Emulator = []string{"qemu-aarch64", "-L", "/usr/aarch64-linux-gnu"} - } - if goarch == "386" && runtime.GOARCH == "amd64" { - spec.CFlags = append(spec.CFlags, "-m32") - spec.LDFlags = append(spec.LDFlags, "-m32") + spec.Emulator = []string{"qemu-aarch64"} } } return &spec, nil diff --git a/lib/musl b/lib/musl new file mode 160000 index 00000000..040c1d16 --- /dev/null +++ b/lib/musl @@ -0,0 +1 @@ +Subproject commit 040c1d16b468c50c04fc94edff521f1637708328 diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index aa3515f1..e53ff357 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -17,8 +17,11 @@ func usleep(usec uint) int func malloc(size uintptr) unsafe.Pointer // void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); +// Note: off_t is defined as int64 because: +// - musl (used on Linux) always defines it as int64 +// - darwin is practically always 64-bit anyway //export mmap -func mmap(addr unsafe.Pointer, length uintptr, prot, flags, fd int, offset int) unsafe.Pointer +func mmap(addr unsafe.Pointer, length uintptr, prot, flags, fd int, offset int64) unsafe.Pointer //export abort func abort() @@ -27,16 +30,39 @@ func abort() func exit(code int) //export clock_gettime -func clock_gettime(clk_id int32, ts *timespec) +func libc_clock_gettime(clk_id int32, ts *timespec) + +//export __clock_gettime64 +func libc_clock_gettime64(clk_id int32, ts *timespec) + +// Portable (64-bit) variant of clock_gettime. +func clock_gettime(clk_id int32, ts *timespec) { + if TargetBits == 32 { + // This is a 32-bit architecture (386, arm, etc). + // We would like to use the 64-bit version of this function so that + // binaries will continue to run after Y2038. + // For more information: + // - https://musl.libc.org/time64.html + // - https://sourceware.org/glibc/wiki/Y2038ProofnessDesign + libc_clock_gettime64(clk_id, ts) + } else { + // This is a 64-bit architecture (amd64, arm64, etc). + // Use the regular variant, because it already fixes the Y2038 problem + // by using 64-bit integer types. + libc_clock_gettime(clk_id, ts) + } +} type timeUnit int64 -// Note: tv_sec and tv_nsec vary in size by platform. They are 32-bit on 32-bit -// systems and 64-bit on 64-bit systems (at least on macOS/Linux), so we can -// simply use the 'int' type which does the same. +// Note: tv_sec and tv_nsec normally vary in size by platform. However, we're +// using the time64 variant (see clock_gettime above), so the formats are the +// same between 32-bit and 64-bit architectures. +// There is one issue though: on big-endian systems, tv_nsec would be incorrect. +// But we don't support big-endian systems yet (as of 2021) so this is fine. type timespec struct { - tv_sec int // time_t: follows the platform bitness - tv_nsec int // long: on Linux and macOS, follows the platform bitness + tv_sec int64 // time_t with time64 support (always 64-bit) + tv_nsec int64 // unsigned 64-bit integer on all time64 platforms } var stackTop uintptr