// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. (() => { // Map multiple JavaScript environments to a single common API, // preferring web standards over Node.js API. // // Environments considered: // - Browsers // - Node.js // - Electron // - Parcel if (typeof global !== 'undefined') { // global already exists } else if (typeof window !== 'undefined') { window.global = window; } else if (typeof self !== 'undefined') { self.global = self; } else { throw new Error( 'cannot export Go (neither global, window nor self is defined)' ); } if (!global.require && typeof require !== 'undefined') { global.require = require; } if (!global.fs && global.require) { global.fs = require('fs'); } const enosys = () => { const err = new Error('not implemented'); err.code = 'ENOSYS'; return err; }; if (!global.fs) { let outputBuf = ''; global.fs = { constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, }, // unused writeSync(fd, buf) { outputBuf += decoder.decode(buf); const nl = outputBuf.lastIndexOf('\n'); if (nl !== -1) { console.log(outputBuf.substr(0, nl)); outputBuf = outputBuf.substr(nl + 1); } return buf.length; }, write(fd, buf, offset, length, position, callback) { if (offset !== 0 || length !== buf.length || position !== null) { callback(enosys()); return; } const n = this.writeSync(fd, buf); callback(null, n); }, 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()); }, fsync(fd, callback) { callback(null); }, 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()); }, open(path, flags, mode, callback) { callback(enosys()); }, read(fd, buffer, offset, length, position, 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()); }, }; } if (!global.process) { global.process = { getuid() { return -1; }, getgid() { return -1; }, geteuid() { return -1; }, getegid() { return -1; }, getgroups() { throw enosys(); }, pid: -1, ppid: -1, umask() { throw enosys(); }, cwd() { throw enosys(); }, chdir() { throw enosys(); }, }; } if (!global.crypto) { const nodeCrypto = require('crypto'); global.crypto = { getRandomValues(b) { nodeCrypto.randomFillSync(b); }, }; } if (!global.performance) { global.performance = { now() { const [sec, nsec] = process.hrtime(); return sec * 1000 + nsec / 1000000; }, }; } if (!global.TextEncoder) { global.TextEncoder = require('util').TextEncoder; } if (!global.TextDecoder) { global.TextDecoder = require('util').TextDecoder; } // End of polyfills for common API. const encoder = new TextEncoder('utf-8'); const decoder = new TextDecoder('utf-8'); global.Go = class { constructor() { 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 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 loadSlice = (addr) => { const array = getInt64(addr + 0); const len = getInt64(addr + 8); return new Uint8Array(this._inst.exports.mem.buffer, array, len); }; 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; }; 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': () => { 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, 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; } _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; }; } }; if ( global.require && global.require.main === module && global.process && global.process.versions && !global.process.versions.electron ) { if (process.argv.length < 3) { console.error('usage: go_js_wasm_exec [wasm binary] [arguments]'); process.exit(1); } const go = new Go(); go.argv = process.argv.slice(2); go.env = Object.assign({ TMPDIR: require('os').tmpdir() }, process.env); go.exit = process.exit; WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject) .then((result) => { process.on('exit', (code) => { // Node.js exits if no event handler is pending if (code === 0 && !go.exited) { // deadlock, make Go print error and stack traces go._pendingEvent = { id: 0 }; go._resume(); } }); return go.run(result.instance); }) .catch((err) => { console.error(err); process.exit(1); }); } })();