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 7e86215ef..af6e1809a 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 6a18278c6..e16f225a8 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 d3e9fcc7f..85a4dd515 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 000000000..31e2f0530 --- /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 58aaed81c..d6470a522 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 d3f4bd1d0..e30679080 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 637712c83..2071c73a5 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 000000000..80528c684 --- /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 000000000..e69de29bb diff --git a/main.go b/main.go index c21b1377b..d4361d13b 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 d7629ad6d..b292f6468 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 04b34039a..46f836770 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 691680b44..000000000 --- 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 513a84634..979c09d1f 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 2ef4f8d1f..f7ac76ebb 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 f96354008..f74c1767d 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 41bc6892c..557dea5cf 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 73cb2b990..fcb688467 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