From 14bb90c3c0f59b752cd4017648521b9adcfeb013 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 24 Sep 2021 01:39:56 +0200 Subject: [PATCH] cgo: add support for stdio in picolibc and wasi-libc This adds support for stdio in picolibc and fixes wasm_exec.js so that it can also support C puts. With this, C stdout works on all supported platforms. --- Dockerfile | 2 +- Makefile | 3 ++ builder/ar.go | 6 +-- builder/picolibc.go | 112 ++++++++++++++++++++++++++++++++++++++- compileopts/config.go | 7 ++- lib/picolibc | 2 +- lib/picolibc-stdio.c | 18 +++++++ src/runtime/baremetal.go | 5 ++ targets/wasm_exec.js | 4 ++ testdata/cgo/main.go | 5 ++ testdata/cgo/out.txt | 1 + 11 files changed, 158 insertions(+), 7 deletions(-) create mode 100644 lib/picolibc-stdio.c diff --git a/Dockerfile b/Dockerfile index 399f74f7..603cfe54 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ RUN cd /tinygo/ && \ git submodule sync && \ git submodule update --init --recursive --force -COPY ./lib/picolibc-include/* /tinygo/lib/picolibc-include/ +COPY ./lib/picolibc-* /tinygo/lib/ RUN cd /tinygo/ && \ go install /tinygo/ diff --git a/Makefile b/Makefile index 8c05ec89..11946b69 100644 --- a/Makefile +++ b/Makefile @@ -480,6 +480,7 @@ build/release: tinygo gen-device wasi-libc @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/picolibc/newlib/libm @mkdir -p build/release/tinygo/lib/wasi-libc @mkdir -p build/release/tinygo/pkg/armv6m-unknown-unknown-eabi @mkdir -p build/release/tinygo/pkg/armv7m-unknown-unknown-eabi @@ -498,7 +499,9 @@ build/release: tinygo gen-device wasi-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/newlib/libm/common build/release/tinygo/lib/picolibc/newlib/libm @cp -rp lib/picolibc-include build/release/tinygo/lib + @cp -rp lib/picolibc-stdio.c 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 diff --git a/builder/ar.go b/builder/ar.go index 37b94af2..ee8ba070 100644 --- a/builder/ar.go +++ b/builder/ar.go @@ -5,6 +5,7 @@ import ( "debug/elf" "encoding/binary" "errors" + "fmt" "io" "os" "path/filepath" @@ -49,7 +50,7 @@ func makeArchive(archivePath string, objs []string) error { // Read the symbols and add them to the symbol table. dbg, err := elf.NewFile(objfile) if err != nil { - return err + return fmt.Errorf("failed to open file %s: %w", objpath, err) } symbols, err := dbg.Symbols() if err != nil { @@ -61,9 +62,8 @@ func makeArchive(archivePath string, objs []string) error { // Don't include local symbols (STB_LOCAL). continue } - if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC { + if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC && elf.ST_TYPE(symbol.Info) != elf.STT_OBJECT { // Not a function. - // TODO: perhaps globals variables should also be included? continue } // Include in archive. diff --git a/builder/picolibc.go b/builder/picolibc.go index 436c3481..93a2639d 100644 --- a/builder/picolibc.go +++ b/builder/picolibc.go @@ -12,7 +12,18 @@ 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", "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", picolibcDir + "/include", "-I" + picolibcDir + "/tinystdio", "-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include"} + return []string{ + "-Werror", + "-Wall", + "-std=gnu11", + "-D_COMPILING_NEWLIB", + "-DHAVE_ALIAS_ATTRIBUTE", + "-DTINY_STDIO", + "-nostdlibinc", + "-Xclang", "-internal-isystem", "-Xclang", picolibcDir + "/include", + "-I" + picolibcDir + "/tinystdio", + "-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include", + } }, sourceDir: "lib/picolibc/newlib/libc", sources: func(target string) []string { @@ -21,6 +32,105 @@ var Picolibc = Library{ } var picolibcSources = []string{ + "../../../picolibc-stdio.c", + + "tinystdio/asprintf.c", + "tinystdio/atod_engine.c", + "tinystdio/atod_ryu.c", + "tinystdio/atof_engine.c", + "tinystdio/atof_ryu.c", + //"tinystdio/atold_engine.c", // have_long_double and not long_double_equals_double + "tinystdio/clearerr.c", + "tinystdio/compare_exchange.c", + "tinystdio/dtoa_data.c", + "tinystdio/dtoa_engine.c", + "tinystdio/dtoa_ryu.c", + "tinystdio/ecvtbuf.c", + "tinystdio/ecvt.c", + "tinystdio/ecvt_data.c", + "tinystdio/ecvtfbuf.c", + "tinystdio/ecvtf.c", + "tinystdio/ecvtf_data.c", + "tinystdio/exchange.c", + //"tinystdio/fclose.c", // posix-io + "tinystdio/fcvtbuf.c", + "tinystdio/fcvt.c", + "tinystdio/fcvtfbuf.c", + "tinystdio/fcvtf.c", + "tinystdio/fdevopen.c", + //"tinystdio/fdopen.c", // posix-io + "tinystdio/feof.c", + "tinystdio/ferror.c", + "tinystdio/fflush.c", + "tinystdio/fgetc.c", + "tinystdio/fgets.c", + "tinystdio/fileno.c", + "tinystdio/filestrget.c", + "tinystdio/filestrputalloc.c", + "tinystdio/filestrput.c", + //"tinystdio/fopen.c", // posix-io + "tinystdio/fprintf.c", + "tinystdio/fputc.c", + "tinystdio/fputs.c", + "tinystdio/fread.c", + "tinystdio/fscanf.c", + "tinystdio/fseek.c", + "tinystdio/ftell.c", + "tinystdio/ftoa_data.c", + "tinystdio/ftoa_engine.c", + "tinystdio/ftoa_ryu.c", + "tinystdio/fwrite.c", + "tinystdio/gcvtbuf.c", + "tinystdio/gcvt.c", + "tinystdio/gcvtfbuf.c", + "tinystdio/gcvtf.c", + "tinystdio/getchar.c", + "tinystdio/gets.c", + "tinystdio/matchcaseprefix.c", + "tinystdio/perror.c", + //"tinystdio/posixiob.c", // posix-io + //"tinystdio/posixio.c", // posix-io + "tinystdio/printf.c", + "tinystdio/putchar.c", + "tinystdio/puts.c", + "tinystdio/ryu_divpow2.c", + "tinystdio/ryu_log10.c", + "tinystdio/ryu_log2pow5.c", + "tinystdio/ryu_pow5bits.c", + "tinystdio/ryu_table.c", + "tinystdio/ryu_umul128.c", + "tinystdio/scanf.c", + "tinystdio/setbuf.c", + "tinystdio/setvbuf.c", + //"tinystdio/sflags.c", // posix-io + "tinystdio/snprintf.c", + "tinystdio/snprintfd.c", + "tinystdio/snprintff.c", + "tinystdio/sprintf.c", + "tinystdio/sprintfd.c", + "tinystdio/sprintff.c", + "tinystdio/sscanf.c", + "tinystdio/strfromd.c", + "tinystdio/strfromf.c", + "tinystdio/strtod.c", + "tinystdio/strtod_l.c", + "tinystdio/strtof.c", + //"tinystdio/strtold.c", // have_long_double and not long_double_equals_double + //"tinystdio/strtold_l.c", // have_long_double and not long_double_equals_double + "tinystdio/ungetc.c", + "tinystdio/vasprintf.c", + "tinystdio/vfiprintf.c", + "tinystdio/vfiscanf.c", + "tinystdio/vfprintf.c", + "tinystdio/vfprintff.c", + "tinystdio/vfscanf.c", + "tinystdio/vfscanff.c", + "tinystdio/vprintf.c", + "tinystdio/vscanf.c", + "tinystdio/vsnprintf.c", + "tinystdio/vsprintf.c", + "tinystdio/vsscanf.c", + "string/bcmp.c", "string/bcopy.c", "string/bzero.c", diff --git a/compileopts/config.go b/compileopts/config.go index 52f1a049..7da0323d 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -206,7 +206,12 @@ func (c *Config) CFlags() []string { } if c.Target.Libc == "picolibc" { root := goenv.Get("TINYGOROOT") - cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include")) + picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc") + cflags = append(cflags, + "-nostdlibinc", + "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(picolibcDir, "include"), + "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(picolibcDir, "tinystdio"), + ) cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include")) } // Always emit debug information. It is optionally stripped at link time. diff --git a/lib/picolibc b/lib/picolibc index 80528c68..f68b8204 160000 --- a/lib/picolibc +++ b/lib/picolibc @@ -1 +1 @@ -Subproject commit 80528c684b10aaee977397e7eb40c4784e6dc433 +Subproject commit f68b8204f797d6b3bfbc7c4da4d257961fbc8770 diff --git a/lib/picolibc-stdio.c b/lib/picolibc-stdio.c new file mode 100644 index 00000000..1d7514ba --- /dev/null +++ b/lib/picolibc-stdio.c @@ -0,0 +1,18 @@ +// This file is included in the picolibc build. +// It makes stdio functions available to the C library. + +#include +#include + +// Defined in the runtime package. Writes to the default console (usually, the +// first UART or an USB-CDC device). +int runtime_putchar(char, FILE*); + +// Define stdin, stdout, and stderr as a single object. +// This object must not reside in ROM. +static FILE __stdio = FDEV_SETUP_STREAM(runtime_putchar, NULL, NULL, _FDEV_SETUP_WRITE); + +// Define the underlying structs for stdin, stdout, and stderr. +FILE *const stdin = &__stdio; +__strong_reference(stdin, stdout); +__strong_reference(stdin, stderr); diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index 5201ae2b..6b9fbcc3 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -46,6 +46,11 @@ func libc_free(ptr unsafe.Pointer) { free(ptr) } +//export runtime_putchar +func runtime_putchar(c byte) { + putchar(c) +} + //go:linkname syscall_Exit syscall.Exit func syscall_Exit(code int) { exit(code) diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index b0545abd..053a2d62 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -256,6 +256,7 @@ let iov_ptr = iovs_ptr+iovs_i*8; // assuming wasm32 let ptr = mem().getUint32(iov_ptr + 0, true); let len = mem().getUint32(iov_ptr + 4, true); + nwritten += len; for (let i=0; i 0, // dummy + fd_fdstat_get: () => 0, // dummy + fd_seek: () => 0, // dummy "proc_exit": (code) => { if (global.process) { // Node.js diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index b1880c7b..cc25152e 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -1,6 +1,7 @@ package main /* +#include int fortytwo(void); #include "main.h" int mul(int, int); @@ -139,6 +140,10 @@ func main() { 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])))])) + + // libc: test basic stdio functionality + putsBuf := []byte("line written using C puts\x00") + C.puts((*C.char)(unsafe.Pointer(&putsBuf[0]))) } func printUnion(union C.joined_t) C.joined_t { diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt index 00444cfa..ca77594f 100644 --- a/testdata/cgo/out.txt +++ b/testdata/cgo/out.txt @@ -62,3 +62,4 @@ option 3A: 21 enum width matches: true CFLAGS value: 17 copied string: foobar +line written using C puts