Commit 1771edb5 authored by Richard Musiol's avatar Richard Musiol Committed by Brad Fitzpatrick

misc/wasm: make wasm_exec.js more flexible

This commit improves wasm_exec.js to give more control to the
code that uses this helper:
- Allow to load and run more than one Go program at the same time.
- Move WebAssembly.instantiate out of wasm_exec.js so the caller
  can optimize for load-time performance, e.g. by using
  instantiateStreaming.
- Allow caller to provide argv, env and exit callback.

Updates #18892

Change-Id: Ib582e6f43848c0118ea5c89f2e24b371c45c2050
Reviewed-on: https://go-review.googlesource.com/113515Reviewed-by: 's avatarAgniva De Sarker <agniva.quicksilver@gmail.com>
Reviewed-by: 's avatarBrad Fitzpatrick <bradfitz@golang.org>
parent db37e160
...@@ -14,17 +14,29 @@ license that can be found in the LICENSE file. ...@@ -14,17 +14,29 @@ license that can be found in the LICENSE file.
<body> <body>
<script src="wasm_exec.js"></script> <script src="wasm_exec.js"></script>
<script> <script>
async function loadAndCompile() { if (!WebAssembly.instantiateStreaming) { // polyfill
let resp = await fetch("test.wasm"); WebAssembly.instantiateStreaming = async (resp, importObject) => {
let bytes = await resp.arrayBuffer(); const source = await (await resp).arrayBuffer();
await go.compile(bytes); return await WebAssembly.instantiate(source, importObject);
document.getElementById("runButton").disabled = false; };
} }
loadAndCompile(); const go = new Go();
let mod, inst;
WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then((result) => {
mod = result.module;
inst = result.instance;
document.getElementById("runButton").disabled = false;
});
async function run() {
console.clear();
await go.run(inst);
inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
}
</script> </script>
<button onClick="console.clear(); go.run();" id="runButton" disabled>Run</button> <button onClick="run();" id="runButton" disabled>Run</button>
</body> </body>
</html> </html>
\ No newline at end of file
...@@ -3,19 +3,10 @@ ...@@ -3,19 +3,10 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
(() => { (() => {
let args = ["js"];
// Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API). // Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API).
const isNodeJS = typeof process !== "undefined"; const isNodeJS = typeof process !== "undefined";
if (isNodeJS) { if (isNodeJS) {
if (process.argv.length < 3) {
process.stderr.write("usage: go_js_wasm_exec [wasm binary]\n");
process.exit(1);
}
args = args.concat(process.argv.slice(3));
global.require = require; global.require = require;
global.fs = require("fs"); global.fs = require("fs");
const nodeCrypto = require("crypto"); const nodeCrypto = require("crypto");
...@@ -40,15 +31,6 @@ ...@@ -40,15 +31,6 @@
} else { } else {
window.global = window; window.global = window;
global.process = {
env: {},
exit(code) {
if (code !== 0) {
console.warn("exit code:", code);
}
},
};
let outputBuf = ""; let outputBuf = "";
global.fs = { global.fs = {
constants: {}, constants: {},
...@@ -67,12 +49,19 @@ ...@@ -67,12 +49,19 @@
const encoder = new TextEncoder("utf-8"); const encoder = new TextEncoder("utf-8");
const decoder = new TextDecoder("utf-8"); const decoder = new TextDecoder("utf-8");
let mod, inst; global.Go = class {
let values = []; // TODO: garbage collection constructor() {
this.argv = [];
this.env = {};
this.exit = (code) => {
if (code !== 0) {
console.warn("exit code:", code);
}
};
const mem = () => { const mem = () => {
// The buffer may change when requesting more memory. // The buffer may change when requesting more memory.
return new DataView(inst.exports.mem.buffer); return new DataView(this._inst.exports.mem.buffer);
} }
const setInt64 = (addr, v) => { const setInt64 = (addr, v) => {
...@@ -88,7 +77,7 @@ ...@@ -88,7 +77,7 @@
const loadValue = (addr) => { const loadValue = (addr) => {
const id = mem().getUint32(addr, true); const id = mem().getUint32(addr, true);
return values[id]; return this._values[id];
} }
const storeValue = (addr, v) => { const storeValue = (addr, v) => {
...@@ -100,14 +89,14 @@ ...@@ -100,14 +89,14 @@
mem().setUint32(addr, 1, true); mem().setUint32(addr, 1, true);
return; return;
} }
values.push(v); this._values.push(v);
mem().setUint32(addr, values.length - 1, true); mem().setUint32(addr, this._values.length - 1, true);
} }
const loadSlice = (addr) => { const loadSlice = (addr) => {
const array = getInt64(addr + 0); const array = getInt64(addr + 0);
const len = getInt64(addr + 8); const len = getInt64(addr + 8);
return new Uint8Array(inst.exports.mem.buffer, array, len); return new Uint8Array(this._inst.exports.mem.buffer, array, len);
} }
const loadSliceOfValues = (addr) => { const loadSliceOfValues = (addr) => {
...@@ -116,7 +105,7 @@ ...@@ -116,7 +105,7 @@
const a = new Array(len); const a = new Array(len);
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
const id = mem().getUint32(array + i * 4, true); const id = mem().getUint32(array + i * 4, true);
a[i] = values[id]; a[i] = this._values[id];
} }
return a; return a;
} }
...@@ -124,25 +113,14 @@ ...@@ -124,25 +113,14 @@
const loadString = (addr) => { const loadString = (addr) => {
const saddr = getInt64(addr + 0); const saddr = getInt64(addr + 0);
const len = getInt64(addr + 8); const len = getInt64(addr + 8);
return decoder.decode(new DataView(inst.exports.mem.buffer, saddr, len)); return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
} }
global.go = { this.importObject = {
async compileAndRun(source) {
await go.compile(source);
await go.run();
},
async compile(source) {
mod = await WebAssembly.compile(source);
},
async run() {
let importObject = {
go: { go: {
// func wasmExit(code int32) // func wasmExit(code int32)
"runtime.wasmExit": (sp) => { "runtime.wasmExit": (sp) => {
process.exit(mem().getInt32(sp + 8, true)); this.exit(mem().getInt32(sp + 8, true));
}, },
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
...@@ -150,7 +128,7 @@ ...@@ -150,7 +128,7 @@
const fd = getInt64(sp + 8); const fd = getInt64(sp + 8);
const p = getInt64(sp + 16); const p = getInt64(sp + 16);
const n = mem().getInt32(sp + 24, true); const n = mem().getInt32(sp + 24, true);
fs.writeSync(fd, new Uint8Array(inst.exports.mem.buffer, p, n)); fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
}, },
// func nanotime() int64 // func nanotime() int64
...@@ -283,51 +261,61 @@ ...@@ -283,51 +261,61 @@
}, },
} }
}; };
}
async run(instance) {
this._inst = instance;
this._values = [undefined, null, global, this._inst.exports.mem]; // TODO: garbage collection
inst = await WebAssembly.instantiate(mod, importObject); const mem = new DataView(this._inst.exports.mem.buffer)
values = [undefined, null, global, inst.exports.mem];
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
let offset = 4096; let offset = 4096;
const strPtr = (str) => { const strPtr = (str) => {
let ptr = offset; let ptr = offset;
new Uint8Array(inst.exports.mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0")); new Uint8Array(mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0"));
offset += str.length + (8 - (str.length % 8)); offset += str.length + (8 - (str.length % 8));
return ptr; return ptr;
}; };
const argc = args.length; const argc = this.argv.length;
const argvPtrs = []; const argvPtrs = [];
args.forEach((arg) => { this.argv.forEach((arg) => {
argvPtrs.push(strPtr(arg)); argvPtrs.push(strPtr(arg));
}); });
const keys = Object.keys(process.env).sort(); const keys = Object.keys(this.env).sort();
argvPtrs.push(keys.length); argvPtrs.push(keys.length);
keys.forEach((key) => { keys.forEach((key) => {
argvPtrs.push(strPtr(`${key}=${process.env[key]}`)); argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
}); });
const argv = offset; const argv = offset;
argvPtrs.forEach((ptr) => { argvPtrs.forEach((ptr) => {
mem().setUint32(offset, ptr, true); mem.setUint32(offset, ptr, true);
mem().setUint32(offset + 4, 0, true); mem.setUint32(offset + 4, 0, true);
offset += 8; offset += 8;
}); });
try { this._inst.exports.run(argc, argv);
inst.exports.run(argc, argv);
} catch (err) {
console.error(err);
process.exit(1);
}
} }
} }
if (isNodeJS) { if (isNodeJS) {
go.compileAndRun(fs.readFileSync(process.argv[2])).catch((err) => { if (process.argv.length < 3) {
process.stderr.write("usage: go_js_wasm_exec [wasm binary] [arguments]\n");
process.exit(1);
}
const go = new Go();
go.argv = process.argv.slice(2);
go.env = process.env;
go.exit = process.exit;
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
return go.run(result.instance);
}).catch((err) => {
console.error(err); console.error(err);
process.exit(1); process.exit(1);
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment