]> Cypherpunks repositories - gostls13.git/commitdiff
misc/wasm: make wasm_exec.js more flexible
authorRichard Musiol <mail@richard-musiol.de>
Thu, 17 May 2018 10:33:01 +0000 (12:33 +0200)
committerBrad Fitzpatrick <bradfitz@golang.org>
Fri, 18 May 2018 18:16:50 +0000 (18:16 +0000)
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/113515
Reviewed-by: Agniva De Sarker <agniva.quicksilver@gmail.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
misc/wasm/wasm_exec.html
misc/wasm/wasm_exec.js

index 634ae20cfcf6737a35a96863b26dffb102256e4b..cc37ea73ce5a71ebebd7657a116d5187883db770 100644 (file)
@@ -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>
+</html>
\ No newline at end of file
index d61bbcc95a45adb61f04a4f1566009f4f9b0f499..142080bf55169db67ef11ac916323a8f415fa5ae 100755 (executable)
@@ -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");
        } else {
                window.global = window;
 
-               global.process = {
-                       env: {},
-                       exit(code) {
-                               if (code !== 0) {
-                                       console.warn("exit code:", code);
-                               }
-                       },
-               };
-
                let outputBuf = "";
                global.fs = {
                        constants: {},
        const encoder = new TextEncoder("utf-8");
        const decoder = new TextDecoder("utf-8");
 
-       let mod, inst;
-       let values = []; // TODO: garbage collection
-
-       const mem = () => {
-               // The buffer may change when requesting more memory.
-               return new DataView(inst.exports.mem.buffer);
-       }
-
-       const setInt64 = (addr, v) => {
-               mem().setUint32(addr + 0, v, true);
-               mem().setUint32(addr + 4, Math.floor(v / 4294967296), true);
-       }
+       global.Go = class {
+               constructor() {
+                       this.argv = [];
+                       this.env = {};
+                       this.exit = (code) => {
+                               if (code !== 0) {
+                                       console.warn("exit code:", code);
+                               }
+                       };
 
-       const getInt64 = (addr) => {
-               const low = mem().getUint32(addr + 0, true);
-               const high = mem().getInt32(addr + 4, true);
-               return low + high * 4294967296;
-       }
+                       const mem = () => {
+                               // The buffer may change when requesting more memory.
+                               return new DataView(this._inst.exports.mem.buffer);
+                       }
 
-       const loadValue = (addr) => {
-               const id = mem().getUint32(addr, true);
-               return values[id];
-       }
+                       const setInt64 = (addr, v) => {
+                               mem().setUint32(addr + 0, v, true);
+                               mem().setUint32(addr + 4, Math.floor(v / 4294967296), true);
+                       }
 
-       const storeValue = (addr, v) => {
-               if (v === undefined) {
-                       mem().setUint32(addr, 0, true);
-                       return;
-               }
-               if (v === null) {
-                       mem().setUint32(addr, 1, true);
-                       return;
-               }
-               values.push(v);
-               mem().setUint32(addr, values.length - 1, true);
-       }
+                       const getInt64 = (addr) => {
+                               const low = mem().getUint32(addr + 0, true);
+                               const high = mem().getInt32(addr + 4, true);
+                               return low + high * 4294967296;
+                       }
 
-       const loadSlice = (addr) => {
-               const array = getInt64(addr + 0);
-               const len = getInt64(addr + 8);
-               return new Uint8Array(inst.exports.mem.buffer, array, len);
-       }
+                       const loadValue = (addr) => {
+                               const id = mem().getUint32(addr, true);
+                               return this._values[id];
+                       }
 
-       const loadSliceOfValues = (addr) => {
-               const array = getInt64(addr + 0);
-               const len = getInt64(addr + 8);
-               const a = new Array(len);
-               for (let i = 0; i < len; i++) {
-                       const id = mem().getUint32(array + i * 4, true);
-                       a[i] = values[id];
-               }
-               return a;
-       }
+                       const storeValue = (addr, v) => {
+                               if (v === undefined) {
+                                       mem().setUint32(addr, 0, true);
+                                       return;
+                               }
+                               if (v === null) {
+                                       mem().setUint32(addr, 1, true);
+                                       return;
+                               }
+                               this._values.push(v);
+                               mem().setUint32(addr, this._values.length - 1, true);
+                       }
 
-       const loadString = (addr) => {
-               const saddr = getInt64(addr + 0);
-               const len = getInt64(addr + 8);
-               return decoder.decode(new DataView(inst.exports.mem.buffer, saddr, len));
-       }
+                       const loadSlice = (addr) => {
+                               const array = getInt64(addr + 0);
+                               const len = getInt64(addr + 8);
+                               return new Uint8Array(this._inst.exports.mem.buffer, array, len);
+                       }
 
-       global.go = {
-               async compileAndRun(source) {
-                       await go.compile(source);
-                       await go.run();
-               },
+                       const loadSliceOfValues = (addr) => {
+                               const array = getInt64(addr + 0);
+                               const len = getInt64(addr + 8);
+                               const a = new Array(len);
+                               for (let i = 0; i < len; i++) {
+                                       const id = mem().getUint32(array + i * 4, true);
+                                       a[i] = this._values[id];
+                               }
+                               return a;
+                       }
 
-               async compile(source) {
-                       mod = await WebAssembly.compile(source);
-               },
+                       const loadString = (addr) => {
+                               const saddr = getInt64(addr + 0);
+                               const len = getInt64(addr + 8);
+                               return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
+                       }
 
-               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)
                                                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
                                        },
                                }
                        };
+               }
+
+               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);
                });