diff options
author | Richard Musiol <mail@richard-musiol.de> | 2018-05-20 00:56:36 +0200 |
---|---|---|
committer | Austin Clements <austin@google.com> | 2018-06-14 21:50:53 +0000 |
commit | e083dc6307b6593bdd44b219ffd21699d6f17fd7 (patch) | |
tree | 2a411d82639a778c6aa107529b1d708034c7a7f1 /misc/wasm | |
parent | 5fdacfa89f871888d6f8fde726b8f95f11e674d6 (diff) | |
download | go-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-x | misc/wasm/wasm_exec.js | 53 |
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); }); } |