From f316ebc23b025e497931cfe420ec143f1a217b57 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 15 Jan 2020 15:59:51 +0100 Subject: [PATCH] all: include picolibc for bare metal targets This is necessary for better CGo support on bare metal. Existing libraries expect to be able to include parts of libc and expect to be able to link to those symbols. Because with this all targets have a working libc, it is now possible to add tests to check that a libc in fact works basically. Not all parts of picolibc are included, such as the math or stdio parts. These should be added later, when needed. This commit also avoids the need for the custom memcpy/memset/memcmp symbols that are sometimes emitted by LLVM. The C library will take care of that. --- .gitmodules | 3 + Makefile | 16 +++- builder/build.go | 9 ++ builder/picolibc.go | 127 +++++++++++++++++++++++++++++ cgo/cgo.go | 5 ++ compileopts/config.go | 6 ++ compileopts/target.go | 4 + lib/picolibc | 1 + lib/picolibc-include/picolibc.h | 0 main.go | 19 ++++- src/runtime/runtime_arm7tdmi.go | 14 ---- src/runtime/runtime_cortexm.go | 20 ----- src/runtime/runtime_tinygoriscv.go | 19 ----- targets/cortex-m.json | 3 +- targets/gameboy-advance.json | 2 +- targets/riscv.json | 2 +- testdata/cgo/main.go | 7 ++ testdata/cgo/out.txt | 1 + 18 files changed, 196 insertions(+), 62 deletions(-) create mode 100644 builder/picolibc.go create mode 160000 lib/picolibc create mode 100644 lib/picolibc-include/picolibc.h delete mode 100644 src/runtime/runtime_tinygoriscv.go diff --git a/.gitmodules b/.gitmodules index 7e86215e..af6e1809 100644 --- a/.gitmodules +++ b/.gitmodules @@ -17,3 +17,6 @@ [submodule "lib/wasi-libc"] path = lib/wasi-libc url = https://github.com/CraneStation/wasi-libc +[submodule "lib/picolibc"] + path = lib/picolibc + url = https://github.com/keith-packard/picolibc.git diff --git a/Makefile b/Makefile index 6a18278c..e16f225a 100644 --- a/Makefile +++ b/Makefile @@ -312,6 +312,7 @@ release: tinygo gen-device wasi-libc @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS @mkdir -p build/release/tinygo/lib/compiler-rt/lib @mkdir -p build/release/tinygo/lib/nrfx + @mkdir -p build/release/tinygo/lib/picolibc/newlib/libc @mkdir -p build/release/tinygo/lib/wasi-libc @mkdir -p build/release/tinygo/pkg/armv6m-none-eabi @mkdir -p build/release/tinygo/pkg/armv7m-none-eabi @@ -325,10 +326,19 @@ release: tinygo gen-device wasi-libc @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/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 + @cp -rp lib/picolibc/newlib/libc/locale build/release/tinygo/lib/picolibc/newlib/libc + @cp -rp lib/picolibc/newlib/libc/string build/release/tinygo/lib/picolibc/newlib/libc + @cp -rp lib/picolibc/newlib/libc/tinystdio build/release/tinygo/lib/picolibc/newlib/libc + @cp -rp lib/picolibc-include build/release/tinygo/lib @cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot @cp -rp src build/release/tinygo/src @cp -rp targets build/release/tinygo/targets - ./build/tinygo build-builtins -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a - ./build/tinygo build-builtins -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a - ./build/tinygo build-builtins -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a + ./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a compiler-rt + ./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a compiler-rt + ./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a compiler-rt + ./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/picolibc.a picolibc + ./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/picolibc.a picolibc + ./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/picolibc.a picolibc tar -czf build/release.tar.gz -C build/release tinygo diff --git a/builder/build.go b/builder/build.go index d3e9fcc7..85a4dd51 100644 --- a/builder/build.go +++ b/builder/build.go @@ -145,6 +145,15 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri ldflags = append(ldflags, librt) } + // Add libc. + if config.Target.Libc == "picolibc" { + libc, err := Picolibc.Load(config.Triple()) + if err != nil { + return err + } + ldflags = append(ldflags, libc) + } + // Compile extra files. root := goenv.Get("TINYGOROOT") for i, path := range config.ExtraFiles() { diff --git a/builder/picolibc.go b/builder/picolibc.go new file mode 100644 index 00000000..31e2f053 --- /dev/null +++ b/builder/picolibc.go @@ -0,0 +1,127 @@ +package builder + +import ( + "path/filepath" + + "github.com/tinygo-org/tinygo/goenv" +) + +// Picolibc is a C library for bare metal embedded devices. It was originally +// based on newlib. +var Picolibc = Library{ + name: "picolibc", + cflags: func() []string { + picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib/libc") + return []string{"-Werror", "-Wall", "-std=gnu11", "-D_COMPILING_NEWLIB", "-fshort-enums", "--sysroot=" + picolibcDir, "-I" + picolibcDir + "/tinystdio", "-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include"} + }, + sourceDir: "lib/picolibc/newlib/libc", + sources: func(target string) []string { + return picolibcSources + }, +} + +var picolibcSources = []string{ + "string/bcmp.c", + "string/bcopy.c", + "string/bzero.c", + "string/explicit_bzero.c", + "string/ffsl.c", + "string/ffsll.c", + "string/fls.c", + "string/flsl.c", + "string/flsll.c", + "string/gnu_basename.c", + "string/index.c", + "string/memccpy.c", + "string/memchr.c", + "string/memcmp.c", + "string/memcpy.c", + "string/memmem.c", + "string/memmove.c", + "string/mempcpy.c", + "string/memrchr.c", + "string/memset.c", + "string/rawmemchr.c", + "string/rindex.c", + "string/stpcpy.c", + "string/stpncpy.c", + "string/strcasecmp.c", + "string/strcasecmp_l.c", + "string/strcasestr.c", + "string/strcat.c", + "string/strchr.c", + "string/strchrnul.c", + "string/strcmp.c", + "string/strcoll.c", + "string/strcoll_l.c", + "string/strcpy.c", + "string/strcspn.c", + "string/strdup.c", + "string/strerror.c", + "string/strerror_r.c", + "string/strlcat.c", + "string/strlcpy.c", + "string/strlen.c", + "string/strlwr.c", + "string/strncasecmp.c", + "string/strncasecmp_l.c", + "string/strncat.c", + "string/strncmp.c", + "string/strncpy.c", + "string/strndup.c", + "string/strnlen.c", + "string/strnstr.c", + "string/strpbrk.c", + "string/strrchr.c", + "string/strsep.c", + "string/strsignal.c", + "string/strspn.c", + "string/strstr.c", + "string/strtok.c", + "string/strtok_r.c", + "string/strupr.c", + "string/strverscmp.c", + "string/strxfrm.c", + "string/strxfrm_l.c", + "string/swab.c", + "string/timingsafe_bcmp.c", + "string/timingsafe_memcmp.c", + "string/u_strerr.c", + "string/wcpcpy.c", + "string/wcpncpy.c", + "string/wcscasecmp.c", + "string/wcscasecmp_l.c", + "string/wcscat.c", + "string/wcschr.c", + "string/wcscmp.c", + "string/wcscoll.c", + "string/wcscoll_l.c", + "string/wcscpy.c", + "string/wcscspn.c", + "string/wcsdup.c", + "string/wcslcat.c", + "string/wcslcpy.c", + "string/wcslen.c", + "string/wcsncasecmp.c", + "string/wcsncasecmp_l.c", + "string/wcsncat.c", + "string/wcsncmp.c", + "string/wcsncpy.c", + "string/wcsnlen.c", + "string/wcspbrk.c", + "string/wcsrchr.c", + "string/wcsspn.c", + "string/wcsstr.c", + "string/wcstok.c", + "string/wcswidth.c", + "string/wcsxfrm.c", + "string/wcsxfrm_l.c", + "string/wcwidth.c", + "string/wmemchr.c", + "string/wmemcmp.c", + "string/wmemcpy.c", + "string/wmemmove.c", + "string/wmempcpy.c", + "string/wmemset.c", + "string/xpg_strerror_r.c", +} diff --git a/cgo/cgo.go b/cgo/cgo.go index 58aaed81..d6470a52 100644 --- a/cgo/cgo.go +++ b/cgo/cgo.go @@ -170,6 +170,11 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string enums: map[string]enumInfo{}, } + // Disable _FORTIFY_SOURCE as it causes problems on macOS. + // Note that it is only disabled for memcpy (etc) calls made from Go, which + // have better alternatives anyway. + cflags = append(cflags, "-D_FORTIFY_SOURCE=0") + // Add a new location for the following file. generatedTokenPos := p.fset.AddFile(dir+"/!cgo.go", -1, 0) generatedTokenPos.SetLines([]int{0}) diff --git a/compileopts/config.go b/compileopts/config.go index d3f4bd1d..e3067908 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -5,6 +5,7 @@ package compileopts import ( "errors" "fmt" + "path/filepath" "regexp" "strconv" "strings" @@ -163,6 +164,11 @@ func (c *Config) CFlags() []string { for _, flag := range c.Target.CFlags { cflags = append(cflags, strings.Replace(flag, "{root}", goenv.Get("TINYGOROOT"), -1)) } + if c.Target.Libc == "picolibc" { + root := goenv.Get("TINYGOROOT") + cflags = append(cflags, "--sysroot="+filepath.Join(root, "lib", "picolibc", "newlib", "libc")) + cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include")) + } return cflags } diff --git a/compileopts/target.go b/compileopts/target.go index 637712c8..2071c73a 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -32,6 +32,7 @@ type TargetSpec struct { Compiler string `json:"compiler"` Linker string `json:"linker"` RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt) + Libc string `json:"libc"` CFlags []string `json:"cflags"` LDFlags []string `json:"ldflags"` LinkerScript string `json:"linkerscript"` @@ -84,6 +85,9 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) { if spec2.RTLib != "" { spec.RTLib = spec2.RTLib } + if spec2.Libc != "" { + spec.Libc = spec2.Libc + } spec.CFlags = append(spec.CFlags, spec2.CFlags...) spec.LDFlags = append(spec.LDFlags, spec2.LDFlags...) if spec2.LinkerScript != "" { diff --git a/lib/picolibc b/lib/picolibc new file mode 160000 index 00000000..80528c68 --- /dev/null +++ b/lib/picolibc @@ -0,0 +1 @@ +Subproject commit 80528c684b10aaee977397e7eb40c4784e6dc433 diff --git a/lib/picolibc-include/picolibc.h b/lib/picolibc-include/picolibc.h new file mode 100644 index 00000000..e69de29b diff --git a/main.go b/main.go index c21b1377..d4361d13 100644 --- a/main.go +++ b/main.go @@ -786,7 +786,7 @@ func main() { } err := Build(pkgName, *outpath, options) handleCompilerError(err) - case "build-builtins": + case "build-library": // Note: this command is only meant to be used while making a release! if *outpath == "" { fmt.Fprintln(os.Stderr, "No output filename supplied (-o).") @@ -796,7 +796,22 @@ func main() { if *target == "" { fmt.Fprintln(os.Stderr, "No target (-target).") } - path, err := builder.CompilerRT.Load(*target) + if flag.NArg() != 1 { + fmt.Fprintf(os.Stderr, "Build-library only accepts exactly one library name as argument, %d given\n", flag.NArg()) + usage() + os.Exit(1) + } + var lib *builder.Library + switch name := flag.Arg(0); name { + case "compiler-rt": + lib = &builder.CompilerRT + case "picolibc": + lib = &builder.Picolibc + default: + fmt.Fprintf(os.Stderr, "Unknown library: %s\n", name) + os.Exit(1) + } + path, err := lib.Load(*target) handleCompilerError(err) copyFile(path, *outpath) case "flash", "gdb": diff --git a/src/runtime/runtime_arm7tdmi.go b/src/runtime/runtime_arm7tdmi.go index d7629ad6..b292f646 100644 --- a/src/runtime/runtime_arm7tdmi.go +++ b/src/runtime/runtime_arm7tdmi.go @@ -76,17 +76,3 @@ func abort() { for { } } - -// Implement memset for LLVM and compiler-rt. -//go:export memset -func libc_memset(ptr unsafe.Pointer, c byte, size uintptr) { - for i := uintptr(0); i < size; i++ { - *(*byte)(unsafe.Pointer(uintptr(ptr) + i)) = c - } -} - -// Implement memmove for LLVM and compiler-rt. -//go:export memmove -func libc_memmove(dst, src unsafe.Pointer, size uintptr) { - memmove(dst, src, size) -} diff --git a/src/runtime/runtime_cortexm.go b/src/runtime/runtime_cortexm.go index 04b34039..46f83677 100644 --- a/src/runtime/runtime_cortexm.go +++ b/src/runtime/runtime_cortexm.go @@ -95,23 +95,3 @@ func handleHardFault(sp *interruptStack) { println() abort() } - -// Implement memset for LLVM and compiler-rt. -//go:export memset -func libc_memset(ptr unsafe.Pointer, c byte, size uintptr) { - for i := uintptr(0); i < size; i++ { - *(*byte)(unsafe.Pointer(uintptr(ptr) + i)) = c - } -} - -// Implement memmove for LLVM and compiler-rt. -//go:export memmove -func libc_memmove(dst, src unsafe.Pointer, size uintptr) { - memmove(dst, src, size) -} - -// Implement memcpy for LLVM and compiler-rt. -//go:export memcpy -func libc_memcpy(dst, src unsafe.Pointer, size uintptr) { - memcpy(dst, src, size) -} diff --git a/src/runtime/runtime_tinygoriscv.go b/src/runtime/runtime_tinygoriscv.go deleted file mode 100644 index 691680b4..00000000 --- a/src/runtime/runtime_tinygoriscv.go +++ /dev/null @@ -1,19 +0,0 @@ -// +build tinygo.riscv - -package runtime - -import "unsafe" - -// Implement memset for LLVM. -//go:export memset -func libc_memset(ptr unsafe.Pointer, c byte, size uintptr) { - for i := uintptr(0); i < size; i++ { - *(*byte)(unsafe.Pointer(uintptr(ptr) + i)) = c - } -} - -// Implement memmove for LLVM. -//go:export memmove -func libc_memmove(dst, src unsafe.Pointer, size uintptr) { - memmove(dst, src, size) -} diff --git a/targets/cortex-m.json b/targets/cortex-m.json index 513a8463..979c09d1 100644 --- a/targets/cortex-m.json +++ b/targets/cortex-m.json @@ -7,13 +7,12 @@ "scheduler": "tasks", "linker": "ld.lld", "rtlib": "compiler-rt", + "libc": "picolibc", "cflags": [ "-Oz", "-mthumb", "-Werror", "-fshort-enums", - "-nostdlibinc", - "-Wno-macro-redefined", "-fno-exceptions", "-fno-unwind-tables", "-ffunction-sections", "-fdata-sections" ], diff --git a/targets/gameboy-advance.json b/targets/gameboy-advance.json index 2ef4f8d1..f7ac76eb 100644 --- a/targets/gameboy-advance.json +++ b/targets/gameboy-advance.json @@ -7,6 +7,7 @@ "compiler": "clang", "linker": "ld.lld", "rtlib": "compiler-rt", + "libc": "picolibc", "cflags": [ "-g", "--target=thumb4-none-eabi", @@ -14,7 +15,6 @@ "-Oz", "-Werror", "-fshort-enums", - "-Wno-macro-redefined", "-Qunused-arguments", "-fno-exceptions", "-fno-unwind-tables", "-ffunction-sections", "-fdata-sections" diff --git a/targets/riscv.json b/targets/riscv.json index f9635400..f74c1767 100644 --- a/targets/riscv.json +++ b/targets/riscv.json @@ -7,13 +7,13 @@ "compiler": "clang", "linker": "ld.lld", "rtlib": "compiler-rt", + "libc": "picolibc", "cflags": [ "--target=riscv32--none", "-march=rv32imac", "-mabi=ilp32", "-Os", "-Werror", - "-nostdinc", "-fno-exceptions", "-fno-unwind-tables", "-ffunction-sections", "-fdata-sections" ], diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index 41bc6892..557dea5c 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -4,6 +4,7 @@ package main int fortytwo(void); #include "main.h" int mul(int, int); +#include */ import "C" @@ -109,6 +110,12 @@ func main() { var _ C.option3_t = C.option3A println("option 2A:", C.option2A) println("option 3A:", C.option3A) + + // libc: test whether C functions work at all. + buf1 := []byte("foobar\x00") + buf2 := make([]byte, len(buf1)) + C.strcpy((*C.char)(unsafe.Pointer(&buf2[0])), (*C.char)(unsafe.Pointer(&buf1[0]))) + println("copied string:", string(buf2[:C.strlen((*C.char)(unsafe.Pointer(&buf2[0])))])) } func printUnion(union C.joined_t) C.joined_t { diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt index 73cb2b99..fcb68846 100644 --- a/testdata/cgo/out.txt +++ b/testdata/cgo/out.txt @@ -55,3 +55,4 @@ option F: 11 option G: 12 option 2A: 20 option 3A: 21 +copied string: foobar