diff --git a/.circleci/config.yml b/.circleci/config.yml index eedf08d27d..15ca09e010 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -44,6 +44,13 @@ commands: command: | wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb sudo apt install ./google-chrome-stable_current_amd64.deb + install-wasmtime: + steps: + - run: + name: "Install wasmtime" + command: | + curl https://wasmtime.dev/install.sh -sSf | bash + sudo ln -s ~/.wasmtime/bin/wasmtime /usr/local/bin/wasmtime install-xtensa-toolchain: parameters: variant: @@ -91,6 +98,7 @@ commands: llvm: "<>" - install-node - install-chrome + - install-wasmtime - restore_cache: keys: - go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} @@ -134,6 +142,7 @@ commands: gcc-avr \ avr-libc - install-node + - install-wasmtime - install-xtensa-toolchain: variant: "linux-amd64" - restore_cache: @@ -194,6 +203,7 @@ commands: gcc-avr \ avr-libc - install-node + - install-wasmtime - install-xtensa-toolchain: variant: "linux-amd64" - restore_cache: diff --git a/loader/goroot.go b/loader/goroot.go index 9648550719..5fa4bea071 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -189,7 +189,7 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides // with the TinyGo version. This is the case on some targets. func needsSyscallPackage(buildTags []string) bool { for _, tag := range buildTags { - if tag == "baremetal" || tag == "darwin" || tag == "nintendoswitch" { + if tag == "baremetal" || tag == "darwin" || tag == "nintendoswitch" || tag == "wasi" { return true } } diff --git a/main_test.go b/main_test.go index f66117b90a..ac01baafaa 100644 --- a/main_test.go +++ b/main_test.go @@ -98,6 +98,10 @@ func TestCompiler(t *testing.T) { runPlatTests("wasm", matches, t) }) } + + t.Run("WASI", func(t *testing.T) { + runPlatTests("wasi", matches, t) + }) } } @@ -109,7 +113,6 @@ func runPlatTests(target string, matches []string, t *testing.T) { t.Run(filepath.Base(path), func(t *testing.T) { t.Parallel() - runTest(path, target, t) }) } @@ -161,6 +164,11 @@ func runTest(path, target string, t *testing.T) { PrintSizes: "", WasmAbi: "js", } + + if target == "wasi" { + config.WasmAbi = "generic" + } + binary := filepath.Join(tmpdir, "test") err = runBuild("./"+path, binary, config) if err != nil { diff --git a/src/os/file_unix.go b/src/os/file_unix.go index e77d6773fb..84d738b0ca 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -1,4 +1,4 @@ -// +build darwin linux,!baremetal freebsd,!baremetal +// +build darwin linux,!baremetal,!wasi freebsd,!baremetal package os diff --git a/src/runtime/arch_arm.go b/src/runtime/arch_arm.go index 0a98f6d532..e2dc4b5bfe 100644 --- a/src/runtime/arch_arm.go +++ b/src/runtime/arch_arm.go @@ -1,4 +1,4 @@ -// +build arm,!baremetal arm,arm7tdmi +// +build arm,!baremetal,!wasm arm,arm7tdmi package runtime diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index a3c2b4471b..ef4b825856 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -1,4 +1,4 @@ -// +build darwin linux,!baremetal freebsd,!baremetal +// +build darwin linux,!baremetal,!wasi freebsd,!baremetal // +build !nintendoswitch package runtime diff --git a/src/runtime/runtime_unix_heap.go b/src/runtime/runtime_unix_heap.go index 14168f4387..1aefd2f615 100644 --- a/src/runtime/runtime_unix_heap.go +++ b/src/runtime/runtime_unix_heap.go @@ -1,4 +1,4 @@ -// +build darwin linux,!baremetal freebsd,!baremetal +// +build darwin linux,!baremetal,!wasi freebsd,!baremetal // +build !nintendoswitch // +build gc.conservative gc.leaking diff --git a/src/runtime/runtime_unix_noheap.go b/src/runtime/runtime_unix_noheap.go index bf7f675987..f9826ac5d7 100644 --- a/src/runtime/runtime_unix_noheap.go +++ b/src/runtime/runtime_unix_noheap.go @@ -1,4 +1,4 @@ -// +build darwin linux,!baremetal freebsd,!baremetal +// +build darwin linux,!baremetal,!wasi freebsd,!baremetal // +build !nintendoswitch diff --git a/src/runtime/runtime_wasm.go b/src/runtime/runtime_wasm.go index aaf95d7846..e37d432e04 100644 --- a/src/runtime/runtime_wasm.go +++ b/src/runtime/runtime_wasm.go @@ -2,35 +2,26 @@ package runtime -import "unsafe" - -type timeUnit float64 // time in milliseconds, just like Date.now() in JavaScript +import ( + "unsafe" +) -// Implements __wasi_ciovec_t and __wasi_iovec_t. -type wasiIOVec struct { +// Implements __wasi_iovec_t. +type __wasi_iovec_t struct { buf unsafe.Pointer bufLen uint } //go:wasm-module wasi_unstable //export fd_write -func fd_write(id uint32, iovs *wasiIOVec, iovs_len uint, nwritten *uint) (errno uint) +func fd_write(id uint32, iovs *__wasi_iovec_t, iovs_len uint, nwritten *uint) (errno uint) func postinit() {} -//export _start -func _start() { - // These need to be initialized early so that the heap can be initialized. - heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) - heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) - - run() -} - // Using global variables to avoid heap allocation. var ( putcharBuf = byte(0) - putcharIOVec = wasiIOVec{ + putcharIOVec = __wasi_iovec_t{ buf: unsafe.Pointer(&putcharBuf), bufLen: 1, } @@ -44,49 +35,6 @@ func putchar(c byte) { fd_write(stdout, &putcharIOVec, 1, &nwritten) } -var handleEvent func() - -//go:linkname setEventHandler syscall/js.setEventHandler -func setEventHandler(fn func()) { - handleEvent = fn -} - -//export resume -func resume() { - go func() { - handleEvent() - }() - scheduler() -} - -//export go_scheduler -func go_scheduler() { - scheduler() -} - -const asyncScheduler = true - -func ticksToNanoseconds(ticks timeUnit) int64 { - // The JavaScript API works in float64 milliseconds, so convert to - // nanoseconds first before converting to a timeUnit (which is a float64), - // to avoid precision loss. - return int64(ticks * 1e6) -} - -func nanosecondsToTicks(ns int64) timeUnit { - // The JavaScript API works in float64 milliseconds, so convert to timeUnit - // (which is a float64) first before dividing, to avoid precision loss. - return timeUnit(ns) / 1e6 -} - -// This function is called by the scheduler. -// Schedule a call to runtime.scheduler, do not actually sleep. -//export runtime.sleepTicks -func sleepTicks(d timeUnit) - -//export runtime.ticks -func ticks() timeUnit - // Abort executes the wasm 'unreachable' instruction. func abort() { trap() diff --git a/src/runtime/runtime_wasm_js.go b/src/runtime/runtime_wasm_js.go new file mode 100644 index 0000000000..ea1cc6e41d --- /dev/null +++ b/src/runtime/runtime_wasm_js.go @@ -0,0 +1,59 @@ +// +build wasm,!wasi + +package runtime + +import "unsafe" + +type timeUnit float64 // time in milliseconds, just like Date.now() in JavaScript + +//export _start +func _start() { + // These need to be initialized early so that the heap can be initialized. + heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) + heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) + + run() +} + +var handleEvent func() + +//go:linkname setEventHandler syscall/js.setEventHandler +func setEventHandler(fn func()) { + handleEvent = fn +} + +//export resume +func resume() { + go func() { + handleEvent() + }() + scheduler() +} + +//export go_scheduler +func go_scheduler() { + scheduler() +} + +const asyncScheduler = true + +func ticksToNanoseconds(ticks timeUnit) int64 { + // The JavaScript API works in float64 milliseconds, so convert to + // nanoseconds first before converting to a timeUnit (which is a float64), + // to avoid precision loss. + return int64(ticks * 1e6) +} + +func nanosecondsToTicks(ns int64) timeUnit { + // The JavaScript API works in float64 milliseconds, so convert to timeUnit + // (which is a float64) first before dividing, to avoid precision loss. + return timeUnit(ns) / 1e6 +} + +// This function is called by the scheduler. +// Schedule a call to runtime.scheduler, do not actually sleep. +//export runtime.sleepTicks +func sleepTicks(d timeUnit) + +//export runtime.ticks +func ticks() timeUnit diff --git a/src/runtime/runtime_wasm_wasi.go b/src/runtime/runtime_wasm_wasi.go new file mode 100644 index 0000000000..99a34ebb4f --- /dev/null +++ b/src/runtime/runtime_wasm_wasi.go @@ -0,0 +1,117 @@ +// +build wasm,wasi + +package runtime + +import ( + "unsafe" +) + +type timeUnit int64 + +//export _start +func _start() { + // These need to be initialized early so that the heap can be initialized. + heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) + heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) + run() +} + +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns) +} + +const ( + asyncScheduler = false + timePrecisionNanoseconds = 1000 // TODO: how can we determine the appropriate `precision`? +) + +var ( + sleepTicksSubscription = __wasi_subscription_t{ + userData: 0, + u: __wasi_subscription_u_t{ + tag: __wasi_eventtype_t_clock, + u: __wasi_subscription_clock_t{ + userData: 0, + id: 0, + timeout: 0, + precision: timePrecisionNanoseconds, + flags: 0, + }, + }, + } + sleepTicksResult = __wasi_event_t{} + sleepTicksNEvents uint32 +) + +func sleepTicks(d timeUnit) { + sleepTicksSubscription.u.u.timeout = int64(d) + poll_oneoff(&sleepTicksSubscription, &sleepTicksResult, 1, &sleepTicksNEvents) +} + +func ticks() timeUnit { + var nano int64 + clock_time_get(0, timePrecisionNanoseconds, &nano) + return timeUnit(nano) +} + +// Implementations of wasi_unstable APIs + +//go:wasm-module wasi_unstable +//export clock_time_get +func clock_time_get(clockid uint32, precision uint64, time *int64) (errno uint16) + +//go:wasm-module wasi_unstable +//export poll_oneoff +func poll_oneoff(in *__wasi_subscription_t, out *__wasi_event_t, nsubscriptions uint32, nevents *uint32) (errno uint16) + +type __wasi_eventtype_t = uint8 + +const ( + __wasi_eventtype_t_clock __wasi_eventtype_t = 0 + // TODO: __wasi_eventtype_t_fd_read __wasi_eventtype_t = 1 + // TODO: __wasi_eventtype_t_fd_write __wasi_eventtype_t = 2 +) + +type ( + // https://github.com/wasmerio/wasmer/blob/1.0.0-alpha3/lib/wasi/src/syscalls/types.rs#L584-L588 + __wasi_subscription_t struct { + userData uint64 + u __wasi_subscription_u_t + } + + __wasi_subscription_u_t struct { + tag __wasi_eventtype_t + + // TODO: support fd_read/fd_write event + u __wasi_subscription_clock_t + } + + // https://github.com/wasmerio/wasmer/blob/1.0.0-alpha3/lib/wasi/src/syscalls/types.rs#L711-L718 + __wasi_subscription_clock_t struct { + userData uint64 + id uint32 + timeout int64 + precision int64 + flags uint16 + } +) + +type ( + // https://github.com/wasmerio/wasmer/blob/1.0.0-alpha3/lib/wasi/src/syscalls/types.rs#L191-L198 + __wasi_event_t struct { + userData uint64 + errno uint16 + eventType __wasi_eventtype_t + + // only used for fd_read or fd_write events + // TODO: support fd_read/fd_write event + _ struct { + nBytes uint64 + flags uint16 + } + } +) diff --git a/src/syscall/syscall_baremetal.go b/src/syscall/syscall_baremetal.go index 28fa39ba85..2331101ba3 100644 --- a/src/syscall/syscall_baremetal.go +++ b/src/syscall/syscall_baremetal.go @@ -1,4 +1,4 @@ -// +build baremetal +// +build baremetal wasi package syscall diff --git a/src/syscall/tables_baremetal.go b/src/syscall/tables_baremetal.go index 47a536bffd..9677d77a09 100644 --- a/src/syscall/tables_baremetal.go +++ b/src/syscall/tables_baremetal.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build baremetal nintendoswitch +// +build baremetal nintendoswitch wasi package syscall diff --git a/targets/wasi.json b/targets/wasi.json new file mode 100644 index 0000000000..97f7848f9b --- /dev/null +++ b/targets/wasi.json @@ -0,0 +1,22 @@ +{ + "llvm-target": "wasm32--wasi", + "build-tags": ["wasm", "wasi"], + "goos": "linux", + "goarch": "arm", + "compiler": "clang", + "linker": "wasm-ld", + "cflags": [ + "--target=wasm32--wasi", + "--sysroot={root}/lib/wasi-libc/sysroot", + "-Oz" + ], + "ldflags": [ + "--allow-undefined", + "--no-threads", + "--stack-first", + "--export-dynamic", + "--no-demangle", + "{root}/lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a" + ], + "emulator": ["wasmtime"] +} diff --git a/transform/gc.go b/transform/gc.go index 9329644522..0688604986 100644 --- a/transform/gc.go +++ b/transform/gc.go @@ -19,6 +19,7 @@ func MakeGCStackSlots(mod llvm.Module) bool { } stackChainStart := mod.NamedGlobal("runtime.stackChainStart") if !stackChainStart.IsNil() { + stackChainStart.SetLinkage(llvm.InternalLinkage) stackChainStart.SetInitializer(llvm.ConstNull(stackChainStart.Type().ElementType())) stackChainStart.SetGlobalConstant(true) } @@ -94,6 +95,7 @@ func MakeGCStackSlots(mod llvm.Module) bool { } return false } + stackChainStart.SetLinkage(llvm.InternalLinkage) stackChainStartType := stackChainStart.Type().ElementType() stackChainStart.SetInitializer(llvm.ConstNull(stackChainStartType)) @@ -333,11 +335,13 @@ func AddGlobalsBitmap(mod llvm.Module) bool { // Update trackedGlobalsStart, which points to the globals bundle. trackedGlobalsStart := llvm.ConstPtrToInt(globalsBundle, uintptrType) mod.NamedGlobal("runtime.trackedGlobalsStart").SetInitializer(trackedGlobalsStart) + mod.NamedGlobal("runtime.trackedGlobalsStart").SetLinkage(llvm.InternalLinkage) // Update trackedGlobalsLength, which contains the length (in words) of the // globals bundle. alignment := targetData.PrefTypeAlignment(llvm.PointerType(ctx.Int8Type(), 0)) trackedGlobalsLength := llvm.ConstInt(uintptrType, targetData.TypeAllocSize(globalsBundleType)/uint64(alignment), false) + mod.NamedGlobal("runtime.trackedGlobalsLength").SetLinkage(llvm.InternalLinkage) mod.NamedGlobal("runtime.trackedGlobalsLength").SetInitializer(trackedGlobalsLength) // Create a bitmap (a new global) that stores for each word in the globals @@ -357,6 +361,7 @@ func AddGlobalsBitmap(mod llvm.Module) bool { bitmapOld.ReplaceAllUsesWith(llvm.ConstBitCast(bitmapNew, bitmapOld.Type())) bitmapNew.SetInitializer(bitmapArray) bitmapNew.SetName("runtime.trackedGlobalsBitmap") + bitmapNew.SetLinkage(llvm.InternalLinkage) return true // the IR was changed } diff --git a/transform/testdata/gc-globals.out.ll b/transform/testdata/gc-globals.out.ll index 45b80b7d84..905e8eafa1 100644 --- a/transform/testdata/gc-globals.out.ll +++ b/transform/testdata/gc-globals.out.ll @@ -7,11 +7,11 @@ target triple = "wasm32-unknown-unknown-wasm" @globalInt = global i32 5 @constString = constant %runtime._string zeroinitializer @constInterface = constant %runtime._interface zeroinitializer -@runtime.trackedGlobalsLength = global i32 4 +@runtime.trackedGlobalsLength = internal global i32 4 @runtime.trackedGlobalsBitmap = external global [0 x i8] -@runtime.trackedGlobalsStart = global i32 ptrtoint ({ %runtime._string, %runtime._interface }* @tinygo.trackedGlobals to i32) +@runtime.trackedGlobalsStart = internal global i32 ptrtoint ({ %runtime._string, %runtime._interface }* @tinygo.trackedGlobals to i32) @tinygo.trackedGlobals = internal unnamed_addr global { %runtime._string, %runtime._interface } zeroinitializer -@runtime.trackedGlobalsBitmap.1 = global [1 x i8] c"\09" +@runtime.trackedGlobalsBitmap.1 = internal global [1 x i8] c"\09" define void @main() { %1 = load i32, i32* @globalInt diff --git a/transform/testdata/gc-stackslots.out.ll b/transform/testdata/gc-stackslots.out.ll index efcc3ed25e..9acb0abe7d 100644 --- a/transform/testdata/gc-stackslots.out.ll +++ b/transform/testdata/gc-stackslots.out.ll @@ -3,7 +3,7 @@ target triple = "wasm32-unknown-unknown-wasm" %runtime.stackChainObject = type { %runtime.stackChainObject*, i32 } -@runtime.stackChainStart = global %runtime.stackChainObject* null +@runtime.stackChainStart = internal global %runtime.stackChainObject* null @someGlobal = global i8 3 declare void @runtime.trackPointer(i8* nocapture readonly)