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.
<body>
<script src="wasm_exec.js"></script>
<script>
async function loadAndCompile() {
let resp = await fetch("test.wasm");
let bytes = await resp.arrayBuffer();
await go.compile(bytes);
document.getElementById("runButton").disabled = false;
if (!WebAssembly.instantiateStreaming) { // polyfill
WebAssembly.instantiateStreaming = async (resp, importObject) => {
const source = await (await resp).arrayBuffer();
return await WebAssembly.instantiate(source, importObject);
};
}
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>
<button onClick="console.clear(); go.run();" id="runButton" disabled>Run</button>
<button onClick="run();" id="runButton" disabled>Run</button>
</body>
</html>
\ No newline at end of file
......@@ -3,19 +3,10 @@
// 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).
const isNodeJS = typeof process !== "undefined";
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.fs = require("fs");
const nodeCrypto = require("crypto");
......@@ -40,15 +31,6 @@
} else {
window.global = window;
global.process = {
env: {},
exit(code) {
if (code !== 0) {
console.warn("exit code:", code);
}
},
};
let outputBuf = "";
global.fs = {
constants: {},
......@@ -67,12 +49,19 @@
const encoder = new TextEncoder("utf-8");
const decoder = new TextDecoder("utf-8");
let mod, inst;
let values = []; // TODO: garbage collection
global.Go = class {
constructor() {
this.argv = [];
this.env = {};
this.exit = (code) => {
if (code !== 0) {
console.warn("exit code:", code);
}
};
const mem = () => {
// 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) => {
......@@ -88,7 +77,7 @@
const loadValue = (addr) => {
const id = mem().getUint32(addr, true);
return values[id];
return this._values[id];
}
const storeValue = (addr, v) => {
......@@ -100,14 +89,14 @@
mem().setUint32(addr, 1, true);
return;
}
values.push(v);
mem().setUint32(addr, values.length - 1, true);
this._values.push(v);
mem().setUint32(addr, this._values.length - 1, true);
}
const loadSlice = (addr) => {
const array = getInt64(addr + 0);
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) => {
......@@ -116,7 +105,7 @@
const a = new Array(len);
for (let i = 0; i < len; i++) {
const id = mem().getUint32(array + i * 4, true);
a[i] = values[id];
a[i] = this._values[id];
}
return a;
}
......@@ -124,25 +113,14 @@
const loadString = (addr) => {
const saddr = getInt64(addr + 0);
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 = {
async compileAndRun(source) {
await go.compile(source);
await go.run();
},
async compile(source) {
mod = await WebAssembly.compile(source);
},
async run() {
let importObject = {
this.importObject = {
go: {
// func wasmExit(code int32)
"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)
......@@ -150,7 +128,7 @@
const fd = getInt64(sp + 8);
const p = getInt64(sp + 16);
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
......@@ -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);
values = [undefined, null, global, inst.exports.mem];
const mem = new DataView(this._inst.exports.mem.buffer)
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
let offset = 4096;
const strPtr = (str) => {
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));
return ptr;
};
const argc = args.length;
const argc = this.argv.length;
const argvPtrs = [];
args.forEach((arg) => {
this.argv.forEach((arg) => {
argvPtrs.push(strPtr(arg));
});
const keys = Object.keys(process.env).sort();
const keys = Object.keys(this.env).sort();
argvPtrs.push(keys.length);
keys.forEach((key) => {
argvPtrs.push(strPtr(`${key}=${process.env[key]}`));
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
});
const argv = offset;
argvPtrs.forEach((ptr) => {
mem().setUint32(offset, ptr, true);
mem().setUint32(offset + 4, 0, true);
mem.setUint32(offset, ptr, true);
mem.setUint32(offset + 4, 0, true);
offset += 8;
});
try {
inst.exports.run(argc, argv);
} catch (err) {
console.error(err);
process.exit(1);
}
this._inst.exports.run(argc, argv);
}
}
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);
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