aboutsummaryrefslogtreecommitdiff
path: root/misc/wasm
diff options
context:
space:
mode:
authorRichard Musiol <mail@richard-musiol.de>2018-10-11 12:46:14 +0200
committerRichard Musiol <neelance@gmail.com>2018-11-10 11:57:17 +0000
commit6dd70fc5e391eb7a47be5eb6353107f38b73f161 (patch)
treee5ba2aa9f1dcaa4ab396417129b0a04c27b8d0e9 /misc/wasm
parente3e043bea4d7547edf004a9e202f66a4d69b5899 (diff)
downloadgo-6dd70fc5e391eb7a47be5eb6353107f38b73f161.tar.gz
go-6dd70fc5e391eb7a47be5eb6353107f38b73f161.zip
all: add support for synchronous callbacks to js/wasm
With this change, callbacks returned by syscall/js.NewCallback get executed synchronously. This is necessary for the APIs of many JavaScript libraries. A callback triggered during a call from Go to JavaScript gets executed on the same goroutine. A callback triggered by JavaScript's event loop gets executed on an extra goroutine. Fixes #26045 Fixes #27441 Change-Id: I591b9e85ab851cef0c746c18eba95fb02ea9e85b Reviewed-on: https://go-review.googlesource.com/c/142004 Reviewed-by: Cherry Zhang <cherryyz@google.com> Run-TryBot: Cherry Zhang <cherryyz@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org>
Diffstat (limited to 'misc/wasm')
-rw-r--r--misc/wasm/wasm_exec.js82
1 files changed, 43 insertions, 39 deletions
diff --git a/misc/wasm/wasm_exec.js b/misc/wasm/wasm_exec.js
index e47663783e..440bba104c 100644
--- a/misc/wasm/wasm_exec.js
+++ b/misc/wasm/wasm_exec.js
@@ -79,6 +79,10 @@
console.warn("exit code:", code);
}
};
+ this._exitPromise = new Promise((resolve) => {
+ this._resolveExitPromise = resolve;
+ });
+ this._pendingCallback = null;
this._callbackTimeouts = new Map();
this._nextCallbackTimeoutID = 1;
@@ -194,6 +198,11 @@
const timeOrigin = Date.now() - performance.now();
this.importObject = {
go: {
+ // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
+ // may trigger a synchronous callback to Go. This makes Go code get executed in the middle of the imported
+ // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
+ // This changes the SP, thus we have to update the SP used by the imported function.
+
// func wasmExit(code int32)
"runtime.wasmExit": (sp) => {
const code = mem().getInt32(sp + 8, true);
@@ -229,7 +238,7 @@
const id = this._nextCallbackTimeoutID;
this._nextCallbackTimeoutID++;
this._callbackTimeouts.set(id, setTimeout(
- () => { this._resolveCallbackPromise(); },
+ () => { this._resume(); },
getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
));
mem().setInt32(sp + 16, id, true);
@@ -254,7 +263,9 @@
// func valueGet(v ref, p string) ref
"syscall/js.valueGet": (sp) => {
- storeValue(sp + 32, Reflect.get(loadValue(sp + 8), loadString(sp + 16)));
+ const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
+ sp = this._inst.exports.getsp(); // see comment above
+ storeValue(sp + 32, result);
},
// func valueSet(v ref, p string, x ref)
@@ -278,7 +289,9 @@
const v = loadValue(sp + 8);
const m = Reflect.get(v, loadString(sp + 16));
const args = loadSliceOfValues(sp + 32);
- storeValue(sp + 56, Reflect.apply(m, v, args));
+ const result = Reflect.apply(m, v, args);
+ sp = this._inst.exports.getsp(); // see comment above
+ storeValue(sp + 56, result);
mem().setUint8(sp + 64, 1);
} catch (err) {
storeValue(sp + 56, err);
@@ -291,7 +304,9 @@
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
- storeValue(sp + 40, Reflect.apply(v, undefined, args));
+ const result = Reflect.apply(v, undefined, args);
+ sp = this._inst.exports.getsp(); // see comment above
+ storeValue(sp + 40, result);
mem().setUint8(sp + 48, 1);
} catch (err) {
storeValue(sp + 40, err);
@@ -304,7 +319,9 @@
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
- storeValue(sp + 40, Reflect.construct(v, args));
+ const result = Reflect.construct(v, args);
+ sp = this._inst.exports.getsp(); // see comment above
+ storeValue(sp + 40, result);
mem().setUint8(sp + 48, 1);
} catch (err) {
storeValue(sp + 40, err);
@@ -355,7 +372,6 @@
this,
];
this._refs = new Map();
- this._callbackShutdown = false;
this.exited = false;
const mem = new DataView(this._inst.exports.mem.buffer)
@@ -390,42 +406,30 @@
offset += 8;
});
- while (true) {
- const callbackPromise = new Promise((resolve) => {
- this._resolveCallbackPromise = () => {
- if (this.exited) {
- throw new Error("bad callback: Go program has already exited");
- }
- setTimeout(resolve, 0); // make sure it is asynchronous
- };
- });
- this._inst.exports.run(argc, argv);
- if (this.exited) {
- break;
- }
- await callbackPromise;
+ this._inst.exports.run(argc, argv);
+ if (this.exited) {
+ this._resolveExitPromise();
}
+ await this._exitPromise;
}
- static _makeCallbackHelper(id, pendingCallbacks, go) {
- return function () {
- pendingCallbacks.push({ id: id, args: arguments });
- go._resolveCallbackPromise();
- };
+ _resume() {
+ if (this.exited) {
+ throw new Error("bad callback: Go program has already exited");
+ }
+ this._inst.exports.resume();
+ if (this.exited) {
+ this._resolveExitPromise();
+ }
}
- static _makeEventCallbackHelper(preventDefault, stopPropagation, stopImmediatePropagation, fn) {
- return function (event) {
- if (preventDefault) {
- event.preventDefault();
- }
- if (stopPropagation) {
- event.stopPropagation();
- }
- if (stopImmediatePropagation) {
- event.stopImmediatePropagation();
- }
- fn(event);
+ _makeCallbackHelper(id) {
+ const go = this;
+ return function () {
+ const cb = { id: id, this: this, args: arguments };
+ go._pendingCallback = cb;
+ go._resume();
+ return cb.result;
};
}
}
@@ -444,8 +448,8 @@
process.on("exit", (code) => { // Node.js exits if no callback is pending
if (code === 0 && !go.exited) {
// deadlock, make Go print error and stack traces
- go._callbackShutdown = true;
- go._inst.exports.run();
+ go._pendingCallback = { id: 0 };
+ go._resume();
}
});
return go.run(result.instance);