aboutsummaryrefslogtreecommitdiff
path: root/misc/wasm
diff options
context:
space:
mode:
authorRichard Musiol <mail@richard-musiol.de>2018-05-20 00:56:36 +0200
committerAustin Clements <austin@google.com>2018-06-14 21:50:53 +0000
commite083dc6307b6593bdd44b219ffd21699d6f17fd7 (patch)
tree2a411d82639a778c6aa107529b1d708034c7a7f1 /misc/wasm
parent5fdacfa89f871888d6f8fde726b8f95f11e674d6 (diff)
downloadgo-e083dc6307b6593bdd44b219ffd21699d6f17fd7.tar.gz
go-e083dc6307b6593bdd44b219ffd21699d6f17fd7.zip
runtime, sycall/js: add support for callbacks from JavaScript
This commit adds support for JavaScript callbacks back into WebAssembly. This is experimental API, just like the rest of the syscall/js package. The time package now also uses this mechanism to properly support timers without resorting to a busy loop. JavaScript code can call into the same entry point multiple times. The new RUN register is used to keep track of the program's run state. Possible values are: starting, running, paused and exited. If no goroutine is ready any more, the scheduler can put the program into the "paused" state and the WebAssembly code will stop running. When a callback occurs, the JavaScript code puts the callback data into a queue and then calls into WebAssembly to allow the Go code to continue running. Updates #18892 Updates #25506 Change-Id: Ib8701cfa0536d10d69bd541c85b0e2a754eb54fb Reviewed-on: https://go-review.googlesource.com/114197 Reviewed-by: Austin Clements <austin@google.com> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Diffstat (limited to 'misc/wasm')
-rwxr-xr-xmisc/wasm/wasm_exec.js53
1 files changed, 51 insertions, 2 deletions
diff --git a/misc/wasm/wasm_exec.js b/misc/wasm/wasm_exec.js
index de4cff7d2c..ada6f0cd92 100755
--- a/misc/wasm/wasm_exec.js
+++ b/misc/wasm/wasm_exec.js
@@ -56,6 +56,8 @@
console.warn("exit code:", code);
}
};
+ this._callbackTimeouts = new Map();
+ this._nextCallbackTimeoutID = 1;
const mem = () => {
// The buffer may change when requesting more memory.
@@ -119,6 +121,7 @@
go: {
// func wasmExit(code int32)
"runtime.wasmExit": (sp) => {
+ this.exited = true;
this.exit(mem().getInt32(sp + 8, true));
},
@@ -142,6 +145,24 @@
mem().setInt32(sp + 16, (msec % 1000) * 1000000, true);
},
+ // func scheduleCallback(delay int64) int32
+ "runtime.scheduleCallback": (sp) => {
+ const id = this._nextCallbackTimeoutID;
+ this._nextCallbackTimeoutID++;
+ this._callbackTimeouts.set(id, setTimeout(
+ () => { this._resolveCallbackPromise(); },
+ getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
+ ));
+ mem().setInt32(sp + 16, id, true);
+ },
+
+ // func clearScheduledCallback(id int32)
+ "runtime.clearScheduledCallback": (sp) => {
+ const id = mem().getInt32(sp + 8, true);
+ clearTimeout(this._callbackTimeouts.get(id));
+ this._callbackTimeouts.delete(id);
+ },
+
// func getRandomData(r []byte)
"runtime.getRandomData": (sp) => {
crypto.getRandomValues(loadSlice(sp + 8));
@@ -269,7 +290,19 @@
async run(instance) {
this._inst = instance;
- this._values = [undefined, null, global, this._inst.exports.mem]; // TODO: garbage collection
+ this._values = [ // TODO: garbage collection
+ undefined,
+ null,
+ global,
+ this._inst.exports.mem,
+ () => { // resolveCallbackPromise
+ if (this.exited) {
+ throw new Error("bad callback: Go program has already exited");
+ }
+ setTimeout(this._resolveCallbackPromise, 0); // make sure it is asynchronous
+ },
+ ];
+ this.exited = false;
const mem = new DataView(this._inst.exports.mem.buffer)
@@ -303,7 +336,16 @@
offset += 8;
});
- this._inst.exports.run(argc, argv);
+ while (true) {
+ const callbackPromise = new Promise((resolve) => {
+ this._resolveCallbackPromise = resolve;
+ });
+ this._inst.exports.run(argc, argv);
+ if (this.exited) {
+ break;
+ }
+ await callbackPromise;
+ }
}
}
@@ -318,9 +360,16 @@
go.env = process.env;
go.exit = process.exit;
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
+ process.on("exit", () => { // Node.js exits if no callback is pending
+ if (!go.exited) {
+ console.error("error: all goroutines asleep and no JavaScript callback pending - deadlock!");
+ process.exit(1);
+ }
+ });
return go.run(result.instance);
}).catch((err) => {
console.error(err);
+ go.exited = true;
process.exit(1);
});
}