From 08a9cf36b2d92764d0e9ab22e95c2326798846b0 Mon Sep 17 00:00:00 2001 From: x1unix Date: Tue, 8 Feb 2022 18:24:52 +0200 Subject: [PATCH 01/11] build: use Node 17 --- build/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index 645ae980..54051940 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,4 +1,4 @@ -FROM node:13-alpine as ui-build +FROM node:17-alpine as ui-build COPY web /tmp/web WORKDIR /tmp/web ARG APP_VERSION=1.0.0 @@ -32,4 +32,4 @@ COPY --from=build /tmp/playground/worker.wasm ./public COPY --from=build /tmp/playground/wasm_exec.js ./public EXPOSE 8000 ENTRYPOINT /opt/playground/server -f=/opt/playground/data/packages.json -addr=:8000 \ - -clean-interval=${APP_CLEAN_INTERVAL} -debug=${APP_DEBUG} -playground-url=${APP_PLAYGROUND_URL} \ No newline at end of file + -clean-interval=${APP_CLEAN_INTERVAL} -debug=${APP_DEBUG} -playground-url=${APP_PLAYGROUND_URL} \ No newline at end of file From c15836052ea2c91e4b7746aa15042fc1338f9dfb Mon Sep 17 00:00:00 2001 From: x1unix Date: Tue, 8 Feb 2022 18:25:43 +0200 Subject: [PATCH 02/11] build: use Go 1.17 --- build/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index 54051940..7bda7fe8 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -7,7 +7,7 @@ ARG GITHUB_URL=https://github.com/x1unix/go-playground RUN yarn install --silent && \ REACT_APP_VERSION=$APP_VERSION REACT_APP_GITHUB_URL=$GITHUB_URL REACT_APP_GTAG=$APP_GTAG yarn build -FROM golang:1.14-alpine as build +FROM golang:1.17-alpine as build WORKDIR /tmp/playground COPY cmd ./cmd COPY pkg ./pkg @@ -19,7 +19,7 @@ RUN echo "Building server with version $APP_VERSION" && \ GOOS=js GOARCH=wasm go build -o ./worker.wasm ./cmd/webworker && \ cp $(go env GOROOT)/misc/wasm/wasm_exec.js . -FROM golang:1.14-alpine as production +FROM golang:1.17-alpine as production WORKDIR /opt/playground ENV GOROOT /usr/local/go ENV APP_CLEAN_INTERVAL=10m From 4f119fce322cf9850ec73f084129ff341abfa1f0 Mon Sep 17 00:00:00 2001 From: x1unix Date: Tue, 8 Feb 2022 18:30:28 +0200 Subject: [PATCH 03/11] wasm: update process.ts --- web/src/services/go/process.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/services/go/process.ts b/web/src/services/go/process.ts index 02729b71..056e3ada 100644 --- a/web/src/services/go/process.ts +++ b/web/src/services/go/process.ts @@ -6,7 +6,7 @@ const CWD_STUB = '/'; /** * Minimal NodeJS.Process implementation for wasm_exec.js * - * Source: wasm_exec.js:87 in Go 1.14 + * Source: wasm_exec.js:87 in Go 1.17 */ const ProcessStub = { getuid() { return PROCID_STUB; }, From 77bc48c1620a0a280bacc7b05f41c553339d2514 Mon Sep 17 00:00:00 2001 From: x1unix Date: Tue, 8 Feb 2022 18:37:45 +0200 Subject: [PATCH 04/11] warm: reformat code --- web/src/services/go/wasm_exec.js | 834 +++++++++++++++---------------- 1 file changed, 417 insertions(+), 417 deletions(-) diff --git a/web/src/services/go/wasm_exec.js b/web/src/services/go/wasm_exec.js index 857d267e..64a961c8 100644 --- a/web/src/services/go/wasm_exec.js +++ b/web/src/services/go/wasm_exec.js @@ -16,11 +16,11 @@ /* eslint-disable */ if (!global.TextEncoder) { - global.TextEncoder = require("util").TextEncoder; + global.TextEncoder = require("util").TextEncoder; } if (!global.TextDecoder) { - global.TextDecoder = require("util").TextDecoder; + global.TextDecoder = require("util").TextDecoder; } // End of polyfills for common API. @@ -29,436 +29,436 @@ const encoder = new TextEncoder("utf-8"); const decoder = new TextDecoder("utf-8"); export class Go { - constructor(globalScope) { - // x1unix: pass custom globalThis and fs - const {fs, process} = globalScope; - this.global = globalScope; - - this.argv = ["js"]; - this.env = {}; - this.exit = (code) => { - if (code !== 0) { - console.warn("exit code:", code); - } - }; - this._exitPromise = new Promise((resolve) => { - this._resolveExitPromise = resolve; - }); - this._pendingEvent = null; - this._scheduledTimeouts = new Map(); - this._nextCallbackTimeoutID = 1; - - const setInt64 = (addr, v) => { - this.mem.setUint32(addr + 0, v, true); - this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); - } + constructor(globalScope) { + // x1unix: pass custom globalThis and fs + const { fs, process } = globalScope; + this.global = globalScope; + + this.argv = ["js"]; + this.env = {}; + this.exit = (code) => { + if (code !== 0) { + console.warn("exit code:", code); + } + }; + this._exitPromise = new Promise((resolve) => { + this._resolveExitPromise = resolve; + }); + this._pendingEvent = null; + this._scheduledTimeouts = new Map(); + this._nextCallbackTimeoutID = 1; + + const setInt64 = (addr, v) => { + this.mem.setUint32(addr + 0, v, true); + this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); + } - const getInt64 = (addr) => { - const low = this.mem.getUint32(addr + 0, true); - const high = this.mem.getInt32(addr + 4, true); - return low + high * 4294967296; - } + const getInt64 = (addr) => { + const low = this.mem.getUint32(addr + 0, true); + const high = this.mem.getInt32(addr + 4, true); + return low + high * 4294967296; + } - const loadValue = (addr) => { - const f = this.mem.getFloat64(addr, true); - if (f === 0) { - return undefined; - } - if (!isNaN(f)) { - return f; - } - - const id = this.mem.getUint32(addr, true); - return this._values[id]; - } + const loadValue = (addr) => { + const f = this.mem.getFloat64(addr, true); + if (f === 0) { + return undefined; + } + if (!isNaN(f)) { + return f; + } + + const id = this.mem.getUint32(addr, true); + return this._values[id]; + } - const storeValue = (addr, v) => { - const nanHead = 0x7FF80000; - - if (typeof v === "number") { - if (isNaN(v)) { - this.mem.setUint32(addr + 4, nanHead, true); - this.mem.setUint32(addr, 0, true); - return; - } - if (v === 0) { - this.mem.setUint32(addr + 4, nanHead, true); - this.mem.setUint32(addr, 1, true); - return; - } - this.mem.setFloat64(addr, v, true); - return; - } - - switch (v) { - case undefined: - this.mem.setFloat64(addr, 0, true); - return; - case null: - this.mem.setUint32(addr + 4, nanHead, true); - this.mem.setUint32(addr, 2, true); - return; - case true: - this.mem.setUint32(addr + 4, nanHead, true); - this.mem.setUint32(addr, 3, true); - return; - case false: - this.mem.setUint32(addr + 4, nanHead, true); - this.mem.setUint32(addr, 4, true); - return; - } - - let id = this._ids.get(v); - if (id === undefined) { - id = this._idPool.pop(); - if (id === undefined) { - id = this._values.length; - } - this._values[id] = v; - this._goRefCounts[id] = 0; - this._ids.set(v, id); - } - this._goRefCounts[id]++; - let typeFlag = 1; - switch (typeof v) { - case "string": - typeFlag = 2; - break; - case "symbol": - typeFlag = 3; - break; - case "function": - typeFlag = 4; - break; - } - this.mem.setUint32(addr + 4, nanHead | typeFlag, true); - this.mem.setUint32(addr, id, true); - } + const storeValue = (addr, v) => { + const nanHead = 0x7FF80000; - const loadSlice = (addr) => { - const array = getInt64(addr + 0); - const len = getInt64(addr + 8); - return new Uint8Array(this._inst.exports.mem.buffer, array, len); + if (typeof v === "number") { + if (isNaN(v)) { + this.mem.setUint32(addr + 4, nanHead, true); + this.mem.setUint32(addr, 0, true); + return; } - - 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++) { - a[i] = loadValue(array + i * 8); - } - return a; + if (v === 0) { + this.mem.setUint32(addr + 4, nanHead, true); + this.mem.setUint32(addr, 1, true); + return; } - - 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)); + this.mem.setFloat64(addr, v, true); + return; + } + + switch (v) { + case undefined: + this.mem.setFloat64(addr, 0, true); + return; + case null: + this.mem.setUint32(addr + 4, nanHead, true); + this.mem.setUint32(addr, 2, true); + return; + case true: + this.mem.setUint32(addr + 4, nanHead, true); + this.mem.setUint32(addr, 3, true); + return; + case false: + this.mem.setUint32(addr + 4, nanHead, true); + this.mem.setUint32(addr, 4, true); + return; + } + + let id = this._ids.get(v); + if (id === undefined) { + id = this._idPool.pop(); + if (id === undefined) { + id = this._values.length; } + this._values[id] = v; + this._goRefCounts[id] = 0; + this._ids.set(v, id); + } + this._goRefCounts[id]++; + let typeFlag = 1; + switch (typeof v) { + case "string": + typeFlag = 2; + break; + case "symbol": + typeFlag = 3; + break; + case "function": + typeFlag = 4; + break; + } + this.mem.setUint32(addr + 4, nanHead | typeFlag, true); + this.mem.setUint32(addr, id, true); + } - const timeOrigin = Date.now() - performance.now(); - this.importObject = { - go: { - // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) - // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported - // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). - // This changes the SP, thus we have to update the SP used by the imported function. - - // func wasmExit(code int32) - "runtime.wasmExit": (sp) => { - const code = this.mem.getInt32(sp + 8, true); - this.exited = true; - delete this._inst; - delete this._values; - delete this._goRefCounts; - delete this._ids; - delete this._idPool; - this.exit(code); - }, - - // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) - "runtime.wasmWrite": (sp) => { - const fd = getInt64(sp + 8); - const p = getInt64(sp + 16); - const n = this.mem.getInt32(sp + 24, true); - fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); - }, - - // func resetMemoryDataView() - "runtime.resetMemoryDataView": (sp) => { - this.mem = new DataView(this._inst.exports.mem.buffer); - }, - - // func nanotime1() int64 - "runtime.nanotime1": (sp) => { - setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); - }, - - // func walltime1() (sec int64, nsec int32) - "runtime.walltime1": (sp) => { - const msec = (new Date).getTime(); - setInt64(sp + 8, msec / 1000); - this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); - }, - - // func scheduleTimeoutEvent(delay int64) int32 - "runtime.scheduleTimeoutEvent": (sp) => { - const id = this._nextCallbackTimeoutID; - this._nextCallbackTimeoutID++; - this._scheduledTimeouts.set(id, setTimeout( - () => { - this._resume(); - while (this._scheduledTimeouts.has(id)) { - // for some reason Go failed to register the timeout event, log and try again - // (temporary workaround for https://github.com/golang/go/issues/28975) - console.warn("scheduleTimeoutEvent: missed timeout event"); - this._resume(); - } - }, - getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early - )); - this.mem.setInt32(sp + 16, id, true); - }, - - // func clearTimeoutEvent(id int32) - "runtime.clearTimeoutEvent": (sp) => { - const id = this.mem.getInt32(sp + 8, true); - clearTimeout(this._scheduledTimeouts.get(id)); - this._scheduledTimeouts.delete(id); - }, - - // func getRandomData(r []byte) - "runtime.getRandomData": (sp) => { - crypto.getRandomValues(loadSlice(sp + 8)); - }, - - // func finalizeRef(v ref) - "syscall/js.finalizeRef": (sp) => { - const id = this.mem.getUint32(sp + 8, true); - this._goRefCounts[id]--; - if (this._goRefCounts[id] === 0) { - const v = this._values[id]; - this._values[id] = null; - this._ids.delete(v); - this._idPool.push(id); - } - }, - - // func stringVal(value string) ref - "syscall/js.stringVal": (sp) => { - storeValue(sp + 24, loadString(sp + 8)); - }, - - // func valueGet(v ref, p string) ref - "syscall/js.valueGet": (sp) => { - const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); - sp = this._inst.exports.getsp(); // see comment above - storeValue(sp + 32, result); - }, - - // func valueSet(v ref, p string, x ref) - "syscall/js.valueSet": (sp) => { - Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); - }, - - // func valueDelete(v ref, p string) - "syscall/js.valueDelete": (sp) => { - Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); - }, - - // func valueIndex(v ref, i int) ref - "syscall/js.valueIndex": (sp) => { - storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); - }, - - // valueSetIndex(v ref, i int, x ref) - "syscall/js.valueSetIndex": (sp) => { - Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); - }, - - // func valueCall(v ref, m string, args []ref) (ref, bool) - "syscall/js.valueCall": (sp) => { - try { - const v = loadValue(sp + 8); - const m = Reflect.get(v, loadString(sp + 16)); - const args = loadSliceOfValues(sp + 32); - const result = Reflect.apply(m, v, args); - sp = this._inst.exports.getsp(); // see comment above - storeValue(sp + 56, result); - this.mem.setUint8(sp + 64, 1); - } catch (err) { - storeValue(sp + 56, err); - this.mem.setUint8(sp + 64, 0); - } - }, - - // func valueInvoke(v ref, args []ref) (ref, bool) - "syscall/js.valueInvoke": (sp) => { - try { - const v = loadValue(sp + 8); - const args = loadSliceOfValues(sp + 16); - const result = Reflect.apply(v, undefined, args); - sp = this._inst.exports.getsp(); // see comment above - storeValue(sp + 40, result); - this.mem.setUint8(sp + 48, 1); - } catch (err) { - storeValue(sp + 40, err); - this.mem.setUint8(sp + 48, 0); - } - }, - - // func valueNew(v ref, args []ref) (ref, bool) - "syscall/js.valueNew": (sp) => { - try { - const v = loadValue(sp + 8); - const args = loadSliceOfValues(sp + 16); - const result = Reflect.construct(v, args); - sp = this._inst.exports.getsp(); // see comment above - storeValue(sp + 40, result); - this.mem.setUint8(sp + 48, 1); - } catch (err) { - storeValue(sp + 40, err); - this.mem.setUint8(sp + 48, 0); - } - }, - - // func valueLength(v ref) int - "syscall/js.valueLength": (sp) => { - setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); - }, - - // valuePrepareString(v ref) (ref, int) - "syscall/js.valuePrepareString": (sp) => { - const str = encoder.encode(String(loadValue(sp + 8))); - storeValue(sp + 16, str); - setInt64(sp + 24, str.length); - }, - - // valueLoadString(v ref, b []byte) - "syscall/js.valueLoadString": (sp) => { - const str = loadValue(sp + 8); - loadSlice(sp + 16).set(str); - }, - - // func valueInstanceOf(v ref, t ref) bool - "syscall/js.valueInstanceOf": (sp) => { - this.mem.setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16)); - }, - - // func copyBytesToGo(dst []byte, src ref) (int, bool) - "syscall/js.copyBytesToGo": (sp) => { - const dst = loadSlice(sp + 8); - const src = loadValue(sp + 32); - if (!(src instanceof Uint8Array)) { - this.mem.setUint8(sp + 48, 0); - return; - } - const toCopy = src.subarray(0, dst.length); - dst.set(toCopy); - setInt64(sp + 40, toCopy.length); - this.mem.setUint8(sp + 48, 1); - }, - - // func copyBytesToJS(dst ref, src []byte) (int, bool) - "syscall/js.copyBytesToJS": (sp) => { - const dst = loadValue(sp + 8); - const src = loadSlice(sp + 16); - if (!(dst instanceof Uint8Array)) { - this.mem.setUint8(sp + 48, 0); - return; - } - const toCopy = src.subarray(0, dst.length); - dst.set(toCopy); - setInt64(sp + 40, toCopy.length); - this.mem.setUint8(sp + 48, 1); - }, - - "debug": (value) => { - console.log(value); - }, - } - }; + const loadSlice = (addr) => { + const array = getInt64(addr + 0); + const len = getInt64(addr + 8); + return new Uint8Array(this._inst.exports.mem.buffer, array, len); } - async run(instance) { - this._inst = instance; - this.mem = new DataView(this._inst.exports.mem.buffer); - this._values = [ // JS values that Go currently has references to, indexed by reference id - NaN, - 0, - null, - true, - false, - this.global, - this, - ]; - this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id - this._ids = new Map(); // mapping from JS values to reference ids - this._idPool = []; // unused ids that have been garbage collected - this.exited = false; // whether the Go program has exited - - // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. - let offset = 4096; - - const strPtr = (str) => { - const ptr = offset; - const bytes = encoder.encode(str + "\0"); - new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); - offset += bytes.length; - if (offset % 8 !== 0) { - offset += 8 - (offset % 8); - } - return ptr; - }; - - const argc = this.argv.length; - - const argvPtrs = []; - this.argv.forEach((arg) => { - argvPtrs.push(strPtr(arg)); - }); - argvPtrs.push(0); - - const keys = Object.keys(this.env).sort(); - keys.forEach((key) => { - argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); - }); - argvPtrs.push(0); - - const argv = offset; - argvPtrs.forEach((ptr) => { - this.mem.setUint32(offset, ptr, true); - this.mem.setUint32(offset + 4, 0, true); - offset += 8; - }); - - this._inst.exports.run(argc, argv); - if (this.exited) { - this._resolveExitPromise(); - } - await this._exitPromise; + 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++) { + a[i] = loadValue(array + i * 8); + } + return a; } - _resume() { - if (this.exited) { - throw new Error("Go program has already exited"); - } - this._inst.exports.resume(); - if (this.exited) { - this._resolveExitPromise(); - } + 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)); + } + + const timeOrigin = Date.now() - performance.now(); + this.importObject = { + go: { + // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) + // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported + // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). + // This changes the SP, thus we have to update the SP used by the imported function. + + // func wasmExit(code int32) + "runtime.wasmExit": (sp) => { + const code = this.mem.getInt32(sp + 8, true); + this.exited = true; + delete this._inst; + delete this._values; + delete this._goRefCounts; + delete this._ids; + delete this._idPool; + this.exit(code); + }, + + // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) + "runtime.wasmWrite": (sp) => { + const fd = getInt64(sp + 8); + const p = getInt64(sp + 16); + const n = this.mem.getInt32(sp + 24, true); + fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); + }, + + // func resetMemoryDataView() + "runtime.resetMemoryDataView": (sp) => { + this.mem = new DataView(this._inst.exports.mem.buffer); + }, + + // func nanotime1() int64 + "runtime.nanotime1": (sp) => { + setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); + }, + + // func walltime1() (sec int64, nsec int32) + "runtime.walltime1": (sp) => { + const msec = (new Date).getTime(); + setInt64(sp + 8, msec / 1000); + this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); + }, + + // func scheduleTimeoutEvent(delay int64) int32 + "runtime.scheduleTimeoutEvent": (sp) => { + const id = this._nextCallbackTimeoutID; + this._nextCallbackTimeoutID++; + this._scheduledTimeouts.set(id, setTimeout( + () => { + this._resume(); + while (this._scheduledTimeouts.has(id)) { + // for some reason Go failed to register the timeout event, log and try again + // (temporary workaround for https://github.com/golang/go/issues/28975) + console.warn("scheduleTimeoutEvent: missed timeout event"); + this._resume(); + } + }, + getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early + )); + this.mem.setInt32(sp + 16, id, true); + }, + + // func clearTimeoutEvent(id int32) + "runtime.clearTimeoutEvent": (sp) => { + const id = this.mem.getInt32(sp + 8, true); + clearTimeout(this._scheduledTimeouts.get(id)); + this._scheduledTimeouts.delete(id); + }, + + // func getRandomData(r []byte) + "runtime.getRandomData": (sp) => { + crypto.getRandomValues(loadSlice(sp + 8)); + }, + + // func finalizeRef(v ref) + "syscall/js.finalizeRef": (sp) => { + const id = this.mem.getUint32(sp + 8, true); + this._goRefCounts[id]--; + if (this._goRefCounts[id] === 0) { + const v = this._values[id]; + this._values[id] = null; + this._ids.delete(v); + this._idPool.push(id); + } + }, + + // func stringVal(value string) ref + "syscall/js.stringVal": (sp) => { + storeValue(sp + 24, loadString(sp + 8)); + }, + + // func valueGet(v ref, p string) ref + "syscall/js.valueGet": (sp) => { + const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); + sp = this._inst.exports.getsp(); // see comment above + storeValue(sp + 32, result); + }, + + // func valueSet(v ref, p string, x ref) + "syscall/js.valueSet": (sp) => { + Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); + }, + + // func valueDelete(v ref, p string) + "syscall/js.valueDelete": (sp) => { + Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); + }, + + // func valueIndex(v ref, i int) ref + "syscall/js.valueIndex": (sp) => { + storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); + }, + + // valueSetIndex(v ref, i int, x ref) + "syscall/js.valueSetIndex": (sp) => { + Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); + }, + + // func valueCall(v ref, m string, args []ref) (ref, bool) + "syscall/js.valueCall": (sp) => { + try { + const v = loadValue(sp + 8); + const m = Reflect.get(v, loadString(sp + 16)); + const args = loadSliceOfValues(sp + 32); + const result = Reflect.apply(m, v, args); + sp = this._inst.exports.getsp(); // see comment above + storeValue(sp + 56, result); + this.mem.setUint8(sp + 64, 1); + } catch (err) { + storeValue(sp + 56, err); + this.mem.setUint8(sp + 64, 0); + } + }, + + // func valueInvoke(v ref, args []ref) (ref, bool) + "syscall/js.valueInvoke": (sp) => { + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + const result = Reflect.apply(v, undefined, args); + sp = this._inst.exports.getsp(); // see comment above + storeValue(sp + 40, result); + this.mem.setUint8(sp + 48, 1); + } catch (err) { + storeValue(sp + 40, err); + this.mem.setUint8(sp + 48, 0); + } + }, + + // func valueNew(v ref, args []ref) (ref, bool) + "syscall/js.valueNew": (sp) => { + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + const result = Reflect.construct(v, args); + sp = this._inst.exports.getsp(); // see comment above + storeValue(sp + 40, result); + this.mem.setUint8(sp + 48, 1); + } catch (err) { + storeValue(sp + 40, err); + this.mem.setUint8(sp + 48, 0); + } + }, + + // func valueLength(v ref) int + "syscall/js.valueLength": (sp) => { + setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); + }, + + // valuePrepareString(v ref) (ref, int) + "syscall/js.valuePrepareString": (sp) => { + const str = encoder.encode(String(loadValue(sp + 8))); + storeValue(sp + 16, str); + setInt64(sp + 24, str.length); + }, + + // valueLoadString(v ref, b []byte) + "syscall/js.valueLoadString": (sp) => { + const str = loadValue(sp + 8); + loadSlice(sp + 16).set(str); + }, + + // func valueInstanceOf(v ref, t ref) bool + "syscall/js.valueInstanceOf": (sp) => { + this.mem.setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16)); + }, + + // func copyBytesToGo(dst []byte, src ref) (int, bool) + "syscall/js.copyBytesToGo": (sp) => { + const dst = loadSlice(sp + 8); + const src = loadValue(sp + 32); + if (!(src instanceof Uint8Array)) { + this.mem.setUint8(sp + 48, 0); + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + setInt64(sp + 40, toCopy.length); + this.mem.setUint8(sp + 48, 1); + }, + + // func copyBytesToJS(dst ref, src []byte) (int, bool) + "syscall/js.copyBytesToJS": (sp) => { + const dst = loadValue(sp + 8); + const src = loadSlice(sp + 16); + if (!(dst instanceof Uint8Array)) { + this.mem.setUint8(sp + 48, 0); + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + setInt64(sp + 40, toCopy.length); + this.mem.setUint8(sp + 48, 1); + }, + + "debug": (value) => { + console.log(value); + }, + } + }; + } + + async run(instance) { + this._inst = instance; + this.mem = new DataView(this._inst.exports.mem.buffer); + this._values = [ // JS values that Go currently has references to, indexed by reference id + NaN, + 0, + null, + true, + false, + this.global, + this, + ]; + this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id + this._ids = new Map(); // mapping from JS values to reference ids + this._idPool = []; // unused ids that have been garbage collected + this.exited = false; // whether the Go program has exited + + // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. + let offset = 4096; + + const strPtr = (str) => { + const ptr = offset; + const bytes = encoder.encode(str + "\0"); + new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); + offset += bytes.length; + if (offset % 8 !== 0) { + offset += 8 - (offset % 8); + } + return ptr; + }; + + const argc = this.argv.length; + + const argvPtrs = []; + this.argv.forEach((arg) => { + argvPtrs.push(strPtr(arg)); + }); + argvPtrs.push(0); + + const keys = Object.keys(this.env).sort(); + keys.forEach((key) => { + argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); + }); + argvPtrs.push(0); + + const argv = offset; + argvPtrs.forEach((ptr) => { + this.mem.setUint32(offset, ptr, true); + this.mem.setUint32(offset + 4, 0, true); + offset += 8; + }); + + this._inst.exports.run(argc, argv); + if (this.exited) { + this._resolveExitPromise(); } + await this._exitPromise; + } - _makeFuncWrapper(id) { - const go = this; - return function () { - const event = { id: id, this: this, args: arguments }; - go._pendingEvent = event; - go._resume(); - return event.result; - }; + _resume() { + if (this.exited) { + throw new Error("Go program has already exited"); + } + this._inst.exports.resume(); + if (this.exited) { + this._resolveExitPromise(); } + } + + _makeFuncWrapper(id) { + const go = this; + return function () { + const event = { id: id, this: this, args: arguments }; + go._pendingEvent = event; + go._resume(); + return event.result; + }; + } } // // global.Go = Go; From b489c152f41c3e51689af60a26771c53e43e11d2 Mon Sep 17 00:00:00 2001 From: x1unix Date: Tue, 8 Feb 2022 18:38:04 +0200 Subject: [PATCH 05/11] wasm: get FS stubs --- web/src/services/go/fs.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/web/src/services/go/fs.ts b/web/src/services/go/fs.ts index b433f012..dac387f5 100644 --- a/web/src/services/go/fs.ts +++ b/web/src/services/go/fs.ts @@ -94,4 +94,25 @@ export class FileSystemWrapper { fsync(fd, callback) { callback(null); } + + chmod(path, mode, callback) { callback(enosys()); } + chown(path, uid, gid, callback) { callback(enosys()); } + close(fd, callback) { callback(enosys()); } + fchmod(fd, mode, callback) { callback(enosys()); } + fchown(fd, uid, gid, callback) { callback(enosys()); } + fstat(fd, callback) { callback(enosys()); } + ftruncate(fd, length, callback) { callback(enosys()); } + lchown(path, uid, gid, callback) { callback(enosys()); } + link(path, link, callback) { callback(enosys()); } + lstat(path, callback) { callback(enosys()); } + mkdir(path, perm, callback) { callback(enosys()); } + readdir(path, callback) { callback(enosys()); } + readlink(path, callback) { callback(enosys()); } + rename(from, to, callback) { callback(enosys()); } + rmdir(path, callback) { callback(enosys()); } + stat(path, callback) { callback(enosys()); } + symlink(path, link, callback) { callback(enosys()); } + truncate(path, length, callback) { callback(enosys()); } + unlink(path, callback) { callback(enosys()); } + utimes(path, atime, mtime, callback) { callback(enosys()); } } From 584b09db8b67f59509e9f284b4b7c14a07eb68a6 Mon Sep 17 00:00:00 2001 From: x1unix Date: Tue, 8 Feb 2022 18:50:00 +0200 Subject: [PATCH 06/11] wasm: update wasm_exec bridge --- web/src/services/go/wasm_exec.js | 99 +++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 34 deletions(-) diff --git a/web/src/services/go/wasm_exec.js b/web/src/services/go/wasm_exec.js index 64a961c8..6a1ce435 100644 --- a/web/src/services/go/wasm_exec.js +++ b/web/src/services/go/wasm_exec.js @@ -31,7 +31,7 @@ const decoder = new TextDecoder("utf-8"); export class Go { constructor(globalScope) { // x1unix: pass custom globalThis and fs - const { fs, process } = globalScope; + const { fs } = globalScope; this.global = globalScope; this.argv = ["js"]; @@ -75,37 +75,19 @@ export class Go { const storeValue = (addr, v) => { const nanHead = 0x7FF80000; - if (typeof v === "number") { + if (typeof v === "number" && v !== 0) { if (isNaN(v)) { this.mem.setUint32(addr + 4, nanHead, true); this.mem.setUint32(addr, 0, true); return; } - if (v === 0) { - this.mem.setUint32(addr + 4, nanHead, true); - this.mem.setUint32(addr, 1, true); - return; - } this.mem.setFloat64(addr, v, true); return; } - switch (v) { - case undefined: - this.mem.setFloat64(addr, 0, true); - return; - case null: - this.mem.setUint32(addr + 4, nanHead, true); - this.mem.setUint32(addr, 2, true); - return; - case true: - this.mem.setUint32(addr + 4, nanHead, true); - this.mem.setUint32(addr, 3, true); - return; - case false: - this.mem.setUint32(addr + 4, nanHead, true); - this.mem.setUint32(addr, 4, true); - return; + if (v === undefined) { + this.mem.setFloat64(addr, 0, true); + return; } let id = this._ids.get(v); @@ -119,8 +101,13 @@ export class Go { this._ids.set(v, id); } this._goRefCounts[id]++; - let typeFlag = 1; + let typeFlag = 0; switch (typeof v) { + case "object": + if (v !== null) { + typeFlag = 1; + } + break; case "string": typeFlag = 2; break; @@ -167,6 +154,7 @@ export class Go { // func wasmExit(code int32) "runtime.wasmExit": (sp) => { + sp >>>= 0; const code = this.mem.getInt32(sp + 8, true); this.exited = true; delete this._inst; @@ -179,6 +167,7 @@ export class Go { // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) "runtime.wasmWrite": (sp) => { + sp >>>= 0; const fd = getInt64(sp + 8); const p = getInt64(sp + 16); const n = this.mem.getInt32(sp + 24, true); @@ -187,16 +176,19 @@ export class Go { // func resetMemoryDataView() "runtime.resetMemoryDataView": (sp) => { + sp >>>= 0; this.mem = new DataView(this._inst.exports.mem.buffer); }, // func nanotime1() int64 "runtime.nanotime1": (sp) => { + sp >>>= 0; setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); }, // func walltime1() (sec int64, nsec int32) "runtime.walltime1": (sp) => { + sp >>>= 0; const msec = (new Date).getTime(); setInt64(sp + 8, msec / 1000); this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); @@ -204,6 +196,7 @@ export class Go { // func scheduleTimeoutEvent(delay int64) int32 "runtime.scheduleTimeoutEvent": (sp) => { + sp >>>= 0; const id = this._nextCallbackTimeoutID; this._nextCallbackTimeoutID++; this._scheduledTimeouts.set(id, setTimeout( @@ -223,6 +216,7 @@ export class Go { // func clearTimeoutEvent(id int32) "runtime.clearTimeoutEvent": (sp) => { + sp >>>= 0; const id = this.mem.getInt32(sp + 8, true); clearTimeout(this._scheduledTimeouts.get(id)); this._scheduledTimeouts.delete(id); @@ -230,11 +224,13 @@ export class Go { // func getRandomData(r []byte) "runtime.getRandomData": (sp) => { + sp >>>= 0; crypto.getRandomValues(loadSlice(sp + 8)); }, // func finalizeRef(v ref) "syscall/js.finalizeRef": (sp) => { + sp >>>= 0; const id = this.mem.getUint32(sp + 8, true); this._goRefCounts[id]--; if (this._goRefCounts[id] === 0) { @@ -247,11 +243,13 @@ export class Go { // func stringVal(value string) ref "syscall/js.stringVal": (sp) => { + sp >>>= 0; storeValue(sp + 24, loadString(sp + 8)); }, // func valueGet(v ref, p string) ref "syscall/js.valueGet": (sp) => { + sp >>>= 0; const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); sp = this._inst.exports.getsp(); // see comment above storeValue(sp + 32, result); @@ -259,35 +257,41 @@ export class Go { // func valueSet(v ref, p string, x ref) "syscall/js.valueSet": (sp) => { + sp >>>= 0; Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); }, // func valueDelete(v ref, p string) "syscall/js.valueDelete": (sp) => { + sp >>>= 0; Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); }, // func valueIndex(v ref, i int) ref "syscall/js.valueIndex": (sp) => { + sp >>>= 0; storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); }, // valueSetIndex(v ref, i int, x ref) "syscall/js.valueSetIndex": (sp) => { + sp >>>= 0; Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); }, // func valueCall(v ref, m string, args []ref) (ref, bool) "syscall/js.valueCall": (sp) => { + sp >>>= 0; try { const v = loadValue(sp + 8); const m = Reflect.get(v, loadString(sp + 16)); const args = loadSliceOfValues(sp + 32); const result = Reflect.apply(m, v, args); - sp = this._inst.exports.getsp(); // see comment above + sp = this._inst.exports.getsp() >>> 0; // see comment above storeValue(sp + 56, result); this.mem.setUint8(sp + 64, 1); } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above storeValue(sp + 56, err); this.mem.setUint8(sp + 64, 0); } @@ -295,14 +299,16 @@ export class Go { // func valueInvoke(v ref, args []ref) (ref, bool) "syscall/js.valueInvoke": (sp) => { + sp >>>= 0; try { const v = loadValue(sp + 8); const args = loadSliceOfValues(sp + 16); const result = Reflect.apply(v, undefined, args); - sp = this._inst.exports.getsp(); // see comment above + sp = this._inst.exports.getsp() >>> 0; // see comment above storeValue(sp + 40, result); this.mem.setUint8(sp + 48, 1); } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above storeValue(sp + 40, err); this.mem.setUint8(sp + 48, 0); } @@ -310,14 +316,16 @@ export class Go { // func valueNew(v ref, args []ref) (ref, bool) "syscall/js.valueNew": (sp) => { + sp >>>= 0; try { const v = loadValue(sp + 8); const args = loadSliceOfValues(sp + 16); const result = Reflect.construct(v, args); - sp = this._inst.exports.getsp(); // see comment above + sp = this._inst.exports.getsp() >>> 0; // see comment above storeValue(sp + 40, result); this.mem.setUint8(sp + 48, 1); } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above storeValue(sp + 40, err); this.mem.setUint8(sp + 48, 0); } @@ -325,11 +333,13 @@ export class Go { // func valueLength(v ref) int "syscall/js.valueLength": (sp) => { + sp >>>= 0; setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); }, // valuePrepareString(v ref) (ref, int) "syscall/js.valuePrepareString": (sp) => { + sp >>>= 0; const str = encoder.encode(String(loadValue(sp + 8))); storeValue(sp + 16, str); setInt64(sp + 24, str.length); @@ -337,20 +347,23 @@ export class Go { // valueLoadString(v ref, b []byte) "syscall/js.valueLoadString": (sp) => { + sp >>>= 0; const str = loadValue(sp + 8); loadSlice(sp + 16).set(str); }, // func valueInstanceOf(v ref, t ref) bool "syscall/js.valueInstanceOf": (sp) => { + sp >>>= 0; this.mem.setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16)); }, // func copyBytesToGo(dst []byte, src ref) (int, bool) "syscall/js.copyBytesToGo": (sp) => { + sp >>>= 0; const dst = loadSlice(sp + 8); const src = loadValue(sp + 32); - if (!(src instanceof Uint8Array)) { + if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { this.mem.setUint8(sp + 48, 0); return; } @@ -362,9 +375,10 @@ export class Go { // func copyBytesToJS(dst ref, src []byte) (int, bool) "syscall/js.copyBytesToJS": (sp) => { + sp >>>= 0; const dst = loadValue(sp + 8); const src = loadSlice(sp + 16); - if (!(dst instanceof Uint8Array)) { + if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { this.mem.setUint8(sp + 48, 0); return; } @@ -382,6 +396,9 @@ export class Go { } async run(instance) { + if (!(instance instanceof WebAssembly.Instance)) { + throw new Error("Go.run: WebAssembly.Instance expected"); + } this._inst = instance; this.mem = new DataView(this._inst.exports.mem.buffer); this._values = [ // JS values that Go currently has references to, indexed by reference id @@ -390,13 +407,20 @@ export class Go { null, true, false, - this.global, + global, this, ]; - this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id - this._ids = new Map(); // mapping from JS values to reference ids - this._idPool = []; // unused ids that have been garbage collected - this.exited = false; // whether the Go program has exited + this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id + this._ids = new Map([ // mapping from JS values to reference ids + [0, 1], + [null, 2], + [true, 3], + [false, 4], + [global, 5], + [this, 6], + ]); + this._idPool = []; // unused ids that have been garbage collected + this.exited = false; // whether the Go program has exited // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. let offset = 4096; @@ -433,6 +457,13 @@ export class Go { offset += 8; }); + // The linker guarantees global data starts from at least wasmMinDataAddr. + // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. + const wasmMinDataAddr = 4096 + 8192; + if (offset >= wasmMinDataAddr) { + throw new Error("total length of command line and environment variables exceeds limit"); + } + this._inst.exports.run(argc, argv); if (this.exited) { this._resolveExitPromise(); From ad40287eff2e8889d13aaa117e9e9e2fe3387e7c Mon Sep 17 00:00:00 2001 From: x1unix Date: Tue, 8 Feb 2022 18:51:30 +0200 Subject: [PATCH 07/11] wasm: update wasm_exec bridge --- web/src/services/go/wasm_exec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/services/go/wasm_exec.js b/web/src/services/go/wasm_exec.js index 6a1ce435..0be10e55 100644 --- a/web/src/services/go/wasm_exec.js +++ b/web/src/services/go/wasm_exec.js @@ -186,8 +186,8 @@ export class Go { setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); }, - // func walltime1() (sec int64, nsec int32) - "runtime.walltime1": (sp) => { + // func walltime() (sec int64, nsec int32) + "runtime.walltime": (sp) => { sp >>>= 0; const msec = (new Date).getTime(); setInt64(sp + 8, msec / 1000); From a945c2f02a9149bda1e8eb7a9b7f80179e94b09e Mon Sep 17 00:00:00 2001 From: x1unix Date: Tue, 8 Feb 2022 18:58:34 +0200 Subject: [PATCH 08/11] wasm: update bridge --- web/src/services/go/wasm_exec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/services/go/wasm_exec.js b/web/src/services/go/wasm_exec.js index 0be10e55..879f0ae5 100644 --- a/web/src/services/go/wasm_exec.js +++ b/web/src/services/go/wasm_exec.js @@ -251,7 +251,7 @@ export class Go { "syscall/js.valueGet": (sp) => { sp >>>= 0; const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); - sp = this._inst.exports.getsp(); // see comment above + sp = this._inst.exports.getsp() >>> 0; // see comment above storeValue(sp + 32, result); }, @@ -355,7 +355,7 @@ export class Go { // func valueInstanceOf(v ref, t ref) bool "syscall/js.valueInstanceOf": (sp) => { sp >>>= 0; - this.mem.setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16)); + this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); }, // func copyBytesToGo(dst []byte, src ref) (int, bool) From 3fb7e0f77530940ba4f93f2341bf24ee623216fe Mon Sep 17 00:00:00 2001 From: x1unix Date: Tue, 8 Feb 2022 20:39:10 +0200 Subject: [PATCH 09/11] wasm: update Go global mock replacement --- web/src/services/go/wasm_exec.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/web/src/services/go/wasm_exec.js b/web/src/services/go/wasm_exec.js index 879f0ae5..ec81a842 100644 --- a/web/src/services/go/wasm_exec.js +++ b/web/src/services/go/wasm_exec.js @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // -// x1unix: Adapted original wasm_exec.js from Go 1.14 SDK +// x1unix: Adapted original wasm_exec.js from Go 1.147SDK // Map multiple JavaScript environments to a single common API, // preferring web standards over Node.js API. @@ -15,6 +15,10 @@ /* eslint-disable */ +// x1unix: globalThis predefined value ID. +// Should be in sync with wasm_exec.js:406 and syscall/js.go:106 +const GLOBAL_PREDEF_VALUE_ID = 5; + if (!global.TextEncoder) { global.TextEncoder = require("util").TextEncoder; } @@ -69,6 +73,16 @@ export class Go { } const id = this.mem.getUint32(addr, true); + + // x1unix: intercept and replace globalThis with custom mock. + // See: wasm_exec.js:18 + // + // Replacing "global" with custom mock at init (line: 410) + // breaks runtime so it should be done on demand. + if (id === GLOBAL_PREDEF_VALUE_ID) { + return this.global; + } + return this._values[id]; } @@ -399,6 +413,7 @@ export class Go { if (!(instance instanceof WebAssembly.Instance)) { throw new Error("Go.run: WebAssembly.Instance expected"); } + this._inst = instance; this.mem = new DataView(this._inst.exports.mem.buffer); this._values = [ // JS values that Go currently has references to, indexed by reference id @@ -491,5 +506,3 @@ export class Go { }; } } -// -// global.Go = Go; From 876b05b388956156262caffc728c1eb249b2ba48 Mon Sep 17 00:00:00 2001 From: x1unix Date: Tue, 8 Feb 2022 20:40:11 +0200 Subject: [PATCH 10/11] wasm: drop obsolete logging --- web/src/services/go/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/web/src/services/go/index.ts b/web/src/services/go/index.ts index ddcf5d6b..3562cac1 100644 --- a/web/src/services/go/index.ts +++ b/web/src/services/go/index.ts @@ -40,10 +40,7 @@ export const bootstrapGo = (logger: ConsoleLogger) => { // Wrap global object to make it accessible to Go's wasm bridge const globalWrapper = new Proxy(window as any, { has: (obj, prop) => prop in obj || prop in mocks, - get: (obj, prop) => { - console.log('go: get %s', prop); - return prop in obj ? obj[prop] : mocks[prop] - } + get: (obj, prop) => prop in obj ? obj[prop] : mocks[prop] }); instance = new Go(globalWrapper); }; From bc5de19068a37c5e7f9ba5d0df04f8ae1e515ed5 Mon Sep 17 00:00:00 2001 From: x1unix Date: Tue, 8 Feb 2022 20:47:35 +0200 Subject: [PATCH 11/11] wasm: fix typo --- web/src/services/go/wasm_exec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/services/go/wasm_exec.js b/web/src/services/go/wasm_exec.js index ec81a842..12cd343e 100644 --- a/web/src/services/go/wasm_exec.js +++ b/web/src/services/go/wasm_exec.js @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // -// x1unix: Adapted original wasm_exec.js from Go 1.147SDK +// x1unix: Adapted original wasm_exec.js from Go 1.17 SDK // Map multiple JavaScript environments to a single common API, // preferring web standards over Node.js API.