Files
wazero/internal/gojs/runtime.go
2022-11-28 10:00:07 +08:00

178 lines
5.9 KiB
Go

package gojs
import (
"context"
"fmt"
"io"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/gojs/spfunc"
"github.com/tetratelabs/wazero/internal/wasm"
)
const (
i32, i64 = api.ValueTypeI32, api.ValueTypeI64
functionWasmExit = "runtime.wasmExit"
functionWasmWrite = "runtime.wasmWrite"
functionResetMemoryDataView = "runtime.resetMemoryDataView"
functionNanotime1 = "runtime.nanotime1"
functionWalltime = "runtime.walltime"
functionScheduleTimeoutEvent = "runtime.scheduleTimeoutEvent" // TODO: trigger usage
functionClearTimeoutEvent = "runtime.clearTimeoutEvent" // TODO: trigger usage
functionGetRandomData = "runtime.getRandomData"
)
// WasmExit implements runtime.wasmExit which supports runtime.exit.
//
// See https://github.com/golang/go/blob/go1.19/src/runtime/sys_wasm.go#L28
var WasmExit = spfunc.MustCallFromSP(false, &wasm.HostFunc{
ExportNames: []string{functionWasmExit},
Name: functionWasmExit,
ParamTypes: []api.ValueType{i32},
ParamNames: []string{"code"},
Code: &wasm.Code{
IsHostFunction: true,
GoFunc: api.GoModuleFunc(wasmExit),
},
})
func wasmExit(ctx context.Context, mod api.Module, stack []uint64) {
code := uint32(stack[0])
getState(ctx).clear()
_ = mod.CloseWithExitCode(ctx, code) // TODO: should ours be signed bit (like -1 == 255)?
}
// WasmWrite implements runtime.wasmWrite which supports runtime.write and
// runtime.writeErr. This implements `println`.
//
// See https://github.com/golang/go/blob/go1.19/src/runtime/os_js.go#L29
var WasmWrite = spfunc.MustCallFromSP(false, &wasm.HostFunc{
ExportNames: []string{functionWasmWrite},
Name: functionWasmWrite,
ParamTypes: []api.ValueType{i32, i32, i32},
ParamNames: []string{"fd", "p", "n"},
Code: &wasm.Code{
IsHostFunction: true,
GoFunc: api.GoModuleFunc(wasmWrite),
},
})
func wasmWrite(ctx context.Context, mod api.Module, stack []uint64) {
fd, p, n := uint32(stack[0]), uint32(stack[1]), uint32(stack[2])
var writer io.Writer
switch fd {
case 1:
writer = mod.(*wasm.CallContext).Sys.Stdout()
case 2:
writer = mod.(*wasm.CallContext).Sys.Stderr()
default:
// Keep things simple by expecting nothing past 2
panic(fmt.Errorf("unexpected fd %d", fd))
}
if _, err := writer.Write(mustRead(ctx, mod.Memory(), "p", p, n)); err != nil {
panic(fmt.Errorf("error writing p: %w", err))
}
}
// ResetMemoryDataView signals wasm.OpcodeMemoryGrow happened, indicating any
// cached view of memory should be reset.
//
// See https://github.com/golang/go/blob/go1.19/src/runtime/mem_js.go#L82
var ResetMemoryDataView = &wasm.HostFunc{
ExportNames: []string{functionResetMemoryDataView},
Name: functionResetMemoryDataView,
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
ParamNames: []string{parameterSp},
// TODO: Compiler-based memory.grow callbacks are ignored until we have a generic solution #601
Code: &wasm.Code{IsHostFunction: true, Body: []byte{wasm.OpcodeEnd}},
}
// Nanotime1 implements runtime.nanotime which supports time.Since.
//
// See https://github.com/golang/go/blob/go1.19/src/runtime/sys_wasm.s#L184
var Nanotime1 = spfunc.MustCallFromSP(false, &wasm.HostFunc{
ExportNames: []string{functionNanotime1},
Name: functionNanotime1,
ResultTypes: []api.ValueType{i64},
Code: &wasm.Code{
IsHostFunction: true,
GoFunc: api.GoModuleFunc(nanotime1),
},
})
func nanotime1(ctx context.Context, mod api.Module, stack []uint64) {
time := mod.(*wasm.CallContext).Sys.Nanotime(ctx)
stack[0] = api.EncodeI64(time)
}
// Walltime implements runtime.walltime which supports time.Now.
//
// See https://github.com/golang/go/blob/go1.19/src/runtime/sys_wasm.s#L188
var Walltime = spfunc.MustCallFromSP(false, &wasm.HostFunc{
ExportNames: []string{functionWalltime},
Name: functionWalltime,
ResultTypes: []api.ValueType{i64, i32},
Code: &wasm.Code{
IsHostFunction: true,
GoFunc: api.GoModuleFunc(walltime),
},
})
func walltime(ctx context.Context, mod api.Module, stack []uint64) {
sec, nsec := mod.(*wasm.CallContext).Sys.Walltime(ctx)
stack[0] = api.EncodeI64(sec)
stack[1] = api.EncodeI32(nsec)
}
// ScheduleTimeoutEvent implements runtime.scheduleTimeoutEvent which supports
// runtime.notetsleepg used by runtime.signal_recv.
//
// Unlike other most functions prefixed by "runtime.", this both launches a
// goroutine and invokes code compiled into wasm "resume".
//
// See https://github.com/golang/go/blob/go1.19/src/runtime/sys_wasm.s#L192
var ScheduleTimeoutEvent = stubFunction(functionScheduleTimeoutEvent)
// ^^ stubbed because signal handling is not implemented in GOOS=js
// ClearTimeoutEvent implements runtime.clearTimeoutEvent which supports
// runtime.notetsleepg used by runtime.signal_recv.
//
// See https://github.com/golang/go/blob/go1.19/src/runtime/sys_wasm.s#L196
var ClearTimeoutEvent = stubFunction(functionClearTimeoutEvent)
// ^^ stubbed because signal handling is not implemented in GOOS=js
// GetRandomData implements runtime.getRandomData, which initializes the seed
// for runtime.fastrand.
//
// See https://github.com/golang/go/blob/go1.19/src/runtime/sys_wasm.s#L200
var GetRandomData = spfunc.MustCallFromSP(false, &wasm.HostFunc{
ExportNames: []string{functionGetRandomData},
Name: functionGetRandomData,
ParamTypes: []api.ValueType{i32, i32},
ParamNames: []string{"buf", "bufLen"},
Code: &wasm.Code{
IsHostFunction: true,
GoFunc: api.GoModuleFunc(getRandomData),
},
})
func getRandomData(ctx context.Context, mod api.Module, stack []uint64) {
randSource := mod.(*wasm.CallContext).Sys.RandSource()
buf, bufLen := uint32(stack[0]), uint32(stack[1])
r := mustRead(ctx, mod.Memory(), "r", buf, bufLen)
if n, err := randSource.Read(r); err != nil {
panic(fmt.Errorf("RandSource.Read(r /* len=%d */) failed: %w", bufLen, err))
} else if uint32(n) != bufLen {
panic(fmt.Errorf("RandSource.Read(r /* len=%d */) read %d bytes", bufLen, n))
}
}