diff --git a/GNUmakefile b/GNUmakefile index ecba864f88..13fcf1c97b 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -1182,6 +1182,7 @@ endif @cp -rp lib/wasi-libc/libc-top-half/musl/src/time build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/unistd build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/sources build/release/tinygo/lib/wasi-libc/libc-top-half + @cp -rp lib/wasi-libc-wasip2-stub.c build/release/tinygo/lib @cp -rp lib/wasi-cli/wit build/release/tinygo/lib/wasi-cli/wit @cp -rp ${LLVM_PROJECTDIR}/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins @cp -rp ${LLVM_PROJECTDIR}/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins diff --git a/builder/build.go b/builder/build.go index 10bda8d45b..4d7b568983 100644 --- a/builder/build.go +++ b/builder/build.go @@ -167,7 +167,11 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe defer unlock() libcDependencies = append(libcDependencies, libcJob) case "wasi-libc": - libcJob, unlock, err := libWasiLibc.load(config, tmpdir) + lib := libWasiLibc + if slices.Contains(config.BuildTags(), "wasip2") { + lib = libWasiLibcWasip2 + } + libcJob, unlock, err := lib.load(config, tmpdir) if err != nil { return BuildResult{}, err } diff --git a/builder/wasilibc.go b/builder/wasilibc.go index f9cd332f12..5a72c0fb58 100644 --- a/builder/wasilibc.go +++ b/builder/wasilibc.go @@ -9,276 +9,370 @@ import ( "github.com/tinygo-org/tinygo/goenv" ) -var libWasiLibc = Library{ - name: "wasi-libc", - makeHeaders: func(target, includeDir string) error { - bits := filepath.Join(includeDir, "bits") - err := os.Mkdir(bits, 0777) - if err != nil { - return err +// generateWasiVersionHeader creates wasi/version.h from its .in template, +// defining __wasip__ (preview is "1", "2", or "3") and leaving the +// other __wasipN__ macros undefined. Upstream generates this file with +// CMake's configure_file as part of the CMake build. +func generateWasiVersionHeader(includeDir, preview string) error { + libcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") + versionHeaderIn := filepath.Join(libcDir, "libc-bottom-half/headers/public/wasi/version.h.in") + data, err := os.ReadFile(versionHeaderIn) + if err != nil { + return err + } + define := "__wasip" + preview + "__" + lines := strings.Split(string(data), "\n") + for i, line := range lines { + if !strings.HasPrefix(line, "#cmakedefine ") { + continue } - - muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "wasi-libc/libc-top-half/musl") - err = buildMuslAllTypes("wasm32", muslDir, bits) - if err != nil { - return err + name := strings.Fields(line)[1] + if name == define { + lines[i] = "#define " + name + } else { + lines[i] = "/* #undef " + name + " */" } + } + return os.WriteFile(filepath.Join(includeDir, "wasi", "version.h"), []byte(strings.Join(lines, "\n")), 0o666) +} - // See MUSL_OMIT_HEADERS in the Makefile. - omitHeaders := map[string]struct{}{ - "syslog.h": {}, - "wait.h": {}, - "ucontext.h": {}, - "paths.h": {}, - "utmp.h": {}, - "utmpx.h": {}, - "lastlog.h": {}, - "elf.h": {}, - "link.h": {}, - "pwd.h": {}, - "shadow.h": {}, - "grp.h": {}, - "mntent.h": {}, - "netdb.h": {}, - "resolv.h": {}, - "pty.h": {}, - "dlfcn.h": {}, - "setjmp.h": {}, - "ulimit.h": {}, - "wordexp.h": {}, - "spawn.h": {}, - "termios.h": {}, - "libintl.h": {}, - "aio.h": {}, +// libWasiLibc and libWasiLibcWasip2 build wasi-libc for the wasip1 and wasip2 +// targets respectively. They're built from the same wasi-libc source tree but +// with different generated headers (see generateWasiVersionHeader) and +// different bottom-half sources selected: wasip1 uses the cloudlibc socket +// syscalls and accept-wasip1.c, while wasip2 uses the descriptor-table-based +// sockets/stdio implementation (generated by wit-bindgen upstream). They use +// distinct library names since compileopts.Config.LibraryPath keys the build +// cache by name plus the LLVM target triple, and both targets share the same +// triple (wasm32-unknown-wasi). +var libWasiLibc = newWasiLibc("1", "wasi-libc") +var libWasiLibcWasip2 = newWasiLibc("2", "wasi-libc-wasip2") - "stdarg.h": {}, - "stddef.h": {}, +func newWasiLibc(preview, name string) Library { + return Library{ + name: name, + makeHeaders: func(target, includeDir string) error { + bits := filepath.Join(includeDir, "bits") + err := os.Mkdir(bits, 0777) + if err != nil { + return err + } - "pthread.h": {}, - } + muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "wasi-libc/libc-top-half/musl") + err = buildMuslAllTypes("wasm32", muslDir, bits) + if err != nil { + return err + } - for _, glob := range [][2]string{ - {"libc-bottom-half/headers/public/*.h", ""}, - {"libc-bottom-half/headers/public/wasi/*.h", "wasi"}, - {"libc-top-half/musl/arch/wasm32/bits/*.h", "bits"}, - {"libc-top-half/musl/include/*.h", ""}, - {"libc-top-half/musl/include/netinet/*.h", "netinet"}, - {"libc-top-half/musl/include/sys/*.h", "sys"}, - } { - matches, _ := filepath.Glob(filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc", glob[0])) - outDir := filepath.Join(includeDir, glob[1]) - os.MkdirAll(outDir, 0o777) - for _, match := range matches { - name := filepath.Base(match) - if _, ok := omitHeaders[name]; ok { - continue - } - data, err := os.ReadFile(match) - if err != nil { - return err - } - err = os.WriteFile(filepath.Join(outDir, name), data, 0o666) - if err != nil { - return err + // See MUSL_OMIT_HEADERS in the Makefile. + omitHeaders := map[string]struct{}{ + "syslog.h": {}, + "wait.h": {}, + "ucontext.h": {}, + "paths.h": {}, + "utmp.h": {}, + "utmpx.h": {}, + "lastlog.h": {}, + "elf.h": {}, + "link.h": {}, + "pwd.h": {}, + "shadow.h": {}, + "grp.h": {}, + "mntent.h": {}, + "resolv.h": {}, + "pty.h": {}, + "dlfcn.h": {}, + "setjmp.h": {}, + "ulimit.h": {}, + "wordexp.h": {}, + "spawn.h": {}, + "termios.h": {}, + "libintl.h": {}, + "aio.h": {}, + + "stdarg.h": {}, + "stddef.h": {}, + + "pthread.h": {}, + } + if preview == "1" { + // netdb.h (getaddrinfo etc.) isn't implemented for wasip1; + // wasip2/wasip3 do implement it (see sources/netdb.c). + omitHeaders["netdb.h"] = struct{}{} + } + + for _, glob := range [][2]string{ + {"libc-bottom-half/headers/public/*.h", ""}, + {"libc-bottom-half/headers/public/wasi/*.h", "wasi"}, + {"libc-top-half/musl/arch/wasm32/bits/*.h", "bits"}, + {"libc-top-half/musl/include/*.h", ""}, + {"libc-top-half/musl/include/netinet/*.h", "netinet"}, + {"libc-top-half/musl/include/sys/*.h", "sys"}, + } { + matches, _ := filepath.Glob(filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc", glob[0])) + outDir := filepath.Join(includeDir, glob[1]) + os.MkdirAll(outDir, 0o777) + for _, match := range matches { + name := filepath.Base(match) + if _, ok := omitHeaders[name]; ok { + continue + } + data, err := os.ReadFile(match) + if err != nil { + return err + } + err = os.WriteFile(filepath.Join(outDir, name), data, 0o666) + if err != nil { + return err + } } } - } - return nil - }, - cflags: func(target, headerPath string) []string { - libcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") - return []string{ - "-Werror", - "-Wall", - "-std=gnu11", - "-nostdlibinc", - "-mnontrapping-fptoint", "-msign-ext", "-mbulk-memory", - "-Wno-null-pointer-arithmetic", "-Wno-unused-parameter", "-Wno-sign-compare", "-Wno-unused-variable", "-Wno-unused-function", "-Wno-ignored-attributes", "-Wno-missing-braces", "-Wno-ignored-pragmas", "-Wno-unused-but-set-variable", "-Wno-unknown-warning-option", - "-Wno-parentheses", "-Wno-shift-op-parentheses", "-Wno-bitwise-op-parentheses", "-Wno-logical-op-parentheses", "-Wno-string-plus-int", "-Wno-dangling-else", "-Wno-unknown-pragmas", - "-DNDEBUG", - "-D__wasilibc_printscan_no_long_double", - "-D__wasilibc_printscan_full_support_option=\"long double support is disabled\"", - "-DBULK_MEMORY_THRESHOLD=32", // default threshold in wasi-libc - "-isystem", headerPath, - "-I" + libcDir + "/libc-top-half/musl/src/include", - "-I" + libcDir + "/libc-top-half/musl/src/internal", - "-I" + libcDir + "/libc-top-half/musl/arch/wasm32", - "-I" + libcDir + "/libc-top-half/musl/arch/generic", - "-I" + libcDir + "/libc-top-half/headers/private", - } - }, - cflagsForFile: func(path string) []string { - if strings.HasPrefix(path, "libc-bottom-half"+string(os.PathSeparator)) { + // wasi/version.h is normally generated by CMake's configure_file + // from version.h.in, defining __wasip1__/__wasip2__/__wasip3__ + // depending on the WASI preview targeted. It's included (directly + // or transitively) by most of wasi-libc, including wasi/api.h. + if err := generateWasiVersionHeader(includeDir, preview); err != nil { + return err + } + + return nil + }, + cflags: func(target, headerPath string) []string { libcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") return []string{ - "-I" + libcDir + "/libc-bottom-half/headers/private", - "-I" + libcDir + "/libc-bottom-half/cloudlibc/src/include", - "-I" + libcDir + "/libc-bottom-half/cloudlibc/src", + "-Werror", + "-Wall", + "-std=gnu11", + "-nostdlibinc", + "-mnontrapping-fptoint", "-msign-ext", "-mbulk-memory", + "-Wno-null-pointer-arithmetic", "-Wno-unused-parameter", "-Wno-sign-compare", "-Wno-unused-variable", "-Wno-unused-function", "-Wno-ignored-attributes", "-Wno-missing-braces", "-Wno-ignored-pragmas", "-Wno-unused-but-set-variable", "-Wno-unknown-warning-option", + "-Wno-parentheses", "-Wno-shift-op-parentheses", "-Wno-bitwise-op-parentheses", "-Wno-logical-op-parentheses", "-Wno-string-plus-int", "-Wno-dangling-else", "-Wno-unknown-pragmas", + "-DNDEBUG", + "-D__wasilibc_printscan_no_long_double", + "-D__wasilibc_printscan_full_support_option=\"long double support is disabled\"", + "-DBULK_MEMORY_THRESHOLD=32", // default threshold in wasi-libc + "-isystem", headerPath, + "-I" + libcDir + "/libc-top-half/musl/src/include", + "-I" + libcDir + "/libc-top-half/musl/src/internal", + "-I" + libcDir + "/libc-top-half/musl/arch/wasm32", + "-I" + libcDir + "/libc-top-half/musl/arch/generic", + "-I" + libcDir + "/libc-top-half/headers/private", + } + }, + cflagsForFile: func(path string) []string { + if strings.HasPrefix(path, "libc-bottom-half"+string(os.PathSeparator)) { + libcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") + return []string{ + "-I" + libcDir + "/libc-bottom-half/headers/private", + "-I" + libcDir + "/libc-bottom-half/cloudlibc/src/include", + "-I" + libcDir + "/libc-bottom-half/cloudlibc/src", + } + } + return nil + }, + sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") }, + librarySources: func(target string, libcNeedsMalloc bool) ([]string, error) { + type filePattern struct { + glob string + exclude []string } - } - return nil - }, - sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") }, - librarySources: func(target string, libcNeedsMalloc bool) ([]string, error) { - type filePattern struct { - glob string - exclude []string - } - - // See: LIBC_TOP_HALF_MUSL_SOURCES in the Makefile - globs := []filePattern{ - // Top half: mostly musl sources. - {glob: "libc-top-half/sources/*.c"}, - {glob: "libc-top-half/musl/src/conf/*.c"}, - {glob: "libc-top-half/musl/src/internal/*.c", exclude: []string{ - "procfdname.c", "syscall.c", "syscall_ret.c", "vdso.c", "version.c", - }}, - {glob: "libc-top-half/musl/src/locale/*.c", exclude: []string{ - "dcngettext.c", "textdomain.c", "bind_textdomain_codeset.c"}}, - {glob: "libc-top-half/musl/src/math/*.c", exclude: []string{ - "__signbit.c", "__signbitf.c", "__signbitl.c", - "__fpclassify.c", "__fpclassifyf.c", "__fpclassifyl.c", - "ceilf.c", "ceil.c", - "floorf.c", "floor.c", - "truncf.c", "trunc.c", - "rintf.c", "rint.c", - "nearbyintf.c", "nearbyint.c", - "sqrtf.c", "sqrt.c", - "fabsf.c", "fabs.c", - "copysignf.c", "copysign.c", - "fminf.c", "fmaxf.c", - "fmin.c", "fmax.c,", - }}, - {glob: "libc-top-half/musl/src/multibyte/*.c"}, - {glob: "libc-top-half/musl/src/stdio/*.c", exclude: []string{ - "vfwscanf.c", "vfwprintf.c", // long double is unsupported - "__lockfile.c", "flockfile.c", "funlockfile.c", "ftrylockfile.c", - "rename.c", - "tmpnam.c", "tmpfile.c", "tempnam.c", - "popen.c", "pclose.c", - "remove.c", - "gets.c"}}, - {glob: "libc-top-half/musl/src/stdlib/*.c"}, - {glob: "libc-top-half/musl/src/string/*.c", exclude: []string{ - "strsignal.c"}}, - // Bottom half: connect top half to WASI equivalents. - {glob: "libc-bottom-half/cloudlibc/src/libc/*/*.c"}, - {glob: "libc-bottom-half/cloudlibc/src/libc/sys/*/*.c"}, - {glob: "libc-bottom-half/sources/*.c"}, - } + // wasip1 uses the cloudlibc socket syscalls (getsockopt.c, + // recv.c, send.c, shutdown.c) plus accept-wasip1.c. wasip2 uses + // the descriptor-table-based sockets (accept.c, bind.c, + // connect.c, listen.c, recv.c, send.c, shutdown.c, socket.c, + // sockopt.c, tcp.c, udp.c, sockets_utils.c, descriptor_table.c, + // getsockpeername.c, netdb.c, file.c, file_utils.c) plus + // wasip2.c/wasip2_stdio.c. Both sets define the same symbol + // names (recv/send/shutdown/getsockopt), so exactly one side + // must be compiled for a given preview. + cloudlibcSockSrcExclude := []string{} + bottomHalfSrcExclude := []string{ + "wasip3.c", "wasip3_block_on.c", "wasip3_stdio.c", // wasip3-only, not yet supported + } + switch preview { + case "1": + bottomHalfSrcExclude = append(bottomHalfSrcExclude, + "accept.c", "bind.c", "connect.c", "descriptor_table.c", + "file.c", "file_utils.c", "getsockpeername.c", "listen.c", + "netdb.c", "recv.c", "send.c", "shutdown.c", "socket.c", + "sockets_utils.c", "sockopt.c", "tcp.c", "udp.c", + "wasip2.c", "wasip2_stdio.c", + ) + case "2": + bottomHalfSrcExclude = append(bottomHalfSrcExclude, "accept-wasip1.c") + cloudlibcSockSrcExclude = append(cloudlibcSockSrcExclude, + "getsockopt.c", "recv.c", "send.c", "shutdown.c") + default: + return nil, fmt.Errorf("wasi-libc: unsupported WASI preview %q", preview) + } - // We're using the Boehm GC, so we need a heap implementation in the libc. - if libcNeedsMalloc { - globs = append(globs, filePattern{glob: "dlmalloc/src/dlmalloc.c"}) - } + // See: LIBC_TOP_HALF_MUSL_SOURCES in the Makefile + globs := []filePattern{ + // Top half: mostly musl sources. + {glob: "libc-top-half/sources/*.c"}, + {glob: "libc-top-half/musl/src/conf/*.c"}, + {glob: "libc-top-half/musl/src/internal/*.c", exclude: []string{ + "procfdname.c", "syscall.c", "syscall_ret.c", "vdso.c", "version.c", + "emulate_wait4.c", // wait4/waitid emulation, not relevant/available on WASI + }}, + {glob: "libc-top-half/musl/src/locale/*.c", exclude: []string{ + "dcngettext.c", "textdomain.c", "bind_textdomain_codeset.c"}}, + {glob: "libc-top-half/musl/src/math/*.c", exclude: []string{ + "__signbit.c", "__signbitf.c", "__signbitl.c", + "__fpclassify.c", "__fpclassifyf.c", "__fpclassifyl.c", + "ceilf.c", "ceil.c", + "floorf.c", "floor.c", + "truncf.c", "trunc.c", + "rintf.c", "rint.c", + "nearbyintf.c", "nearbyint.c", + "sqrtf.c", "sqrt.c", + "fabsf.c", "fabs.c", + "copysignf.c", "copysign.c", + "fminf.c", "fmaxf.c", + "fmin.c", "fmax.c,", + }}, + {glob: "libc-top-half/musl/src/multibyte/*.c"}, + {glob: "libc-top-half/musl/src/stdio/*.c", exclude: []string{ + "vfwscanf.c", "vfwprintf.c", // long double is unsupported + "__lockfile.c", "flockfile.c", "funlockfile.c", "ftrylockfile.c", + "rename.c", + "tmpnam.c", "tmpfile.c", "tempnam.c", + "popen.c", "pclose.c", + "remove.c", + "gets.c"}}, + {glob: "libc-top-half/musl/src/stdlib/*.c"}, + {glob: "libc-top-half/musl/src/string/*.c", exclude: []string{ + "strsignal.c"}}, - // See: LIBC_TOP_HALF_MUSL_SOURCES in the Makefile - sources := []string{ - "libc-top-half/musl/src/misc/a64l.c", - "libc-top-half/musl/src/misc/basename.c", - "libc-top-half/musl/src/misc/dirname.c", - "libc-top-half/musl/src/misc/ffs.c", - "libc-top-half/musl/src/misc/ffsl.c", - "libc-top-half/musl/src/misc/ffsll.c", - "libc-top-half/musl/src/misc/fmtmsg.c", - "libc-top-half/musl/src/misc/getdomainname.c", - "libc-top-half/musl/src/misc/gethostid.c", - "libc-top-half/musl/src/misc/getopt.c", - "libc-top-half/musl/src/misc/getopt_long.c", - "libc-top-half/musl/src/misc/getsubopt.c", - "libc-top-half/musl/src/misc/uname.c", - "libc-top-half/musl/src/misc/nftw.c", - "libc-top-half/musl/src/errno/strerror.c", - "libc-top-half/musl/src/network/htonl.c", - "libc-top-half/musl/src/network/htons.c", - "libc-top-half/musl/src/network/ntohl.c", - "libc-top-half/musl/src/network/ntohs.c", - "libc-top-half/musl/src/network/inet_ntop.c", - "libc-top-half/musl/src/network/inet_pton.c", - "libc-top-half/musl/src/network/inet_aton.c", - "libc-top-half/musl/src/network/in6addr_any.c", - "libc-top-half/musl/src/network/in6addr_loopback.c", - "libc-top-half/musl/src/fenv/fenv.c", - "libc-top-half/musl/src/fenv/fesetround.c", - "libc-top-half/musl/src/fenv/feupdateenv.c", - "libc-top-half/musl/src/fenv/fesetexceptflag.c", - "libc-top-half/musl/src/fenv/fegetexceptflag.c", - "libc-top-half/musl/src/fenv/feholdexcept.c", - "libc-top-half/musl/src/exit/exit.c", - "libc-top-half/musl/src/exit/atexit.c", - "libc-top-half/musl/src/exit/assert.c", - "libc-top-half/musl/src/exit/quick_exit.c", - "libc-top-half/musl/src/exit/at_quick_exit.c", - "libc-top-half/musl/src/time/strftime.c", - "libc-top-half/musl/src/time/asctime.c", - "libc-top-half/musl/src/time/asctime_r.c", - "libc-top-half/musl/src/time/ctime.c", - "libc-top-half/musl/src/time/ctime_r.c", - "libc-top-half/musl/src/time/wcsftime.c", - "libc-top-half/musl/src/time/strptime.c", - "libc-top-half/musl/src/time/difftime.c", - "libc-top-half/musl/src/time/timegm.c", - "libc-top-half/musl/src/time/ftime.c", - "libc-top-half/musl/src/time/gmtime.c", - "libc-top-half/musl/src/time/gmtime_r.c", - "libc-top-half/musl/src/time/timespec_get.c", - "libc-top-half/musl/src/time/getdate.c", - "libc-top-half/musl/src/time/localtime.c", - "libc-top-half/musl/src/time/localtime_r.c", - "libc-top-half/musl/src/time/mktime.c", - "libc-top-half/musl/src/time/__tm_to_secs.c", - "libc-top-half/musl/src/time/__month_to_secs.c", - "libc-top-half/musl/src/time/__secs_to_tm.c", - "libc-top-half/musl/src/time/__year_to_secs.c", - "libc-top-half/musl/src/time/__tz.c", - "libc-top-half/musl/src/fcntl/creat.c", - "libc-top-half/musl/src/dirent/alphasort.c", - "libc-top-half/musl/src/dirent/versionsort.c", - "libc-top-half/musl/src/env/__stack_chk_fail.c", - "libc-top-half/musl/src/env/clearenv.c", - "libc-top-half/musl/src/env/getenv.c", - "libc-top-half/musl/src/env/putenv.c", - "libc-top-half/musl/src/env/setenv.c", - "libc-top-half/musl/src/env/unsetenv.c", - "libc-top-half/musl/src/unistd/posix_close.c", - "libc-top-half/musl/src/stat/futimesat.c", - "libc-top-half/musl/src/legacy/getpagesize.c", - "libc-top-half/musl/src/thread/thrd_sleep.c", - } + // Bottom half: connect top half to WASI equivalents. + {glob: "libc-bottom-half/cloudlibc/src/libc/*/*.c"}, + {glob: "libc-bottom-half/cloudlibc/src/libc/sys/*/*.c", exclude: cloudlibcSockSrcExclude}, + {glob: "libc-bottom-half/sources/math/*.c"}, + {glob: "libc-bottom-half/sources/*.c", exclude: bottomHalfSrcExclude}, + } - basepath := goenv.Get("TINYGOROOT") + "/lib/wasi-libc/" - for _, pattern := range globs { - matches, err := filepath.Glob(basepath + pattern.glob) - 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. - return nil, fmt.Errorf("wasi-libc: could not glob source dirs: %w", err) + // We're using the Boehm GC, so we need a heap implementation in the libc. + if libcNeedsMalloc { + globs = append(globs, filePattern{glob: "dlmalloc/src/dlmalloc.c"}) } - if len(matches) == 0 { - return nil, fmt.Errorf("wasi-libc: did not find any files for pattern %#v", pattern) + + // See: LIBC_TOP_HALF_MUSL_SOURCES in the Makefile + sources := []string{ + "libc-top-half/musl/src/misc/a64l.c", + "libc-top-half/musl/src/misc/basename.c", + "libc-top-half/musl/src/misc/dirname.c", + "libc-top-half/musl/src/misc/ffs.c", + "libc-top-half/musl/src/misc/ffsl.c", + "libc-top-half/musl/src/misc/ffsll.c", + "libc-top-half/musl/src/misc/fmtmsg.c", + "libc-top-half/musl/src/misc/getdomainname.c", + "libc-top-half/musl/src/misc/gethostid.c", + "libc-top-half/musl/src/misc/getopt.c", + "libc-top-half/musl/src/misc/getopt_long.c", + "libc-top-half/musl/src/misc/getsubopt.c", + "libc-top-half/musl/src/misc/uname.c", + "libc-top-half/musl/src/misc/nftw.c", + "libc-top-half/musl/src/errno/strerror.c", + "libc-top-half/musl/src/network/htonl.c", + "libc-top-half/musl/src/network/htons.c", + "libc-top-half/musl/src/network/ntohl.c", + "libc-top-half/musl/src/network/ntohs.c", + "libc-top-half/musl/src/network/inet_ntop.c", + "libc-top-half/musl/src/network/inet_pton.c", + "libc-top-half/musl/src/network/inet_aton.c", + "libc-top-half/musl/src/network/in6addr_any.c", + "libc-top-half/musl/src/network/in6addr_loopback.c", + "libc-top-half/musl/src/fenv/fenv.c", + "libc-top-half/musl/src/fenv/fesetround.c", + "libc-top-half/musl/src/fenv/feupdateenv.c", + "libc-top-half/musl/src/fenv/fesetexceptflag.c", + "libc-top-half/musl/src/fenv/fegetexceptflag.c", + "libc-top-half/musl/src/fenv/feholdexcept.c", + "libc-top-half/musl/src/exit/exit.c", + "libc-top-half/musl/src/exit/atexit.c", + "libc-top-half/musl/src/exit/assert.c", + "libc-top-half/musl/src/exit/quick_exit.c", + "libc-top-half/musl/src/exit/at_quick_exit.c", + "libc-top-half/musl/src/time/strftime.c", + "libc-top-half/musl/src/time/asctime.c", + "libc-top-half/musl/src/time/asctime_r.c", + "libc-top-half/musl/src/time/ctime.c", + "libc-top-half/musl/src/time/ctime_r.c", + "libc-top-half/musl/src/time/wcsftime.c", + "libc-top-half/musl/src/time/strptime.c", + "libc-top-half/musl/src/time/difftime.c", + "libc-top-half/musl/src/time/timegm.c", + "libc-top-half/musl/src/time/ftime.c", + "libc-top-half/musl/src/time/gmtime.c", + "libc-top-half/musl/src/time/gmtime_r.c", + "libc-top-half/musl/src/time/timespec_get.c", + "libc-top-half/musl/src/time/getdate.c", + "libc-top-half/musl/src/time/localtime.c", + "libc-top-half/musl/src/time/localtime_r.c", + "libc-top-half/musl/src/time/mktime.c", + "libc-top-half/musl/src/time/__tm_to_secs.c", + "libc-top-half/musl/src/time/__month_to_secs.c", + "libc-top-half/musl/src/time/__secs_to_tm.c", + "libc-top-half/musl/src/time/__year_to_secs.c", + "libc-top-half/musl/src/time/__tz.c", + "libc-top-half/musl/src/fcntl/creat.c", + "libc-top-half/musl/src/dirent/alphasort.c", + "libc-top-half/musl/src/dirent/versionsort.c", + "libc-top-half/musl/src/env/__stack_chk_fail.c", + "libc-top-half/musl/src/env/clearenv.c", + "libc-top-half/musl/src/env/getenv.c", + "libc-top-half/musl/src/env/putenv.c", + "libc-top-half/musl/src/env/setenv.c", + "libc-top-half/musl/src/env/unsetenv.c", + "libc-top-half/musl/src/unistd/posix_close.c", + "libc-top-half/musl/src/unistd/gethostname.c", + "libc-top-half/musl/src/stat/futimesat.c", + "libc-top-half/musl/src/legacy/getpagesize.c", + "libc-top-half/musl/src/thread/common/thrd_sleep.c", + "libc-top-half/musl/src/misc/realpath.c", + "libc-top-half/musl/src/network/inet_addr.c", + "libc-top-half/musl/src/network/inet_legacy.c", + "libc-top-half/musl/src/network/inet_ntoa.c", } - excludeSet := map[string]struct{}{} - for _, exclude := range pattern.exclude { - excludeSet[exclude] = struct{}{} + if preview == "2" { + // See lib/wasi-libc-wasip2-stub.c. + sources = append(sources, "../wasi-libc-wasip2-stub.c") } - for _, match := range matches { - if _, ok := excludeSet[filepath.Base(match)]; ok { - continue - } - relpath, err := filepath.Rel(basepath, match) + + basepath := goenv.Get("TINYGOROOT") + "/lib/wasi-libc/" + for _, pattern := range globs { + matches, err := filepath.Glob(basepath + pattern.glob) if err != nil { - // Not sure if this is even possible. - return nil, err + // 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. + return nil, fmt.Errorf("wasi-libc: could not glob source dirs: %w", err) + } + if len(matches) == 0 { + return nil, fmt.Errorf("wasi-libc: did not find any files for pattern %#v", pattern) + } + excludeSet := map[string]struct{}{} + for _, exclude := range pattern.exclude { + excludeSet[exclude] = struct{}{} + } + for _, match := range matches { + if _, ok := excludeSet[filepath.Base(match)]; ok { + continue + } + relpath, err := filepath.Rel(basepath, match) + if err != nil { + // Not sure if this is even possible. + return nil, err + } + sources = append(sources, relpath) } - sources = append(sources, relpath) } - } - return sources, nil - }, + return sources, nil + }, + } } diff --git a/compileopts/config.go b/compileopts/config.go index a5edfd395a..1b81b21ac4 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -23,8 +23,10 @@ import ( // builder.Library struct but that's hard to do since we want to know the // library path in advance in several places). var libVersions = map[string]int{ - "musl": 3, - "bdwgc": 2, + "musl": 3, + "bdwgc": 2, + "wasi-libc": 2, + "wasi-libc-wasip2": 1, } // Config keeps all configuration affecting the build in a single struct. diff --git a/lib/wasi-libc b/lib/wasi-libc index 1dfe5c302d..2fc32bc81b 160000 --- a/lib/wasi-libc +++ b/lib/wasi-libc @@ -1 +1 @@ -Subproject commit 1dfe5c302d1c5ab621f7abf04620fae92700fd22 +Subproject commit 2fc32bc81b9f07f8d9525edea59bfbaf760c06d6 diff --git a/lib/wasi-libc-wasip2-stub.c b/lib/wasi-libc-wasip2-stub.c new file mode 100644 index 0000000000..a41a289f3b --- /dev/null +++ b/lib/wasi-libc-wasip2-stub.c @@ -0,0 +1,11 @@ +// wasi-libc's libc-bottom-half/sources/wasip2.c references +// __component_type_object_force_link_wasip2 to force the linker to retain +// the vendored wasip2_component_type.o object, which encodes WIT type +// metadata for wasi-libc's own crt1 "wasi:cli/run" export. +// +// TinyGo doesn't use wasi-libc's crt1 (it provides its own entry points in +// src/runtime/runtime_wasmentry.go) or that metadata (its own +// component-embedding step in builder/build.go generates the wasi:cli/run +// export separately), so this stub satisfies the linker without pulling in +// the real object, which we don't otherwise build. +void __component_type_object_force_link_wasip2(void) {} diff --git a/src/runtime/env.go b/src/runtime/env.go index e6c96f2417..2c5eeecc84 100644 --- a/src/runtime/env.go +++ b/src/runtime/env.go @@ -1,4 +1,4 @@ -//go:build (linux || darwin || windows || wasip1) && !wasip2 && !baremetal && !wasm_unknown +//go:build (linux || darwin || windows || wasip1 || wasip2) && !baremetal && !wasm_unknown package runtime diff --git a/src/runtime/env_unix.go b/src/runtime/env_unix.go index 42dfd5158f..4f5797ee54 100644 --- a/src/runtime/env_unix.go +++ b/src/runtime/env_unix.go @@ -1,4 +1,4 @@ -//go:build linux || darwin || wasip1 +//go:build linux || darwin || wasip1 || wasip2 package runtime diff --git a/src/runtime/env_wasip2.go b/src/runtime/env_wasip2.go deleted file mode 100644 index ffc18d9253..0000000000 --- a/src/runtime/env_wasip2.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build wasip2 - -package runtime - -// Notify the runtime when environment variables change. -// On wasip2, the environment is managed in Go (no C setenv), but -// internal/godebug still needs to be notified of GODEBUG changes. - -//go:linkname syscallSetenv syscall.runtimeSetenv -func syscallSetenv(key, value string) { - if key == "GODEBUG" && godebugUpdate != nil { - godebugUpdate(key, value) - } -} - -//go:linkname syscallUnsetenv syscall.runtimeUnsetenv -func syscallUnsetenv(key string) { - if key == "GODEBUG" && godebugUpdate != nil { - godebugUpdate(key, "") - } -} diff --git a/src/runtime/runtime_wasip2.go b/src/runtime/runtime_wasip2.go index 46ce3d853b..77f2157965 100644 --- a/src/runtime/runtime_wasip2.go +++ b/src/runtime/runtime_wasip2.go @@ -31,7 +31,11 @@ func os_runtime_args() []string { //export cabi_realloc func cabi_realloc(ptr, oldsize, align, newsize unsafe.Pointer) unsafe.Pointer { - return realloc(ptr, uintptr(newsize)) + // Use libc_realloc (not the GC-internal realloc) so this allocation is + // tracked in the same allocs map as malloc/free: wasi-libc's own C code + // (e.g. wasip2_string_free) takes ownership of buffers allocated here + // during canonical-ABI lifting and later frees them with a plain free(). + return libc_realloc(ptr, uintptr(newsize)) } func ticksToNanoseconds(ticks timeUnit) int64 { diff --git a/src/syscall/env_libc.go b/src/syscall/env_libc.go index 6eda55564c..075310f038 100644 --- a/src/syscall/env_libc.go +++ b/src/syscall/env_libc.go @@ -1,4 +1,4 @@ -//go:build nintendoswitch || wasip1 +//go:build nintendoswitch || wasip1 || wasip2 package syscall @@ -6,8 +6,12 @@ import ( "unsafe" ) -func Environ() []string { - +// environFromPointer walks a NULL-terminated char** environment array (in +// the same layout as libc's environ) and converts it into a []string. The +// starting pointer is supplied by the caller since how it's obtained +// (directly vs. via an accessor function) differs by platform/target -- see +// environ_libc.go and environ_wasip2.go. +func environFromPointer(environ *unsafe.Pointer) []string { // This function combines all the environment into a single allocation. // While this optimizes for memory usage and garbage collector // overhead, it does run the risk of potentially pinning a "large" @@ -18,10 +22,10 @@ func Environ() []string { // calculate total memory required var length uintptr var vars int - for environ := libc_environ; *environ != nil; { - length += libc_strlen(*environ) + for e := environ; *e != nil; { + length += libc_strlen(*e) vars++ - environ = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(environ), unsafe.Sizeof(environ))) + e = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(e), unsafe.Sizeof(e))) } // allocate our backing slice for the strings @@ -30,15 +34,15 @@ func Environ() []string { envs := make([]string, 0, vars) // loop over the environment again, this time copying over the data to the backing slice - for environ := libc_environ; *environ != nil; { - length = libc_strlen(*environ) + for e := environ; *e != nil; { + length = libc_strlen(*e) // construct a Go string pointing at the libc-allocated environment variable data var envVar string rawEnvVar := (*struct { ptr unsafe.Pointer length uintptr })(unsafe.Pointer(&envVar)) - rawEnvVar.ptr = *environ + rawEnvVar.ptr = *e rawEnvVar.length = length // pull off the number of bytes we need for this environment variable var bs []byte @@ -49,8 +53,8 @@ func Environ() []string { s := *(*string)(unsafe.Pointer(&bs)) // add s to our list of environment variables envs = append(envs, s) - // environ++ - environ = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(environ), unsafe.Sizeof(environ))) + // e++ + e = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(e), unsafe.Sizeof(e))) } return envs } @@ -105,6 +109,3 @@ func Clearenv() { } } } - -//go:extern environ -var libc_environ *unsafe.Pointer diff --git a/src/syscall/env_wasip2.go b/src/syscall/env_wasip2.go deleted file mode 100644 index f9468936bb..0000000000 --- a/src/syscall/env_wasip2.go +++ /dev/null @@ -1,58 +0,0 @@ -//go:build wasip2 - -package syscall - -import ( - "internal/wasi/cli/v0.2.0/environment" -) - -var libc_envs map[string]string - -func populateEnvironment() { - libc_envs = make(map[string]string) - for _, kv := range environment.GetEnvironment().Slice() { - libc_envs[kv[0]] = kv[1] - } -} - -func Environ() []string { - var env []string - for k, v := range libc_envs { - env = append(env, k+"="+v) - } - return env -} - -func Getenv(key string) (value string, found bool) { - value, found = libc_envs[key] - return -} - -func Setenv(key, val string) (err error) { - if len(key) == 0 { - return EINVAL - } - for i := 0; i < len(key); i++ { - if key[i] == '=' || key[i] == 0 { - return EINVAL - } - } - for i := 0; i < len(val); i++ { - if val[i] == 0 { - return EINVAL - } - } - libc_envs[key] = val - runtimeSetenv(key, val) - return nil -} - -func Unsetenv(key string) (err error) { - delete(libc_envs, key) - runtimeUnsetenv(key) - return nil -} - -func Clearenv() { - clear(libc_envs) -} diff --git a/src/syscall/environ_libc.go b/src/syscall/environ_libc.go new file mode 100644 index 0000000000..e9ea13a6ef --- /dev/null +++ b/src/syscall/environ_libc.go @@ -0,0 +1,12 @@ +//go:build nintendoswitch || wasip1 + +package syscall + +import "unsafe" + +func Environ() []string { + return environFromPointer(libc_environ) +} + +//go:extern environ +var libc_environ *unsafe.Pointer diff --git a/src/syscall/environ_wasip2.go b/src/syscall/environ_wasip2.go new file mode 100644 index 0000000000..db73f06393 --- /dev/null +++ b/src/syscall/environ_wasip2.go @@ -0,0 +1,23 @@ +//go:build wasip2 + +package syscall + +import "unsafe" + +func Environ() []string { + // __wasilibc_get_environ (rather than referencing the `environ` symbol + // directly, as environ_libc.go does for wasip1) triggers lazy + // initialization of the environment instead of eager, + // constructor-based initialization (see wasi-libc's environ.c vs. + // __wasilibc_environ.c). Eager initialization runs from a global + // constructor, which under the wasip2 component model can end up + // running while cabi_realloc is being invoked reentrantly to service an + // unrelated, still in-flight host->guest call -- wasmtime rejects that + // ("cannot leave component instance"). Lazy initialization only runs + // when Environ() is actually called by user code, well after module + // instantiation has completed, so it doesn't hit that restriction. + return environFromPointer(libc_get_environ()) +} + +//export __wasilibc_get_environ +func libc_get_environ() *unsafe.Pointer diff --git a/src/syscall/errno_wasilibc.go b/src/syscall/errno_wasilibc.go index efb97260f5..3c1af91931 100644 --- a/src/syscall/errno_wasilibc.go +++ b/src/syscall/errno_wasilibc.go @@ -1,4 +1,4 @@ -//go:build wasip1 || js +//go:build wasip1 || wasip2 || js package syscall diff --git a/src/syscall/errno_wasip2.go b/src/syscall/errno_wasip2.go deleted file mode 100644 index 39f1f8b403..0000000000 --- a/src/syscall/errno_wasip2.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build wasip2 - -package syscall - -// The errno for libc_wasip2.go - -var libcErrno Errno diff --git a/src/syscall/libc_wasip2.go b/src/syscall/libc_wasip2.go deleted file mode 100644 index 7d0dd63cf4..0000000000 --- a/src/syscall/libc_wasip2.go +++ /dev/null @@ -1,1308 +0,0 @@ -//go:build wasip2 - -// mini libc wrapping wasi preview2 calls in a libc api - -package syscall - -import ( - "unsafe" - - "internal/cm" - - "internal/wasi/cli/v0.2.0/environment" - "internal/wasi/cli/v0.2.0/stderr" - "internal/wasi/cli/v0.2.0/stdin" - "internal/wasi/cli/v0.2.0/stdout" - wallclock "internal/wasi/clocks/v0.2.0/wall-clock" - "internal/wasi/filesystem/v0.2.0/preopens" - "internal/wasi/filesystem/v0.2.0/types" - ioerror "internal/wasi/io/v0.2.0/error" - "internal/wasi/io/v0.2.0/streams" - "internal/wasi/random/v0.2.0/random" -) - -func goString(cstr *byte) string { - return unsafe.String(cstr, strlen(cstr)) -} - -//export strlen -func strlen(cstr *byte) uintptr { - if cstr == nil { - return 0 - } - ptr := unsafe.Pointer(cstr) - var i uintptr - for p := (*byte)(ptr); *p != 0; p = (*byte)(unsafe.Add(unsafe.Pointer(p), 1)) { - i++ - } - return i -} - -// ssize_t write(int fd, const void *buf, size_t count) -// -//export write -func write(fd int32, buf *byte, count uint) int { - if stream, ok := wasiStreams[fd]; ok { - return writeStream(stream, buf, count, 0) - } - - stream, ok := wasiFiles[fd] - if !ok { - libcErrno = EBADF - return -1 - } - if stream.d == cm.ResourceNone { - libcErrno = EBADF - return -1 - } - - n := pwrite(fd, buf, count, int64(stream.offset)) - if n == -1 { - return -1 - } - stream.offset += int64(n) - return int(n) -} - -// ssize_t read(int fd, void *buf, size_t count); -// -//export read -func read(fd int32, buf *byte, count uint) int { - if stream, ok := wasiStreams[fd]; ok { - return readStream(stream, buf, count, 0) - } - - stream, ok := wasiFiles[fd] - if !ok { - libcErrno = EBADF - return -1 - } - if stream.d == cm.ResourceNone { - libcErrno = EBADF - return -1 - } - - n := pread(fd, buf, count, int64(stream.offset)) - if n == -1 { - // error during pread - return -1 - } - stream.offset += int64(n) - return int(n) -} - -// At the moment, each time we have a file read or write we create a new stream. Future implementations -// could change the current in or out file stream lazily. We could do this by tracking input and output -// offsets individually, and if they don't match the current main offset, reopen the file stream at that location. - -type wasiFile struct { - d types.Descriptor - oflag int32 // original open flags: O_RDONLY, O_WRONLY, O_RDWR - offset int64 // current fd offset; updated with each read/write - refs int -} - -// Need to figure out which system calls we're using: -// stdin/stdout/stderr want streams, so we use stream read/write -// but for regular files we can use the descriptor and explicitly write a buffer to the offset? -// The mismatch comes from trying to combine these. - -var wasiFiles map[int32]*wasiFile = make(map[int32]*wasiFile) - -func findFreeFD() int32 { - var newfd int32 - for wasiStreams[newfd] != nil || wasiFiles[newfd] != nil { - newfd++ - } - return newfd -} - -var wasiErrno ioerror.Error - -type wasiStream struct { - in *streams.InputStream - out *streams.OutputStream - refs int -} - -// This holds entries for stdin/stdout/stderr. - -var wasiStreams map[int32]*wasiStream - -func init() { - sin := stdin.GetStdin() - sout := stdout.GetStdout() - serr := stderr.GetStderr() - wasiStreams = map[int32]*wasiStream{ - 0: &wasiStream{ - in: &sin, - refs: 1, - }, - 1: &wasiStream{ - out: &sout, - refs: 1, - }, - 2: &wasiStream{ - out: &serr, - refs: 1, - }, - } -} - -func readStream(stream *wasiStream, buf *byte, count uint, offset int64) int { - if stream.in == nil { - // not a stream we can read from - libcErrno = EBADF - return -1 - } - - if offset != 0 { - libcErrno = EINVAL - return -1 - } - - libcErrno = 0 - list, err, isErr := stream.in.BlockingRead(uint64(count)).Result() - if isErr { - if err.Closed() { - libcErrno = 0 - return 0 - } else if err := err.LastOperationFailed(); err != nil { - wasiErrno = *err - libcErrno = EWASIERROR - } - return -1 - } - - copy(unsafe.Slice(buf, count), list.Slice()) - return int(list.Len()) -} - -func writeStream(stream *wasiStream, buf *byte, count uint, offset int64) int { - if stream.out == nil { - // not a stream we can write to - libcErrno = EBADF - return -1 - } - - if offset != 0 { - libcErrno = EINVAL - return -1 - } - - src := unsafe.Slice(buf, count) - var remaining = count - - // The blocking-write-and-flush call allows a maximum of 4096 bytes at a time. - // We loop here by instead of doing subscribe/check-write/poll-one/write by hand. - for remaining > 0 { - len := uint(4096) - if len > remaining { - len = remaining - } - _, err, isErr := stream.out.BlockingWriteAndFlush(cm.ToList(src[:len])).Result() - if isErr { - if err.Closed() { - libcErrno = 0 - return 0 - } else if err := err.LastOperationFailed(); err != nil { - wasiErrno = *err - libcErrno = EWASIERROR - } - return -1 - } - remaining -= len - src = src[len:] - } - - return int(count) -} - -//go:linkname memcpy runtime.memcpy -func memcpy(dst, src unsafe.Pointer, size uintptr) - -// ssize_t pread(int fd, void *buf, size_t count, off_t offset); -// -//export pread -func pread(fd int32, buf *byte, count uint, offset int64) int { - // TODO(dgryski): Need to be consistent about all these checks; EBADF/EINVAL/... ? - - if stream, ok := wasiStreams[fd]; ok { - return readStream(stream, buf, count, offset) - - } - - streams, ok := wasiFiles[fd] - if !ok { - // TODO(dgryski): EINVAL? - libcErrno = EBADF - return -1 - } - if streams.d == cm.ResourceNone { - libcErrno = EBADF - return -1 - } - if streams.oflag&O_RDONLY == 0 { - libcErrno = EBADF - return -1 - } - - listEOF, err, isErr := streams.d.Read(types.FileSize(count), types.FileSize(offset)).Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return -1 - } - - list := listEOF.F0 - copy(unsafe.Slice(buf, count), list.Slice()) - - // TODO(dgryski): EOF bool is ignored? - return int(list.Len()) -} - -// ssize_t pwrite(int fd, void *buf, size_t count, off_t offset); -// -//export pwrite -func pwrite(fd int32, buf *byte, count uint, offset int64) int { - // TODO(dgryski): Need to be consistent about all these checks; EBADF/EINVAL/... ? - if stream, ok := wasiStreams[fd]; ok { - return writeStream(stream, buf, count, 0) - } - - streams, ok := wasiFiles[fd] - if !ok { - // TODO(dgryski): EINVAL? - libcErrno = EBADF - return -1 - } - if streams.d == cm.ResourceNone { - libcErrno = EBADF - return -1 - } - if streams.oflag&O_WRONLY == 0 { - libcErrno = EBADF - return -1 - } - - n, err, isErr := streams.d.Write(cm.NewList(buf, count), types.FileSize(offset)).Result() - if isErr { - // TODO(dgryski): - libcErrno = errorCodeToErrno(err) - return -1 - } - - return int(n) -} - -// ssize_t lseek(int fd, off_t offset, int whence); -// -//export lseek -func lseek(fd int32, offset int64, whence int) int64 { - if _, ok := wasiStreams[fd]; ok { - // can't lseek a stream - libcErrno = EBADF - return -1 - } - - stream, ok := wasiFiles[fd] - if !ok { - libcErrno = EBADF - return -1 - } - if stream.d == cm.ResourceNone { - libcErrno = EBADF - return -1 - } - - switch whence { - case 0: // SEEK_SET - stream.offset = offset - case 1: // SEEK_CUR - stream.offset += offset - case 2: // SEEK_END - stat, err, isErr := stream.d.Stat().Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return -1 - } - stream.offset = int64(stat.Size) + offset - } - - return int64(stream.offset) -} - -// int close(int fd) -// -//export close -func close(fd int32) int32 { - if streams, ok := wasiStreams[fd]; ok { - if streams.out != nil { - // ignore any error - streams.out.BlockingFlush() - } - - if streams.refs--; streams.refs == 0 { - if streams.out != nil { - streams.out.ResourceDrop() - streams.out = nil - } - if streams.in != nil { - streams.in.ResourceDrop() - streams.in = nil - } - } - - delete(wasiStreams, fd) - return 0 - } - - streams, ok := wasiFiles[fd] - if !ok { - libcErrno = EBADF - return -1 - } - if streams.refs--; streams.refs == 0 && streams.d != cm.ResourceNone { - streams.d.ResourceDrop() - streams.d = 0 - } - delete(wasiFiles, fd) - - return 0 -} - -// int dup(int fd) -// -//export dup -func dup(fd int32) int32 { - // is fd a stream? - if stream, ok := wasiStreams[fd]; ok { - newfd := findFreeFD() - stream.refs++ - wasiStreams[newfd] = stream - return newfd - } - - // is fd a file? - if file, ok := wasiFiles[fd]; ok { - // scan for first free file descriptor - newfd := findFreeFD() - file.refs++ - wasiFiles[newfd] = file - return newfd - } - - // unknown file descriptor - libcErrno = EBADF - return -1 -} - -// void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); -// -//export mmap -func mmap(addr unsafe.Pointer, length uintptr, prot, flags, fd int32, offset uintptr) unsafe.Pointer { - libcErrno = ENOSYS - return unsafe.Pointer(^uintptr(0)) -} - -// int munmap(void *addr, size_t length); -// -//export munmap -func munmap(addr unsafe.Pointer, length uintptr) int32 { - libcErrno = ENOSYS - return -1 -} - -// int mprotect(void *addr, size_t len, int prot); -// -//export mprotect -func mprotect(addr unsafe.Pointer, len uintptr, prot int32) int32 { - libcErrno = ENOSYS - return -1 -} - -// int chmod(const char *pathname, mode_t mode); -// -//export chmod -func chmod(pathname *byte, mode uint32) int32 { - return 0 -} - -// int mkdir(const char *pathname, mode_t mode); -// -//export mkdir -func mkdir(pathname *byte, mode uint32) int32 { - path := goString(pathname) - dir, relPath := findPreopenForPath(path) - if dir.d == cm.ResourceNone { - libcErrno = EACCES - return -1 - } - - _, err, isErr := dir.d.CreateDirectoryAt(relPath).Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return -1 - } - - return 0 -} - -// int rmdir(const char *pathname); -// -//export rmdir -func rmdir(pathname *byte) int32 { - path := goString(pathname) - dir, relPath := findPreopenForPath(path) - if dir.d == cm.ResourceNone { - libcErrno = EACCES - return -1 - } - - _, err, isErr := dir.d.RemoveDirectoryAt(relPath).Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return -1 - } - - return 0 -} - -// int rename(const char *from, *to); -// -//export rename -func rename(from, to *byte) int32 { - fromPath := goString(from) - fromDir, fromRelPath := findPreopenForPath(fromPath) - if fromDir.d == cm.ResourceNone { - libcErrno = EACCES - return -1 - } - - toPath := goString(to) - toDir, toRelPath := findPreopenForPath(toPath) - if toDir.d == cm.ResourceNone { - libcErrno = EACCES - return -1 - } - - _, err, isErr := fromDir.d.RenameAt(fromRelPath, toDir.d, toRelPath).Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return -1 - } - - return 0 -} - -// int symlink(const char *from, *to); -// -//export symlink -func symlink(from, to *byte) int32 { - fromPath := goString(from) - fromDir, fromRelPath := findPreopenForPath(fromPath) - if fromDir.d == cm.ResourceNone { - libcErrno = EACCES - return -1 - } - - toPath := goString(to) - toDir, toRelPath := findPreopenForPath(toPath) - if toDir.d == cm.ResourceNone { - libcErrno = EACCES - return -1 - } - - if fromDir.d != toDir.d { - libcErrno = EACCES - return -1 - } - - // TODO(dgryski): check fromDir == toDir? - - _, err, isErr := fromDir.d.SymlinkAt(fromRelPath, toRelPath).Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return -1 - } - - return 0 -} - -// int link(const char *from, *to); -// -//export link -func link(from, to *byte) int32 { - fromPath := goString(from) - fromDir, fromRelPath := findPreopenForPath(fromPath) - if fromDir.d == cm.ResourceNone { - libcErrno = EACCES - return -1 - } - - toPath := goString(to) - toDir, toRelPath := findPreopenForPath(toPath) - if toDir.d == cm.ResourceNone { - libcErrno = EACCES - return -1 - } - - if fromDir.d != toDir.d { - libcErrno = EACCES - return -1 - } - - // TODO(dgryski): check fromDir == toDir? - - _, err, isErr := fromDir.d.LinkAt(0, fromRelPath, toDir.d, toRelPath).Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return -1 - } - - return 0 -} - -// int fsync(int fd); -// -//export fsync -func fsync(fd int32) int32 { - if _, ok := wasiStreams[fd]; ok { - // can't sync a stream - libcErrno = EBADF - return -1 - } - - streams, ok := wasiFiles[fd] - if !ok { - libcErrno = EBADF - return -1 - } - if streams.d == cm.ResourceNone { - libcErrno = EBADF - return -1 - } - if streams.oflag&O_WRONLY == 0 { - libcErrno = EBADF - return -1 - } - - _, err, isErr := streams.d.SyncData().Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return -1 - } - - return 0 -} - -// ssize_t readlink(const char *path, void *buf, size_t count); -// -//export readlink -func readlink(pathname *byte, buf *byte, count uint) int { - path := goString(pathname) - dir, relPath := findPreopenForPath(path) - if dir.d == cm.ResourceNone { - libcErrno = EACCES - return -1 - } - - s, err, isErr := dir.d.ReadLinkAt(relPath).Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return -1 - } - - size := uintptr(count) - if size > uintptr(len(s)) { - size = uintptr(len(s)) - } - - memcpy(unsafe.Pointer(buf), unsafe.Pointer(unsafe.StringData(s)), size) - return int(size) -} - -// int unlink(const char *pathname); -// -//export unlink -func unlink(pathname *byte) int32 { - path := goString(pathname) - dir, relPath := findPreopenForPath(path) - if dir.d == cm.ResourceNone { - libcErrno = EACCES - return -1 - } - - _, err, isErr := dir.d.UnlinkFileAt(relPath).Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return -1 - } - - return 0 -} - -// int getpagesize(void); -// -//export getpagesize -func getpagesize() int { - return 65536 -} - -// int stat(const char *path, struct stat * buf); -// -//export stat -func stat(pathname *byte, dst *Stat_t) int32 { - path := goString(pathname) - dir, relPath := findPreopenForPath(path) - if dir.d == cm.ResourceNone { - libcErrno = EACCES - return -1 - } - - stat, err, isErr := dir.d.StatAt(0, relPath).Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return -1 - } - - setStatFromWASIStat(dst, &stat) - - return 0 -} - -// int fstat(int fd, struct stat * buf); -// -//export fstat -func fstat(fd int32, dst *Stat_t) int32 { - if _, ok := wasiStreams[fd]; ok { - // TODO(dgryski): fill in stat buffer for stdin etc - return -1 - } - - stream, ok := wasiFiles[fd] - if !ok { - libcErrno = EBADF - return -1 - } - if stream.d == cm.ResourceNone { - libcErrno = EBADF - return -1 - } - stat, err, isErr := stream.d.Stat().Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return -1 - } - - setStatFromWASIStat(dst, &stat) - - return 0 -} - -func setStatFromWASIStat(sstat *Stat_t, wstat *types.DescriptorStat) { - // This will cause problems for people who want to compare inodes - sstat.Dev = 0 - sstat.Ino = 0 - sstat.Rdev = 0 - - sstat.Nlink = uint64(wstat.LinkCount) - - sstat.Mode = p2fileTypeToStatType(wstat.Type) - - // No uid/gid - sstat.Uid = 0 - sstat.Gid = 0 - sstat.Size = int64(wstat.Size) - - // made up numbers - sstat.Blksize = 512 - sstat.Blocks = (sstat.Size + 511) / int64(sstat.Blksize) - - setOptTime := func(t *Timespec, o *wallclock.DateTime) { - t.Sec = 0 - t.Nsec = 0 - if o != nil { - t.Sec = int32(o.Seconds) - t.Nsec = int64(o.Nanoseconds) - } - } - - setOptTime(&sstat.Atim, wstat.DataAccessTimestamp.Some()) - setOptTime(&sstat.Mtim, wstat.DataModificationTimestamp.Some()) - setOptTime(&sstat.Ctim, wstat.StatusChangeTimestamp.Some()) -} - -// int lstat(const char *path, struct stat * buf); -// -//export lstat -func lstat(pathname *byte, dst *Stat_t) int32 { - path := goString(pathname) - dir, relPath := findPreopenForPath(path) - if dir.d == cm.ResourceNone { - libcErrno = EACCES - return -1 - } - - stat, err, isErr := dir.d.StatAt(0, relPath).Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return -1 - } - - setStatFromWASIStat(dst, &stat) - - return 0 -} - -func init() { - populateEnvironment() - populatePreopens() -} - -type wasiDir struct { - d types.Descriptor // wasip2 descriptor - root string // root path for this descriptor - rel string // relative path under root -} - -var libcCWD wasiDir - -var wasiPreopens map[string]types.Descriptor - -func populatePreopens() { - var cwd string - - // find CWD - result := environment.InitialCWD() - if s := result.Some(); s != nil { - cwd = *s - } else if s, _ := Getenv("PWD"); s != "" { - cwd = s - } - - dirs := preopens.GetDirectories().Slice() - preopens := make(map[string]types.Descriptor, len(dirs)) - for _, tup := range dirs { - desc, path := tup.F0, tup.F1 - if path == cwd { - libcCWD.d = desc - libcCWD.root = path - libcCWD.rel = "" - } - preopens[path] = desc - } - wasiPreopens = preopens -} - -// -- BEGIN fs_wasip1.go -- -// The following section has been taken from upstream Go with the following copyright: -// Copyright 2023 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:nosplit -func appendCleanPath(buf []byte, path string, lookupParent bool) ([]byte, bool) { - i := 0 - for i < len(path) { - for i < len(path) && path[i] == '/' { - i++ - } - - j := i - for j < len(path) && path[j] != '/' { - j++ - } - - s := path[i:j] - i = j - - switch s { - case "": - continue - case ".": - continue - case "..": - if !lookupParent { - k := len(buf) - for k > 0 && buf[k-1] != '/' { - k-- - } - for k > 1 && buf[k-1] == '/' { - k-- - } - buf = buf[:k] - if k == 0 { - lookupParent = true - } else { - s = "" - continue - } - } - default: - lookupParent = false - } - - if len(buf) > 0 && buf[len(buf)-1] != '/' { - buf = append(buf, '/') - } - buf = append(buf, s...) - } - return buf, lookupParent -} - -// joinPath concatenates dir and file paths, producing a cleaned path where -// "." and ".." have been removed, unless dir is relative and the references -// to parent directories in file represented a location relative to a parent -// of dir. -// -// This function is used for path resolution of all wasi functions expecting -// a path argument; the returned string is heap allocated, which we may want -// to optimize in the future. Instead of returning a string, the function -// could append the result to an output buffer that the functions in this -// file can manage to have allocated on the stack (e.g. initializing to a -// fixed capacity). Since it will significantly increase code complexity, -// we prefer to optimize for readability and maintainability at this time. -func joinPath(dir, file string) string { - buf := make([]byte, 0, len(dir)+len(file)+1) - if isAbs(dir) { - buf = append(buf, '/') - } - - buf, lookupParent := appendCleanPath(buf, dir, true) - buf, _ = appendCleanPath(buf, file, lookupParent) - // The appendCleanPath function cleans the path so it does not inject - // references to the current directory. If both the dir and file args - // were ".", this results in the output buffer being empty so we handle - // this condition here. - if len(buf) == 0 { - buf = append(buf, '.') - } - // If the file ended with a '/' we make sure that the output also ends - // with a '/'. This is needed to ensure that programs have a mechanism - // to represent dereferencing symbolic links pointing to directories. - if buf[len(buf)-1] != '/' && isDir(file) { - buf = append(buf, '/') - } - return unsafe.String(&buf[0], len(buf)) -} - -func isAbs(path string) bool { - return hasPrefix(path, "/") -} - -func isDir(path string) bool { - return hasSuffix(path, "/") -} - -func hasPrefix(s, p string) bool { - return len(s) >= len(p) && s[:len(p)] == p -} - -func hasSuffix(s, x string) bool { - return len(s) >= len(x) && s[len(s)-len(x):] == x -} - -// findPreopenForPath finds which preopen it relates to and return that descriptor/root and the path relative to that directory descriptor/root -func findPreopenForPath(path string) (wasiDir, string) { - dir := "/" - var wasidir wasiDir - - if !isAbs(path) { - dir = libcCWD.root - wasidir = libcCWD - if libcCWD.rel != "" && libcCWD.rel != "." && libcCWD.rel != "./" { - path = libcCWD.rel + "/" + path - } - } - path = joinPath(dir, path) - - var best string - for k, v := range wasiPreopens { - if len(k) > len(best) && hasPrefix(path, k) { - wasidir = wasiDir{d: v, root: k} - best = wasidir.root - } - } - - if hasPrefix(path, wasidir.root) { - path = path[len(wasidir.root):] - } - for isAbs(path) { - path = path[1:] - } - if len(path) == 0 { - path = "." - } - - return wasidir, path -} - -// -- END fs_wasip1.go -- - -// int open(const char *pathname, int flags, mode_t mode); -// -//export open -func open(pathname *byte, flags int32, mode uint32) int32 { - path := goString(pathname) - dir, relPath := findPreopenForPath(path) - if dir.d == cm.ResourceNone { - libcErrno = EACCES - return -1 - } - - var dflags types.DescriptorFlags - if (flags & O_RDONLY) == O_RDONLY { - dflags |= types.DescriptorFlagsRead - } - if (flags & O_WRONLY) == O_WRONLY { - dflags |= types.DescriptorFlagsWrite - } - - var oflags types.OpenFlags - if flags&O_CREAT == O_CREAT { - oflags |= types.OpenFlagsCreate - } - if flags&O_DIRECTORY == O_DIRECTORY { - oflags |= types.OpenFlagsDirectory - } - if flags&O_EXCL == O_EXCL { - oflags |= types.OpenFlagsExclusive - } - if flags&O_TRUNC == O_TRUNC { - oflags |= types.OpenFlagsTruncate - } - - // By default, follow symlinks for open() unless O_NOFOLLOW was passed - var pflags types.PathFlags = types.PathFlagsSymlinkFollow - if flags&O_NOFOLLOW == O_NOFOLLOW { - // O_NOFOLLOW was passed, so turn off SymlinkFollow - pflags &^= types.PathFlagsSymlinkFollow - } - - descriptor, err, isErr := dir.d.OpenAt(pflags, relPath, oflags, dflags).Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return -1 - } - - stream := wasiFile{ - d: descriptor, - oflag: flags, - refs: 1, - } - - if flags&(O_WRONLY|O_APPEND) == (O_WRONLY | O_APPEND) { - stat, err, isErr := stream.d.Stat().Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return -1 - } - stream.offset = int64(stat.Size) - } - - libcfd := findFreeFD() - - wasiFiles[libcfd] = &stream - - return int32(libcfd) -} - -func errorCodeToErrno(err types.ErrorCode) Errno { - switch err { - case types.ErrorCodeAccess: - return EACCES - case types.ErrorCodeWouldBlock: - return EAGAIN - case types.ErrorCodeAlready: - return EALREADY - case types.ErrorCodeBadDescriptor: - return EBADF - case types.ErrorCodeBusy: - return EBUSY - case types.ErrorCodeDeadlock: - return EDEADLK - case types.ErrorCodeQuota: - return EDQUOT - case types.ErrorCodeExist: - return EEXIST - case types.ErrorCodeFileTooLarge: - return EFBIG - case types.ErrorCodeIllegalByteSequence: - return EILSEQ - case types.ErrorCodeInProgress: - return EINPROGRESS - case types.ErrorCodeInterrupted: - return EINTR - case types.ErrorCodeInvalid: - return EINVAL - case types.ErrorCodeIO: - return EIO - case types.ErrorCodeIsDirectory: - return EISDIR - case types.ErrorCodeLoop: - return ELOOP - case types.ErrorCodeTooManyLinks: - return EMLINK - case types.ErrorCodeMessageSize: - return EMSGSIZE - case types.ErrorCodeNameTooLong: - return ENAMETOOLONG - case types.ErrorCodeNoDevice: - return ENODEV - case types.ErrorCodeNoEntry: - return ENOENT - case types.ErrorCodeNoLock: - return ENOLCK - case types.ErrorCodeInsufficientMemory: - return ENOMEM - case types.ErrorCodeInsufficientSpace: - return ENOSPC - case types.ErrorCodeNotDirectory: - return ENOTDIR - case types.ErrorCodeNotEmpty: - return ENOTEMPTY - case types.ErrorCodeNotRecoverable: - return ENOTRECOVERABLE - case types.ErrorCodeUnsupported: - return ENOSYS - case types.ErrorCodeNoTTY: - return ENOTTY - case types.ErrorCodeNoSuchDevice: - return ENXIO - case types.ErrorCodeOverflow: - return EOVERFLOW - case types.ErrorCodeNotPermitted: - return EPERM - case types.ErrorCodePipe: - return EPIPE - case types.ErrorCodeReadOnly: - return EROFS - case types.ErrorCodeInvalidSeek: - return ESPIPE - case types.ErrorCodeTextFileBusy: - return ETXTBSY - case types.ErrorCodeCrossDevice: - return EXDEV - } - return Errno(err) -} - -type libc_DIR struct { - d types.DirectoryEntryStream -} - -// DIR *fdopendir(int); -// -//export fdopendir -func fdopendir(fd int32) unsafe.Pointer { - if _, ok := wasiStreams[fd]; ok { - libcErrno = EBADF - return nil - } - - stream, ok := wasiFiles[fd] - if !ok { - libcErrno = EBADF - return nil - } - if stream.d == cm.ResourceNone { - libcErrno = EBADF - return nil - } - - dir, err, isErr := stream.d.ReadDirectory().Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return nil - } - - return unsafe.Pointer(&libc_DIR{d: dir}) -} - -// int fdclosedir(DIR *); -// -//export fdclosedir -func fdclosedir(dirp unsafe.Pointer) int32 { - if dirp == nil { - return 0 - - } - dir := (*libc_DIR)(dirp) - if dir.d == cm.ResourceNone { - return 0 - } - - dir.d.ResourceDrop() - dir.d = cm.ResourceNone - - return 0 -} - -// struct dirent *readdir(DIR *); -// -//export readdir -func readdir(dirp unsafe.Pointer) *Dirent { - if dirp == nil { - return nil - - } - dir := (*libc_DIR)(dirp) - if dir.d == cm.ResourceNone { - return nil - } - - someEntry, err, isErr := dir.d.ReadDirectoryEntry().Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return nil - } - - entry := someEntry.Some() - if entry == nil { - libcErrno = 0 - return nil - } - - // The dirent C struct uses a flexible array member to indicate that the - // directory name is laid out in memory right after the struct data: - // - // struct dirent { - // ino_t d_ino; - // unsigned char d_type; - // char d_name[]; - // }; - buf := make([]byte, unsafe.Sizeof(Dirent{})+uintptr(len(entry.Name))) - dirent := (*Dirent)((unsafe.Pointer)(&buf[0])) - - // No inodes in wasi - dirent.Ino = 0 - dirent.Type = p2fileTypeToDirentType(entry.Type) - copy(buf[unsafe.Offsetof(dirent.Type)+1:], entry.Name) - - return dirent -} - -func p2fileTypeToDirentType(t types.DescriptorType) uint8 { - switch t { - case types.DescriptorTypeUnknown: - return DT_UNKNOWN - case types.DescriptorTypeBlockDevice: - return DT_BLK - case types.DescriptorTypeCharacterDevice: - return DT_CHR - case types.DescriptorTypeDirectory: - return DT_DIR - case types.DescriptorTypeFIFO: - return DT_FIFO - case types.DescriptorTypeSymbolicLink: - return DT_LNK - case types.DescriptorTypeRegularFile: - return DT_REG - case types.DescriptorTypeSocket: - return DT_FIFO - } - - return DT_UNKNOWN -} - -func p2fileTypeToStatType(t types.DescriptorType) uint32 { - switch t { - case types.DescriptorTypeUnknown: - return 0 - case types.DescriptorTypeBlockDevice: - return S_IFBLK - case types.DescriptorTypeCharacterDevice: - return S_IFCHR - case types.DescriptorTypeDirectory: - return S_IFDIR - case types.DescriptorTypeFIFO: - return S_IFIFO - case types.DescriptorTypeSymbolicLink: - return S_IFLNK - case types.DescriptorTypeRegularFile: - return S_IFREG - case types.DescriptorTypeSocket: - return S_IFSOCK - } - - return 0 -} - -// void arc4random_buf (void *, size_t); -// -//export arc4random_buf -func arc4random_buf(p unsafe.Pointer, l uint) { - result := random.GetRandomBytes(uint64(l)) - s := result.Slice() - memcpy(unsafe.Pointer(p), unsafe.Pointer(unsafe.SliceData(s)), uintptr(l)) -} - -// int chdir(char *name) -// -//export chdir -func chdir(name *byte) int { - path := goString(name) + "/" - - if !isAbs(path) { - path = joinPath(libcCWD.root+"/"+libcCWD.rel+"/", path) - } - - if path == "." { - return 0 - } - - dir, rel := findPreopenForPath(path) - if dir.d == cm.ResourceNone { - libcErrno = EACCES - return -1 - } - - _, err, isErr := dir.d.OpenAt(types.PathFlagsSymlinkFollow, rel, types.OpenFlagsDirectory, types.DescriptorFlagsRead).Result() - if isErr { - libcErrno = errorCodeToErrno(err) - return -1 - } - - libcCWD = dir - // keep the same cwd base but update "rel" to point to new base path - libcCWD.rel = rel - - return 0 -} - -// char *getcwd(char *buf, size_t size) -// -//export getcwd -func getcwd(buf *byte, size uint) *byte { - - cwd := libcCWD.root - if libcCWD.rel != "" && libcCWD.rel != "." && libcCWD.rel != "./" { - cwd += libcCWD.rel - } - - if buf == nil { - b := make([]byte, len(cwd)+1) - buf = unsafe.SliceData(b) - } else if size == 0 { - libcErrno = EINVAL - return nil - } - - if size < uint(len(cwd)+1) { - libcErrno = ERANGE - return nil - } - - s := unsafe.Slice(buf, size) - s[size-1] = 0 // Enforce NULL termination - copy(s, cwd) - return buf -} - -// int truncate(const char *path, off_t length); -// -//export truncate -func truncate(path *byte, length int64) int32 { - libcErrno = ENOSYS - return -1 -} diff --git a/targets/wasip2.json b/targets/wasip2.json index 5000d158a5..935ef74b34 100644 --- a/targets/wasip2.json +++ b/targets/wasip2.json @@ -7,7 +7,7 @@ "goos": "linux", "goarch": "arm", "linker": "wasm-ld", - "libc": "wasmbuiltins", + "libc": "wasi-libc", "rtlib": "compiler-rt", "gc": "precise", "scheduler": "asyncify",