From 4e1234676c67ee39c619cfc576621ec7cab43ca3 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 18 Oct 2024 12:29:28 +0200 Subject: [PATCH 1/2] wasm: add test for js.FuncOf While there are some browser tests, Node.js is just a lot better for testing this kind of stuff because it's much faster and we don't need a browser for this. --- main_test.go | 27 +++++++++++++++++++++++++++ testdata/wasmfunc.go | 17 +++++++++++++++++ testdata/wasmfunc.js | 21 +++++++++++++++++++++ testdata/wasmfunc.txt | 7 +++++++ 4 files changed, 72 insertions(+) create mode 100644 testdata/wasmfunc.go create mode 100644 testdata/wasmfunc.js create mode 100644 testdata/wasmfunc.txt diff --git a/main_test.go b/main_test.go index 95fad9442b..93da107366 100644 --- a/main_test.go +++ b/main_test.go @@ -690,6 +690,33 @@ func TestWasmExport(t *testing.T) { } } +// Test js.FuncOf (for syscall/js). +// This test might be extended in the future to cover more cases in syscall/js. +func TestWasmFuncOf(t *testing.T) { + // Build the wasm binary. + tmpdir := t.TempDir() + options := optionsFromTarget("wasm", sema) + buildConfig, err := builder.NewConfig(&options) + if err != nil { + t.Fatal(err) + } + result, err := builder.Build("testdata/wasmfunc.go", ".wasm", tmpdir, buildConfig) + if err != nil { + t.Fatal("failed to build binary:", err) + } + + // Test the resulting binary using NodeJS. + output := &bytes.Buffer{} + cmd := exec.Command("node", "testdata/wasmfunc.js", result.Binary, buildConfig.BuildMode()) + cmd.Stdout = output + cmd.Stderr = output + err = cmd.Run() + if err != nil { + t.Error("failed to run node:", err) + } + checkOutput(t, "testdata/wasmfunc.txt", output.Bytes()) +} + // Test //go:wasmexport in JavaScript (using NodeJS). func TestWasmExportJS(t *testing.T) { type testCase struct { diff --git a/testdata/wasmfunc.go b/testdata/wasmfunc.go new file mode 100644 index 0000000000..9d0d690e4f --- /dev/null +++ b/testdata/wasmfunc.go @@ -0,0 +1,17 @@ +package main + +import "syscall/js" + +func main() { + js.Global().Call("setCallback", js.FuncOf(func(this js.Value, args []js.Value) any { + println("inside callback! parameters:") + sum := 0 + for _, value := range args { + n := value.Int() + println(" parameter:", n) + sum += n + } + return sum + })) + js.Global().Call("callCallback") +} diff --git a/testdata/wasmfunc.js b/testdata/wasmfunc.js new file mode 100644 index 0000000000..3b1831ee4c --- /dev/null +++ b/testdata/wasmfunc.js @@ -0,0 +1,21 @@ +require('../targets/wasm_exec.js'); + +var callback; + +global.setCallback = (cb) => { + callback = cb; +}; + +global.callCallback = () => { + console.log('calling callback!'); + let result = callback(1, 2, 3, 4); + console.log('result from callback:', result); +}; + +let go = new Go(); +WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { + go.run(result.instance); +}).catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/testdata/wasmfunc.txt b/testdata/wasmfunc.txt new file mode 100644 index 0000000000..be41eba3c6 --- /dev/null +++ b/testdata/wasmfunc.txt @@ -0,0 +1,7 @@ +calling callback! +inside callback! parameters: + parameter: 1 + parameter: 2 + parameter: 3 + parameter: 4 +result from callback: 10 From 3f803ae76ed904841c3fd563ff5531e09ee0032a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 18 Oct 2024 12:31:11 +0200 Subject: [PATCH 2/2] runtime: remove minSched hack for wasm I am not entirely sure what it's doing (it seems related to js.FuncOf), but tests still seem to pass when this code is removed. So let's remove it. --- src/runtime/runtime_wasm_js.go | 7 ------- src/runtime/runtime_wasm_js_scheduler.go | 14 -------------- src/runtime/scheduler.go | 18 ------------------ 3 files changed, 39 deletions(-) diff --git a/src/runtime/runtime_wasm_js.go b/src/runtime/runtime_wasm_js.go index 89898b554e..b49ffd15d6 100644 --- a/src/runtime/runtime_wasm_js.go +++ b/src/runtime/runtime_wasm_js.go @@ -4,13 +4,6 @@ package runtime type timeUnit float64 // time in milliseconds, just like Date.now() in JavaScript -// wasmNested is used to detect scheduler nesting (WASM calls into JS calls back into WASM). -// When this happens, we need to use a reduced version of the scheduler. -// -// TODO: this variable can probably be removed once //go:wasmexport is the only -// allowed way to export a wasm function (currently, //export also works). -var wasmNested bool - var handleEvent func() //go:linkname setEventHandler syscall/js.setEventHandler diff --git a/src/runtime/runtime_wasm_js_scheduler.go b/src/runtime/runtime_wasm_js_scheduler.go index 94018336e4..9fd8c45541 100644 --- a/src/runtime/runtime_wasm_js_scheduler.go +++ b/src/runtime/runtime_wasm_js_scheduler.go @@ -8,24 +8,10 @@ func resume() { handleEvent() }() - if wasmNested { - minSched() - return - } - - wasmNested = true scheduler(false) - wasmNested = false } //export go_scheduler func go_scheduler() { - if wasmNested { - minSched() - return - } - - wasmNested = true scheduler(false) - wasmNested = false } diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index 2f22876527..3f726a0641 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -247,24 +247,6 @@ func scheduler(returnAtDeadlock bool) { } } -// This horrible hack exists to make WASM work properly. -// When a WASM program calls into JS which calls back into WASM, the event with which we called back in needs to be handled before returning. -// Thus there are two copies of the scheduler running at once. -// This is a reduced version of the scheduler which does not deal with the timer queue (that is a problem for the outer scheduler). -func minSched() { - scheduleLog("start nested scheduler") - for !schedulerDone { - t := runqueue.Pop() - if t == nil { - break - } - - scheduleLogTask(" run:", t) - t.Resume() - } - scheduleLog("stop nested scheduler") -} - func Gosched() { runqueue.Push(task.Current()) task.Pause()