diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 9e87265152..2bf0ffa449 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -298,37 +298,6 @@ jobs: -f ../.github/wasi_testsuite_skip.json \ -r ../.github/wasi_testsuite_adapter.py - gojs_stdlib: - name: Go (js) (${{ matrix.os }}) - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false # don't fail fast as sometimes failures are arch/OS specific - matrix: - os: [ubuntu-22.04, macos-12] # GOOS=js isn't supposed to work on windows. See #1222 - - steps: - - uses: actions/setup-go@v4 - with: - go-version: ${{ env.GO_VERSION }} - - - name: Checkout wazero - uses: actions/checkout@v3 - - - name: Install wazero - run: go install ./cmd/wazero - - - name: Build gojs test binaries - env: - GOOS: js - GOARCH: wasm - run: | # Only test os package as this is being replaced by GOOS=wasip1 - mkdir ~/bin && cd ~/bin - go test -c -o os.wasm os - - - name: Run tests - run: | # skip tests that use functionality not also used in GOOS=wasip1 - cd $(go env GOROOT)/src/os; wazero run -mount=/:/ ~/bin/os.wasm -test.v -test.skip '^Test(Chmod|Truncate|LongPath|Chown|FileChown).*$' - go_tests: # Due to the embedding of the GOROOT of the building env(https://github.com/golang/go/blob/3c59639b902fada0a2e5a6a35bafd10fc9183b89/src/os/os_test.go#L112), # we have to build and cache on each OS unlike others in this file. diff --git a/Makefile b/Makefile index 8037534525..9017050180 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ build.bench: .PHONY: test.examples test.examples: - @go test $(go_test_options) ./examples/... ./imports/assemblyscript/example/... ./imports/emscripten/... ./experimental/gojs/example/... ./imports/wasi_snapshot_preview1/example/... + @go test $(go_test_options) ./examples/... ./imports/assemblyscript/example/... ./imports/emscripten/... ./imports/wasi_snapshot_preview1/example/... .PHONY: build.examples.as build.examples.as: @@ -232,7 +232,7 @@ check: @GOARCH=amd64 GOOS=plan9 go build ./... # Ensure we build on gojs. See #1526. @GOARCH=wasm GOOS=js go build ./... -# Ensure we build on gojs. See #1526. +# Ensure we build on wasip1. See #1526. @GOARCH=wasm GOOS=wasip1 go build ./... # Ensure we build on aix. See #1723 @GOARCH=ppc64 GOOS=aix go build ./... diff --git a/cmd/wazero/wazero.go b/cmd/wazero/wazero.go index 45cfc5f120..7d4aae4664 100644 --- a/cmd/wazero/wazero.go +++ b/cmd/wazero/wazero.go @@ -18,13 +18,11 @@ import ( "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/experimental" - "github.com/tetratelabs/wazero/experimental/gojs" "github.com/tetratelabs/wazero/experimental/logging" "github.com/tetratelabs/wazero/experimental/opt" "github.com/tetratelabs/wazero/experimental/sock" "github.com/tetratelabs/wazero/experimental/sysfs" "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" - "github.com/tetratelabs/wazero/internal/platform" internalsys "github.com/tetratelabs/wazero/internal/sys" "github.com/tetratelabs/wazero/internal/version" "github.com/tetratelabs/wazero/sys" @@ -258,7 +256,7 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer) int { env = append(env, fields[0], fields[1]) } - rc, rootPath, fsConfig := validateMounts(mounts, stdErr) + rc, _, fsConfig := validateMounts(mounts, stdErr) if rc != 0 { return rc } @@ -347,30 +345,6 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer) int { // Instantiate our binary, but using the old import names. _, err = rt.InstantiateModule(ctx, guest, conf) } - case modeGo: - // Fail fast on multiple mounts with the deprecated GOOS=js. - // GOOS=js will be removed in favor of GOOS=wasip1 once v1.22 is out. - if count := len(mounts); count > 1 || (count == 1 && rootPath == "") { - fmt.Fprintf(stdErr, "invalid mount: only root mounts supported in GOOS=js: %v\n"+ - "Consider switching to GOOS=wasip1.\n", mounts) - return 1 - } - - gojs.MustInstantiate(ctx, rt, guest) - - config := gojs.NewConfig(conf) - - // Strip the volume of the path, for example C:\ - rootDir := rootPath[len(filepath.VolumeName(rootPath)):] - - // If the user mounted the entire filesystem, try to inherit the CWD. - // This is better than introducing a flag just for GOOS=js, especially - // as removing flags breaks syntax compat. - if platform.ToPosixPath(rootDir) == "/" { - config = config.WithOSWorkdir() - } - - err = gojs.Run(ctx, rt, guest, config) case modeDefault: _, err = rt.InstantiateModule(ctx, guest, conf) } @@ -468,7 +442,6 @@ const ( modeDefault importMode = iota modeWasi modeWasiUnstable - modeGo ) type importMode uint @@ -481,8 +454,6 @@ func detectImports(imports []api.FunctionDefinition) importMode { return modeWasi case "wasi_unstable": return modeWasiUnstable - case "go", "gojs": - return modeGo } } return modeDefault diff --git a/cmd/wazero/wazero_test.go b/cmd/wazero/wazero_test.go index c57799dec8..e311249c00 100644 --- a/cmd/wazero/wazero_test.go +++ b/cmd/wazero/wazero_test.go @@ -10,7 +10,6 @@ import ( "os/exec" "path" "path/filepath" - "runtime" "strings" "testing" @@ -39,9 +38,6 @@ var wasmWasiFd []byte //go:embed testdata/wasi_random_get.wasm var wasmWasiRandomGet []byte -// wasmCatGo is compiled on demand with `GOOS=js GOARCH=wasm` -var wasmCatGo []byte - //go:embed testdata/cat/cat-tinygo.wasm var wasmCatTinygo []byte @@ -49,15 +45,9 @@ var wasmCatTinygo []byte var wasmWasiUnstable []byte func TestMain(m *testing.M) { - // For some reason, riscv64 fails to see directory listings. - if a := runtime.GOARCH; a == "riscv64" { - log.Println("main: skipping due to not yet supported GOARCH:", a) - os.Exit(0) - } - - // Notably our scratch containers don't have go, so don't fail tests. - if err := compileGoJS(); err != nil { - log.Println("main: Skipping GOOS=js GOARCH=wasm tests due to:", err) + cmd := exec.Command("go", "version") + if _, err := cmd.CombinedOutput(); err != nil { + log.Println("main: cli test is only supported on a machine with Go installed") os.Exit(0) } os.Exit(m.Run()) @@ -231,12 +221,7 @@ func TestRun(t *testing.T) { bearPath := filepath.Join(bearDir, "bear.txt") bearStat, err := os.Stat(bearPath) require.NoError(t, err) - bearMtime := bearStat.ModTime().UnixMilli() bearMtimeNano := bearStat.ModTime().UnixNano() - // The file is world read, but windows cannot see that and reports world - // write. Hence, we save off the current interpretation of mode for - // comparison. - bearMode := bearStat.Mode() existingDir1 := filepath.Join(tmpDir, "existing1") require.NoError(t, os.Mkdir(existingDir1, 0o700)) @@ -369,71 +354,6 @@ func TestRun(t *testing.T) { <== errno=ESUCCESS `, }, - { - name: "GOOS=js GOARCH=wasm", - wasm: wasmCatGo, - wazeroOpts: []string{fmt.Sprintf("--mount=%s:/", bearDir)}, - wasmArgs: []string{"/bear.txt"}, - expectedStdout: "pooh\n", - }, - { - name: "GOOS=js GOARCH=wasm workdir", - wasm: wasmCatGo, - wazeroOpts: []string{ - // --mount=X:\:/ on Windows, --mount=/:/ everywhere else - "--mount=" + filepath.VolumeName(bearDir) + string(os.PathSeparator) + ":/", - }, - workdir: bearDir, - wasmArgs: []string{"bear.txt"}, - expectedStdout: "pooh\n", - }, - { - name: "GOOS=js GOARCH=wasm readonly", - wasm: wasmCatGo, - wazeroOpts: []string{fmt.Sprintf("--mount=%s:/:ro", bearDir)}, - wasmArgs: []string{"/bear.txt"}, - expectedStdout: "pooh\n", - }, - { - name: "GOOS=js GOARCH=wasm hostlogging=proc", - wasm: wasmCatGo, - wazeroOpts: []string{"--hostlogging=proc", fmt.Sprintf("--mount=%s:/:ro", bearDir)}, - wasmArgs: []string{"/not-bear.txt"}, - expectedStderr: `==> go.runtime.wasmExit(code=1) -<== -`, - expectedExitCode: 1, - }, - { - name: "GOOS=js GOARCH=wasm hostlogging=filesystem", - wasm: wasmCatGo, - wazeroOpts: []string{"--hostlogging=filesystem", fmt.Sprintf("--mount=%s:/", bearDir)}, - wasmArgs: []string{"/bear.txt"}, - expectedStdout: "pooh\n", - expectedStderr: fmt.Sprintf(`==> go.syscall/js.valueCall(fs.open(path=/bear.txt,flags=,perm=----------)) -<== (err=,fd=4) -==> go.syscall/js.valueCall(fs.fstat(fd=4)) -<== (err=,stat={isDir=false,mode=%[1]s,size=5,mtimeMs=%[2]d}) -==> go.syscall/js.valueCall(fs.fstat(fd=4)) -<== (err=,stat={isDir=false,mode=%[1]s,size=5,mtimeMs=%[2]d}) -==> go.syscall/js.valueCall(fs.read(fd=4,offset=0,byteCount=512,fOffset=)) -<== (err=,n=5) -==> go.syscall/js.valueCall(fs.read(fd=4,offset=0,byteCount=507,fOffset=)) -<== (err=,n=0) -==> go.syscall/js.valueCall(fs.close(fd=4)) -<== (err=,ok=true) -`, bearMode, bearMtime), - }, - { - name: "GOOS=js GOARCH=wasm not root mount", - wasm: wasmCatGo, - wazeroOpts: []string{"--hostlogging=proc", fmt.Sprintf("--mount=%s:/animals:ro", bearDir)}, - wasmArgs: []string{"/not-bear.txt"}, - expectedStderr: fmt.Sprintf(`invalid mount: only root mounts supported in GOOS=js: [%s:/animals:ro] -Consider switching to GOOS=wasip1. -`, bearDir), - expectedExitCode: 1, - }, { name: "cachedir existing absolute", wazeroOpts: []string{"--cachedir=" + existingDir1}, @@ -530,22 +450,7 @@ Consider switching to GOOS=wasip1. }, } - cryptoTest := test{ - name: "GOOS=js GOARCH=wasm hostlogging=filesystem,random", - wasm: wasmCatGo, - wazeroOpts: []string{"--hostlogging=filesystem,random"}, - wasmArgs: []string{"/bear.txt"}, - expectedStderr: `==> go.runtime.getRandomData(r_len=32) -<== -==> go.runtime.getRandomData(r_len=8) -<== -==> go.syscall/js.valueCall(fs.open(path=/bear.txt,flags=,perm=----------)) -<== (err=functionality not supported,fd=0) -`, // Test only shows logging happens in two scopes; it is ok to fail. - expectedExitCode: 1, - } - - for _, tt := range append(tests, cryptoTest) { + for _, tt := range tests { tc := tt if tc.wasm == nil { @@ -681,13 +586,6 @@ func Test_detectImports(t *testing.T) { }, mode: modeWasiUnstable, }, - { - message: "GOOS=js GOARCH=wasm", - imports: []api.FunctionDefinition{ - importer{internalapi.WazeroOnlyType{}, "go", "syscall/js.valueCall"}, - }, - mode: modeGo, - }, } for _, tc := range tests { @@ -814,32 +712,7 @@ func runMain(t *testing.T, workdir string, args []string) (int, string, string) stderr := new(bytes.Buffer) exitCode := doMain(stdout, stderr) - // Handle "go" -> "gojs" module rename in Go 1.21 - stderrString := strings.ReplaceAll(stderr.String(), "==> gojs", "==> go") - return exitCode, stdout.String(), stderrString -} - -// compileGoJS compiles "testdata/cat/cat.go" on demand as the binary generated -// is too big (1.6MB) to check into the source tree. -func compileGoJS() (err error) { - dir, err := os.Getwd() - if err != nil { - return err - } - - srcDir := path.Join(dir, "testdata", "cat") - outPath := path.Join(srcDir, "cat-go.wasm") - - // This doesn't add "-ldflags=-s -w", as the binary size only changes 28KB. - cmd := exec.Command("go", "build", "-o", outPath, ".") - cmd.Dir = srcDir - cmd.Env = append(os.Environ(), "GOARCH=wasm", "GOOS=js", "GOWASM=satconv,signext") - if out, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("go build: %v\n%s", err, out) - } - - wasmCatGo, err = os.ReadFile(outPath) - return + return exitCode, stdout.String(), stderr.String() } func exist(path string) error { diff --git a/experimental/gojs/README.md b/experimental/gojs/README.md deleted file mode 100644 index 0cab3a4c9d..0000000000 --- a/experimental/gojs/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Overview - -When `GOOS=js` and `GOARCH=wasm`, Go's compiler targets WebAssembly Binary -format (%.wasm). - -Wazero's "github.com/tetratelabs/wazero/experimental/gojs" package allows you to run -a `%.wasm` file compiled by Go. This is similar to what is implemented in -[wasm_exec.js][1]. See https://wazero.io/languages/go/ for more. - -## Example - -wazero includes an [example](example) that implements the `cat` utility. - -## Experimental - -Go defines js "EXPERIMENTAL... exempt from the Go compatibility promise." -Accordingly, wazero cannot guarantee this will work from release to release, -or that usage will be relatively free of bugs. Moreover, [`GOOS=wasip1`][2] -will be shipped in Go 1.21. wazero will remove this package after Go 1.22 is -released. - -Due to these concerns and the relatively high implementation overhead, most -will choose TinyGo instead of gojs. - -[1]: https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js -[2]: https://github.com/golang/go/issues/58141 diff --git a/experimental/gojs/example/.gitignore b/experimental/gojs/example/.gitignore deleted file mode 100644 index f7aa9b7a36..0000000000 --- a/experimental/gojs/example/.gitignore +++ /dev/null @@ -1 +0,0 @@ -main.wasm diff --git a/experimental/gojs/example/README.md b/experimental/gojs/example/README.md deleted file mode 100644 index e0870a3d28..0000000000 --- a/experimental/gojs/example/README.md +++ /dev/null @@ -1,18 +0,0 @@ -## gojs example - -This shows how to use Wasm built by go using `GOOS=js GOARCH=wasm`. Notably, -this uses filesystem support. - -```bash -$ go run cat.go /test.txt -greet filesystem -``` - -Internally, this uses [gojs](../README.md), which implements the custom host -functions required by Go. - -Notes: -* `GOOS=js GOARCH=wasm` wazero be removed after Go 1.22 is released. Please - switch to `GOOS=wasip1 GOARCH=wasm` released in Go 1.21. -* `GOWASM=satconv,signext` enables features in WebAssembly Core Specification - 2.0. diff --git a/experimental/gojs/example/cat.go b/experimental/gojs/example/cat.go deleted file mode 100644 index fbed5f406b..0000000000 --- a/experimental/gojs/example/cat.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "context" - "log" - "os" - "path" - "testing/fstest" - "time" - - "github.com/tetratelabs/wazero" - "github.com/tetratelabs/wazero/experimental/gojs" -) - -// main invokes Wasm compiled via `GOOS=js GOARCH=wasm`, which writes an input -// file to stdout, just like `cat`. -// -// This shows how to integrate a filesystem with wasm using gojs. -func main() { - // Read the binary compiled with `GOOS=js GOARCH=wasm`. - bin, err := os.ReadFile(path.Join("cat", "main.wasm")) - if err != nil { - log.Panicln(err) - } - - // Choose the context to use for function calls. - ctx := context.Background() - - // Create a new WebAssembly Runtime. - r := wazero.NewRuntime(ctx) - defer r.Close(ctx) // This closes everything this Runtime created. - - // Compile the wasm binary to machine code. - start := time.Now() - guest, err := r.CompileModule(ctx, bin) - if err != nil { - log.Panicln(err) - } - compilationTime := time.Since(start).Milliseconds() - log.Printf("CompileModule took %dms", compilationTime) - - // Instantiate the host functions needed by the guest. - start = time.Now() - gojs.MustInstantiate(ctx, r, guest) - instantiateTime := time.Since(start).Milliseconds() - log.Printf("gojs.MustInstantiate took %dms", instantiateTime) - - fakeFilesystem := fstest.MapFS{"test.txt": {Data: []byte("greet filesystem\n")}} - - // Create the sandbox configuration used by the guest. - guestConfig := wazero.NewModuleConfig(). - // By default, I/O streams are discarded and there's no file system. - WithStdout(os.Stdout).WithStderr(os.Stderr). - WithFS(fakeFilesystem). - WithArgs("gojs", os.Args[1]) // only what's in the filesystem will work! - - // Execute the "run" function, which corresponds to "main" in stars/main.go. - start = time.Now() - err = gojs.Run(ctx, r, guest, gojs.NewConfig(guestConfig)) - runTime := time.Since(start).Milliseconds() - log.Printf("gojs.Run took %dms", runTime) - if err != nil { - log.Panicln(err) - } -} diff --git a/experimental/gojs/example/cat/.gitignore b/experimental/gojs/example/cat/.gitignore deleted file mode 100644 index c6698dea1a..0000000000 --- a/experimental/gojs/example/cat/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# GOOS=js GOARCH=wasm binaries are too huge to check-in -main.wasm diff --git a/experimental/gojs/example/cat/main.go b/experimental/gojs/example/cat/main.go deleted file mode 100644 index 0888df56e4..0000000000 --- a/experimental/gojs/example/cat/main.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -import ( - "os" -) - -// main runs cat: concatenate and print files. -func main() { - // Start at arg[1] because args[0] is the program name. - for i := 1; i < len(os.Args); i++ { - bytes, err := os.ReadFile(os.Args[i]) - if err != nil { - os.Exit(1) - } - - // Use write to avoid needing to worry about Windows newlines. - os.Stdout.Write(bytes) - } -} diff --git a/experimental/gojs/example/cat_test.go b/experimental/gojs/example/cat_test.go deleted file mode 100644 index 76668aff0f..0000000000 --- a/experimental/gojs/example/cat_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - "os/exec" - "testing" - - "github.com/tetratelabs/wazero/internal/testing/maintester" - "github.com/tetratelabs/wazero/internal/testing/require" -) - -// Test_main ensures the following will work: -// -// go run cat.go /test.txt -func Test_main(t *testing.T) { - stdout, stderr := maintester.TestMain(t, main, "cat", "test.txt") - require.Equal(t, "", stderr) - require.Equal(t, "greet filesystem\n", stdout) -} - -// TestMain compiles the wasm on-demand, which uses the runner's Go as opposed -// to a binary checked in, which would be pinned to one version. This is -// separate from Test_main to show that compilation doesn't dominate the -// execution time. -func TestMain(m *testing.M) { - // Notably our scratch containers don't have go, so don't fail tests. - if err := compileFromGo(); err != nil { - log.Println("Skipping tests due to:", err) - os.Exit(0) - } - os.Exit(m.Run()) -} - -// compileFromGo compiles "stars/main.go" on demand as the binary generated is -// too big (>7MB) to check into the source tree. -func compileFromGo() error { - cmd := exec.Command("go", "build", "-o", "main.wasm", ".") - cmd.Dir = "cat" - cmd.Env = append(os.Environ(), "GOARCH=wasm", "GOOS=js", "GOWASM=satconv,signext") - if out, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("go build: %v\n%s", err, out) - } - return nil -} diff --git a/experimental/gojs/gojs.go b/experimental/gojs/gojs.go deleted file mode 100644 index 4bbef6aa6d..0000000000 --- a/experimental/gojs/gojs.go +++ /dev/null @@ -1,199 +0,0 @@ -// Package gojs allows you to run wasm binaries compiled by Go when -// `GOOS=js GOARCH=wasm`. See https://wazero.io/languages/go/ for more. -// -// # Experimental -// -// Go defines js "EXPERIMENTAL... exempt from the Go compatibility promise." -// Accordingly, wazero cannot guarantee this will work from release to release, -// or that usage will be relatively free of bugs. Moreover, `GOOS=wasi` will -// happen, and once that's available in two releases wazero will remove this -// package. -// -// Due to these concerns and the relatively high implementation overhead, most -// will choose TinyGo instead of gojs. -package gojs - -import ( - "context" - "errors" - - "github.com/tetratelabs/wazero" - "github.com/tetratelabs/wazero/api" - "github.com/tetratelabs/wazero/internal/gojs" - internalconfig "github.com/tetratelabs/wazero/internal/gojs/config" - "github.com/tetratelabs/wazero/internal/gojs/run" - "github.com/tetratelabs/wazero/internal/wasm" -) - -// MustInstantiate calls Instantiate or panics on error. -// -// This is a simpler function for those who know host functions are not already -// instantiated, and don't need to unload them separate from the runtime. -func MustInstantiate(ctx context.Context, r wazero.Runtime, guest wazero.CompiledModule) { - if _, err := Instantiate(ctx, r, guest); err != nil { - panic(err) - } -} - -// Instantiate detects and instantiates host functions for wasm compiled with -// `GOOS=js GOARCH=wasm`. `guest` must be a result of `r.CompileModule`. -// -// # Notes -// -// - Failure cases are documented on wazero.Runtime InstantiateModule. -// - Closing the wazero.Runtime has the same effect as closing the result. -// - To add more functions to `goModule`, use FunctionExporter. -func Instantiate(ctx context.Context, r wazero.Runtime, guest wazero.CompiledModule) (api.Closer, error) { - goModule, err := detectGoModule(guest.ImportedFunctions()) - if err != nil { - return nil, err - } - builder := r.NewHostModuleBuilder(goModule) - NewFunctionExporter().ExportFunctions(builder) - return builder.Instantiate(ctx) -} - -// detectGoModule is needed because the module name defining host functions for -// `GOOS=js GOARCH=wasm` was renamed from "go" to "gojs" in Go 1.21. We can't -// use the version that compiles wazero because it could be different from what -// compiled the guest. -// -// See https://github.com/golang/go/commit/02411bcd7c8eda9c694a5755aff0a516d4983952 -func detectGoModule(imports []api.FunctionDefinition) (string, error) { - for _, f := range imports { - moduleName, _, _ := f.Import() - switch moduleName { - case "go", "gojs": - return moduleName, nil - } - } - return "", errors.New("guest wasn't compiled with GOOS=js GOARCH=wasm") -} - -// FunctionExporter builds host functions for wasm compiled with -// `GOOS=js GOARCH=wasm`. -type FunctionExporter interface { - // ExportFunctions builds functions to an existing host module builder. - // - // This should be named "go" or "gojs", depending on the version of Go the - // guest was compiled with. The module name changed from "go" to "gojs" in - // Go 1.21. - ExportFunctions(wazero.HostModuleBuilder) -} - -// NewFunctionExporter returns a FunctionExporter object. -func NewFunctionExporter() FunctionExporter { - return &functionExporter{} -} - -type functionExporter struct{} - -// ExportFunctions implements FunctionExporter.ExportFunctions -func (e *functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) { - hfExporter := builder.(wasm.HostFuncExporter) - - hfExporter.ExportHostFunc(gojs.GetRandomData) - hfExporter.ExportHostFunc(gojs.Nanotime1) - hfExporter.ExportHostFunc(gojs.WasmExit) - hfExporter.ExportHostFunc(gojs.CopyBytesToJS) - hfExporter.ExportHostFunc(gojs.ValueCall) - hfExporter.ExportHostFunc(gojs.ValueGet) - hfExporter.ExportHostFunc(gojs.ValueIndex) - hfExporter.ExportHostFunc(gojs.ValueLength) - hfExporter.ExportHostFunc(gojs.ValueNew) - hfExporter.ExportHostFunc(gojs.ValueSet) - hfExporter.ExportHostFunc(gojs.WasmWrite) - hfExporter.ExportHostFunc(gojs.ResetMemoryDataView) - hfExporter.ExportHostFunc(gojs.Walltime) - hfExporter.ExportHostFunc(gojs.ScheduleTimeoutEvent) - hfExporter.ExportHostFunc(gojs.ClearTimeoutEvent) - hfExporter.ExportHostFunc(gojs.FinalizeRef) - hfExporter.ExportHostFunc(gojs.StringVal) - hfExporter.ExportHostFunc(gojs.ValueDelete) - hfExporter.ExportHostFunc(gojs.ValueSetIndex) - hfExporter.ExportHostFunc(gojs.ValueInvoke) - hfExporter.ExportHostFunc(gojs.ValuePrepareString) - hfExporter.ExportHostFunc(gojs.ValueInstanceOf) - hfExporter.ExportHostFunc(gojs.ValueLoadString) - hfExporter.ExportHostFunc(gojs.CopyBytesToGo) - hfExporter.ExportHostFunc(gojs.Debug) -} - -// Config extends wazero.ModuleConfig with GOOS=js specific extensions. -// Use NewConfig to create an instance. -type Config interface { - // WithOSWorkdir sets the initial working directory used to Run Wasm to - // the value of os.Getwd instead of the default of root "/". - // - // Here's an example that overrides this to the current directory: - // - // err = gojs.Run(ctx, r, compiled, gojs.NewConfig(moduleConfig). - // WithOSWorkdir()) - // - // Note: To use this feature requires mounting the real root directory via - // wazero.FSConfig `WithDirMount`. On windows, this root must be the same drive - // as the value of os.Getwd. For example, it would be an error to mount `C:\` - // as the guest path "", while the current directory is inside `D:\`. - WithOSWorkdir() Config -} - -// NewConfig returns a Config that can be used for configuring module instantiation. -func NewConfig(moduleConfig wazero.ModuleConfig) Config { - return &cfg{moduleConfig: moduleConfig, internal: internalconfig.NewConfig()} -} - -type cfg struct { - moduleConfig wazero.ModuleConfig - internal *internalconfig.Config -} - -func (c *cfg) clone() *cfg { - return &cfg{moduleConfig: c.moduleConfig, internal: c.internal.Clone()} -} - -// WithOSWorkdir implements Config.WithOSWorkdir -func (c *cfg) WithOSWorkdir() Config { - ret := c.clone() - ret.internal.OsWorkdir = true - return ret -} - -// Run instantiates a new module and calls "run" with the given config. -// -// # Parameters -// -// - ctx: context to use when instantiating the module and calling "run". -// - r: runtime to instantiate both the host and guest (compiled) module in. -// - compiled: guest binary compiled with `GOOS=js GOARCH=wasm` -// - config: the Config to use including wazero.ModuleConfig or extensions of -// it. -// -// # Example -// -// After compiling your Wasm binary with wazero.Runtime's `CompileModule`, run -// it like below: -// -// // Instantiate host functions needed by gojs -// gojs.MustInstantiate(ctx, r) -// -// // Assign any configuration relevant for your compiled wasm. -// config := gojs.NewConfig(wazero.NewConfig()) -// -// // Run your wasm, notably handing any ExitError -// err = gojs.Run(ctx, r, compiled, config) -// if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 { -// log.Panicln(err) -// } else if !ok { -// log.Panicln(err) -// } -// -// # Notes -// -// - Wasm generated by `GOOS=js GOARCH=wasm` is very slow to compile: Use -// wazero.RuntimeConfig with wazero.CompilationCache when re-running the -// same binary. -// - The guest module is closed after being run. -func Run(ctx context.Context, r wazero.Runtime, compiled wazero.CompiledModule, moduleConfig Config) error { - c := moduleConfig.(*cfg) - return run.Run(ctx, r, compiled, c.moduleConfig, c.internal) -} diff --git a/experimental/logging/log_listener.go b/experimental/logging/log_listener.go index 04e6d7a456..4b9205489b 100644 --- a/experimental/logging/log_listener.go +++ b/experimental/logging/log_listener.go @@ -8,7 +8,6 @@ import ( "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/experimental" aslogging "github.com/tetratelabs/wazero/internal/assemblyscript/logging" - gologging "github.com/tetratelabs/wazero/internal/gojs/logging" "github.com/tetratelabs/wazero/internal/logging" "github.com/tetratelabs/wazero/internal/wasip1" wasilogging "github.com/tetratelabs/wazero/internal/wasip1/logging" @@ -115,11 +114,6 @@ func (f *loggingListenerFactory) NewFunctionListener(fnd api.FunctionDefinition) return nil } pSampler, pLoggers, rLoggers = wasilogging.Config(fnd) - case "go", "gojs": - if !gologging.IsInLogScope(fnd, f.scopes) { - return nil - } - pSampler, pLoggers, rLoggers = gologging.Config(fnd, f.scopes) case "env": // env is difficult because the same module name is used for different // ABI. diff --git a/internal/fstest/fstest.go b/internal/fstest/fstest.go index 0c4f8d493a..300e306681 100644 --- a/internal/fstest/fstest.go +++ b/internal/fstest/fstest.go @@ -12,7 +12,7 @@ // } // // Failures found here should result in new tests in the appropriate package, -// for example, gojs, sysfs or wasi_snapshot_preview1. +// for example, sysfs or wasi_snapshot_preview1. // // This package must have no dependencies. Otherwise, compiling this with // TinyGo or `GOOS=js GOARCH=wasm` can become bloated or complicated. diff --git a/internal/gojs/argsenv.go b/internal/gojs/argsenv.go deleted file mode 100644 index f41a5b16f7..0000000000 --- a/internal/gojs/argsenv.go +++ /dev/null @@ -1,73 +0,0 @@ -package gojs - -import ( - "encoding/binary" - "errors" - - "github.com/tetratelabs/wazero/api" - "github.com/tetratelabs/wazero/internal/gojs/util" - "github.com/tetratelabs/wazero/internal/wasm" -) - -// Constants about memory layout. See REFERENCE.md -const ( - endOfPageZero = uint32(4096) // runtime.minLegalPointer - maxArgsAndEnviron = uint32(8192) // ld.wasmMinDataAddr - runtime.minLegalPointer - wasmMinDataAddr = endOfPageZero + maxArgsAndEnviron // ld.wasmMinDataAddr -) - -var le = binary.LittleEndian - -// WriteArgsAndEnviron writes arguments and environment variables to memory, so -// they can be read by main, Go compiles as the function export "run". -func WriteArgsAndEnviron(mod api.Module) (argc, argv uint32, err error) { - mem := mod.Memory() - sysCtx := mod.(*wasm.ModuleInstance).Sys - args := sysCtx.Args() - environ := sysCtx.Environ() - - argc = uint32(len(args)) - offset := endOfPageZero - - strPtr := func(val []byte, field string, i int) (ptr uint32) { - // TODO: return err and format "%s[%d], field, i" - ptr = offset - util.MustWrite(mem, field, offset, append(val, 0)) - offset += uint32(len(val) + 1) - if pad := offset % 8; pad != 0 { - offset += 8 - pad - } - return - } - - argvPtrLen := len(args) + 1 + len(environ) + 1 - argvPtrs := make([]uint32, 0, argvPtrLen) - for i, arg := range args { - argvPtrs = append(argvPtrs, strPtr(arg, "args", i)) - } - argvPtrs = append(argvPtrs, 0) - - for i, env := range environ { - argvPtrs = append(argvPtrs, strPtr(env, "env", i)) - } - argvPtrs = append(argvPtrs, 0) - - argv = offset - - stop := uint32(argvPtrLen << 3) // argvPtrLen * 8 - if offset+stop >= wasmMinDataAddr { - err = errors.New("total length of command line and environment variables exceeds limit") - } - - buf, ok := mem.Read(argv, stop) - if !ok { - panic("out of memory reading argvPtrs") - } - pos := uint32(0) - for _, ptr := range argvPtrs { - le.PutUint64(buf[pos:], uint64(ptr)) - pos += 8 - } - - return -} diff --git a/internal/gojs/argsenv_test.go b/internal/gojs/argsenv_test.go deleted file mode 100644 index 99592b5162..0000000000 --- a/internal/gojs/argsenv_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package gojs_test - -import ( - "testing" - - "github.com/tetratelabs/wazero" - "github.com/tetratelabs/wazero/internal/gojs/config" - "github.com/tetratelabs/wazero/internal/testing/require" -) - -func Test_argsAndEnv(t *testing.T) { - t.Parallel() - - stdout, stderr, err := compileAndRun(testCtx, "argsenv", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) { - return moduleConfig.WithEnv("c", "d").WithEnv("a", "b"), config.NewConfig() - }) - - require.Zero(t, stderr) - require.NoError(t, err) - require.Equal(t, ` -args 0 = test -args 1 = argsenv -environ 0 = c=d -environ 1 = a=b -`, stdout) -} diff --git a/internal/gojs/builtin.go b/internal/gojs/builtin.go deleted file mode 100644 index dc9a1053f7..0000000000 --- a/internal/gojs/builtin.go +++ /dev/null @@ -1,48 +0,0 @@ -package gojs - -import ( - "github.com/tetratelabs/wazero/internal/gojs/config" - "github.com/tetratelabs/wazero/internal/gojs/goos" -) - -// newJsGlobal = js.Global() // js.go init -func newJsGlobal(config *config.Config) *jsVal { - var fetchProperty interface{} = goos.Undefined - proc := &processState{ - cwd: config.Workdir, - umask: config.Umask, - } - - return newJsVal(goos.RefValueGlobal, "global"). - addProperties(map[string]interface{}{ - "Object": objectConstructor, - "Array": arrayConstructor, - "crypto": jsCrypto, - "Uint8Array": uint8ArrayConstructor, - "fetch": fetchProperty, - "process": newJsProcess(proc), - "fs": newJsFs(proc), - "Date": jsDateConstructor, - }) -} - -var ( - // Values below are not built-in, but verifiable by looking at Go's source. - // When marked "XX.go init", these are eagerly referenced during syscall.init - - // jsGo is not a constant - - // objectConstructor is used by js.ValueOf to make `map[string]any`. - // Get("Object") // js.go init - objectConstructor = newJsVal(goos.RefObjectConstructor, "Object") - - // arrayConstructor is used by js.ValueOf to make `[]any`. - // Get("Array") // js.go init - arrayConstructor = newJsVal(goos.RefArrayConstructor, "Array") - - // uint8ArrayConstructor = js.Global().Get("Uint8Array") - // // fs_js.go, rand_js.go init - // - // It has only one invocation pattern: `buf := uint8Array.New(len(b))` - uint8ArrayConstructor = newJsVal(goos.RefUint8ArrayConstructor, "Uint8Array") -) diff --git a/internal/gojs/compiler_test.go b/internal/gojs/compiler_test.go deleted file mode 100644 index b9cd319afc..0000000000 --- a/internal/gojs/compiler_test.go +++ /dev/null @@ -1,174 +0,0 @@ -package gojs_test - -import ( - "bytes" - "context" - _ "embed" - "fmt" - "log" - "os" - "os/exec" - "path" - "path/filepath" - "reflect" - "runtime" - "strings" - "testing" - "time" - - "github.com/tetratelabs/wazero" - "github.com/tetratelabs/wazero/experimental" - "github.com/tetratelabs/wazero/experimental/gojs" - "github.com/tetratelabs/wazero/internal/fstest" - internalgojs "github.com/tetratelabs/wazero/internal/gojs" - "github.com/tetratelabs/wazero/internal/gojs/config" - "github.com/tetratelabs/wazero/internal/gojs/run" -) - -type newConfig func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) - -func defaultConfig(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) { - return moduleConfig, config.NewConfig() -} - -func compileAndRun(ctx context.Context, arg string, config newConfig) (stdout, stderr string, err error) { - rt := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig(). - // In order to avoid race condition on scheduleTimeoutEvent, we need to set the memory max - // and WithMemoryCapacityFromMax(true) above. See #992. - WithMemoryCapacityFromMax(true). - // Set max to a high value, e.g. so that Test_stdio_large can pass. - WithMemoryLimitPages(1024). // 64MB - WithCompilationCache(cache)) - return compileAndRunWithRuntime(ctx, rt, arg, config) // use global runtime -} - -func compileAndRunWithRuntime(ctx context.Context, r wazero.Runtime, arg string, config newConfig) (stdout, stderr string, err error) { - // Note: this hits the file cache. - var guest wazero.CompiledModule - if guest, err = r.CompileModule(testCtx, testBin); err != nil { - log.Panicln(err) - } - - if _, err = gojs.Instantiate(ctx, r, guest); err != nil { - return - } - - var stdoutBuf, stderrBuf bytes.Buffer - mc, c := config(wazero.NewModuleConfig(). - WithStdout(&stdoutBuf). - WithStderr(&stderrBuf). - WithArgs("test", arg)) - - ctx = experimental.WithCloseNotifier(ctx, experimental.CloseNotifyFunc(func(ctx context.Context, exitCode uint32) { - s := ctx.Value(internalgojs.StateKey{}) - if want, have := internalgojs.NewState(c), s; !reflect.DeepEqual(want, have) { - log.Panicf("unexpected state: want %#v, have %#v", want, have) - } - })) - err = run.Run(ctx, r, guest, mc, c) - stdout = stdoutBuf.String() - stderr = stderrBuf.String() - return -} - -// testBin is not checked in as it is >7.5MB -var testBin []byte - -// testCtx is configured in TestMain to re-use wazero's compilation cache. -var ( - testCtx = context.Background() - testFS = fstest.FS - cache = wazero.NewCompilationCache() -) - -func TestMain(m *testing.M) { - // For some reason, windows and freebsd fail to compile with exit status 1. - if o := runtime.GOOS; o != "darwin" && o != "linux" { - log.Println("gojs: skipping due to not yet supported OS:", o) - os.Exit(0) - } - - // Find the go binary (if present), and compile the Wasm binary. - goBin, err := findGoBin() - if err != nil { - log.Println("gojs: skipping due missing Go binary:", err) - os.Exit(0) - } - if err = compileJsWasm(goBin); err != nil { - log.Panicln(err) - } - - // Define a compilation cache so that tests run faster. This works because - // all tests use the same binary. - compilationCacheDir, err := os.MkdirTemp("", "gojs") - if err != nil { - log.Panicln(err) - } - defer os.RemoveAll(compilationCacheDir) - cache, err := wazero.NewCompilationCacheWithDir(compilationCacheDir) - if err != nil { - log.Panicln(err) - } - - // Seed wazero's compilation cache to see any error up-front and to prevent - // one test from a cache-miss performance penalty. - r := wazero.NewRuntimeWithConfig(testCtx, wazero.NewRuntimeConfig().WithCompilationCache(cache)) - _, err = r.CompileModule(testCtx, testBin) - if err != nil { - log.Panicln(err) - } - - var exit int - defer func() { - cache.Close(testCtx) - r.Close(testCtx) - os.Exit(exit) - }() - exit = m.Run() -} - -// compileJsWasm allows us to generate a binary with runtime.GOOS=js and -// runtime.GOARCH=wasm. This intentionally does so on-demand, as it allows us -// to test the user's current version of Go, as opposed to a specific one. -// For example, this allows testing both Go 1.19 and 1.20 in CI. -func compileJsWasm(goBin string) error { - // Prepare the working directory. - workdir, err := os.MkdirTemp("", "example") - if err != nil { - return err - } - defer os.RemoveAll(workdir) - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - bin := path.Join(workdir, "out.wasm") - cmd := exec.CommandContext(ctx, goBin, "build", "-o", bin, ".") //nolint:gosec - cmd.Env = append(os.Environ(), "GOOS=js", "GOARCH=wasm", "GOWASM=satconv,signext") - cmd.Dir = "testdata" - out, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("couldn't compile %s: %w", string(out), err) - } - - testBin, err = os.ReadFile(bin) //nolint:gosec - return err -} - -func findGoBin() (string, error) { - binName := "go" - if runtime.GOOS == "windows" { - binName += ".exe" - } - goBin := filepath.Join(runtime.GOROOT(), "bin", binName) - if _, err := os.Stat(goBin); err == nil { - return goBin, nil - } - // Now, search the path - return exec.LookPath(binName) -} - -// logString handles the "go" -> "gojs" module rename in Go 1.21 -func logString(log bytes.Buffer) string { - return strings.ReplaceAll(log.String(), "==> gojs", "==> go") -} diff --git a/internal/gojs/config/config.go b/internal/gojs/config/config.go deleted file mode 100644 index b046a46ea2..0000000000 --- a/internal/gojs/config/config.go +++ /dev/null @@ -1,45 +0,0 @@ -// Package config exists to avoid dependency cycles when keeping most of gojs -// code internal. -package config - -import ( - "os" - "path/filepath" - - "github.com/tetratelabs/wazero/internal/platform" -) - -type Config struct { - OsWorkdir bool - - // Workdir is the actual working directory value. - Workdir string - Umask uint32 -} - -func NewConfig() *Config { - return &Config{ - OsWorkdir: false, - Workdir: "/", - Umask: uint32(0o0022), - } -} - -func (c *Config) Clone() *Config { - ret := *c // copy except maps which share a ref - return &ret -} - -func (c *Config) Init() error { - if c.OsWorkdir { - workdir, err := os.Getwd() - if err != nil { - return err - } - // Ensure if used on windows, the input path is translated to a POSIX one. - workdir = platform.ToPosixPath(workdir) - // Strip the volume of the path, for example C:\ - c.Workdir = workdir[len(filepath.VolumeName(workdir)):] - } - return nil -} diff --git a/internal/gojs/config/config_test.go b/internal/gojs/config/config_test.go deleted file mode 100644 index 621ed1646d..0000000000 --- a/internal/gojs/config/config_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package config - -import ( - "strings" - "testing" - - "github.com/tetratelabs/wazero/internal/testing/require" -) - -func TestConfig_Init(t *testing.T) { - t.Parallel() - - t.Run("Workdir", func(t *testing.T) { - c := NewConfig() - require.Equal(t, "/", c.Workdir) - require.False(t, c.OsWorkdir) - - c.OsWorkdir = true - - require.NoError(t, c.Init()) - actual := c.Workdir - - // Check c:\ or d:\ aren't retained. - require.Equal(t, -1, strings.IndexByte(actual, '\\')) - require.Equal(t, -1, strings.IndexByte(actual, ':')) - }) -} diff --git a/internal/gojs/crypto.go b/internal/gojs/crypto.go deleted file mode 100644 index 7b528da397..0000000000 --- a/internal/gojs/crypto.go +++ /dev/null @@ -1,31 +0,0 @@ -package gojs - -import ( - "context" - - "github.com/tetratelabs/wazero/api" - "github.com/tetratelabs/wazero/internal/gojs/custom" - "github.com/tetratelabs/wazero/internal/gojs/goos" - "github.com/tetratelabs/wazero/internal/wasm" -) - -// jsCrypto is used by crypto/rand.Read to gets random values. -// -// It has only one invocation pattern: -// -// jsCrypto.Call("getRandomValues", a /* uint8Array */) -// -// This is defined as `Get("crypto")` in rand_js.go init -var jsCrypto = newJsVal(goos.RefJsCrypto, custom.NameCrypto). - addFunction(custom.NameCryptoGetRandomValues, cryptoGetRandomValues{}) - -// cryptoGetRandomValues implements jsFn -type cryptoGetRandomValues struct{} - -func (cryptoGetRandomValues) invoke(_ context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - randSource := mod.(*wasm.ModuleInstance).Sys.RandSource() - - r := args[0].(*goos.ByteArray) - n, err := randSource.Read(r.Unwrap()) - return uint32(n), err -} diff --git a/internal/gojs/crypto_test.go b/internal/gojs/crypto_test.go deleted file mode 100644 index d496ae55b8..0000000000 --- a/internal/gojs/crypto_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package gojs_test - -import ( - "bytes" - "context" - "testing" - - "github.com/tetratelabs/wazero/experimental" - "github.com/tetratelabs/wazero/experimental/logging" - "github.com/tetratelabs/wazero/internal/testing/require" -) - -func Test_crypto(t *testing.T) { - t.Parallel() - - var log bytes.Buffer - loggingCtx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{}, - logging.NewHostLoggingListenerFactory(&log, logging.LogScopeRandom)) - - stdout, stderr, err := compileAndRun(loggingCtx, "crypto", defaultConfig) - - require.Zero(t, stderr) - require.NoError(t, err) - require.Equal(t, `7a0c9f9f0d -`, stdout) - require.Equal(t, `==> go.runtime.getRandomData(r_len=32) -<== -==> go.runtime.getRandomData(r_len=8) -<== -==> go.syscall/js.valueCall(crypto.getRandomValues(r_len=5)) -<== (n=5) -`, logString(log)) -} diff --git a/internal/gojs/custom/crypto.go b/internal/gojs/custom/crypto.go deleted file mode 100644 index ba7cc6d6f7..0000000000 --- a/internal/gojs/custom/crypto.go +++ /dev/null @@ -1,17 +0,0 @@ -package custom - -const ( - NameCrypto = "crypto" - NameCryptoGetRandomValues = "getRandomValues" -) - -// CryptoNameSection are the functions defined in the object named NameCrypto. -// Results here are those set to the current event object, but effectively are -// results of the host function. -var CryptoNameSection = map[string]*Names{ - NameCryptoGetRandomValues: { - Name: NameCryptoGetRandomValues, - ParamNames: []string{"r"}, - ResultNames: []string{"n"}, - }, -} diff --git a/internal/gojs/custom/date.go b/internal/gojs/custom/date.go deleted file mode 100644 index 0649122ac3..0000000000 --- a/internal/gojs/custom/date.go +++ /dev/null @@ -1,17 +0,0 @@ -package custom - -const ( - NameDate = "Date" - NameDateGetTimezoneOffset = "getTimezoneOffset" -) - -// DateNameSection are the functions defined in the object named NameDate. -// Results here are those set to the current event object, but effectively are -// results of the host function. -var DateNameSection = map[string]*Names{ - NameDateGetTimezoneOffset: { - Name: NameDateGetTimezoneOffset, - ParamNames: []string{}, - ResultNames: []string{"tz"}, - }, -} diff --git a/internal/gojs/custom/fs.go b/internal/gojs/custom/fs.go deleted file mode 100644 index fbdd8eeadf..0000000000 --- a/internal/gojs/custom/fs.go +++ /dev/null @@ -1,248 +0,0 @@ -package custom - -import "io/fs" - -const ( - NameFs = "fs" - NameFsOpen = "open" - NameFsStat = "stat" - NameFsFstat = "fstat" - NameFsLstat = "lstat" - NameFsClose = "close" - NameFsWrite = "write" - NameFsRead = "read" - NameFsReaddir = "readdir" - NameFsMkdir = "mkdir" - NameFsRmdir = "rmdir" - NameFsRename = "rename" - NameFsUnlink = "unlink" - NameFsUtimes = "utimes" - NameFsChmod = "chmod" - NameFsFchmod = "fchmod" - NameFsChown = "chown" - NameFsFchown = "fchown" - NameFsLchown = "lchown" - NameFsTruncate = "truncate" - NameFsFtruncate = "ftruncate" - NameFsReadlink = "readlink" - NameFsLink = "link" - NameFsSymlink = "symlink" - NameFsFsync = "fsync" -) - -// FsNameSection are the functions defined in the object named NameFs. Results -// here are those set to the current event object, but effectively are results -// of the host function. -var FsNameSection = map[string]*Names{ - NameFsOpen: { - Name: NameFsOpen, - ParamNames: []string{"path", "flags", "perm", NameCallback}, - ResultNames: []string{"err", "fd"}, - }, - NameFsStat: { - Name: NameFsStat, - ParamNames: []string{"path", NameCallback}, - ResultNames: []string{"err", "stat"}, - }, - NameFsFstat: { - Name: NameFsFstat, - ParamNames: []string{"fd", NameCallback}, - ResultNames: []string{"err", "stat"}, - }, - NameFsLstat: { - Name: NameFsLstat, - ParamNames: []string{"path", NameCallback}, - ResultNames: []string{"err", "stat"}, - }, - NameFsClose: { - Name: NameFsClose, - ParamNames: []string{"fd", NameCallback}, - ResultNames: []string{"err", "ok"}, - }, - NameFsRead: { - Name: NameFsRead, - ParamNames: []string{"fd", "buf", "offset", "byteCount", "fOffset", NameCallback}, - ResultNames: []string{"err", "n"}, - }, - NameFsWrite: { - Name: NameFsWrite, - ParamNames: []string{"fd", "buf", "offset", "byteCount", "fOffset", NameCallback}, - ResultNames: []string{"err", "n"}, - }, - NameFsReaddir: { - Name: NameFsReaddir, - ParamNames: []string{"path", NameCallback}, - ResultNames: []string{"err", "dirents"}, - }, - NameFsMkdir: { - Name: NameFsMkdir, - ParamNames: []string{"path", "perm", NameCallback}, - ResultNames: []string{"err", "fd"}, - }, - NameFsRmdir: { - Name: NameFsRmdir, - ParamNames: []string{"path", NameCallback}, - ResultNames: []string{"err", "ok"}, - }, - NameFsRename: { - Name: NameFsRename, - ParamNames: []string{"from", "to", NameCallback}, - ResultNames: []string{"err", "ok"}, - }, - NameFsUnlink: { - Name: NameFsUnlink, - ParamNames: []string{"path", NameCallback}, - ResultNames: []string{"err", "ok"}, - }, - NameFsUtimes: { - Name: NameFsUtimes, - ParamNames: []string{"path", "atime", "mtime", NameCallback}, - ResultNames: []string{"err", "ok"}, - }, - NameFsChmod: { - Name: NameFsChmod, - ParamNames: []string{"path", "mode", NameCallback}, - ResultNames: []string{"err", "ok"}, - }, - NameFsFchmod: { - Name: NameFsFchmod, - ParamNames: []string{"fd", "mode", NameCallback}, - ResultNames: []string{"err", "ok"}, - }, - NameFsChown: { - Name: NameFsChown, - ParamNames: []string{"path", "uid", "gid", NameCallback}, - ResultNames: []string{"err", "ok"}, - }, - NameFsFchown: { - Name: NameFsFchown, - ParamNames: []string{"fd", "uid", "gid", NameCallback}, - ResultNames: []string{"err", "ok"}, - }, - NameFsLchown: { - Name: NameFsLchown, - ParamNames: []string{"path", "uid", "gid", NameCallback}, - ResultNames: []string{"err", "ok"}, - }, - NameFsTruncate: { - Name: NameFsTruncate, - ParamNames: []string{"path", "length", NameCallback}, - ResultNames: []string{"err", "ok"}, - }, - NameFsFtruncate: { - Name: NameFsFtruncate, - ParamNames: []string{"fd", "length", NameCallback}, - ResultNames: []string{"err", "ok"}, - }, - NameFsReadlink: { - Name: NameFsReadlink, - ParamNames: []string{"path", NameCallback}, - ResultNames: []string{"err", "dst"}, - }, - NameFsLink: { - Name: NameFsLink, - ParamNames: []string{"path", "link", NameCallback}, - ResultNames: []string{"err", "ok"}, - }, - NameFsSymlink: { - Name: NameFsSymlink, - ParamNames: []string{"path", "link", NameCallback}, - ResultNames: []string{"err", "ok"}, - }, - NameFsFsync: { - Name: NameFsFsync, - ParamNames: []string{"fd", NameCallback}, - ResultNames: []string{"err", "ok"}, - }, -} - -// mode constants from syscall_js.go -const ( - S_IFSOCK = uint32(0o000140000) - S_IFLNK = uint32(0o000120000) - S_IFREG = uint32(0o000100000) - S_IFBLK = uint32(0o000060000) - S_IFDIR = uint32(0o000040000) - S_IFCHR = uint32(0o000020000) - S_IFIFO = uint32(0o000010000) - - S_ISUID = uint32(0o004000) - S_ISGID = uint32(0o002000) - S_ISVTX = uint32(0o001000) -) - -// ToJsMode is required because the mode property read in `GOOS=js` is -// incompatible with normal go. Particularly the directory flag isn't the same. -func ToJsMode(fm fs.FileMode) (jsMode uint32) { - switch { - case fm.IsRegular(): - jsMode = S_IFREG - case fm.IsDir(): - jsMode = S_IFDIR - case fm&fs.ModeSymlink != 0: - jsMode = S_IFLNK - case fm&fs.ModeDevice != 0: - // Unlike ModeDevice and ModeCharDevice, S_IFCHR and S_IFBLK are set - // mutually exclusively. - if fm&fs.ModeCharDevice != 0 { - jsMode = S_IFCHR - } else { - jsMode = S_IFBLK - } - case fm&fs.ModeNamedPipe != 0: - jsMode = S_IFIFO - case fm&fs.ModeSocket != 0: - jsMode = S_IFSOCK - default: // unknown - jsMode = 0 - } - - jsMode |= uint32(fm & fs.ModePerm) - - if fm&fs.ModeSetgid != 0 { - jsMode |= S_ISGID - } - if fm&fs.ModeSetuid != 0 { - jsMode |= S_ISUID - } - if fm&fs.ModeSticky != 0 { - jsMode |= S_ISVTX - } - return -} - -// FromJsMode is required because the mode property read in `GOOS=js` is -// incompatible with normal go. Particularly the directory flag isn't the same. -func FromJsMode(jsMode, umask uint32) (fm fs.FileMode) { - switch { - case jsMode&S_IFREG != 0: // zero - case jsMode&S_IFDIR != 0: - fm = fs.ModeDir - case jsMode&S_IFLNK != 0: - fm = fs.ModeSymlink - case jsMode&S_IFCHR != 0: - fm = fs.ModeDevice | fs.ModeCharDevice - case jsMode&S_IFBLK != 0: - fm = fs.ModeDevice - case jsMode&S_IFIFO != 0: - fm = fs.ModeNamedPipe - case jsMode&S_IFSOCK != 0: - fm = fs.ModeSocket - default: // unknown - fm = 0 - } - - fm |= fs.FileMode(jsMode) & fs.ModePerm - - if jsMode&S_ISGID != 0 { - fm |= fs.ModeSetgid - } - if jsMode&S_ISUID != 0 { - fm |= fs.ModeSetuid - } - if jsMode&S_ISVTX != 0 { - fm |= fs.ModeSticky - } - fm &= ^(fs.FileMode(umask)) - return -} diff --git a/internal/gojs/custom/fs_test.go b/internal/gojs/custom/fs_test.go deleted file mode 100644 index d3da015f72..0000000000 --- a/internal/gojs/custom/fs_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package custom - -import ( - "io/fs" - "os" - "testing" - - "github.com/tetratelabs/wazero/internal/testing/require" -) - -func Test_ToJsMode(t *testing.T) { - t.Run("/dev/null", func(t *testing.T) { - st, err := os.Stat(os.DevNull) - require.NoError(t, err) - - fm := ToJsMode(st.Mode()) - - // Should be a character device, and retain the permissions. - require.Equal(t, S_IFCHR|uint32(st.Mode().Perm()), fm) - }) -} - -func Test_FromJsMode(t *testing.T) { - t.Run("sticky bit", func(t *testing.T) { - jsMode := ToJsMode(0o0755 | fs.ModeSticky) - require.Equal(t, 0o0755|S_IFREG|S_ISVTX, jsMode) - }) -} diff --git a/internal/gojs/custom/names.go b/internal/gojs/custom/names.go deleted file mode 100644 index d74a663132..0000000000 --- a/internal/gojs/custom/names.go +++ /dev/null @@ -1,196 +0,0 @@ -// Package custom is similar to the WebAssembly Custom Sections. These are -// needed because `GOOS=js GOARCH=wasm` functions aren't defined naturally -// in WebAssembly. For example, every function has a single parameter "sp", -// which implicitly maps to stack parameters in this package. -package custom - -const ( - // NamePadding is a marker for a parameter which has no purpose, except - // padding. It should not be logged. - NamePadding = "padding" -) - -type Names struct { - // Name is the WebAssembly function name. - Name string - - // ParamNames are the parameters read in 8-byte strides from the stack - // pointer (SP). This may be nil or include NamePadding. - ParamNames []string - - // ResultNames are the results written in 8-byte strides from the stack - // pointer (SP), after ParamNames. - ResultNames []string -} - -const ( - NameCallback = "callback" - NameDebug = "debug" -) - -const ( - NameRuntimeWasmExit = "runtime.wasmExit" - NameRuntimeWasmWrite = "runtime.wasmWrite" - NameRuntimeResetMemoryDataView = "runtime.resetMemoryDataView" - NameRuntimeNanotime1 = "runtime.nanotime1" - NameRuntimeWalltime = "runtime.walltime" - NameRuntimeScheduleTimeoutEvent = "runtime.scheduleTimeoutEvent" - NameRuntimeClearTimeoutEvent = "runtime.clearTimeoutEvent" - NameRuntimeGetRandomData = "runtime.getRandomData" -) - -const ( - NameSyscallFinalizeRef = "syscall/js.finalizeRef" - NameSyscallStringVal = "syscall/js.stringVal" - NameSyscallValueGet = "syscall/js.valueGet" - NameSyscallValueSet = "syscall/js.valueSet" - NameSyscallValueDelete = "syscall/js.valueDelete" // stubbed - NameSyscallValueIndex = "syscall/js.valueIndex" - NameSyscallValueSetIndex = "syscall/js.valueSetIndex" // stubbed - NameSyscallValueCall = "syscall/js.valueCall" - NameSyscallValueInvoke = "syscall/js.valueInvoke" // stubbed - NameSyscallValueNew = "syscall/js.valueNew" - NameSyscallValueLength = "syscall/js.valueLength" - NameSyscallValuePrepareString = "syscall/js.valuePrepareString" - NameSyscallValueLoadString = "syscall/js.valueLoadString" - NameSyscallValueInstanceOf = "syscall/js.valueInstanceOf" // stubbed - NameSyscallCopyBytesToGo = "syscall/js.copyBytesToGo" - NameSyscallCopyBytesToJS = "syscall/js.copyBytesToJS" -) - -var NameSection = map[string]*Names{ - NameDebug: { - Name: NameDebug, - ParamNames: []string{}, - ResultNames: []string{}, - }, - - NameRuntimeWasmExit: { - Name: NameRuntimeWasmExit, - ParamNames: []string{"code"}, - ResultNames: []string{}, - }, - NameRuntimeWasmWrite: { - Name: NameRuntimeWasmWrite, - ParamNames: []string{"fd", "p", "p_len"}, - ResultNames: []string{}, - }, - NameRuntimeResetMemoryDataView: { - Name: NameRuntimeResetMemoryDataView, - ParamNames: []string{}, - ResultNames: []string{}, - }, - NameRuntimeNanotime1: { - Name: NameRuntimeNanotime1, - ParamNames: []string{}, - ResultNames: []string{"nsec"}, - }, - NameRuntimeWalltime: { - Name: NameRuntimeWalltime, - ParamNames: []string{}, - ResultNames: []string{"sec", "nsec"}, - }, - NameRuntimeScheduleTimeoutEvent: { - Name: NameRuntimeScheduleTimeoutEvent, - ParamNames: []string{"ms"}, - ResultNames: []string{"id"}, - }, - NameRuntimeClearTimeoutEvent: { - Name: NameRuntimeClearTimeoutEvent, - ParamNames: []string{"id"}, - ResultNames: []string{}, - }, - NameRuntimeGetRandomData: { - Name: NameRuntimeGetRandomData, - ParamNames: []string{"r", "r_len"}, - ResultNames: []string{}, - }, - - NameSyscallFinalizeRef: { - Name: NameSyscallFinalizeRef, - ParamNames: []string{"r"}, - ResultNames: []string{}, - }, - NameSyscallStringVal: { - Name: NameSyscallStringVal, - ParamNames: []string{"x", "x_len"}, - ResultNames: []string{"r"}, - }, - NameSyscallValueGet: { - Name: NameSyscallValueGet, - ParamNames: []string{"v", "p", "p_len"}, - ResultNames: []string{"r"}, - }, - NameSyscallValueSet: { - Name: NameSyscallValueSet, - ParamNames: []string{"v", "p", "p_len", "x"}, - ResultNames: []string{}, - }, - NameSyscallValueDelete: { - Name: NameSyscallValueDelete, - ParamNames: []string{"v", "p", "p_len"}, - ResultNames: []string{}, - }, - NameSyscallValueIndex: { - Name: NameSyscallValueIndex, - ParamNames: []string{"v", "i"}, - ResultNames: []string{"r"}, - }, - NameSyscallValueSetIndex: { - Name: NameSyscallValueSetIndex, - ParamNames: []string{"v", "i", "x"}, - ResultNames: []string{}, - }, - NameSyscallValueCall: { - Name: NameSyscallValueCall, - ParamNames: []string{"v", "m", "m_len", "args", "args_len", NamePadding}, - ResultNames: []string{"res", "ok"}, - }, - NameSyscallValueInvoke: { - Name: NameSyscallValueInvoke, - ParamNames: []string{"v", "args", "args_len", NamePadding}, - ResultNames: []string{"res", "ok"}, - }, - NameSyscallValueNew: { - Name: NameSyscallValueNew, - ParamNames: []string{"v", "args", "args_len", NamePadding}, - ResultNames: []string{"res", "ok"}, - }, - NameSyscallValueLength: { - Name: NameSyscallValueLength, - ParamNames: []string{"v"}, - ResultNames: []string{"len"}, - }, - NameSyscallValuePrepareString: { - Name: NameSyscallValuePrepareString, - ParamNames: []string{"v"}, - ResultNames: []string{"str", "length"}, - }, - NameSyscallValueLoadString: { - Name: NameSyscallValueLoadString, - ParamNames: []string{"v", "b", "b_len"}, - ResultNames: []string{}, - }, - NameSyscallValueInstanceOf: { - Name: NameSyscallValueInstanceOf, - ParamNames: []string{"v", "t"}, - ResultNames: []string{"ok"}, - }, - NameSyscallCopyBytesToGo: { - Name: NameSyscallCopyBytesToGo, - ParamNames: []string{"dst", "dst_len", NamePadding, "src"}, - ResultNames: []string{"n", "ok"}, - }, - NameSyscallCopyBytesToJS: { - Name: NameSyscallCopyBytesToJS, - ParamNames: []string{"dst", "src", "src_len", NamePadding}, - ResultNames: []string{"n", "ok"}, - }, -} - -var NameSectionSyscallValueCall = map[string]map[string]*Names{ - NameCrypto: CryptoNameSection, - NameDate: DateNameSection, - NameFs: FsNameSection, - NameProcess: ProcessNameSection, -} diff --git a/internal/gojs/custom/process.go b/internal/gojs/custom/process.go deleted file mode 100644 index d55b1cb86f..0000000000 --- a/internal/gojs/custom/process.go +++ /dev/null @@ -1,59 +0,0 @@ -package custom - -const ( - NameProcess = "process" - NameProcessArgv0 = "argv0" - NameProcessCwd = "cwd" - NameProcessChdir = "chdir" - NameProcessGetuid = "getuid" - NameProcessGetgid = "getgid" - NameProcessGeteuid = "geteuid" - NameProcessGetgroups = "getgroups" - NameProcessUmask = "umask" -) - -// ProcessNameSection are the functions defined in the object named NameProcess. -// Results here are those set to the current event object, but effectively are -// results of the host function. -var ProcessNameSection = map[string]*Names{ - NameProcessArgv0: { - Name: NameProcessArgv0, - ParamNames: []string{}, - ResultNames: []string{"argv0"}, - }, - NameProcessCwd: { - Name: NameProcessCwd, - ParamNames: []string{}, - ResultNames: []string{"cwd"}, - }, - NameProcessChdir: { - Name: NameProcessChdir, - ParamNames: []string{"path"}, - ResultNames: []string{"err"}, - }, - NameProcessGetuid: { - Name: NameProcessGetuid, - ParamNames: []string{}, - ResultNames: []string{"uid"}, - }, - NameProcessGetgid: { - Name: NameProcessGetgid, - ParamNames: []string{}, - ResultNames: []string{"gid"}, - }, - NameProcessGeteuid: { - Name: NameProcessGeteuid, - ParamNames: []string{}, - ResultNames: []string{"euid"}, - }, - NameProcessGetgroups: { - Name: NameProcessGetgroups, - ParamNames: []string{}, - ResultNames: []string{"groups"}, - }, - NameProcessUmask: { - Name: NameProcessUmask, - ParamNames: []string{"mask"}, - ResultNames: []string{"oldmask"}, - }, -} diff --git a/internal/gojs/errno.go b/internal/gojs/errno.go deleted file mode 100644 index 48284da278..0000000000 --- a/internal/gojs/errno.go +++ /dev/null @@ -1,112 +0,0 @@ -package gojs - -import ( - "io" - - "github.com/tetratelabs/wazero/experimental/sys" -) - -// Errno is a (GOARCH=wasm) error, which must match a key in mapJSError. -// -// See https://github.com/golang/go/blob/go1.20/src/syscall/tables_js.go#L371-L494 -type Errno struct { - s string -} - -// Error implements error. -func (e *Errno) Error() string { - return e.s -} - -// This order match constants from wasi_snapshot_preview1.ErrnoSuccess for -// easier maintenance. -var ( - // ErrnoAcces Permission denied. - ErrnoAcces = &Errno{"EACCES"} - // ErrnoAgain Resource unavailable, or operation would block. - ErrnoAgain = &Errno{"EAGAIN"} - // ErrnoBadf Bad file descriptor. - ErrnoBadf = &Errno{"EBADF"} - // ErrnoExist File exists. - ErrnoExist = &Errno{"EEXIST"} - // ErrnoFault Bad address. - ErrnoFault = &Errno{"EFAULT"} - // ErrnoIntr Interrupted function. - ErrnoIntr = &Errno{"EINTR"} - // ErrnoInval Invalid argument. - ErrnoInval = &Errno{"EINVAL"} - // ErrnoIo I/O error. - ErrnoIo = &Errno{"EIO"} - // ErrnoIsdir Is a directory. - ErrnoIsdir = &Errno{"EISDIR"} - // ErrnoLoop Too many levels of symbolic links. - ErrnoLoop = &Errno{"ELOOP"} - // ErrnoNametoolong Filename too long. - ErrnoNametoolong = &Errno{"ENAMETOOLONG"} - // ErrnoNoent No such file or directory. - ErrnoNoent = &Errno{"ENOENT"} - // ErrnoNosys function not supported. - ErrnoNosys = &Errno{"ENOSYS"} - // ErrnoNotdir Not a directory or a symbolic link to a directory. - ErrnoNotdir = &Errno{"ENOTDIR"} - // ErrnoNotempty Directory not empty. - ErrnoNotempty = &Errno{"ENOTEMPTY"} - // ErrnoNotsup Not supported, or operation not supported on socket. - ErrnoNotsup = &Errno{"ENOTSUP"} - // ErrnoPerm Operation not permitted. - ErrnoPerm = &Errno{"EPERM"} - // ErrnoRofs read-only file system. - ErrnoRofs = &Errno{"EROFS"} -) - -// ToErrno maps I/O errors as the message must be the code, ex. "EINVAL", not -// the message, e.g. "invalid argument". -func ToErrno(err error) *Errno { - if err == nil || err == io.EOF { - return nil // io.EOF has no value in GOOS=js, and isn't an error. - } - errno, ok := err.(sys.Errno) - if !ok { - return ErrnoIo - } - switch errno { - case sys.EACCES: - return ErrnoAcces - case sys.EAGAIN: - return ErrnoAgain - case sys.EBADF: - return ErrnoBadf - case sys.EEXIST: - return ErrnoExist - case sys.EFAULT: - return ErrnoFault - case sys.EINTR: - return ErrnoIntr - case sys.EINVAL: - return ErrnoInval - case sys.EIO: - return ErrnoIo - case sys.EISDIR: - return ErrnoIsdir - case sys.ELOOP: - return ErrnoLoop - case sys.ENAMETOOLONG: - return ErrnoNametoolong - case sys.ENOENT: - return ErrnoNoent - case sys.ENOSYS: - return ErrnoNosys - case sys.ENOTDIR: - return ErrnoNotdir - case sys.ENOTEMPTY: - return ErrnoNotempty - case sys.ENOTSUP: - return ErrnoNotsup - case sys.EPERM: - return ErrnoPerm - case sys.EROFS: - return ErrnoRofs - default: - return ErrnoIo - } -} diff --git a/internal/gojs/errno_test.go b/internal/gojs/errno_test.go deleted file mode 100644 index d8b9f3992a..0000000000 --- a/internal/gojs/errno_test.go +++ /dev/null @@ -1,128 +0,0 @@ -package gojs - -import ( - "io" - "testing" - - "github.com/tetratelabs/wazero/experimental/sys" -) - -func TestToErrno(t *testing.T) { - tests := []struct { - name string - input error - expected *Errno - }{ - { - name: "nil is not an error", - }, - { - name: "io.EOF is not an error", - input: io.EOF, - }, - { - name: "sys.EACCES", - input: sys.EACCES, - expected: ErrnoAcces, - }, - { - name: "sys.EAGAIN", - input: sys.EAGAIN, - expected: ErrnoAgain, - }, - { - name: "sys.EBADF", - input: sys.EBADF, - expected: ErrnoBadf, - }, - { - name: "sys.EEXIST", - input: sys.EEXIST, - expected: ErrnoExist, - }, - { - name: "sys.EFAULT", - input: sys.EFAULT, - expected: ErrnoFault, - }, - { - name: "sys.EINTR", - input: sys.EINTR, - expected: ErrnoIntr, - }, - { - name: "sys.EINVAL", - input: sys.EINVAL, - expected: ErrnoInval, - }, - { - name: "sys.EIO", - input: sys.EIO, - expected: ErrnoIo, - }, - { - name: "sys.EISDIR", - input: sys.EISDIR, - expected: ErrnoIsdir, - }, - { - name: "sys.ELOOP", - input: sys.ELOOP, - expected: ErrnoLoop, - }, - { - name: "sys.ENAMETOOLONG", - input: sys.ENAMETOOLONG, - expected: ErrnoNametoolong, - }, - { - name: "sys.ENOENT", - input: sys.ENOENT, - expected: ErrnoNoent, - }, - { - name: "sys.ENOSYS", - input: sys.ENOSYS, - expected: ErrnoNosys, - }, - { - name: "sys.ENOTDIR", - input: sys.ENOTDIR, - expected: ErrnoNotdir, - }, - { - name: "sys.ENOTEMPTY", - input: sys.ENOTEMPTY, - expected: ErrnoNotempty, - }, - { - name: "sys.ENOTSUP", - input: sys.ENOTSUP, - expected: ErrnoNotsup, - }, - { - name: "sys.EPERM", - input: sys.EPERM, - expected: ErrnoPerm, - }, - { - name: "sys.EROFS", - input: sys.EROFS, - expected: ErrnoRofs, - }, - { - name: "sys.Errno unexpected == ErrnoIo", - input: sys.Errno(0xfe), - expected: ErrnoIo, - }, - } - - for _, tt := range tests { - tc := tt - t.Run(tc.name, func(t *testing.T) { - if errno := ToErrno(tc.input); errno != tc.expected { - t.Fatalf("expected %#v but was %#v", tc.expected, errno) - } - }) - } -} diff --git a/internal/gojs/fs.go b/internal/gojs/fs.go deleted file mode 100644 index ca15655791..0000000000 --- a/internal/gojs/fs.go +++ /dev/null @@ -1,724 +0,0 @@ -package gojs - -import ( - "context" - "fmt" - - "github.com/tetratelabs/wazero/api" - experimentalsys "github.com/tetratelabs/wazero/experimental/sys" - "github.com/tetratelabs/wazero/internal/gojs/custom" - "github.com/tetratelabs/wazero/internal/gojs/goos" - "github.com/tetratelabs/wazero/internal/gojs/util" - internalsys "github.com/tetratelabs/wazero/internal/sys" - "github.com/tetratelabs/wazero/internal/wasm" - "github.com/tetratelabs/wazero/sys" -) - -var ( - // jsfsConstants = jsfs Get("constants") // fs_js.go init - jsfsConstants = newJsVal(goos.RefJsfsConstants, "constants"). - addProperties(map[string]interface{}{ - "O_WRONLY": oWRONLY, - "O_RDWR": oRDWR, - "O_CREAT": oCREAT, - "O_TRUNC": oTRUNC, - "O_APPEND": oAPPEND, - "O_EXCL": oEXCL, - }) - - // oWRONLY = jsfsConstants Get("O_WRONLY").Int() // fs_js.go init - oWRONLY = float64(experimentalsys.O_WRONLY) - - // oRDWR = jsfsConstants Get("O_RDWR").Int() // fs_js.go init - oRDWR = float64(experimentalsys.O_RDWR) - - // o CREAT = jsfsConstants Get("O_CREAT").Int() // fs_js.go init - oCREAT = float64(experimentalsys.O_CREAT) - - // oTRUNC = jsfsConstants Get("O_TRUNC").Int() // fs_js.go init - oTRUNC = float64(experimentalsys.O_TRUNC) - - // oAPPEND = jsfsConstants Get("O_APPEND").Int() // fs_js.go init - oAPPEND = float64(experimentalsys.O_APPEND) - - // oEXCL = jsfsConstants Get("O_EXCL").Int() // fs_js.go init - oEXCL = float64(experimentalsys.O_EXCL) -) - -// jsfs = js.Global().Get("fs") // fs_js.go init -// -// js.fsCall conventions: -// * funcWrapper callback is the last parameter -// - arg0 is error and up to one result in arg1 -func newJsFs(proc *processState) *jsVal { - return newJsVal(goos.RefJsfs, custom.NameFs). - addProperties(map[string]interface{}{ - "constants": jsfsConstants, // = jsfs.Get("constants") // init - }). - addFunction(custom.NameFsOpen, &jsfsOpen{proc: proc}). - addFunction(custom.NameFsStat, &jsfsStat{proc: proc}). - addFunction(custom.NameFsFstat, jsfsFstat{}). - addFunction(custom.NameFsLstat, &jsfsLstat{proc: proc}). - addFunction(custom.NameFsClose, jsfsClose{}). - addFunction(custom.NameFsRead, jsfsRead{}). - addFunction(custom.NameFsWrite, jsfsWrite{}). - addFunction(custom.NameFsReaddir, &jsfsReaddir{proc: proc}). - addFunction(custom.NameFsMkdir, &jsfsMkdir{proc: proc}). - addFunction(custom.NameFsRmdir, &jsfsRmdir{proc: proc}). - addFunction(custom.NameFsRename, &jsfsRename{proc: proc}). - addFunction(custom.NameFsUnlink, &jsfsUnlink{proc: proc}). - addFunction(custom.NameFsUtimes, &jsfsUtimes{proc: proc}). - addFunction(custom.NameFsChmod, &jsfsChmod{proc: proc}). - addFunction(custom.NameFsFchmod, jsfsFchmod{}). - addFunction(custom.NameFsChown, &jsfsChown{proc: proc}). - addFunction(custom.NameFsFchown, jsfsFchown{}). - addFunction(custom.NameFsLchown, &jsfsLchown{proc: proc}). - addFunction(custom.NameFsTruncate, &jsfsTruncate{proc: proc}). - addFunction(custom.NameFsFtruncate, jsfsFtruncate{}). - addFunction(custom.NameFsReadlink, &jsfsReadlink{proc: proc}). - addFunction(custom.NameFsLink, &jsfsLink{proc: proc}). - addFunction(custom.NameFsSymlink, &jsfsSymlink{proc: proc}). - addFunction(custom.NameFsFsync, jsfsFsync{}) -} - -// jsfsOpen implements implements jsFn for syscall.Open -// -// jsFD /* Int */, err := fsCall("open", path, flags, perm) -type jsfsOpen struct { - proc *processState -} - -func (o *jsfsOpen) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - path := util.ResolvePath(o.proc.cwd, args[0].(string)) - // Note: these are already sys.Flag because Go uses constants we define: - // https://github.com/golang/go/blob/go1.20/src/syscall/fs_js.go#L24-L31 - flags := experimentalsys.Oflag(toUint64(args[1])) - perm := custom.FromJsMode(goos.ValueToUint32(args[2]), o.proc.umask) - callback := args[3].(funcWrapper) - - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - - fd, errno := fsc.OpenFile(fsc.RootFS(), path, flags, perm) - - return callback.invoke(ctx, mod, goos.RefJsfs, maybeError(errno), fd) // note: error first -} - -// jsfsStat implements jsFn for syscall.Stat -// -// jsSt, err := fsCall("stat", path) -type jsfsStat struct { - proc *processState -} - -func (s *jsfsStat) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - path := util.ResolvePath(s.proc.cwd, args[0].(string)) - callback := args[1].(funcWrapper) - - stat, err := syscallStat(mod, path) - return callback.invoke(ctx, mod, goos.RefJsfs, err, stat) // note: error first -} - -// syscallStat is like syscall.Stat -func syscallStat(mod api.Module, path string) (*jsSt, error) { - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - - if st, errno := fsc.RootFS().Stat(path); errno != 0 { - return nil, errno - } else { - return newJsSt(st), nil - } -} - -// jsfsLstat implements jsFn for syscall.Lstat -// -// jsSt, err := fsCall("lstat", path) -type jsfsLstat struct { - proc *processState -} - -func (l *jsfsLstat) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - path := util.ResolvePath(l.proc.cwd, args[0].(string)) - callback := args[1].(funcWrapper) - - lstat, err := syscallLstat(mod, path) - - return callback.invoke(ctx, mod, goos.RefJsfs, err, lstat) // note: error first -} - -// syscallLstat is like syscall.Lstat -func syscallLstat(mod api.Module, path string) (*jsSt, error) { - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - - if st, errno := fsc.RootFS().Lstat(path); errno != 0 { - return nil, errno - } else { - return newJsSt(st), nil - } -} - -// jsfsFstat implements jsFn for syscall.Open -// -// stat, err := fsCall("fstat", fd); err == nil && stat.Call("isDirectory").Bool() -type jsfsFstat struct{} - -func (jsfsFstat) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - - fd := goos.ValueToInt32(args[0]) - callback := args[1].(funcWrapper) - - fstat, err := syscallFstat(fsc, fd) - return callback.invoke(ctx, mod, goos.RefJsfs, err, fstat) // note: error first -} - -// syscallFstat is like syscall.Fstat -func syscallFstat(fsc *internalsys.FSContext, fd int32) (*jsSt, error) { - f, ok := fsc.LookupFile(fd) - if !ok { - return nil, experimentalsys.EBADF - } - - if st, errno := f.File.Stat(); errno != 0 { - return nil, errno - } else { - return newJsSt(st), nil - } -} - -func newJsSt(st sys.Stat_t) *jsSt { - ret := &jsSt{} - ret.isDir = st.Mode.IsDir() - ret.dev = st.Dev - ret.ino = st.Ino - ret.mode = custom.ToJsMode(st.Mode) - ret.nlink = uint32(st.Nlink) - ret.size = st.Size - ret.atimeMs = st.Atim / 1e6 - ret.mtimeMs = st.Mtim / 1e6 - ret.ctimeMs = st.Ctim / 1e6 - return ret -} - -// jsfsClose implements jsFn for syscall.Close -type jsfsClose struct{} - -func (jsfsClose) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - - fd := goos.ValueToInt32(args[0]) - callback := args[1].(funcWrapper) - - errno := fsc.CloseFile(fd) - - return jsfsInvoke(ctx, mod, callback, errno) -} - -// jsfsRead implements jsFn for syscall.Read and syscall.Pread, called by -// src/internal/poll/fd_unix.go poll.Read. -// -// n, err := fsCall("read", fd, buf, 0, len(b), nil) -type jsfsRead struct{} - -func (jsfsRead) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - fd := goos.ValueToInt32(args[0]) - buf, ok := args[1].(*goos.ByteArray) - if !ok { - return nil, fmt.Errorf("arg[1] is %v not a []byte", args[1]) - } - offset := goos.ValueToUint32(args[2]) - byteCount := goos.ValueToUint32(args[3]) - fOffset := args[4] // nil unless Pread - callback := args[5].(funcWrapper) - - var err error - n, errno := syscallRead(mod, fd, fOffset, buf.Unwrap()[offset:offset+byteCount]) - if errno != 0 { - err = errno - } - // It is safe to cast to uint32 because n <= uint32(byteCount). - return callback.invoke(ctx, mod, goos.RefJsfs, err, uint32(n)) // note: error first -} - -// syscallRead is like syscall.Read -func syscallRead(mod api.Module, fd int32, offset interface{}, buf []byte) (n int, errno experimentalsys.Errno) { - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - - if f, ok := fsc.LookupFile(fd); !ok { - return 0, experimentalsys.EBADF - } else if offset != nil { - return f.File.Pread(buf, toInt64(offset)) - } else { - return f.File.Read(buf) - } -} - -// jsfsWrite implements jsFn for syscall.Write and syscall.Pwrite. -// -// Notably, offset is non-nil in Pwrite. -// -// n, err := fsCall("write", fd, buf, 0, len(b), nil) -type jsfsWrite struct{} - -func (jsfsWrite) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - fd := goos.ValueToInt32(args[0]) - buf, ok := args[1].(*goos.ByteArray) - if !ok { - return nil, fmt.Errorf("arg[1] is %v not a []byte", args[1]) - } - offset := goos.ValueToUint32(args[2]) - byteCount := goos.ValueToUint32(args[3]) - fOffset := args[4] // nil unless Pwrite - callback := args[5].(funcWrapper) - - if byteCount > 0 { // empty is possible on EOF - n, errno := syscallWrite(mod, fd, fOffset, buf.Unwrap()[offset:offset+byteCount]) - var err error - if errno != 0 { - err = errno - } - // It is safe to cast to uint32 because n <= uint32(byteCount). - return callback.invoke(ctx, mod, goos.RefJsfs, err, uint32(n)) // note: error first - } - return callback.invoke(ctx, mod, goos.RefJsfs, nil, goos.RefValueZero) -} - -// syscallWrite is like syscall.Write -func syscallWrite(mod api.Module, fd int32, offset interface{}, buf []byte) (n int, errno experimentalsys.Errno) { - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - if f, ok := fsc.LookupFile(fd); !ok { - errno = experimentalsys.EBADF - } else if offset != nil { - n, errno = f.File.Pwrite(buf, toInt64(offset)) - } else { - n, errno = f.File.Write(buf) - } - if errno == experimentalsys.ENOSYS { - errno = experimentalsys.EBADF // e.g. unimplemented for write - } - return -} - -// jsfsReaddir implements jsFn for syscall.Open -// -// dir, err := fsCall("readdir", path) -// dir.Length(), dir.Index(i).String() -type jsfsReaddir struct { - proc *processState -} - -func (r *jsfsReaddir) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - path := util.ResolvePath(r.proc.cwd, args[0].(string)) - callback := args[1].(funcWrapper) - - stat, err := syscallReaddir(ctx, mod, path) - return callback.invoke(ctx, mod, goos.RefJsfs, err, stat) // note: error first -} - -func syscallReaddir(_ context.Context, mod api.Module, name string) (*objectArray, error) { - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - - // don't allocate a file descriptor - f, errno := fsc.RootFS().OpenFile(name, experimentalsys.O_RDONLY, 0) - if errno != 0 { - return nil, errno - } - defer f.Close() //nolint - - if dirents, errno := f.Readdir(-1); errno != 0 { - return nil, errno - } else { - entries := make([]interface{}, 0, len(dirents)) - for _, e := range dirents { - entries = append(entries, e.Name) - } - return &objectArray{entries}, nil - } -} - -// jsfsMkdir implements implements jsFn for fs.Mkdir -// -// jsFD /* Int */, err := fsCall("mkdir", path, perm) -type jsfsMkdir struct { - proc *processState -} - -func (m *jsfsMkdir) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - path := util.ResolvePath(m.proc.cwd, args[0].(string)) - perm := custom.FromJsMode(goos.ValueToUint32(args[1]), m.proc.umask) - callback := args[2].(funcWrapper) - - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - root := fsc.RootFS() - - var fd int32 - var errno experimentalsys.Errno - // We need at least read access to open the file descriptor - if perm == 0 { - perm = 0o0500 - } - if errno = root.Mkdir(path, perm); errno == 0 { - fd, errno = fsc.OpenFile(root, path, experimentalsys.O_RDONLY, 0) - } - - return callback.invoke(ctx, mod, goos.RefJsfs, maybeError(errno), fd) // note: error first -} - -// jsfsRmdir implements jsFn for the following -// -// _, err := fsCall("rmdir", path) // syscall.Rmdir -type jsfsRmdir struct { - proc *processState -} - -func (r *jsfsRmdir) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - path := util.ResolvePath(r.proc.cwd, args[0].(string)) - callback := args[1].(funcWrapper) - - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - errno := fsc.RootFS().Rmdir(path) - - return jsfsInvoke(ctx, mod, callback, errno) -} - -// jsfsRename implements jsFn for the following -// -// _, err := fsCall("rename", from, to) // syscall.Rename -type jsfsRename struct { - proc *processState -} - -func (r *jsfsRename) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - cwd := r.proc.cwd - from := util.ResolvePath(cwd, args[0].(string)) - to := util.ResolvePath(cwd, args[1].(string)) - callback := args[2].(funcWrapper) - - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - errno := fsc.RootFS().Rename(from, to) - - return jsfsInvoke(ctx, mod, callback, errno) -} - -// jsfsUnlink implements jsFn for the following -// -// _, err := fsCall("unlink", path) // syscall.Unlink -type jsfsUnlink struct { - proc *processState -} - -func (u *jsfsUnlink) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - path := util.ResolvePath(u.proc.cwd, args[0].(string)) - callback := args[1].(funcWrapper) - - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - errno := fsc.RootFS().Unlink(path) - - return jsfsInvoke(ctx, mod, callback, errno) -} - -// jsfsUtimes implements jsFn for the following -// -// _, err := fsCall("utimes", path, atime, mtime) // syscall.Utimens -type jsfsUtimes struct { - proc *processState -} - -func (u *jsfsUtimes) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - path := util.ResolvePath(u.proc.cwd, args[0].(string)) - atimeSec := toInt64(args[1]) - mtimeSec := toInt64(args[2]) - callback := args[3].(funcWrapper) - - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - errno := fsc.RootFS().Utimens(path, atimeSec*1e9, mtimeSec*1e9) - - return jsfsInvoke(ctx, mod, callback, errno) -} - -// jsfsChmod implements jsFn for the following -// -// _, err := fsCall("chmod", path, mode) // syscall.Chmod -type jsfsChmod struct { - proc *processState -} - -func (c *jsfsChmod) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - path := util.ResolvePath(c.proc.cwd, args[0].(string)) - mode := custom.FromJsMode(goos.ValueToUint32(args[1]), 0) - callback := args[2].(funcWrapper) - - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - errno := fsc.RootFS().Chmod(path, mode) - - return jsfsInvoke(ctx, mod, callback, errno) -} - -// jsfsFchmod implements jsFn for the following -// -// _, err := fsCall("fchmod", fd, mode) // syscall.Fchmod -type jsfsFchmod struct{} - -func (jsfsFchmod) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - fd := goos.ValueToInt32(args[0]) - _ = args[1] // mode - callback := args[2].(funcWrapper) - - // Check to see if the file descriptor is available - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - var errno experimentalsys.Errno - if _, ok := fsc.LookupFile(fd); !ok { - errno = experimentalsys.EBADF - } else { - errno = experimentalsys.ENOSYS // We only support functions used in wasip1 - } - - return jsfsInvoke(ctx, mod, callback, errno) -} - -// jsfsChown implements jsFn for the following -// -// _, err := fsCall("chown", path, uint32(uid), uint32(gid)) // syscall.Chown -type jsfsChown struct { - proc *processState -} - -func (c *jsfsChown) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - _ = args[0] // path - _ = args[1] // uid - _ = args[2] // gid - callback := args[3].(funcWrapper) - - errno := experimentalsys.ENOSYS // We only support functions used in wasip1 - - return jsfsInvoke(ctx, mod, callback, errno) -} - -// jsfsFchown implements jsFn for the following -// -// _, err := fsCall("fchown", fd, uint32(uid), uint32(gid)) // syscall.Fchown -type jsfsFchown struct{} - -func (jsfsFchown) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - fd := goos.ValueToInt32(args[0]) - _ = args[1] // uid - _ = args[2] // gid - callback := args[3].(funcWrapper) - - // Check to see if the file descriptor is available - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - var errno experimentalsys.Errno - if _, ok := fsc.LookupFile(fd); !ok { - errno = experimentalsys.EBADF - } else { - errno = experimentalsys.ENOSYS // We only support functions used in wasip1 - } - - return jsfsInvoke(ctx, mod, callback, errno) -} - -// jsfsLchown implements jsFn for the following -// -// _, err := fsCall("lchown", path, uint32(uid), uint32(gid)) // syscall.Lchown -type jsfsLchown struct { - proc *processState -} - -func (l *jsfsLchown) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - _ = args[0] // path - _ = args[1] // uid - _ = args[2] // gid - callback := args[3].(funcWrapper) - - errno := experimentalsys.ENOSYS // We only support functions used in wasip1 - - return jsfsInvoke(ctx, mod, callback, errno) -} - -// jsfsTruncate implements jsFn for the following -// -// _, err := fsCall("truncate", path, length) // syscall.Truncate -type jsfsTruncate struct { - proc *processState -} - -func (t *jsfsTruncate) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - _ = args[0] // path - _ = args[1] // length - callback := args[2].(funcWrapper) - - errno := experimentalsys.ENOSYS // We only support functions used in wasip1 - - return jsfsInvoke(ctx, mod, callback, errno) -} - -// jsfsFtruncate implements jsFn for the following -// -// _, err := fsCall("ftruncate", fd, length) // syscall.Ftruncate -type jsfsFtruncate struct{} - -func (jsfsFtruncate) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - fd := goos.ValueToInt32(args[0]) - length := toInt64(args[1]) - callback := args[2].(funcWrapper) - - // Check to see if the file descriptor is available - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - var errno experimentalsys.Errno - if f, ok := fsc.LookupFile(fd); !ok { - errno = experimentalsys.EBADF - } else { - errno = f.File.Truncate(length) - } - - return jsfsInvoke(ctx, mod, callback, errno) -} - -// jsfsReadlink implements jsFn for syscall.Readlink -// -// dst, err := fsCall("readlink", path) // syscall.Readlink -type jsfsReadlink struct { - proc *processState -} - -func (r *jsfsReadlink) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - path := util.ResolvePath(r.proc.cwd, args[0].(string)) - callback := args[1].(funcWrapper) - - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - dst, errno := fsc.RootFS().Readlink(path) - - return callback.invoke(ctx, mod, goos.RefJsfs, maybeError(errno), dst) // note: error first -} - -// jsfsLink implements jsFn for the following -// -// _, err := fsCall("link", path, link) // syscall.Link -type jsfsLink struct { - proc *processState -} - -func (l *jsfsLink) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - cwd := l.proc.cwd - path := util.ResolvePath(cwd, args[0].(string)) - link := util.ResolvePath(cwd, args[1].(string)) - callback := args[2].(funcWrapper) - - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - errno := fsc.RootFS().Link(path, link) - - return jsfsInvoke(ctx, mod, callback, errno) -} - -// jsfsSymlink implements jsFn for the following -// -// _, err := fsCall("symlink", path, link) // syscall.Symlink -type jsfsSymlink struct { - proc *processState -} - -func (s *jsfsSymlink) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - dst := args[0].(string) // The dst of a symlink must not be resolved, as it should be resolved during readLink. - link := util.ResolvePath(s.proc.cwd, args[1].(string)) - callback := args[2].(funcWrapper) - - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - errno := fsc.RootFS().Symlink(dst, link) - - return jsfsInvoke(ctx, mod, callback, errno) -} - -// jsfsFsync implements jsFn for the following -// -// _, err := fsCall("fsync", fd) // syscall.Fsync -type jsfsFsync struct{} - -func (jsfsFsync) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - fd := goos.ValueToInt32(args[0]) - callback := args[1].(funcWrapper) - - // Check to see if the file descriptor is available - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - var errno experimentalsys.Errno - if f, ok := fsc.LookupFile(fd); !ok { - errno = experimentalsys.EBADF - } else { - errno = f.File.Sync() - } - - return jsfsInvoke(ctx, mod, callback, errno) -} - -// jsSt is pre-parsed from fs_js.go setStat to avoid thrashing -type jsSt struct { - isDir bool - dev uint64 - ino uint64 - mode uint32 - nlink uint32 - uid uint32 - gid uint32 - rdev int64 - size int64 - blksize int32 - blocks int32 - atimeMs int64 - mtimeMs int64 - ctimeMs int64 -} - -// String implements fmt.Stringer -func (s *jsSt) String() string { - return fmt.Sprintf("{isDir=%v,mode=%s,size=%d,mtimeMs=%d}", s.isDir, custom.FromJsMode(s.mode, 0), s.size, s.mtimeMs) -} - -// Get implements the same method as documented on goos.GetFunction -func (s *jsSt) Get(propertyKey string) interface{} { - switch propertyKey { - case "dev": - return s.dev - case "ino": - return s.ino - case "mode": - return s.mode - case "nlink": - return s.nlink - case "uid": - return s.uid - case "gid": - return s.gid - case "rdev": - return s.rdev - case "size": - return s.size - case "blksize": - return s.blksize - case "blocks": - return s.blocks - case "atimeMs": - return s.atimeMs - case "mtimeMs": - return s.mtimeMs - case "ctimeMs": - return s.ctimeMs - } - panic(fmt.Sprintf("TODO: stat.%s", propertyKey)) -} - -// call implements jsCall.call -func (s *jsSt) call(_ context.Context, _ api.Module, _ goos.Ref, method string, _ ...interface{}) (interface{}, error) { - if method == "isDirectory" { - return s.isDir, nil - } - panic(fmt.Sprintf("TODO: stat.%s", method)) -} - -func jsfsInvoke(ctx context.Context, mod api.Module, callback funcWrapper, err experimentalsys.Errno) (interface{}, error) { - return callback.invoke(ctx, mod, goos.RefJsfs, maybeError(err), err == 0) // note: error first -} - -func maybeError(errno experimentalsys.Errno) error { - if errno != 0 { - return errno - } - return nil -} diff --git a/internal/gojs/fs_test.go b/internal/gojs/fs_test.go deleted file mode 100644 index 2fe8bf64b9..0000000000 --- a/internal/gojs/fs_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package gojs_test - -import ( - "os" - "path" - "testing" - - "github.com/tetratelabs/wazero" - "github.com/tetratelabs/wazero/internal/fstest" - "github.com/tetratelabs/wazero/internal/gojs/config" - "github.com/tetratelabs/wazero/internal/platform" - "github.com/tetratelabs/wazero/internal/testing/require" -) - -func Test_fs(t *testing.T) { - t.Parallel() - - stdout, stderr, err := compileAndRun(testCtx, "fs", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) { - return defaultConfig(moduleConfig.WithFS(testFS)) - }) - - require.Zero(t, stderr) - require.NoError(t, err) - require.Equal(t, `sub mode drwxr-xr-x -/animals.txt mode -rw-r--r-- -animals.txt mode -rw-r--r-- -contents: bear -cat -shark -dinosaur -human - -empty: -`, stdout) -} - -// Test_testsfs runs fstest.TestFS inside wasm. -func Test_testfs(t *testing.T) { - t.Parallel() - - // Setup /testfs which is used in the wasm invocation of testfs.TestFS. - tmpDir := t.TempDir() - testfsDir := path.Join(tmpDir, "testfs") - require.NoError(t, os.Mkdir(testfsDir, 0o700)) - require.NoError(t, fstest.WriteTestFiles(testfsDir)) - - fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/") - stdout, stderr, err := compileAndRun(testCtx, "testfs", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) { - return defaultConfig(moduleConfig.WithFSConfig(fsConfig)) - }) - - require.Zero(t, stderr) - require.NoError(t, err) - require.Zero(t, stdout) -} - -func Test_writefs(t *testing.T) { - t.Parallel() - tmpDir := t.TempDir() - fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/") - - // test expects to write under /tmp - require.NoError(t, os.Mkdir(path.Join(tmpDir, "tmp"), 0o700)) - - stdout, stderr, err := compileAndRun(testCtx, "writefs", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) { - return defaultConfig(moduleConfig.WithFSConfig(fsConfig)) - }) - - require.Zero(t, stderr) - require.NoError(t, err) - - if platform.CompilerSupported() { - // Note: as of Go 1.19, only the Sec field is set on update in fs_js.go. - require.Equal(t, `/tmp/dir mode drwx------ -/tmp/dir/file mode -rw------- -dir times: 123000000000 567000000000 -`, stdout) - } else { // only mtimes will return on a plarform we don't support in sysfs - require.Equal(t, `/tmp/dir mode drwx------ -/tmp/dir/file mode -rw------- -dir times: 567000000000 567000000000 -`, stdout) - } -} diff --git a/internal/gojs/goarch/wasm.go b/internal/gojs/goarch/wasm.go deleted file mode 100644 index 9f2f56c37c..0000000000 --- a/internal/gojs/goarch/wasm.go +++ /dev/null @@ -1,180 +0,0 @@ -// Package goarch isolates code from runtime.GOARCH=wasm in a way that avoids -// cyclic dependencies when re-used from other packages. -package goarch - -import ( - "context" - "encoding/binary" - - "github.com/tetratelabs/wazero/api" - "github.com/tetratelabs/wazero/internal/gojs/custom" - "github.com/tetratelabs/wazero/internal/gojs/util" - "github.com/tetratelabs/wazero/internal/wasm" -) - -// StubFunction stubs functions not used in Go's main source tree. -// This traps (unreachable opcode) to ensure the function is never called. -func StubFunction(name string) *wasm.HostFunc { - return &wasm.HostFunc{ - ExportName: name, - Name: name, - ParamTypes: []wasm.ValueType{wasm.ValueTypeI32}, - ParamNames: []string{"sp"}, - Code: wasm.Code{GoFunc: api.GoModuleFunc(func(ctx context.Context, _ api.Module, stack []uint64) {})}, - } -} - -var le = binary.LittleEndian - -type Stack interface { - // Name is the function name being invoked. - Name() string - - Param(i int) uint64 - - // ParamBytes reads a byte slice, given its memory offset and length (stack - // positions i, i+1) - ParamBytes(mem api.Memory, i int) []byte - - // ParamString reads a string, given its memory offset and length (stack - // positions i, i+1) - ParamString(mem api.Memory, i int) string - - ParamInt32(i int) int32 - - ParamUint32(i int) uint32 - - // Refresh the stack from the current stack pointer (SP). - // - // Note: This is needed prior to storing a value when in an operation that - // can trigger a Go event handler. - Refresh(api.Module) - - SetResult(i int, v uint64) - - SetResultBool(i int, v bool) - - SetResultI32(i int, v int32) - - SetResultI64(i int, v int64) - - SetResultUint32(i int, v uint32) -} - -func NewStack(name string, mem api.Memory, sp uint32) Stack { - names := custom.NameSection[name] - s := &stack{name: name, paramCount: len(names.ParamNames), resultCount: len(names.ResultNames)} - s.refresh(mem, sp) - return s -} - -type stack struct { - name string - paramCount, resultCount int - buf []byte -} - -// Name implements Stack.Name -func (s *stack) Name() string { - return s.name -} - -// Param implements Stack.Param -func (s *stack) Param(i int) (res uint64) { - pos := i << 3 - res = le.Uint64(s.buf[pos:]) - return -} - -// ParamBytes implements Stack.ParamBytes -func (s *stack) ParamBytes(mem api.Memory, i int) (res []byte) { - offset := s.ParamUint32(i) - byteCount := s.ParamUint32(i + 1) - return util.MustRead(mem, s.name, i, offset, byteCount) -} - -// ParamString implements Stack.ParamString -func (s *stack) ParamString(mem api.Memory, i int) string { - return string(s.ParamBytes(mem, i)) // safe copy of guest memory -} - -// ParamInt32 implements Stack.ParamInt32 -func (s *stack) ParamInt32(i int) int32 { - return int32(s.Param(i)) -} - -// ParamUint32 implements Stack.ParamUint32 -func (s *stack) ParamUint32(i int) uint32 { - return uint32(s.Param(i)) -} - -// Refresh implements Stack.Refresh -func (s *stack) Refresh(mod api.Module) { - s.refresh(mod.Memory(), GetSP(mod)) -} - -func (s *stack) refresh(mem api.Memory, sp uint32) { - count := uint32(s.paramCount + s.resultCount) - buf, ok := mem.Read(sp+8, count<<3) - if !ok { - panic("out of memory reading stack") - } - s.buf = buf -} - -// SetResult implements Stack.SetResult -func (s *stack) SetResult(i int, v uint64) { - pos := (s.paramCount + i) << 3 - le.PutUint64(s.buf[pos:], v) -} - -// SetResultBool implements Stack.SetResultBool -func (s *stack) SetResultBool(i int, v bool) { - if v { - s.SetResultUint32(i, 1) - } else { - s.SetResultUint32(i, 0) - } -} - -// SetResultI32 implements Stack.SetResultI32 -func (s *stack) SetResultI32(i int, v int32) { - s.SetResult(i, uint64(v)) -} - -// SetResultI64 implements Stack.SetResultI64 -func (s *stack) SetResultI64(i int, v int64) { - s.SetResult(i, uint64(v)) -} - -// SetResultUint32 implements Stack.SetResultUint32 -func (s *stack) SetResultUint32(i int, v uint32) { - s.SetResult(i, uint64(v)) -} - -// GetSP gets the stack pointer, which is needed prior to storing a value when -// in an operation that can trigger a Go event handler. -// -// See https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L210-L213 -func GetSP(mod api.Module) uint32 { - // Cheat by reading global[0] directly instead of through a function proxy. - // https://github.com/golang/go/blob/go1.20/src/runtime/rt0_js_wasm.s#L87-L90 - return uint32(mod.(*wasm.ModuleInstance).GlobalVal(0)) -} - -func NewFunc(name string, goFunc Func) *wasm.HostFunc { - return util.NewFunc(name, (&stackFunc{name: name, f: goFunc}).Call) -} - -type Func func(context.Context, api.Module, Stack) - -type stackFunc struct { - name string - f Func -} - -// Call implements the same method as defined on api.GoModuleFunction. -func (f *stackFunc) Call(ctx context.Context, mod api.Module, wasmStack []uint64) { - s := NewStack(f.name, mod.Memory(), uint32(wasmStack[0])) - f.f(ctx, mod, s) -} diff --git a/internal/gojs/goos/goos.go b/internal/gojs/goos/goos.go deleted file mode 100644 index 8121eae72e..0000000000 --- a/internal/gojs/goos/goos.go +++ /dev/null @@ -1,307 +0,0 @@ -// Package goos isolates code from runtime.GOOS=js in a way that avoids cyclic -// dependencies when re-used from other packages. -package goos - -import ( - "context" - "encoding/binary" - "fmt" - - "github.com/tetratelabs/wazero/api" - "github.com/tetratelabs/wazero/internal/gojs/goarch" - "github.com/tetratelabs/wazero/internal/gojs/util" - "github.com/tetratelabs/wazero/internal/wasm" -) - -// Ref is used to identify a JavaScript value, since the value itself cannot -// be passed to WebAssembly. -// -// The JavaScript value "undefined" is represented by the value 0. -// -// A JavaScript number (64-bit float, except 0 and NaN) is represented by its -// IEEE 754 binary representation. -// -// All other values are represented as an IEEE 754 binary representation of NaN -// with bits 0-31 used as an ID and bits 32-34 used to differentiate between -// string, symbol, function and object. -type Ref uint64 - -const ( - // predefined - - IdValueNaN uint32 = iota - IdValueZero - IdValueNull - IdValueTrue - IdValueFalse - IdValueGlobal - IdJsGo - - // The below are derived from analyzing `*_js.go` source. - - IdObjectConstructor - IdArrayConstructor - IdJsProcess - IdJsfs - IdJsfsConstants - IdUint8ArrayConstructor - IdJsCrypto - IdJsDateConstructor - IdJsDate - NextID -) - -const ( - RefValueUndefined = Ref(0) - RefValueNaN = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueNaN) - RefValueZero = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueZero) - RefValueNull = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueNull) - RefValueTrue = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueTrue) - RefValueFalse = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueFalse) - RefValueGlobal = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdValueGlobal) - RefJsGo = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsGo) - - RefObjectConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdObjectConstructor) - RefArrayConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdArrayConstructor) - RefJsProcess = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsProcess) - RefJsfs = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsfs) - RefJsfsConstants = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsfsConstants) - RefUint8ArrayConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdUint8ArrayConstructor) - RefJsCrypto = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdJsCrypto) - RefJsDateConstructor = (NanHead|Ref(TypeFlagFunction))<<32 | Ref(IdJsDateConstructor) - RefJsDate = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsDate) -) - -type TypeFlag byte - -// the type flags need to be in sync with gojs.js -const ( - TypeFlagNone TypeFlag = iota - TypeFlagObject - TypeFlagString - TypeFlagSymbol //nolint - TypeFlagFunction -) - -func ValueRef(id uint32, typeFlag TypeFlag) Ref { - return (NanHead|Ref(typeFlag))<<32 | Ref(id) -} - -var le = binary.LittleEndian - -// NanHead are the upper 32 bits of a Ref which are set if the value is not encoded as an IEEE 754 number (see above). -const NanHead = 0x7FF80000 - -func (ref Ref) ParseFloat() (v float64, ok bool) { - if (ref>>32)&NanHead != NanHead { - v = api.DecodeF64(uint64(ref)) - ok = true - } - return -} - -// GetLastEventArgs returns the arguments to the last event created by -// custom.NameSyscallValueCall. -type GetLastEventArgs func(context.Context) []interface{} - -type ValLoader func(context.Context, Ref) interface{} - -type Stack interface { - goarch.Stack - - ParamRef(i int) Ref - - ParamRefs(mem api.Memory, i int) []Ref - - ParamVal(ctx context.Context, i int, loader ValLoader) interface{} - - // ParamVals is used by functions whose final parameter is an arg array. - ParamVals(ctx context.Context, mem api.Memory, i int, loader ValLoader) []interface{} - - SetResultRef(i int, v Ref) -} - -type stack struct { - s goarch.Stack -} - -// Name implements the same method as documented on goarch.Stack -func (s *stack) Name() string { - return s.s.Name() -} - -// Param implements the same method as documented on goarch.Stack -func (s *stack) Param(i int) uint64 { - return s.s.Param(i) -} - -// ParamBytes implements the same method as documented on goarch.Stack -func (s *stack) ParamBytes(mem api.Memory, i int) []byte { - return s.s.ParamBytes(mem, i) -} - -// ParamRef implements Stack.ParamRef -func (s *stack) ParamRef(i int) Ref { - return Ref(s.s.Param(i)) -} - -// ParamRefs implements Stack.ParamRefs -func (s *stack) ParamRefs(mem api.Memory, i int) []Ref { - offset := s.s.ParamUint32(i) - size := s.s.ParamUint32(i + 1) - byteCount := size << 3 // size * 8 - - result := make([]Ref, 0, size) - - buf := util.MustRead(mem, s.Name(), i, offset, byteCount) - for pos := uint32(0); pos < byteCount; pos += 8 { - ref := Ref(le.Uint64(buf[pos:])) - result = append(result, ref) - } - return result -} - -// ParamString implements the same method as documented on goarch.Stack -func (s *stack) ParamString(mem api.Memory, i int) string { - return s.s.ParamString(mem, i) -} - -// ParamInt32 implements the same method as documented on goarch.Stack -func (s *stack) ParamInt32(i int) int32 { - return s.s.ParamInt32(i) -} - -// ParamUint32 implements the same method as documented on goarch.Stack -func (s *stack) ParamUint32(i int) uint32 { - return s.s.ParamUint32(i) -} - -// ParamVal implements Stack.ParamVal -func (s *stack) ParamVal(ctx context.Context, i int, loader ValLoader) interface{} { - ref := s.ParamRef(i) - return loader(ctx, ref) -} - -// ParamVals implements Stack.ParamVals -func (s *stack) ParamVals(ctx context.Context, mem api.Memory, i int, loader ValLoader) []interface{} { - offset := s.s.ParamUint32(i) - size := s.s.ParamUint32(i + 1) - byteCount := size << 3 // size * 8 - - result := make([]interface{}, 0, size) - - buf := util.MustRead(mem, s.Name(), i, offset, byteCount) - for pos := uint32(0); pos < byteCount; pos += 8 { - ref := Ref(le.Uint64(buf[pos:])) - result = append(result, loader(ctx, ref)) - } - return result -} - -// Refresh implements the same method as documented on goarch.Stack -func (s *stack) Refresh(mod api.Module) { - s.s.Refresh(mod) -} - -// SetResult implements the same method as documented on goarch.Stack -func (s *stack) SetResult(i int, v uint64) { - s.s.SetResult(i, v) -} - -// SetResultBool implements the same method as documented on goarch.Stack -func (s *stack) SetResultBool(i int, v bool) { - s.s.SetResultBool(i, v) -} - -// SetResultI32 implements the same method as documented on goarch.Stack -func (s *stack) SetResultI32(i int, v int32) { - s.s.SetResultI32(i, v) -} - -// SetResultI64 implements the same method as documented on goarch.Stack -func (s *stack) SetResultI64(i int, v int64) { - s.s.SetResultI64(i, v) -} - -// SetResultRef implements Stack.SetResultRef -func (s *stack) SetResultRef(i int, v Ref) { - s.s.SetResult(i, uint64(v)) -} - -// SetResultUint32 implements the same method as documented on goarch.Stack -func (s *stack) SetResultUint32(i int, v uint32) { - s.s.SetResultUint32(i, v) -} - -func NewFunc(name string, goFunc Func) *wasm.HostFunc { - sf := &stackFunc{name: name, f: goFunc} - return util.NewFunc(name, sf.Call) -} - -type Func func(context.Context, api.Module, Stack) - -type stackFunc struct { - name string - f Func -} - -// Call implements the same method as defined on api.GoModuleFunction. -func (f *stackFunc) Call(ctx context.Context, mod api.Module, wasmStack []uint64) { - s := NewStack(f.name, mod.Memory(), uint32(wasmStack[0])) - f.f(ctx, mod, s) -} - -func NewStack(name string, mem api.Memory, sp uint32) *stack { - return &stack{goarch.NewStack(name, mem, sp)} -} - -var Undefined = struct{ name string }{name: "undefined"} - -func ValueToUint32(arg interface{}) uint32 { - if arg == RefValueZero || arg == Undefined { - return 0 - } else if u, ok := arg.(uint32); ok { - return u - } - return uint32(arg.(float64)) -} - -func ValueToInt32(arg interface{}) int32 { - if arg == RefValueZero || arg == Undefined { - return 0 - } else if u, ok := arg.(int); ok { - return int32(u) - } - return int32(uint32(arg.(float64))) -} - -// GetFunction allows getting a JavaScript property by name. -type GetFunction interface { - Get(propertyKey string) interface{} -} - -// ByteArray is a result of uint8ArrayConstructor which temporarily stores -// binary data outside linear memory. -// -// Note: This is a wrapper because a slice is not hashable. -type ByteArray struct { - slice []byte -} - -func WrapByteArray(buf []byte) *ByteArray { - return &ByteArray{buf} -} - -// Unwrap returns the underlying byte slice -func (a *ByteArray) Unwrap() []byte { - return a.slice -} - -// Get implements GetFunction -func (a *ByteArray) Get(propertyKey string) interface{} { - switch propertyKey { - case "byteLength": - return uint32(len(a.slice)) - } - panic(fmt.Sprintf("TODO: get byteArray.%s", propertyKey)) -} diff --git a/internal/gojs/js.go b/internal/gojs/js.go deleted file mode 100644 index 2667fa6e40..0000000000 --- a/internal/gojs/js.go +++ /dev/null @@ -1,83 +0,0 @@ -package gojs - -import ( - "context" - "fmt" - - "github.com/tetratelabs/wazero/api" - "github.com/tetratelabs/wazero/internal/gojs/goos" -) - -// jsFn is a jsCall.call function, configured via jsVal.addFunction. -// -// Note: This is not a `func` because we need it to be a hashable type. -type jsFn interface { - invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) -} - -// jsCall allows calling a method/function by name. -type jsCall interface { - call(ctx context.Context, mod api.Module, this goos.Ref, method string, args ...interface{}) (interface{}, error) -} - -func newJsVal(ref goos.Ref, name string) *jsVal { - return &jsVal{ref: ref, name: name, properties: map[string]interface{}{}, functions: map[string]jsFn{}} -} - -// jsVal corresponds to a generic js.Value in go, when `GOOS=js`. -type jsVal struct { - // ref is the constant reference used for built-in values, such as - // objectConstructor. - ref goos.Ref - name string - properties map[string]interface{} - functions map[string]jsFn -} - -func (v *jsVal) addProperties(properties map[string]interface{}) *jsVal { - for k, val := range properties { - v.properties[k] = val - } - return v -} - -func (v *jsVal) addFunction(method string, fn jsFn) *jsVal { - v.functions[method] = fn - // If fn returns an error, js.Call does a type lookup to verify it is a - // function. - // See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L389 - v.properties[method] = fn - return v -} - -// Get implements the same method as documented on goos.GetFunction -func (v *jsVal) Get(propertyKey string) interface{} { - if v, ok := v.properties[propertyKey]; ok { - return v - } - panic(fmt.Sprintf("TODO: get %s.%s", v.name, propertyKey)) -} - -// call implements jsCall.call -func (v *jsVal) call(ctx context.Context, mod api.Module, this goos.Ref, method string, args ...interface{}) (interface{}, error) { - if v, ok := v.functions[method]; ok { - return v.invoke(ctx, mod, args...) - } - panic(fmt.Sprintf("TODO: call %s.%s", v.name, method)) -} - -// objectArray is a result of arrayConstructor typically used to pass -// indexed arguments. -// -// Note: This is a wrapper because a slice is not hashable. -type objectArray struct { - slice []interface{} -} - -// object is a result of objectConstructor typically used to pass named -// arguments. -// -// Note: This is a wrapper because a map is not hashable. -type object struct { - properties map[string]interface{} -} diff --git a/internal/gojs/logging/logging.go b/internal/gojs/logging/logging.go deleted file mode 100644 index 7ca69dc3ae..0000000000 --- a/internal/gojs/logging/logging.go +++ /dev/null @@ -1,360 +0,0 @@ -package logging - -import ( - "context" - "fmt" - "io/fs" - "os" - "strconv" - - "github.com/tetratelabs/wazero/api" - "github.com/tetratelabs/wazero/internal/gojs" - "github.com/tetratelabs/wazero/internal/gojs/custom" - "github.com/tetratelabs/wazero/internal/gojs/goarch" - "github.com/tetratelabs/wazero/internal/gojs/goos" - "github.com/tetratelabs/wazero/internal/logging" - "github.com/tetratelabs/wazero/internal/sys" -) - -// IsInLogScope returns true if the current function is in any of the scopes. -func IsInLogScope(fnd api.FunctionDefinition, scopes logging.LogScopes) bool { - if scopes.IsEnabled(logging.LogScopeClock) { - switch fnd.Name() { - case custom.NameRuntimeNanotime1, custom.NameRuntimeWalltime: - return true - case custom.NameSyscallValueCall: // e.g. Date.getTimezoneOffset - return true - } - } - - if scopes.IsEnabled(logging.LogScopeProc) { - switch fnd.Name() { - case custom.NameRuntimeWasmExit: - return true - case custom.NameSyscallValueCall: // e.g. proc.* - return true - } - } - - if scopes.IsEnabled(logging.LogScopeFilesystem) { - if fnd.Name() == custom.NameSyscallValueCall { - return true // e.g. fs.open - } - } - - if scopes.IsEnabled(logging.LogScopeMemory) { - switch fnd.Name() { - case custom.NameRuntimeResetMemoryDataView: - return true - } - } - - if scopes.IsEnabled(logging.LogScopePoll) { - switch fnd.Name() { - case custom.NameRuntimeScheduleTimeoutEvent, custom.NameRuntimeClearTimeoutEvent: - return true - } - } - - if scopes.IsEnabled(logging.LogScopeRandom) { - switch fnd.Name() { - case custom.NameRuntimeGetRandomData: - return true - case custom.NameSyscallValueCall: // e.g. crypto.getRandomValues - return true - } - } - - return scopes == logging.LogScopeAll -} - -func Config(fnd api.FunctionDefinition, scopes logging.LogScopes) (pSampler logging.ParamSampler, pLoggers []logging.ParamLogger, rLoggers []logging.ResultLogger) { - switch fnd.Name() { - case custom.NameRuntimeWasmExit: - pLoggers = []logging.ParamLogger{runtimeWasmExitParamLogger} - // no results - case custom.NameRuntimeWasmWrite: - return // Don't log NameRuntimeWasmWrite as it is used in panics - case custom.NameRuntimeResetMemoryDataView: - // no params or results - case custom.NameRuntimeNanotime1: - // no params - rLoggers = []logging.ResultLogger{runtimeNanotime1ResultLogger} - case custom.NameRuntimeWalltime: - // no params - rLoggers = []logging.ResultLogger{runtimeWalltimeResultLogger} - case custom.NameRuntimeScheduleTimeoutEvent: - pLoggers = []logging.ParamLogger{runtimeScheduleTimeoutEventParamLogger} - rLoggers = []logging.ResultLogger{runtimeScheduleTimeoutEventResultLogger} - case custom.NameRuntimeClearTimeoutEvent: - pLoggers = []logging.ParamLogger{runtimeClearTimeoutEventParamLogger} - // no results - case custom.NameRuntimeGetRandomData: - pLoggers = []logging.ParamLogger{runtimeGetRandomDataParamLogger} - // no results - case custom.NameSyscallValueCall: - p := &syscallValueCallParamSampler{scopes: scopes} - pSampler = p.isSampled - pLoggers = []logging.ParamLogger{syscallValueCallParamLogger} - rLoggers = []logging.ResultLogger{syscallValueCallResultLogger} - default: // TODO: make generic logger for gojs - } - return -} - -func runtimeGetRandomDataParamLogger(_ context.Context, mod api.Module, w logging.Writer, params []uint64) { - paramIdx := 1 /* there are two params, only write the length */ - writeParameter(w, custom.NameRuntimeGetRandomData, mod, params, paramIdx) -} - -func runtimeScheduleTimeoutEventParamLogger(_ context.Context, mod api.Module, w logging.Writer, params []uint64) { - writeParameter(w, custom.NameRuntimeScheduleTimeoutEvent, mod, params, 0) -} - -func runtimeClearTimeoutEventParamLogger(_ context.Context, mod api.Module, w logging.Writer, params []uint64) { - writeParameter(w, custom.NameRuntimeClearTimeoutEvent, mod, params, 0) -} - -func runtimeWasmExitParamLogger(_ context.Context, mod api.Module, w logging.Writer, params []uint64) { - writeParameter(w, custom.NameRuntimeWasmExit, mod, params, 0) -} - -func writeParameter(w logging.Writer, funcName string, mod api.Module, params []uint64, paramIdx int) { - paramNames := custom.NameSection[funcName].ParamNames - - stack := goos.NewStack(funcName, mod.Memory(), uint32(params[0])) - w.WriteString(paramNames[paramIdx]) //nolint - w.WriteByte('=') //nolint - writeI32(w, stack.ParamUint32(paramIdx)) -} - -func runtimeNanotime1ResultLogger(_ context.Context, mod api.Module, w logging.Writer, params, _ []uint64) { - writeResults(w, custom.NameRuntimeNanotime1, mod, params, 0) -} - -func runtimeWalltimeResultLogger(_ context.Context, mod api.Module, w logging.Writer, params, _ []uint64) { - writeResults(w, custom.NameRuntimeWalltime, mod, params, 0) -} - -func runtimeScheduleTimeoutEventResultLogger(_ context.Context, mod api.Module, w logging.Writer, params, _ []uint64) { - writeResults(w, custom.NameRuntimeScheduleTimeoutEvent, mod, params, 1) -} - -func writeResults(w logging.Writer, funcName string, mod api.Module, params []uint64, resultOffset int) { - stack := goos.NewStack(funcName, mod.Memory(), uint32(params[0])) - - resultNames := custom.NameSection[funcName].ResultNames - results := make([]interface{}, len(resultNames)) - for i := range resultNames { - results[i] = stack.ParamUint32(i + resultOffset) - } - - w.WriteByte('(') //nolint - writeVals(w, resultNames, results) - w.WriteByte(')') //nolint -} - -type syscallValueCallParamSampler struct { - scopes logging.LogScopes -} - -func (s *syscallValueCallParamSampler) isSampled(ctx context.Context, mod api.Module, params []uint64) bool { - vRef, m, args := syscallValueCallParams(ctx, mod, params) - - switch vRef { - case goos.RefJsCrypto: - return logging.LogScopeRandom.IsEnabled(s.scopes) - case goos.RefJsDate: - return logging.LogScopeClock.IsEnabled(s.scopes) - case goos.RefJsfs: - if !logging.LogScopeFilesystem.IsEnabled(s.scopes) { - return false - } - // Don't amplify logs with stdio reads or writes - switch m { - case custom.NameFsWrite, custom.NameFsRead: - fd := goos.ValueToInt32(args[0]) - return fd > sys.FdStderr - } - return true - case goos.RefJsProcess: - return logging.LogScopeProc.IsEnabled(s.scopes) - } - - return s.scopes == logging.LogScopeAll -} - -func syscallValueCallParamLogger(ctx context.Context, mod api.Module, w logging.Writer, params []uint64) { - vRef, m, args := syscallValueCallParams(ctx, mod, params) - - switch vRef { - case goos.RefJsCrypto: - logSyscallValueCallArgs(w, custom.NameCrypto, m, args) - case goos.RefJsDate: - logSyscallValueCallArgs(w, custom.NameDate, m, args) - case goos.RefJsfs: - logFsParams(m, w, args) - case goos.RefJsProcess: - logSyscallValueCallArgs(w, custom.NameProcess, m, args) - default: - // TODO: other scopes - } -} - -func logFsParams(m string, w logging.Writer, args []interface{}) { - if m == custom.NameFsOpen { - w.WriteString("fs.open(") //nolint - w.WriteString("path=") //nolint - w.WriteString(args[0].(string)) //nolint - w.WriteString(",flags=") //nolint - writeOFlags(w, int(args[1].(float64))) - w.WriteString(",perm=") //nolint - w.WriteString(fs.FileMode(uint32(args[2].(float64))).String()) //nolint - w.WriteByte(')') //nolint - return - } - - logSyscallValueCallArgs(w, custom.NameFs, m, args) -} - -func logSyscallValueCallArgs(w logging.Writer, n, m string, args []interface{}) { - argNames := custom.NameSectionSyscallValueCall[n][m].ParamNames - w.WriteString(n) //nolint - w.WriteByte('.') //nolint - w.WriteString(m) //nolint - w.WriteByte('(') //nolint - writeVals(w, argNames, args) - w.WriteByte(')') //nolint -} - -func syscallValueCallParams(ctx context.Context, mod api.Module, params []uint64) (goos.Ref, string, []interface{}) { - mem := mod.Memory() - funcName := custom.NameSyscallValueCall - stack := goos.NewStack(funcName, mem, uint32(params[0])) - vRef := stack.ParamRef(0) //nolint - m := stack.ParamString(mem, 1 /*, 2 */) //nolint - args := stack.ParamVals(ctx, mem, 3 /*, 4 */, gojs.LoadValue) - return vRef, m, args -} - -func syscallValueCallResultLogger(ctx context.Context, mod api.Module, w logging.Writer, params, results []uint64) { - mem := mod.Memory() - funcName := custom.NameSyscallValueCall - stack := goos.NewStack(funcName, mem, goarch.GetSP(mod)) - vRef := stack.ParamRef(0) //nolint - m := stack.ParamString(mem, 1 /*, 2 */) //nolint - - var resultNames []string - var resultVals []interface{} - switch vRef { - case goos.RefJsCrypto: - resultNames = custom.CryptoNameSection[m].ResultNames - rRef := stack.ParamVal(ctx, 6, gojs.LoadValue) // val is after padding - resultVals = []interface{}{rRef} - case goos.RefJsDate: - resultNames = custom.DateNameSection[m].ResultNames - rRef := stack.ParamVal(ctx, 6, gojs.LoadValue) // val is after padding - resultVals = []interface{}{rRef} - case goos.RefJsfs: - resultNames = custom.FsNameSection[m].ResultNames - resultVals = gojs.GetLastEventArgs(ctx) - case goos.RefJsProcess: - resultNames = custom.ProcessNameSection[m].ResultNames - rRef := stack.ParamVal(ctx, 6, gojs.LoadValue) // val is after padding - resultVals = []interface{}{rRef} - default: - // TODO: other scopes - } - - w.WriteByte('(') //nolint - writeVals(w, resultNames, resultVals) - w.WriteByte(')') //nolint -} - -func writeVals(w logging.Writer, names []string, vals []interface{}) { - valLen := len(vals) - if valLen > 0 { - writeVal(w, names[0], vals[0]) - for i := 1; i < valLen; i++ { - name := names[i] - val := vals[i] - - switch name { - case custom.NameCallback: - return // last val - case "buf": // always equal size with byteCount - continue - } - - w.WriteByte(',') //nolint - writeVal(w, name, val) - } - } -} - -func writeVal(w logging.Writer, name string, val interface{}) { - if b, ok := val.(*goos.ByteArray); ok { - // Write the length instead of a byte array. - w.WriteString(name) //nolint - w.WriteString("_len=") //nolint - writeI32(w, uint32(len(b.Unwrap()))) //nolint - return - } - switch name { - case "uid", "gid": - w.WriteString(name) //nolint - w.WriteByte('=') //nolint - id := int(goos.ValueToInt32(val)) - w.WriteString(strconv.Itoa(id)) //nolint - case "mask", "mode", "oldmask", "perm": - w.WriteString(name) //nolint - w.WriteByte('=') //nolint - perm := custom.FromJsMode(goos.ValueToUint32(val), 0) - w.WriteString(perm.String()) //nolint - default: - w.WriteString(name) //nolint - w.WriteByte('=') //nolint - w.WriteString(fmt.Sprintf("%v", val)) //nolint - } -} - -func writeOFlags(w logging.Writer, f int) { - // Iterate a subflagset in order to avoid OS differences, notably for windows - first := true - for i, sf := range oFlags { - if f&sf != 0 { - if !first { - w.WriteByte('|') //nolint - } else { - first = false - } - w.WriteString(oflagToString[i]) //nolint - } - } -} - -var oFlags = [...]int{ - os.O_RDONLY, - os.O_WRONLY, - os.O_RDWR, - os.O_APPEND, - os.O_CREATE, - os.O_EXCL, - os.O_SYNC, - os.O_TRUNC, -} - -var oflagToString = [...]string{ - "RDONLY", - "WRONLY", - "RDWR", - "APPEND", - "CREATE", - "EXCL", - "SYNC", - "TRUNC", -} - -func writeI32(w logging.Writer, v uint32) { - w.WriteString(strconv.FormatInt(int64(int32(v)), 10)) //nolint -} diff --git a/internal/gojs/logging/logging_test.go b/internal/gojs/logging/logging_test.go deleted file mode 100644 index 1574724db9..0000000000 --- a/internal/gojs/logging/logging_test.go +++ /dev/null @@ -1,157 +0,0 @@ -package logging - -import ( - "testing" - - "github.com/tetratelabs/wazero/api" - "github.com/tetratelabs/wazero/internal/gojs/custom" - "github.com/tetratelabs/wazero/internal/logging" - "github.com/tetratelabs/wazero/internal/testing/require" - "github.com/tetratelabs/wazero/internal/wasm" -) - -type testFunctionDefinition struct { - name string - *wasm.FunctionDefinition -} - -// Name implements the same method as documented on api.FunctionDefinition. -func (f *testFunctionDefinition) Name() string { - return f.name -} - -func TestIsInLogScope(t *testing.T) { - runtimeGetRandomData := &testFunctionDefinition{name: custom.NameRuntimeGetRandomData} - runtimeResetMemoryDataView := &testFunctionDefinition{name: custom.NameRuntimeResetMemoryDataView} - runtimeWasmExit := &testFunctionDefinition{name: custom.NameRuntimeWasmExit} - syscallValueCall := &testFunctionDefinition{name: custom.NameSyscallValueCall} - tests := []struct { - name string - fnd api.FunctionDefinition - scopes logging.LogScopes - expected bool - }{ - { - name: "runtimeWasmExit in LogScopeProc", - fnd: runtimeWasmExit, - scopes: logging.LogScopeProc, - expected: true, - }, - { - name: "runtimeWasmExit not in LogScopeFilesystem", - fnd: runtimeWasmExit, - scopes: logging.LogScopeFilesystem, - expected: false, - }, - { - name: "runtimeWasmExit in LogScopeProc|LogScopeFilesystem", - fnd: runtimeWasmExit, - scopes: logging.LogScopeProc | logging.LogScopeFilesystem, - expected: true, - }, - { - name: "runtimeWasmExit not in LogScopeNone", - fnd: runtimeWasmExit, - scopes: logging.LogScopeNone, - expected: false, - }, - { - name: "runtimeWasmExit in LogScopeAll", - fnd: runtimeWasmExit, - scopes: logging.LogScopeAll, - expected: true, - }, - { - name: "runtimeResetMemoryDataView in LogScopeMemory", - fnd: runtimeResetMemoryDataView, - scopes: logging.LogScopeMemory, - expected: true, - }, - { - name: "runtimeResetMemoryDataView not in LogScopeFilesystem", - fnd: runtimeResetMemoryDataView, - scopes: logging.LogScopeFilesystem, - expected: false, - }, - { - name: "runtimeResetMemoryDataView in LogScopeMemory|LogScopeFilesystem", - fnd: runtimeResetMemoryDataView, - scopes: logging.LogScopeMemory | logging.LogScopeFilesystem, - expected: true, - }, - { - name: "runtimeResetMemoryDataView not in LogScopeNone", - fnd: runtimeResetMemoryDataView, - scopes: logging.LogScopeNone, - expected: false, - }, - { - name: "runtimeResetMemoryDataView in LogScopeAll", - fnd: runtimeResetMemoryDataView, - scopes: logging.LogScopeAll, - expected: true, - }, - { - name: "runtimeGetRandomData not in LogScopeFilesystem", - fnd: runtimeGetRandomData, - scopes: logging.LogScopeFilesystem, - expected: false, - }, - { - name: "runtimeGetRandomData in LogScopeRandom|LogScopeFilesystem", - fnd: runtimeGetRandomData, - scopes: logging.LogScopeRandom | logging.LogScopeFilesystem, - expected: true, - }, - { - name: "runtimeGetRandomData not in LogScopeNone", - fnd: runtimeGetRandomData, - scopes: logging.LogScopeNone, - expected: false, - }, - { - name: "runtimeGetRandomData in LogScopeAll", - fnd: runtimeGetRandomData, - scopes: logging.LogScopeAll, - expected: true, - }, - { - name: "syscallValueCall in LogScopeFilesystem", - fnd: syscallValueCall, - scopes: logging.LogScopeFilesystem, - expected: true, - }, - { - name: "syscallValueCall in LogScopeRandom", - fnd: syscallValueCall, - scopes: logging.LogScopeRandom, - expected: true, - }, - { - name: "syscallValueCall in LogScopeRandom|LogScopeFilesystem", - fnd: syscallValueCall, - scopes: logging.LogScopeRandom | logging.LogScopeFilesystem, - expected: true, - }, - { - name: "syscallValueCall in LogScopeAll", - fnd: syscallValueCall, - scopes: logging.LogScopeAll, - expected: true, - }, - { - name: "syscallValueCall not in LogScopeNone", - fnd: syscallValueCall, - scopes: logging.LogScopeNone, - expected: false, - }, - } - - for _, tt := range tests { - tc := tt - - t.Run(tc.name, func(t *testing.T) { - require.Equal(t, tc.expected, IsInLogScope(tc.fnd, tc.scopes)) - }) - } -} diff --git a/internal/gojs/misc_test.go b/internal/gojs/misc_test.go deleted file mode 100644 index b845469795..0000000000 --- a/internal/gojs/misc_test.go +++ /dev/null @@ -1,109 +0,0 @@ -package gojs_test - -import ( - "bytes" - "context" - "fmt" - "strings" - "testing" - - "github.com/tetratelabs/wazero" - "github.com/tetratelabs/wazero/experimental" - "github.com/tetratelabs/wazero/experimental/logging" - "github.com/tetratelabs/wazero/internal/gojs/config" - "github.com/tetratelabs/wazero/internal/testing/require" -) - -func Test_exit(t *testing.T) { - t.Parallel() - - var log bytes.Buffer - loggingCtx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{}, - logging.NewHostLoggingListenerFactory(&log, logging.LogScopeProc)) - - stdout, stderr, err := compileAndRun(loggingCtx, "exit", defaultConfig) - - require.Zero(t, stderr) - require.EqualError(t, err, `module closed with exit_code(255)`) - require.Zero(t, stdout) - require.Equal(t, `==> go.runtime.wasmExit(code=255) -<== -`, logString(log)) // Note: gojs doesn't panic on exit, so you see "<==" -} - -func Test_goroutine(t *testing.T) { - t.Parallel() - - stdout, stderr, err := compileAndRun(testCtx, "goroutine", defaultConfig) - - require.Zero(t, stderr) - require.NoError(t, err) - require.Equal(t, `producer -consumer -`, stdout) -} - -func Test_mem(t *testing.T) { - t.Parallel() - - var log bytes.Buffer - loggingCtx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{}, - logging.NewHostLoggingListenerFactory(&log, logging.LogScopeMemory)) - - stdout, stderr, err := compileAndRun(loggingCtx, "mem", defaultConfig) - - require.Zero(t, stderr) - require.NoError(t, err) - require.Zero(t, stdout) - - // The memory view is reset at least once. - require.Contains(t, logString(log), `==> go.runtime.resetMemoryDataView() -<== -`) -} - -func Test_stdio(t *testing.T) { - t.Parallel() - - input := "stdin\n" - stdout, stderr, err := compileAndRun(testCtx, "stdio", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) { - return defaultConfig(moduleConfig.WithStdin(strings.NewReader(input))) - }) - - require.Equal(t, "stderr 6\n", stderr) - require.NoError(t, err) - require.Equal(t, "stdout 6\n", stdout) -} - -func Test_stdio_large(t *testing.T) { - t.Parallel() - - // Large stdio will trigger GC which will trigger events. - var log bytes.Buffer - loggingCtx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{}, - logging.NewHostLoggingListenerFactory(&log, logging.LogScopePoll)) - - size := 2 * 1024 * 1024 // 2MB - input := make([]byte, size) - stdout, stderr, err := compileAndRun(loggingCtx, "stdio", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) { - return defaultConfig(moduleConfig.WithStdin(bytes.NewReader(input))) - }) - - require.NoError(t, err) - require.Equal(t, fmt.Sprintf("stderr %d\n", size), stderr) - require.Equal(t, fmt.Sprintf("stdout %d\n", size), stdout) - - // There's no guarantee of a timeout event (in Go 1.21 there isn't), so we - // don't verify this. gojs is in maintenance mode until it is removed after - // Go 1.22 is out. -} - -func Test_gc(t *testing.T) { - t.Parallel() - - stdout, stderr, err := compileAndRun(testCtx, "gc", defaultConfig) - - require.NoError(t, err) - require.Equal(t, "", stderr) - require.Equal(t, "before gc\nafter gc\n", stdout) -} diff --git a/internal/gojs/process.go b/internal/gojs/process.go deleted file mode 100644 index 64af320464..0000000000 --- a/internal/gojs/process.go +++ /dev/null @@ -1,101 +0,0 @@ -package gojs - -import ( - "context" - "path" - - "github.com/tetratelabs/wazero/api" - "github.com/tetratelabs/wazero/experimental/sys" - "github.com/tetratelabs/wazero/internal/gojs/custom" - "github.com/tetratelabs/wazero/internal/gojs/goos" - "github.com/tetratelabs/wazero/internal/gojs/util" -) - -// processState are the mutable fields of the current process. -type processState struct { - cwd string - umask uint32 -} - -func newJsProcess(proc *processState) *jsVal { - // Fill fake values for user/group info as we don't support it. - uidRef := goos.RefValueZero - gidRef := goos.RefValueZero - euidRef := goos.RefValueZero - groupSlice := []interface{}{goos.RefValueZero} - - // jsProcess = js.Global().Get("process") // fs_js.go init - return newJsVal(goos.RefJsProcess, custom.NameProcess). - addProperties(map[string]interface{}{ - "pid": float64(1), // Get("pid").Int() in syscall_js.go for syscall.Getpid - "ppid": goos.RefValueZero, // Get("ppid").Int() in syscall_js.go for syscall.Getppid - }). - addFunction(custom.NameProcessCwd, &processCwd{proc: proc}). // syscall.Cwd in fs_js.go - addFunction(custom.NameProcessChdir, &processChdir{proc: proc}). // syscall.Chdir in fs_js.go - addFunction(custom.NameProcessGetuid, getId(uidRef)). // syscall.Getuid in syscall_js.go - addFunction(custom.NameProcessGetgid, getId(gidRef)). // syscall.Getgid in syscall_js.go - addFunction(custom.NameProcessGeteuid, getId(euidRef)). // syscall.Geteuid in syscall_js.go - addFunction(custom.NameProcessGetgroups, returnSlice(groupSlice)). // syscall.Getgroups in syscall_js.go - addFunction(custom.NameProcessUmask, &processUmask{proc: proc}) // syscall.Umask in syscall_js.go -} - -// processCwd implements jsFn for fs.Open syscall.Getcwd in fs_js.go -type processCwd struct { - proc *processState -} - -func (p *processCwd) invoke(_ context.Context, _ api.Module, _ ...interface{}) (interface{}, error) { - return p.proc.cwd, nil -} - -// processChdir implements jsFn for fs.Open syscall.Chdir in fs_js.go -type processChdir struct { - proc *processState -} - -func (p *processChdir) invoke(_ context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - oldWd := p.proc.cwd - newWd := util.ResolvePath(oldWd, args[0].(string)) - - newWd = path.Clean(newWd) - if newWd == oldWd { // handle . - return nil, nil - } - - if s, err := syscallStat(mod, newWd); err != nil { - return nil, err - } else if !s.isDir { - return nil, sys.ENOTDIR - } else { - p.proc.cwd = newWd - return nil, nil - } -} - -// processUmask implements jsFn for fs.Open syscall.Umask in fs_js.go -type processUmask struct { - proc *processState -} - -func (p *processUmask) invoke(_ context.Context, _ api.Module, args ...interface{}) (interface{}, error) { - newUmask := goos.ValueToUint32(args[0]) - - oldUmask := p.proc.umask - p.proc.umask = newUmask - - return oldUmask, nil -} - -// getId implements jsFn for syscall.Getuid, syscall.Getgid and syscall.Geteuid in syscall_js.go -type getId goos.Ref - -func (i getId) invoke(_ context.Context, _ api.Module, _ ...interface{}) (interface{}, error) { - return goos.Ref(i), nil -} - -// returnSlice implements jsFn for syscall.Getgroups in syscall_js.go -type returnSlice []interface{} - -func (s returnSlice) invoke(context.Context, api.Module, ...interface{}) (interface{}, error) { - return &objectArray{slice: s}, nil -} diff --git a/internal/gojs/process_test.go b/internal/gojs/process_test.go deleted file mode 100644 index 9965c1e5d5..0000000000 --- a/internal/gojs/process_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package gojs_test - -import ( - "os" - "testing" - - "github.com/tetratelabs/wazero" - "github.com/tetratelabs/wazero/internal/gojs/config" - "github.com/tetratelabs/wazero/internal/testing/require" -) - -func Test_process(t *testing.T) { - t.Parallel() - - require.NoError(t, os.Chdir("/..")) - stdout, stderr, err := compileAndRun(testCtx, "process", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) { - return defaultConfig(moduleConfig.WithFS(testFS)) - }) - - require.Zero(t, stderr) - require.NoError(t, err) - require.Equal(t, `syscall.Getpid()=1 -syscall.Getppid()=0 -syscall.Getuid()=0 -syscall.Getgid()=0 -syscall.Geteuid()=0 -syscall.Umask(0077)=0o22 -syscall.Getgroups()=[0] -os.FindProcess(1).Pid=1 -wd ok -Not a directory -`, stdout) -} diff --git a/internal/gojs/run/gojs.go b/internal/gojs/run/gojs.go deleted file mode 100644 index 651debb803..0000000000 --- a/internal/gojs/run/gojs.go +++ /dev/null @@ -1,43 +0,0 @@ -// Package run exists to avoid dependency cycles when keeping most of gojs -// code internal. -package run - -import ( - "context" - - "github.com/tetratelabs/wazero" - "github.com/tetratelabs/wazero/internal/gojs" - "github.com/tetratelabs/wazero/internal/gojs/config" - "github.com/tetratelabs/wazero/sys" -) - -func Run(ctx context.Context, r wazero.Runtime, compiled wazero.CompiledModule, moduleConfig wazero.ModuleConfig, config *config.Config) error { - if err := config.Init(); err != nil { - return err - } - - // Instantiate the module compiled by go, noting it has no init function. - mod, err := r.InstantiateModule(ctx, compiled, moduleConfig) - if err != nil { - return err - } - defer mod.Close(ctx) - - // Extract the args and env from the module Config and write it to memory. - argc, argv, err := gojs.WriteArgsAndEnviron(mod) - if err != nil { - return err - } - - // Create host-side state for JavaScript values and events. - ctx = context.WithValue(ctx, gojs.StateKey{}, gojs.NewState(config)) - - // Invoke the run function. - _, err = mod.ExportedFunction("run").Call(ctx, uint64(argc), uint64(argv)) - if se, ok := err.(*sys.ExitError); ok { - if se.ExitCode() == 0 { // Don't err on success. - err = nil - } - } - return err -} diff --git a/internal/gojs/runtime.go b/internal/gojs/runtime.go deleted file mode 100644 index eec1c86be8..0000000000 --- a/internal/gojs/runtime.go +++ /dev/null @@ -1,165 +0,0 @@ -package gojs - -import ( - "context" - "fmt" - "time" - - "github.com/tetratelabs/wazero/api" - "github.com/tetratelabs/wazero/experimental/sys" - "github.com/tetratelabs/wazero/internal/gojs/custom" - "github.com/tetratelabs/wazero/internal/gojs/goarch" - "github.com/tetratelabs/wazero/internal/wasm" -) - -// Debug has unknown use, so stubbed. -// -// See https://github.com/golang/go/blob/go1.20/src/cmd/link/internal/wasm/asm.go#L131-L136 -var Debug = goarch.StubFunction(custom.NameDebug) - -// TODO: should this call runtime.Breakpoint()? - -// WasmExit implements runtime.wasmExit which supports runtime.exit. -// -// See https://github.com/golang/go/blob/go1.20/src/runtime/sys_wasm.go#L24 -var WasmExit = goarch.NewFunc(custom.NameRuntimeWasmExit, wasmExit) - -func wasmExit(ctx context.Context, mod api.Module, stack goarch.Stack) { - code := stack.ParamUint32(0) - - getState(ctx).close() - _ = mod.CloseWithExitCode(ctx, code) -} - -// WasmWrite implements runtime.wasmWrite which supports runtime.write and -// runtime.writeErr. This implements `println`. -// -// See https://github.com/golang/go/blob/go1.20/src/runtime/os_js.go#L30 -var WasmWrite = goarch.NewFunc(custom.NameRuntimeWasmWrite, wasmWrite) - -func wasmWrite(_ context.Context, mod api.Module, stack goarch.Stack) { - fd := stack.ParamInt32(0) - p := stack.ParamBytes(mod.Memory(), 1 /*, 2 */) - - fsc := mod.(*wasm.ModuleInstance).Sys.FS() - if f, ok := fsc.LookupFile(fd); ok { - _, errno := f.File.Write(p) - switch errno { - case 0: - return // success - case sys.ENOSYS: - return // e.g. unimplemented for write - case sys.EBADF: - return // e.g. not opened for write - default: - panic(fmt.Errorf("error writing p: %w", errno)) - } - } else { - panic(fmt.Errorf("fd %d invalid", fd)) - } -} - -// ResetMemoryDataView signals wasm.OpcodeMemoryGrow happened, indicating any -// cached view of memory should be reset. -// -// See https://github.com/golang/go/blob/go1.20/src/runtime/mem_js.go#L82 -var ResetMemoryDataView = goarch.NewFunc(custom.NameRuntimeResetMemoryDataView, resetMemoryDataView) - -func resetMemoryDataView(context.Context, api.Module, goarch.Stack) { - // context state does not cache a memory view, and all byte slices used - // are safely copied. Also, user-defined functions are not supported. - // Hence, there's currently no known reason to reset anything. -} - -// Nanotime1 implements runtime.nanotime which supports time.Since. -// -// See https://github.com/golang/go/blob/go1.20/src/runtime/sys_wasm.s#L117 -var Nanotime1 = goarch.NewFunc(custom.NameRuntimeNanotime1, nanotime1) - -func nanotime1(_ context.Context, mod api.Module, stack goarch.Stack) { - nsec := mod.(*wasm.ModuleInstance).Sys.Nanotime() - - stack.SetResultI64(0, nsec) -} - -// Walltime implements runtime.walltime which supports time.Now. -// -// See https://github.com/golang/go/blob/go1.20/src/runtime/sys_wasm.s#L121 -var Walltime = goarch.NewFunc(custom.NameRuntimeWalltime, walltime) - -func walltime(_ context.Context, mod api.Module, stack goarch.Stack) { - sec, nsec := mod.(*wasm.ModuleInstance).Sys.Walltime() - - stack.SetResultI64(0, sec) - stack.SetResultI32(1, 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.20/src/runtime/sys_wasm.s#L125 -var ScheduleTimeoutEvent = goarch.NewFunc(custom.NameRuntimeScheduleTimeoutEvent, scheduleTimeoutEvent) - -// Note: Signal handling is not implemented in GOOS=js. -func scheduleTimeoutEvent(ctx context.Context, mod api.Module, stack goarch.Stack) { - ms := stack.Param(0) - - s := getState(ctx) - id := s._nextCallbackTimeoutID - stack.SetResultUint32(0, id) - s._nextCallbackTimeoutID++ - - cleared := make(chan bool) - timeout := time.Millisecond * time.Duration(ms) - s._scheduledTimeouts[id] = cleared - - // As wasm is currently not concurrent, a timeout on another goroutine may - // not make sense. However, this implements what wasm_exec.js does anyway. - go func() { - select { - case <-cleared: // do nothing - case <-time.After(timeout): - if _, err := mod.ExportedFunction("resume").Call(ctx); err != nil { - println(err) - } - } - }() -} - -// ClearTimeoutEvent implements runtime.clearTimeoutEvent which supports -// runtime.notetsleepg used by runtime.signal_recv. -// -// See https://github.com/golang/go/blob/go1.20/src/runtime/sys_wasm.s#L129 -var ClearTimeoutEvent = goarch.NewFunc(custom.NameRuntimeClearTimeoutEvent, clearTimeoutEvent) - -// Note: Signal handling is not implemented in GOOS=js. -func clearTimeoutEvent(ctx context.Context, _ api.Module, stack goarch.Stack) { - id := stack.ParamUint32(0) - s := getState(ctx) - if cancel, ok := s._scheduledTimeouts[id]; ok { - delete(s._scheduledTimeouts, id) - cancel <- true - } -} - -// GetRandomData implements runtime.getRandomData, which initializes the seed -// for runtime.fastrand. -// -// See https://github.com/golang/go/blob/go1.20/src/runtime/sys_wasm.s#L133 -var GetRandomData = goarch.NewFunc(custom.NameRuntimeGetRandomData, getRandomData) - -func getRandomData(_ context.Context, mod api.Module, stack goarch.Stack) { - r := stack.ParamBytes(mod.Memory(), 0 /*, 1 */) - - randSource := mod.(*wasm.ModuleInstance).Sys.RandSource() - - bufLen := len(r) - if n, err := randSource.Read(r); err != nil { - panic(fmt.Errorf("RandSource.Read(r /* len=%d */) failed: %w", bufLen, err)) - } else if n != bufLen { - panic(fmt.Errorf("RandSource.Read(r /* len=%d */) read %d bytes", bufLen, n)) - } -} diff --git a/internal/gojs/state.go b/internal/gojs/state.go deleted file mode 100644 index a0ac6c92d3..0000000000 --- a/internal/gojs/state.go +++ /dev/null @@ -1,244 +0,0 @@ -package gojs - -import ( - "context" - "fmt" - "math" - - "github.com/tetratelabs/wazero/api" - "github.com/tetratelabs/wazero/internal/gojs/config" - "github.com/tetratelabs/wazero/internal/gojs/goos" - "github.com/tetratelabs/wazero/internal/gojs/values" -) - -func NewState(config *config.Config) *State { - return &State{ - config: config, - values: values.NewValues(), - valueGlobal: newJsGlobal(config), - _nextCallbackTimeoutID: 1, - _scheduledTimeouts: map[uint32]chan bool{}, - } -} - -// StateKey is a context.Context Value key. The value must be a state pointer. -type StateKey struct{} - -func getState(ctx context.Context) *State { - return ctx.Value(StateKey{}).(*State) -} - -// GetLastEventArgs implements goos.GetLastEventArgs -func GetLastEventArgs(ctx context.Context) []interface{} { - if ls := ctx.Value(StateKey{}).(*State)._lastEvent; ls != nil { - if args := ls.args; args != nil { - return args.slice - } - } - return nil -} - -type event struct { - // id is the funcWrapper.id - id uint32 - this goos.Ref - args *objectArray - result interface{} -} - -// Get implements the same method as documented on goos.GetFunction -func (e *event) Get(propertyKey string) interface{} { - switch propertyKey { - case "id": - return e.id - case "this": // ex fs - return e.this - case "args": - return e.args - } - panic(fmt.Sprintf("TODO: event.%s", propertyKey)) -} - -var NaN = math.NaN() - -// LoadValue reads up to 8 bytes at the memory offset `addr` to return the -// value written by storeValue. -// -// See https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L122-L133 -func LoadValue(ctx context.Context, ref goos.Ref) interface{} { //nolint - switch ref { - case 0: - return goos.Undefined - case goos.RefValueNaN: - return NaN - case goos.RefValueZero: - return float64(0) - case goos.RefValueNull: - return nil - case goos.RefValueTrue: - return true - case goos.RefValueFalse: - return false - case goos.RefValueGlobal: - return getState(ctx).valueGlobal - case goos.RefJsGo: - return getState(ctx) - case goos.RefObjectConstructor: - return objectConstructor - case goos.RefArrayConstructor: - return arrayConstructor - case goos.RefJsProcess: - return getState(ctx).valueGlobal.Get("process") - case goos.RefJsfs: - return getState(ctx).valueGlobal.Get("fs") - case goos.RefJsfsConstants: - return jsfsConstants - case goos.RefUint8ArrayConstructor: - return uint8ArrayConstructor - case goos.RefJsCrypto: - return jsCrypto - case goos.RefJsDateConstructor: - return jsDateConstructor - case goos.RefJsDate: - return jsDate - default: - if f, ok := ref.ParseFloat(); ok { // numbers are passed through as a Ref - return f - } - return getState(ctx).values.Get(uint32(ref)) - } -} - -// storeValue stores a value prior to returning to wasm from a host function. -// This returns 8 bytes to represent either the value or a reference to it. -// Any side effects besides memory must be cleaned up on wasmExit. -// -// See https://github.com/golang/go/blob/de4748c47c67392a57f250714509f590f68ad395/misc/wasm/wasm_exec.js#L135-L183 -func storeValue(ctx context.Context, v interface{}) goos.Ref { //nolint - // allow-list because we control all implementations - if v == goos.Undefined { - return goos.RefValueUndefined - } else if v == nil { - return goos.RefValueNull - } else if r, ok := v.(goos.Ref); ok { - return r - } else if b, ok := v.(bool); ok { - if b { - return goos.RefValueTrue - } else { - return goos.RefValueFalse - } - } else if c, ok := v.(*jsVal); ok { - return c.ref // already stored - } else if _, ok := v.(*event); ok { - id := getState(ctx).values.Increment(v) - return goos.ValueRef(id, goos.TypeFlagFunction) - } else if _, ok := v.(funcWrapper); ok { - id := getState(ctx).values.Increment(v) - return goos.ValueRef(id, goos.TypeFlagFunction) - } else if _, ok := v.(jsFn); ok { - id := getState(ctx).values.Increment(v) - return goos.ValueRef(id, goos.TypeFlagFunction) - } else if _, ok := v.(string); ok { - id := getState(ctx).values.Increment(v) - return goos.ValueRef(id, goos.TypeFlagString) - } else if i32, ok := v.(int32); ok { - return toFloatRef(float64(i32)) - } else if u32, ok := v.(uint32); ok { - return toFloatRef(float64(u32)) - } else if i64, ok := v.(int64); ok { - return toFloatRef(float64(i64)) - } else if u64, ok := v.(uint64); ok { - return toFloatRef(float64(u64)) - } else if f64, ok := v.(float64); ok { - return toFloatRef(f64) - } - id := getState(ctx).values.Increment(v) - return goos.ValueRef(id, goos.TypeFlagObject) -} - -func toFloatRef(f float64) goos.Ref { - if f == 0 { - return goos.RefValueZero - } - // numbers are encoded as float and passed through as a Ref - return goos.Ref(api.EncodeF64(f)) -} - -// State holds state used by the "go" imports used by gojs. -// Note: This is module-scoped. -type State struct { - config *config.Config - values *values.Values - _pendingEvent *event - // _lastEvent was the last _pendingEvent value - _lastEvent *event - - valueGlobal *jsVal - - _nextCallbackTimeoutID uint32 - _scheduledTimeouts map[uint32]chan bool -} - -// Get implements the same method as documented on goos.GetFunction -func (s *State) Get(propertyKey string) interface{} { - switch propertyKey { - case "_pendingEvent": - return s._pendingEvent - } - panic(fmt.Sprintf("TODO: state.%s", propertyKey)) -} - -// call implements jsCall.call -func (s *State) call(_ context.Context, _ api.Module, _ goos.Ref, method string, args ...interface{}) (interface{}, error) { - switch method { - case "_makeFuncWrapper": - return funcWrapper(args[0].(float64)), nil - } - panic(fmt.Sprintf("TODO: state.%s", method)) -} - -// close releases any state including values and underlying slices for garbage -// collection. -func (s *State) close() { - // _scheduledTimeouts may have in-flight goroutines, so cancel them. - for k, cancel := range s._scheduledTimeouts { - delete(s._scheduledTimeouts, k) - cancel <- true - } - // Reset all state recursively to their initial values. This allows our - // unit tests to check we closed everything. - s.values.Reset() - s._pendingEvent = nil - s._lastEvent = nil - s.valueGlobal = newJsGlobal(s.config) - s._nextCallbackTimeoutID = 1 - s._scheduledTimeouts = map[uint32]chan bool{} -} - -func toInt64(arg interface{}) int64 { - if arg == goos.RefValueZero || arg == goos.Undefined { - return 0 - } else if u, ok := arg.(int64); ok { - return u - } - return int64(arg.(float64)) -} - -func toUint64(arg interface{}) uint64 { - if arg == goos.RefValueZero || arg == goos.Undefined { - return 0 - } else if u, ok := arg.(uint64); ok { - return u - } - return uint64(arg.(float64)) -} - -// valueString returns the string form of JavaScript string, boolean and number types. -func valueString(v interface{}) string { //nolint - if s, ok := v.(string); ok { - return s - } else { - return fmt.Sprintf("%v", v) - } -} diff --git a/internal/gojs/syscall.go b/internal/gojs/syscall.go deleted file mode 100644 index 6594caf8c1..0000000000 --- a/internal/gojs/syscall.go +++ /dev/null @@ -1,376 +0,0 @@ -package gojs - -import ( - "context" - "fmt" - - "github.com/tetratelabs/wazero/api" - "github.com/tetratelabs/wazero/internal/gojs/custom" - "github.com/tetratelabs/wazero/internal/gojs/goarch" - "github.com/tetratelabs/wazero/internal/gojs/goos" - "github.com/tetratelabs/wazero/sys" -) - -// FinalizeRef implements js.finalizeRef, which is used as a -// runtime.SetFinalizer on the given reference. -// -// See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L61 -var FinalizeRef = goos.NewFunc(custom.NameSyscallFinalizeRef, finalizeRef) - -func finalizeRef(ctx context.Context, _ api.Module, stack goos.Stack) { - r := stack.ParamRef(0) - - id := uint32(r) // 32-bits of the ref are the ID - - getState(ctx).values.Decrement(id) -} - -// StringVal implements js.stringVal, which is used to load the string for -// `js.ValueOf(x)`. For example, this is used when setting HTTP headers. -// -// See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L212 -// and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L305-L308 -var StringVal = goos.NewFunc(custom.NameSyscallStringVal, stringVal) - -func stringVal(ctx context.Context, mod api.Module, stack goos.Stack) { - x := stack.ParamString(mod.Memory(), 0) - - r := storeValue(ctx, x) - - stack.SetResultRef(0, r) -} - -// ValueGet implements js.valueGet, which is used to load a js.Value property -// by name, e.g. `v.Get("address")`. Notably, this is used by js.handleEvent to -// get the pending event. -// -// See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L295 -// and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L311-L316 -var ValueGet = goos.NewFunc(custom.NameSyscallValueGet, valueGet) - -func valueGet(ctx context.Context, mod api.Module, stack goos.Stack) { - v := stack.ParamVal(ctx, 0, LoadValue) - p := stack.ParamString(mod.Memory(), 1 /*, 2 */) - - var result interface{} - if g, ok := v.(goos.GetFunction); ok { - result = g.Get(p) - } else if e, ok := v.(error); ok { - switch p { - case "message": // js (GOOS=js) error, can be anything. - result = e.Error() - case "code": // syscall (GOARCH=wasm) error, must match key in mapJSError in fs_js.go - result = ToErrno(e).Error() - default: - panic(fmt.Errorf("TODO: valueGet(v=%v, p=%s)", v, p)) - } - } else { - panic(fmt.Errorf("TODO: valueGet(v=%v, p=%s)", v, p)) - } - - r := storeValue(ctx, result) - stack.SetResultRef(0, r) -} - -// ValueSet implements js.valueSet, which is used to store a js.Value property -// by name, e.g. `v.Set("address", a)`. Notably, this is used by js.handleEvent -// set the event result. -// -// See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L309 -// and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L318-L322 -var ValueSet = goos.NewFunc(custom.NameSyscallValueSet, valueSet) - -func valueSet(ctx context.Context, mod api.Module, stack goos.Stack) { - v := stack.ParamVal(ctx, 0, LoadValue) - p := stack.ParamString(mod.Memory(), 1 /*, 2 */) - x := stack.ParamVal(ctx, 3, LoadValue) - - if p := p; v == getState(ctx) { - switch p { - case "_pendingEvent": - if x == nil { // syscall_js.handleEvent - s := v.(*State) - s._lastEvent = s._pendingEvent - s._pendingEvent = nil - return - } - } - } else if e, ok := v.(*event); ok { // syscall_js.handleEvent - switch p { - case "result": - e.result = x - return - } - } else if m, ok := v.(*object); ok { - m.properties[p] = x // e.g. opt.Set("method", req.Method) - return - } - panic(fmt.Errorf("TODO: valueSet(v=%v, p=%s, x=%v)", v, p, x)) -} - -// ValueDelete is stubbed as it isn't used in Go's main source tree. -// -// See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L321 -var ValueDelete = goarch.StubFunction(custom.NameSyscallValueDelete) - -// ValueIndex implements js.valueIndex, which is used to load a js.Value property -// by index, e.g. `v.Index(0)`. Notably, this is used by js.handleEvent to read -// event arguments -// -// See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L334 -// and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L331-L334 -var ValueIndex = goos.NewFunc(custom.NameSyscallValueIndex, valueIndex) - -func valueIndex(ctx context.Context, _ api.Module, stack goos.Stack) { - v := stack.ParamVal(ctx, 0, LoadValue) - i := stack.ParamUint32(1) - - result := v.(*objectArray).slice[i] - - r := storeValue(ctx, result) - stack.SetResultRef(0, r) -} - -// ValueSetIndex is stubbed as it is only used for js.ValueOf when the input is -// []interface{}, which doesn't appear to occur in Go's source tree. -// -// See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L348 -var ValueSetIndex = goarch.StubFunction(custom.NameSyscallValueSetIndex) - -// ValueCall implements js.valueCall, which is used to call a js.Value function -// by name, e.g. `document.Call("createElement", "div")`. -// -// See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L394 -// and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L343-L358 -var ValueCall = goos.NewFunc(custom.NameSyscallValueCall, valueCall) - -func valueCall(ctx context.Context, mod api.Module, stack goos.Stack) { - mem := mod.Memory() - vRef := stack.ParamRef(0) - m := stack.ParamString(mem, 1 /*, 2 */) - args := stack.ParamVals(ctx, mem, 3 /*, 4 */, LoadValue) - // 5 = padding - - v := LoadValue(ctx, vRef) - c, isCall := v.(jsCall) - if !isCall { - panic(fmt.Errorf("TODO: valueCall(v=%v, m=%s, args=%v)", v, m, args)) - } - - var res goos.Ref - var ok bool - if result, err := c.call(ctx, mod, vRef, m, args...); err != nil { - res = storeValue(ctx, err) - } else { - res = storeValue(ctx, result) - ok = true - } - - stack.Refresh(mod) - stack.SetResultRef(0, res) - stack.SetResultBool(1, ok) -} - -// ValueInvoke is stubbed as it isn't used in Go's main source tree. -// -// See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L413 -var ValueInvoke = goarch.StubFunction(custom.NameSyscallValueInvoke) - -// ValueNew implements js.valueNew, which is used to call a js.Value, e.g. -// `array.New(2)`. -// -// See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L432 -// and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L378-L392 -var ValueNew = goos.NewFunc(custom.NameSyscallValueNew, valueNew) - -func valueNew(ctx context.Context, mod api.Module, stack goos.Stack) { - mem := mod.Memory() - vRef := stack.ParamRef(0) - args := stack.ParamVals(ctx, mem, 1 /*, 2 */, LoadValue) - // 3 = padding - - var res goos.Ref - var ok bool - switch vRef { - case goos.RefArrayConstructor: - result := &objectArray{} - res = storeValue(ctx, result) - ok = true - case goos.RefUint8ArrayConstructor: - var result interface{} - a := args[0] - if n, ok := a.(float64); ok { - result = goos.WrapByteArray(make([]byte, uint32(n))) - } else if _, ok := a.(*goos.ByteArray); ok { - // In case of wrapping, increment the counter of the same ref. - // uint8arrayWrapper := uint8Array.New(args[0]) - result = stack.ParamRefs(mem, 1)[0] - } else { - panic(fmt.Errorf("TODO: valueNew(v=%v, args=%v)", vRef, args)) - } - res = storeValue(ctx, result) - ok = true - case goos.RefObjectConstructor: - result := &object{properties: map[string]interface{}{}} - res = storeValue(ctx, result) - ok = true - case goos.RefJsDateConstructor: - res = goos.RefJsDate - ok = true - default: - panic(fmt.Errorf("TODO: valueNew(v=%v, args=%v)", vRef, args)) - } - - stack.Refresh(mod) - stack.SetResultRef(0, res) - stack.SetResultBool(1, ok) -} - -// ValueLength implements js.valueLength, which is used to load the length -// property of a value, e.g. `array.length`. -// -// See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L372 -// and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L395-L398 -var ValueLength = goos.NewFunc(custom.NameSyscallValueLength, valueLength) - -func valueLength(ctx context.Context, _ api.Module, stack goos.Stack) { - v := stack.ParamVal(ctx, 0, LoadValue) - - len := len(v.(*objectArray).slice) - - stack.SetResultUint32(0, uint32(len)) -} - -// ValuePrepareString implements js.valuePrepareString, which is used to load -// the string for `o.String()` (via js.jsString) for string, boolean and -// number types. Notably, http.Transport uses this in RoundTrip to coerce the -// URL to a string. -// -// See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L531 -// and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L401-L406 -var ValuePrepareString = goos.NewFunc(custom.NameSyscallValuePrepareString, valuePrepareString) - -func valuePrepareString(ctx context.Context, _ api.Module, stack goos.Stack) { - v := stack.ParamVal(ctx, 0, LoadValue) - - s := valueString(v) - - sRef := storeValue(ctx, s) - sLen := uint32(len(s)) - - stack.SetResultRef(0, sRef) - stack.SetResultUint32(1, sLen) -} - -// ValueLoadString implements js.valueLoadString, which is used copy a string -// value for `o.String()`. -// -// See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L533 -// and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L409-L413 -var ValueLoadString = goos.NewFunc(custom.NameSyscallValueLoadString, valueLoadString) - -func valueLoadString(ctx context.Context, mod api.Module, stack goos.Stack) { - v := stack.ParamVal(ctx, 0, LoadValue) - b := stack.ParamBytes(mod.Memory(), 1 /*, 2 */) - - s := valueString(v) - copy(b, s) -} - -// ValueInstanceOf is stubbed as it isn't used in Go's main source tree. -// -// See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L543 -var ValueInstanceOf = goarch.StubFunction(custom.NameSyscallValueInstanceOf) - -// CopyBytesToGo copies a JavaScript managed byte array to linear memory. -// For example, this is used to read an HTTP response body. -// -// # Results -// -// - n is the count of bytes written. -// - ok is false if the src was not a uint8Array. -// -// See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L569 -// and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L437-L449 -var CopyBytesToGo = goos.NewFunc(custom.NameSyscallCopyBytesToGo, copyBytesToGo) - -func copyBytesToGo(ctx context.Context, mod api.Module, stack goos.Stack) { - dst := stack.ParamBytes(mod.Memory(), 0 /*, 1 */) - // padding = 2 - src := stack.ParamVal(ctx, 3, LoadValue) - - var n uint32 - var ok bool - if src, isBuf := src.(*goos.ByteArray); isBuf { - n = uint32(copy(dst, src.Unwrap())) - ok = true - } - - stack.SetResultUint32(0, n) - stack.SetResultBool(1, ok) -} - -// CopyBytesToJS copies linear memory to a JavaScript managed byte array. -// For example, this is used to read an HTTP request body. -// -// # Results -// -// - n is the count of bytes written. -// - ok is false if the dst was not a uint8Array. -// -// See https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go#L583 -// and https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js#L438-L448 -var CopyBytesToJS = goos.NewFunc(custom.NameSyscallCopyBytesToJS, copyBytesToJS) - -func copyBytesToJS(ctx context.Context, mod api.Module, stack goos.Stack) { - dst := stack.ParamVal(ctx, 0, LoadValue) - src := stack.ParamBytes(mod.Memory(), 1 /*, 2 */) - // padding = 3 - - var n uint32 - var ok bool - if dst, isBuf := dst.(*goos.ByteArray); isBuf { - if dst != nil { // empty is possible on EOF - n = uint32(copy(dst.Unwrap(), src)) - } - ok = true - } - - stack.SetResultUint32(0, n) - stack.SetResultBool(1, ok) -} - -// funcWrapper is the result of go's js.FuncOf ("_makeFuncWrapper" here). -// -// This ID is managed on the Go side an increments (possibly rolling over). -type funcWrapper uint32 - -// invoke implements jsFn -func (f funcWrapper) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { - e := &event{id: uint32(f), this: args[0].(goos.Ref)} - - if len(args) > 1 { // Ensure arguments are hashable. - e.args = &objectArray{args[1:]} - for i, v := range e.args.slice { - if s, ok := v.([]byte); ok { - args[i] = goos.WrapByteArray(s) - } else if s, ok := v.([]interface{}); ok { - args[i] = &objectArray{s} - } else if e, ok := v.(error); ok { - args[i] = e - } - } - } - - getState(ctx)._pendingEvent = e // Note: _pendingEvent reference is cleared during resume! - - if _, err := mod.ExportedFunction("resume").Call(ctx); err != nil { - if _, ok := err.(*sys.ExitError); ok { - return nil, nil // allow error-handling to unwind when wasm calls exit due to a panic - } else { - return nil, err - } - } - - return e.result, nil -} diff --git a/internal/gojs/testdata/argsenv/main.go b/internal/gojs/testdata/argsenv/main.go deleted file mode 100644 index a279076f78..0000000000 --- a/internal/gojs/testdata/argsenv/main.go +++ /dev/null @@ -1,17 +0,0 @@ -package argsenv - -import ( - _ "flag" // to ensure flags parse - "fmt" - "os" -) - -func Main() { - fmt.Println() - for i, a := range os.Args { - fmt.Println("args", i, "=", a) - } - for i, e := range os.Environ() { - fmt.Println("environ", i, "=", e) - } -} diff --git a/internal/gojs/testdata/crypto/main.go b/internal/gojs/testdata/crypto/main.go deleted file mode 100644 index 0ea9f045b7..0000000000 --- a/internal/gojs/testdata/crypto/main.go +++ /dev/null @@ -1,18 +0,0 @@ -package crypto - -import ( - "crypto/rand" - "encoding/hex" - "fmt" - "log" -) - -func Main() { - b := make([]byte, 5) - if n, err := rand.Read(b); err != nil { - log.Panicln(err) - } else if n != 5 { - log.Panicln("expected 5, but have ", n) - } - fmt.Println(hex.EncodeToString(b)) -} diff --git a/internal/gojs/testdata/fs/main.go b/internal/gojs/testdata/fs/main.go deleted file mode 100644 index 410e4a5e67..0000000000 --- a/internal/gojs/testdata/fs/main.go +++ /dev/null @@ -1,59 +0,0 @@ -package fs - -import ( - "bytes" - "fmt" - "io" - "log" - "os" -) - -func Main() { - testAdHoc() -} - -func testAdHoc() { - // Ensure stat works, particularly mode. - for _, path := range []string{"sub", "/animals.txt", "animals.txt"} { - if stat, err := os.Stat(path); err != nil { - log.Panicln(err) - } else { - fmt.Println(path, "mode", stat.Mode()) - } - } - - // Read the full contents of the file using io.Reader - b, err := os.ReadFile("/animals.txt") - if err != nil { - log.Panicln(err) - } - fmt.Println("contents:", string(b)) - - // Re-open the same file to test io.ReaderAt - f, err := os.Open("/animals.txt") - if err != nil { - log.Panicln(err) - } - defer f.Close() - - // Seek to an arbitrary position. - if _, err = f.Seek(4, io.SeekStart); err != nil { - log.Panicln(err) - } - - b1 := make([]byte, len(b)) - // We expect to revert to the original position on ReadAt zero. - if _, err = f.ReadAt(b1, 0); err != nil { - log.Panicln(err) - } - - if !bytes.Equal(b, b1) { - log.Panicln("unexpected ReadAt contents: ", string(b1)) - } - - b, err = os.ReadFile("/empty.txt") - if err != nil { - log.Panicln(err) - } - fmt.Println("empty:" + string(b)) -} diff --git a/internal/gojs/testdata/gc/main.go b/internal/gojs/testdata/gc/main.go deleted file mode 100644 index 85fd6f3834..0000000000 --- a/internal/gojs/testdata/gc/main.go +++ /dev/null @@ -1,12 +0,0 @@ -package gc - -import ( - "fmt" - "runtime" -) - -func Main() { - fmt.Println("before gc") - runtime.GC() - fmt.Println("after gc") -} diff --git a/internal/gojs/testdata/goroutine/main.go b/internal/gojs/testdata/goroutine/main.go deleted file mode 100644 index 35ea8522eb..0000000000 --- a/internal/gojs/testdata/goroutine/main.go +++ /dev/null @@ -1,18 +0,0 @@ -package goroutine - -import "fmt" - -func Main() { - msg := make(chan int) - finished := make(chan int) - go func() { - <-msg - fmt.Println("consumer") - finished <- 1 - }() - go func() { - fmt.Println("producer") - msg <- 1 - }() - <-finished -} diff --git a/internal/gojs/testdata/main.go b/internal/gojs/testdata/main.go deleted file mode 100644 index a389d4e421..0000000000 --- a/internal/gojs/testdata/main.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/tetratelabs/wazero/internal/gojs/testdata/argsenv" - "github.com/tetratelabs/wazero/internal/gojs/testdata/crypto" - "github.com/tetratelabs/wazero/internal/gojs/testdata/fs" - "github.com/tetratelabs/wazero/internal/gojs/testdata/gc" - "github.com/tetratelabs/wazero/internal/gojs/testdata/goroutine" - "github.com/tetratelabs/wazero/internal/gojs/testdata/mem" - "github.com/tetratelabs/wazero/internal/gojs/testdata/process" - "github.com/tetratelabs/wazero/internal/gojs/testdata/stdio" - "github.com/tetratelabs/wazero/internal/gojs/testdata/testfs" - "github.com/tetratelabs/wazero/internal/gojs/testdata/time" - "github.com/tetratelabs/wazero/internal/gojs/testdata/writefs" -) - -// main includes a registry of all tests to reduce compilation time. -func main() { - switch os.Args[1] { - case "argsenv": - argsenv.Main() - case "crypto": - crypto.Main() - case "exit": - os.Exit(255) - case "fs": - fs.Main() - case "gc": - gc.Main() - case "goroutine": - goroutine.Main() - case "mem": - mem.Main() - case "process": - process.Main() - case "stdio": - stdio.Main() - case "testfs": - testfs.Main() - case "time": - time.Main() - case "writefs": - writefs.Main() - default: - panic(fmt.Errorf("unsupported arg: %s", os.Args[1])) - } -} diff --git a/internal/gojs/testdata/mem/main.go b/internal/gojs/testdata/mem/main.go deleted file mode 100644 index dbb6973ed5..0000000000 --- a/internal/gojs/testdata/mem/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package mem - -func Main() { - // Go compiles into Wasm with a 16MB heap. - // As there will be some used already, allocating 16MB should force growth. - _ = make([]byte, 16*1024*1024) -} diff --git a/internal/gojs/testdata/process/main.go b/internal/gojs/testdata/process/main.go deleted file mode 100644 index c85df2224f..0000000000 --- a/internal/gojs/testdata/process/main.go +++ /dev/null @@ -1,64 +0,0 @@ -// Package process is an integration test of system calls mapped to the -// JavaScript object "process". e.g. `go.syscall/js.valueCall(process.chdir...` -package process - -import ( - "fmt" - "log" - "os" - "syscall" -) - -func Main() { - fmt.Printf("syscall.Getpid()=%d\n", syscall.Getpid()) - fmt.Printf("syscall.Getppid()=%d\n", syscall.Getppid()) - fmt.Printf("syscall.Getuid()=%d\n", syscall.Getuid()) - fmt.Printf("syscall.Getgid()=%d\n", syscall.Getgid()) - fmt.Printf("syscall.Geteuid()=%d\n", syscall.Geteuid()) - fmt.Printf("syscall.Umask(0077)=%O\n", syscall.Umask(0o077)) - if g, err := syscall.Getgroups(); err != nil { - log.Panicln(err) - } else { - fmt.Printf("syscall.Getgroups()=%v\n", g) - } - - pid := syscall.Getpid() - if p, err := os.FindProcess(pid); err != nil { - log.Panicln(err) - } else { - fmt.Printf("os.FindProcess(%d).Pid=%d\n", pid, p.Pid) - } - - if wd, err := syscall.Getwd(); err != nil { - log.Panicln(err) - } else if wd != "/" { - log.Panicln("not root") - } - fmt.Println("wd ok") - - dirs := []struct { - path, wd string - }{ - {"dir", "/dir"}, - {".", "/dir"}, - {"..", "/"}, - {".", "/"}, - {"..", "/"}, - } - - for _, dir := range dirs { - if err := syscall.Chdir(dir.path); err != nil { - log.Panicln(dir.path, err) - } else if wd, err := syscall.Getwd(); err != nil { - log.Panicln(dir.path, err) - } else if wd != dir.wd { - log.Panicf("cd %s: expected wd=%s, but have %s", dir.path, dir.wd, wd) - } - } - - if err := syscall.Chdir("/animals.txt"); err == nil { - log.Panicln("shouldn't be able to chdir to file") - } else { - fmt.Println(err) // should be the textual message of the errno. - } -} diff --git a/internal/gojs/testdata/stdio/main.go b/internal/gojs/testdata/stdio/main.go deleted file mode 100644 index c894f92573..0000000000 --- a/internal/gojs/testdata/stdio/main.go +++ /dev/null @@ -1,35 +0,0 @@ -package stdio - -import ( - "errors" - "fmt" - "io" - "os" - "syscall" -) - -func Main() { - b, err := io.ReadAll(os.Stdin) - if err != nil { - panic(err) - } - - if _, err = fmt.Fprintln(os.Stdin, " "); errors.Unwrap(err) != syscall.EBADF { - panic(fmt.Sprint(err.Error(), "!=", syscall.EBADF)) - } - printToFile("stdout", os.Stdout, len(b)) - printToFile("stderr", os.Stderr, len(b)) -} - -func printToFile(name string, file *os.File, size int) { - message := fmt.Sprint(name, " ", size) - n, err := fmt.Fprintln(file, message) - if err != nil { - println(err.Error()) - panic(name) - } - if n != len(message)+1 /* \n */ { - println(n, "!=", len(message)) - panic(name) - } -} diff --git a/internal/gojs/testdata/testfs/main.go b/internal/gojs/testdata/testfs/main.go deleted file mode 100644 index 1d8cc3d5c9..0000000000 --- a/internal/gojs/testdata/testfs/main.go +++ /dev/null @@ -1,16 +0,0 @@ -// Package testfs is an integration test of system calls mapped to the -// JavaScript object "fs". e.g. `go.syscall/js.valueCall(fs.openDir...` -package testfs - -import ( - "log" - "os" - - "github.com/tetratelabs/wazero/internal/fstest" -) - -func Main() { - if err := fstest.TestFS(os.DirFS("testfs")); err != nil { - log.Panicln(err) - } -} diff --git a/internal/gojs/testdata/time/main.go b/internal/gojs/testdata/time/main.go deleted file mode 100644 index d546fe0d57..0000000000 --- a/internal/gojs/testdata/time/main.go +++ /dev/null @@ -1,12 +0,0 @@ -package time - -import ( - "fmt" - "time" -) - -func Main() { - fmt.Println(time.Local.String()) // trigger initLocal - t := time.Now() // uses walltime - fmt.Println(time.Since(t)) // uses nanotime1 -} diff --git a/internal/gojs/testdata/writefs/main.go b/internal/gojs/testdata/writefs/main.go deleted file mode 100644 index d06b95cce9..0000000000 --- a/internal/gojs/testdata/writefs/main.go +++ /dev/null @@ -1,275 +0,0 @@ -package writefs - -import ( - "errors" - "fmt" - "io/fs" - "log" - "os" - "path" - "syscall" - "time" -) - -func Main() { - // Create a test directory - dir := path.Join(os.TempDir(), "dir") - dir1 := path.Join(os.TempDir(), "dir1") - if err := os.Mkdir(dir, 0o700); err != nil { - log.Panicln(err) - return - } - defer os.Remove(dir) - - // Create a test file in that directory - file := path.Join(dir, "file") - file1 := path.Join(os.TempDir(), "file1") - - if err := os.WriteFile(file, []byte{}, 0o600); err != nil { - log.Panicln(err) - return - } - defer os.Remove(file) - - // Ensure stat works, particularly mode. - for _, path := range []string{dir, file} { - if stat, err := os.Stat(path); err != nil { - log.Panicln(err) - } else { - fmt.Println(path, "mode", stat.Mode()) - } - } - - // Now, test that syscall.WriteAt works - f, err := os.OpenFile(file1, os.O_RDWR|os.O_CREATE, 0o600) - if err != nil { - log.Panicln(err) - } - defer f.Close() - - // Write segments to the file, noting map iteration isn't ordered. - bytes := []byte("wazero") - for o, b := range map[int][]byte{3: bytes[3:], 0: bytes[:3]} { - n, err := f.WriteAt(b, int64(o)) - if err != nil { - log.Panicln(err) - } else if n != 3 { - log.Panicln("expected 3, but wrote", n) - } - } - - // Now, use ReadAt (tested in testfs package) to verify everything wrote! - if _, err = f.ReadAt(bytes, 0); err != nil { - log.Panicln(err) - } else if string(bytes) != "wazero" { - log.Panicln("unexpected contents:", string(bytes)) - } - - // Next, truncate it. - if err = f.Truncate(2); err != nil { - log.Panicln(err) - } - // Next, sync it. - if err = f.Sync(); err != nil { - log.Panicln(err) - } - - if stat, err := f.Stat(); err != nil { - log.Panicln(err) - } else if mode := stat.Mode() & fs.ModePerm; mode != 0o600 { - log.Panicln("expected mode = 0o600", mode) - } - - // Finally, close it. - if err = f.Close(); err != nil { - log.Panicln(err) - } - - // Set to read-only - if err = syscall.Chmod(file1, 0o400); err != nil { - log.Panicln(err) - } - - // Test stat - stat, err := os.Stat(file1) - if err != nil { - log.Panicln(err) - } - - if stat.Mode().Type() != 0 { - log.Panicln("expected type = 0", stat.Mode().Type()) - } - if stat.Mode().Perm() != 0o400 { - log.Panicln("expected perm = 0o400", stat.Mode().Perm()) - } - - // Check the file was truncated. - if bytes, err := os.ReadFile(file1); err != nil { - log.Panicln(err) - } else if string(bytes) != "wa" { - log.Panicln("unexpected contents:", string(bytes)) - } - - // create a hard link - link := file1 + "-link" - if err = os.Link(file1, link); err != nil { - log.Panicln(err) - } - - // Ensure this is a hard link, so they have the same inode. - file1Stat, err := os.Lstat(file1) - if err != nil { - log.Panicln(err) - } - linkStat, err := os.Lstat(link) - if err != nil { - log.Panicln(err) - } - if !os.SameFile(file1Stat, linkStat) { - log.Panicln("expected file == link stat", file1Stat, linkStat) - } - - // create a symbolic link - symlink := file1 + "-symlink" - if err = os.Symlink(file1, symlink); err != nil { - log.Panicln(err) - } - - // verify we can read the symbolic link back - if dst, err := os.Readlink(symlink); err != nil { - log.Panicln(err) - } else if dst != dst { - log.Panicln("expected link dst = old value", dst, dst) - } - - // Test lstat which should be about the link not its target. - symlinkStat, err := os.Lstat(symlink) - if err != nil { - log.Panicln(err) - } - - if symlinkStat.Mode().Type() != fs.ModeSymlink { - log.Panicln("expected type = symlink", symlinkStat.Mode().Type()) - } - if size := int64(len(file1)); symlinkStat.Size() != size { - log.Panicln("unexpected symlink size", symlinkStat.Size(), size) - } - // A symbolic link isn't the same file as what it points to. - if os.SameFile(file1Stat, symlinkStat) { - log.Panicln("expected file != link stat", file1Stat, symlinkStat) - } - - // Test removing a non-empty empty directory - if err = syscall.Rmdir(dir); err != syscall.ENOTEMPTY { - log.Panicln("unexpected error", err) - } - - // Test updating the mod time of a file, noting JS has millis precision. - atime := time.Unix(123, 4*1e6) - mtime := time.Unix(567, 8*1e6) - - // Ensure errors propagate - if err = os.Chtimes("noexist", atime, mtime); !errors.Is(err, syscall.ENOENT) { - log.Panicln("unexpected error", err) - } - - // Now, try a real update. - if err = os.Chtimes(dir, atime, mtime); err != nil { - log.Panicln("unexpected error", err) - } - - // Ensure the times translated properly. - dirAtimeNsec, dirMtimeNsec, dirDev, dirInode := statFields(dir) - fmt.Println("dir times:", dirAtimeNsec, dirMtimeNsec) - - // Ensure we were able to read the dev and inode. - // - // Note: The size of syscall.Stat_t.Dev (32-bit) in js is smaller than - // linux (64-bit), so we can't compare its real value against the host. - if dirDev == 0 { - log.Panicln("expected dir dev != 0", dirDev) - } - if dirInode == 0 { - log.Panicln("expected dir inode != 0", dirInode) - } - - // Test renaming a file, noting we can't verify error numbers as they - // vary per operating system. - if err = syscall.Rename(file, dir); err == nil { - log.Panicln("expected error") - } - if err = syscall.Rename(file, file1); err != nil { - log.Panicln("unexpected error", err) - } - - // Test renaming a directory - if err = syscall.Rename(dir, file1); err == nil { - log.Panicln("expected error") - } - if err = syscall.Rename(dir, dir1); err != nil { - log.Panicln("unexpected error", err) - } - - // Compare stat after renaming. - atimeNsec, mtimeNsec, dev, inode := statFields(dir1) - // atime shouldn't change as we didn't access (re-open) the directory. - if atimeNsec != dirAtimeNsec { - log.Panicln("expected dir atimeNsec = previous value", atimeNsec, dirAtimeNsec) - } - // mtime should change because we renamed the directory. - if mtimeNsec <= dirMtimeNsec { - log.Panicln("expected dir mtimeNsec > previous value", mtimeNsec, dirMtimeNsec) - } - // dev/inode shouldn't change during rename. - if dev != dirDev { - log.Panicln("expected dir dev = previous value", dev, dirDev) - } - if inode != dirInode { - log.Panicln("expected dir inode = previous value", dev, dirInode) - } - - // Test unlinking a file - if err = syscall.Rmdir(file1); err != syscall.ENOTDIR { - log.Panicln("unexpected error", err) - } - if err = syscall.Unlink(file1); err != nil { - log.Panicln("unexpected error", err) - } - - // Test removing an empty directory - if err = syscall.Unlink(dir1); err != syscall.EISDIR { - log.Panicln("unexpected error", err) - } - if err = syscall.Rmdir(dir1); err != nil { - log.Panicln("unexpected error", err) - } - - // shouldn't fail - if err = os.RemoveAll(dir1); err != nil { - log.Panicln(err) - return - } - - // ensure we can use zero as is used in TestRemoveReadOnlyDir - if err = os.Mkdir(dir1, 0); err != nil { - log.Panicln(err) - return - } - defer os.Remove(dir) - - // Symlink and Readlink tests. - s := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - from := "/symlink.txt" - err = os.Symlink(s, from) - if err != nil { - log.Panicln(err) - } - - r, err := os.Readlink(from) - if err != nil { - log.Fatalf("readlink %q failed: %v", from, err) - } - if r != s { - log.Fatalf("after symlink %q != %q", r, s) - } -} diff --git a/internal/gojs/testdata/writefs/stat.go b/internal/gojs/testdata/writefs/stat.go deleted file mode 100644 index 8091840bae..0000000000 --- a/internal/gojs/testdata/writefs/stat.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build !js - -package writefs - -import ( - "github.com/tetratelabs/wazero/experimental/sys" -) - -// statFields isn't used outside JS, it is only for compilation -func statFields(string) (atimeNsec, mtimeNsec int64, dev, inode uint64) { - panic(sys.ENOSYS) -} diff --git a/internal/gojs/testdata/writefs/stat_js.go b/internal/gojs/testdata/writefs/stat_js.go deleted file mode 100644 index e35d0a399e..0000000000 --- a/internal/gojs/testdata/writefs/stat_js.go +++ /dev/null @@ -1,16 +0,0 @@ -package writefs - -import ( - "fmt" - "os" - "syscall" -) - -func statFields(path string) (atimeNsec, mtimeNsec int64, dev, inode uint64) { - if t, err := os.Stat(path); err != nil { - panic(fmt.Errorf("failed to stat path %s: %v", path, err)) - } else { - d := t.Sys().(*syscall.Stat_t) - return d.Atime*1e9 + d.AtimeNsec, d.Mtime*1e9 + d.MtimeNsec, uint64(d.Dev), uint64(d.Ino) - } -} diff --git a/internal/gojs/time.go b/internal/gojs/time.go deleted file mode 100644 index 8b40b7d849..0000000000 --- a/internal/gojs/time.go +++ /dev/null @@ -1,28 +0,0 @@ -package gojs - -import ( - "context" - - "github.com/tetratelabs/wazero/api" - "github.com/tetratelabs/wazero/internal/gojs/custom" - "github.com/tetratelabs/wazero/internal/gojs/goos" -) - -var ( - // jsDateConstructor returns jsDate. - // - // This is defined as `Get("Date")` in zoneinfo_js.go time.initLocal - jsDateConstructor = newJsVal(goos.RefJsDateConstructor, custom.NameDate) - - // jsDate is used inline in zoneinfo_js.go for time.initLocal. - // `.Call("getTimezoneOffset").Int()` returns a timezone offset. - jsDate = newJsVal(goos.RefJsDate, custom.NameDate). - addFunction(custom.NameDateGetTimezoneOffset, jsDateGetTimezoneOffset{}) -) - -// jsDateGetTimezoneOffset implements jsFn -type jsDateGetTimezoneOffset struct{} - -func (jsDateGetTimezoneOffset) invoke(context.Context, api.Module, ...interface{}) (interface{}, error) { - return uint32(0), nil // UTC -} diff --git a/internal/gojs/time_test.go b/internal/gojs/time_test.go deleted file mode 100644 index eb7502d117..0000000000 --- a/internal/gojs/time_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package gojs_test - -import ( - "bytes" - "context" - "testing" - - "github.com/tetratelabs/wazero/experimental" - "github.com/tetratelabs/wazero/experimental/logging" - "github.com/tetratelabs/wazero/internal/testing/require" -) - -func Test_time(t *testing.T) { - t.Parallel() - - var log bytes.Buffer - loggingCtx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{}, - logging.NewHostLoggingListenerFactory(&log, logging.LogScopeClock)) - - stdout, stderr, err := compileAndRun(loggingCtx, "time", defaultConfig) - - require.Zero(t, stderr) - require.NoError(t, err) - require.Equal(t, `Local -1ms -`, stdout) - - // To avoid multiple similar assertions, just check three functions we - // expect were called. - logString := logString(log) - require.Contains(t, logString, `==> go.runtime.nanotime1() -<== (nsec=0)`) - require.Contains(t, logString, `==> go.runtime.walltime() -<== (sec=1640995200,nsec=0) -`) - require.Contains(t, logString, `==> go.syscall/js.valueCall(Date.getTimezoneOffset()) -<== (tz=0) -`) -} diff --git a/internal/gojs/util/util.go b/internal/gojs/util/util.go deleted file mode 100644 index 5fc29aab0b..0000000000 --- a/internal/gojs/util/util.go +++ /dev/null @@ -1,70 +0,0 @@ -package util - -import ( - "fmt" - pathutil "path" - - "github.com/tetratelabs/wazero/api" - "github.com/tetratelabs/wazero/internal/gojs/custom" - "github.com/tetratelabs/wazero/internal/wasm" -) - -// MustWrite is like api.Memory except that it panics if the offset -// is out of range. -func MustWrite(mem api.Memory, fieldName string, offset uint32, val []byte) { - if ok := mem.Write(offset, val); !ok { - panic(fmt.Errorf("out of memory writing %s", fieldName)) - } -} - -// MustRead is like api.Memory except that it panics if the offset and -// byteCount are out of range. -func MustRead(mem api.Memory, funcName string, paramIdx int, offset, byteCount uint32) []byte { - buf, ok := mem.Read(offset, byteCount) - if ok { - return buf - } - var paramName string - if names, ok := custom.NameSection[funcName]; ok { - if paramIdx < len(names.ParamNames) { - paramName = names.ParamNames[paramIdx] - } - } - if paramName == "" { - paramName = fmt.Sprintf("%s param[%d]", funcName, paramIdx) - } - panic(fmt.Errorf("out of memory reading %s", paramName)) -} - -func NewFunc(name string, goFunc api.GoModuleFunc) *wasm.HostFunc { - return &wasm.HostFunc{ - ExportName: name, - Name: name, - ParamTypes: []api.ValueType{api.ValueTypeI32}, - ParamNames: []string{"sp"}, - Code: wasm.Code{GoFunc: goFunc}, - } -} - -// ResolvePath is needed when a non-absolute path is given to a function. -// Unlike other host ABI, GOOS=js maintains the CWD host side. -func ResolvePath(cwd, path string) (resolved string) { - pathLen := len(path) - switch { - case pathLen == 0: - return cwd - case pathLen == 1 && path[0] == '.': - return cwd - case path[0] == '/': - resolved = pathutil.Clean(path) - default: - resolved = pathutil.Join(cwd, path) - } - - // If there's a trailing slash, we need to retain it for symlink edge - // cases. See https://github.com/golang/go/issues/27225 - if len(resolved) > 1 && path[pathLen-1] == '/' { - return resolved + "/" - } - return resolved -} diff --git a/internal/gojs/util/util_test.go b/internal/gojs/util/util_test.go deleted file mode 100644 index db02beb6ec..0000000000 --- a/internal/gojs/util/util_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package util - -import ( - "fmt" - "testing" - - "github.com/tetratelabs/wazero/internal/gojs/custom" - "github.com/tetratelabs/wazero/internal/testing/require" - "github.com/tetratelabs/wazero/internal/wasm" -) - -func TestMustRead(t *testing.T) { - mem := &wasm.MemoryInstance{Buffer: []byte{1, 2, 3, 4, 5, 6, 7, 8}, Min: 1} - - tests := []struct { - name string - funcName string - paramIdx int - offset, byteCount uint32 - expected []byte - expectedPanic string - }{ - { - name: "read nothing", - }, - { - name: "read all", - offset: 0, - byteCount: 8, - expected: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - }, - { - name: "read some", - offset: 4, - byteCount: 2, - expected: []byte{5, 6}, - }, - { - name: "read too many", - funcName: custom.NameSyscallCopyBytesToGo, - offset: 4, - byteCount: 5, - expectedPanic: "out of memory reading dst", - }, - { - name: "read too many - function not in names", - funcName: "not_in_names", - offset: 4, - byteCount: 5, - expectedPanic: "out of memory reading not_in_names param[0]", - }, - { - name: "read too many - in names, but no params", - funcName: custom.NameDebug, - offset: 4, - byteCount: 5, - expectedPanic: "out of memory reading debug param[0]", - }, - } - - for _, tt := range tests { - tc := tt - - t.Run(tc.name, func(t *testing.T) { - if tc.expectedPanic != "" { - err := require.CapturePanic(func() { - MustRead(mem, tc.funcName, tc.paramIdx, tc.offset, tc.byteCount) - }) - require.EqualError(t, err, tc.expectedPanic) - } else { - buf := MustRead(mem, tc.funcName, tc.paramIdx, tc.offset, tc.byteCount) - require.Equal(t, tc.expected, buf) - } - }) - } -} - -func TestResolvePath(t *testing.T) { - t.Parallel() - - tests := []struct { - cwd, path string - expected string - }{ - {cwd: "/", path: ".", expected: "/"}, - {cwd: "/", path: "/", expected: "/"}, - {cwd: "/", path: "..", expected: "/"}, - {cwd: "/", path: "a", expected: "/a"}, - {cwd: "/", path: "/a", expected: "/a"}, - {cwd: "/", path: "./a/", expected: "/a/"}, // retain trailing slash - {cwd: "/", path: "./a/.", expected: "/a"}, - {cwd: "/", path: "a/.", expected: "/a"}, - {cwd: "/a", path: "/..", expected: "/"}, - {cwd: "/a", path: "/", expected: "/"}, - {cwd: "/a", path: "b", expected: "/a/b"}, - {cwd: "/a", path: "/b", expected: "/b"}, - {cwd: "/a", path: "/b/", expected: "/b/"}, // retain trailing slash - {cwd: "/a", path: "./b/.", expected: "/a/b"}, - {cwd: "/a/b", path: ".", expected: "/a/b"}, - {cwd: "/a/b", path: "../.", expected: "/a"}, - {cwd: "/a/b", path: "../..", expected: "/"}, - {cwd: "/a/b", path: "../../..", expected: "/"}, - } - - for _, tt := range tests { - tc := tt - - t.Run(fmt.Sprintf("%s,%s", tc.cwd, tc.path), func(t *testing.T) { - require.Equal(t, tc.expected, ResolvePath(tc.cwd, tc.path)) - }) - } -} diff --git a/internal/gojs/values/values.go b/internal/gojs/values/values.go deleted file mode 100644 index 53dce81aa7..0000000000 --- a/internal/gojs/values/values.go +++ /dev/null @@ -1,73 +0,0 @@ -package values - -import ( - "fmt" - - "github.com/tetratelabs/wazero/internal/gojs/goos" -) - -func NewValues() *Values { - ret := &Values{} - ret.Reset() - return ret -} - -type Values struct { - // Below is needed to avoid exhausting the ID namespace finalizeRef reclaims - // See https://go-review.googlesource.com/c/go/+/203600 - - values []interface{} // values indexed by ID, nil - goRefCounts []uint32 // recount pair-indexed with values - ids map[interface{}]uint32 // live values - idPool []uint32 // reclaimed IDs (values[i] = nil, goRefCounts[i] nil -} - -func (j *Values) Get(id uint32) interface{} { - index := id - goos.NextID - if index >= uint32(len(j.values)) { - panic(fmt.Errorf("id %d is out of range %d", id, len(j.values))) - } - if v := j.values[index]; v == nil { - panic(fmt.Errorf("value for %d was nil", id)) - } else { - return v - } -} - -func (j *Values) Increment(v interface{}) uint32 { - id, ok := j.ids[v] - if !ok { - if len(j.idPool) == 0 { - id, j.values, j.goRefCounts = uint32(len(j.values)), append(j.values, v), append(j.goRefCounts, 0) - } else { - id, j.idPool = j.idPool[len(j.idPool)-1], j.idPool[:len(j.idPool)-1] - j.values[id], j.goRefCounts[id] = v, 0 - } - j.ids[v] = id - } - j.goRefCounts[id]++ - - return id + goos.NextID -} - -func (j *Values) Decrement(id uint32) { - // Special IDs are not goos.Refcounted. - if id < goos.NextID { - return - } - id -= goos.NextID - j.goRefCounts[id]-- - if j.goRefCounts[id] == 0 { - v := j.values[id] - j.values[id] = nil - delete(j.ids, v) - j.idPool = append(j.idPool, id) - } -} - -func (j *Values) Reset() { - j.values = nil - j.goRefCounts = nil - j.ids = map[interface{}]uint32{} - j.idPool = nil -} diff --git a/internal/gojs/values/values_test.go b/internal/gojs/values/values_test.go deleted file mode 100644 index 1b84cf32b0..0000000000 --- a/internal/gojs/values/values_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package values - -import ( - "testing" - - "github.com/tetratelabs/wazero/internal/gojs/goos" - "github.com/tetratelabs/wazero/internal/testing/require" -) - -func Test_Values(t *testing.T) { - t.Parallel() - - vs := NewValues() - - err := require.CapturePanic(func() { - _ = vs.Get(goos.NextID) - }) - require.Contains(t, err.Error(), "is out of range 0") - - v1 := "foo" - id1 := vs.Increment(v1) - v2 := "bar" - id2 := vs.Increment(v2) - - require.Equal(t, goos.NextID, id1) - require.Equal(t, v1, vs.Get(id1)) - - // Second value should be at a sequential position - require.Equal(t, id1+1, id2) - require.Equal(t, v2, vs.Get(id2)) - - // Incrementing the ref count should return the same ID - require.Equal(t, id1, vs.Increment(v1)) - require.Equal(t, v1, vs.Get(id1)) - - // Decrement and we should still get the value - vs.Decrement(id1) - require.Equal(t, v1, vs.Get(id1)) - - // Decrement again, and we should panic, as go should never attempt to - // get a value it already decremented to zero. - vs.Decrement(id1) - err = require.CapturePanic(func() { - _ = vs.Get(id1) - }) - require.Contains(t, err.Error(), "was nil") - - // Since the ID is no longer in use, we should be able to revive it. - require.Equal(t, id1, vs.Increment(v1)) - require.Equal(t, v1, vs.Get(id1)) -} diff --git a/internal/wasm/store.go b/internal/wasm/store.go index e6ae75ccac..fe9d6d150d 100644 --- a/internal/wasm/store.go +++ b/internal/wasm/store.go @@ -95,8 +95,7 @@ type ( // or external objects (unimplemented). ElementInstances []ElementInstance - // Sys is exposed for use in special imports such as WASI, assemblyscript - // and gojs. + // Sys is exposed for use in special imports such as WASI, assemblyscript. // // # Notes // diff --git a/site/content/languages/go.md b/site/content/languages/go.md index 31d1ce08c9..644eaf2ba8 100644 --- a/site/content/languages/go.md +++ b/site/content/languages/go.md @@ -26,10 +26,6 @@ Moreover, Go defines js "EXPERIMENTAL... exempt from the Go compatibility promise." While WebAssembly signatures haven't broken between 1.18 and 1.19, then have in the past and can in the future. -For this reason implementations such as wazero's [gojs][14], cannot guarantee -portability from release to release, or that the code will work well in -production. - Due to lack of adoption, support and relatively high implementation overhead, most choose [TinyGo]({{< relref "/tinygo.md" >}}) to compile source code, even if it supports less features. @@ -361,7 +357,6 @@ the host operating system's underlying controls permit. [11]: https://github.com/WebAssembly/proposals [12]: https://github.com/golang/go/blob/go1.20/src/cmd/link/internal/ld/data.go#L2457 [13]: https://github.com/golang/go/blob/go1.20/src/syscall/tables_js.go#L371-L494 -[14]: https://github.com/tetratelabs/wazero/tree/main/experimental/gojs/example [15]: https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/ [16]: https://github.com/golang/go/blob/go1.20/src/internal/buildcfg/cfg.go#L136-L150 [17]: https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/nontrapping-float-to-int-conversion/Overview.md