1// Copyright 2018 The Go Authors. All rights reserved.
  2// Use of this source code is governed by a BSD-style
  3// license that can be found in the LICENSE file.
  4
  5"use strict";
  6
  7(() => {
  8	const enosys = () => {
  9		const err = new Error("not implemented");
 10		err.code = "ENOSYS";
 11		return err;
 12	};
 13
 14	if (!globalThis.fs) {
 15		let outputBuf = "";
 16		globalThis.fs = {
 17			constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_DIRECTORY: -1 }, // unused
 18			writeSync(fd, buf) {
 19				outputBuf += decoder.decode(buf);
 20				const nl = outputBuf.lastIndexOf("\n");
 21				if (nl != -1) {
 22					console.log(outputBuf.substring(0, nl));
 23					outputBuf = outputBuf.substring(nl + 1);
 24				}
 25				return buf.length;
 26			},
 27			write(fd, buf, offset, length, position, callback) {
 28				if (offset !== 0 || length !== buf.length || position !== null) {
 29					callback(enosys());
 30					return;
 31				}
 32				const n = this.writeSync(fd, buf);
 33				callback(null, n);
 34			},
 35			chmod(path, mode, callback) { callback(enosys()); },
 36			chown(path, uid, gid, callback) { callback(enosys()); },
 37			close(fd, callback) { callback(enosys()); },
 38			fchmod(fd, mode, callback) { callback(enosys()); },
 39			fchown(fd, uid, gid, callback) { callback(enosys()); },
 40			fstat(fd, callback) { callback(enosys()); },
 41			fsync(fd, callback) { callback(null); },
 42			ftruncate(fd, length, callback) { callback(enosys()); },
 43			lchown(path, uid, gid, callback) { callback(enosys()); },
 44			link(path, link, callback) { callback(enosys()); },
 45			lstat(path, callback) { callback(enosys()); },
 46			mkdir(path, perm, callback) { callback(enosys()); },
 47			open(path, flags, mode, callback) { callback(enosys()); },
 48			read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
 49			readdir(path, callback) { callback(enosys()); },
 50			readlink(path, callback) { callback(enosys()); },
 51			rename(from, to, callback) { callback(enosys()); },
 52			rmdir(path, callback) { callback(enosys()); },
 53			stat(path, callback) { callback(enosys()); },
 54			symlink(path, link, callback) { callback(enosys()); },
 55			truncate(path, length, callback) { callback(enosys()); },
 56			unlink(path, callback) { callback(enosys()); },
 57			utimes(path, atime, mtime, callback) { callback(enosys()); },
 58		};
 59	}
 60
 61	if (!globalThis.process) {
 62		globalThis.process = {
 63			getuid() { return -1; },
 64			getgid() { return -1; },
 65			geteuid() { return -1; },
 66			getegid() { return -1; },
 67			getgroups() { throw enosys(); },
 68			pid: -1,
 69			ppid: -1,
 70			umask() { throw enosys(); },
 71			cwd() { throw enosys(); },
 72			chdir() { throw enosys(); },
 73		}
 74	}
 75
 76	if (!globalThis.path) {
 77		globalThis.path = {
 78			resolve(...pathSegments) {
 79				return pathSegments.join("/");
 80			}
 81		}
 82	}
 83
 84	if (!globalThis.crypto) {
 85		throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
 86	}
 87
 88	if (!globalThis.performance) {
 89		throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
 90	}
 91
 92	if (!globalThis.TextEncoder) {
 93		throw new Error("globalThis.TextEncoder is not available, polyfill required");
 94	}
 95
 96	if (!globalThis.TextDecoder) {
 97		throw new Error("globalThis.TextDecoder is not available, polyfill required");
 98	}
 99
100	const encoder = new TextEncoder("utf-8");
101	const decoder = new TextDecoder("utf-8");
102
103	globalThis.Go = class {
104		constructor() {
105			this.argv = ["js"];
106			this.env = {};
107			this.exit = (code) => {
108				if (code !== 0) {
109					console.warn("exit code:", code);
110				}
111			};
112			this._exitPromise = new Promise((resolve) => {
113				this._resolveExitPromise = resolve;
114			});
115			this._pendingEvent = null;
116			this._scheduledTimeouts = new Map();
117			this._nextCallbackTimeoutID = 1;
118
119			const setInt64 = (addr, v) => {
120				this.mem.setUint32(addr + 0, v, true);
121				this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
122			}
123
124			const setInt32 = (addr, v) => {
125				this.mem.setUint32(addr + 0, v, true);
126			}
127
128			const getInt64 = (addr) => {
129				const low = this.mem.getUint32(addr + 0, true);
130				const high = this.mem.getInt32(addr + 4, true);
131				return low + high * 4294967296;
132			}
133
134			const loadValue = (addr) => {
135				const f = this.mem.getFloat64(addr, true);
136				if (f === 0) {
137					return undefined;
138				}
139				if (!isNaN(f)) {
140					return f;
141				}
142
143				const id = this.mem.getUint32(addr, true);
144				return this._values[id];
145			}
146
147			const storeValue = (addr, v) => {
148				const nanHead = 0x7FF80000;
149
150				if (typeof v === "number" && v !== 0) {
151					if (isNaN(v)) {
152						this.mem.setUint32(addr + 4, nanHead, true);
153						this.mem.setUint32(addr, 0, true);
154						return;
155					}
156					this.mem.setFloat64(addr, v, true);
157					return;
158				}
159
160				if (v === undefined) {
161					this.mem.setFloat64(addr, 0, true);
162					return;
163				}
164
165				let id = this._ids.get(v);
166				if (id === undefined) {
167					id = this._idPool.pop();
168					if (id === undefined) {
169						id = this._values.length;
170					}
171					this._values[id] = v;
172					this._goRefCounts[id] = 0;
173					this._ids.set(v, id);
174				}
175				this._goRefCounts[id]++;
176				let typeFlag = 0;
177				switch (typeof v) {
178					case "object":
179						if (v !== null) {
180							typeFlag = 1;
181						}
182						break;
183					case "string":
184						typeFlag = 2;
185						break;
186					case "symbol":
187						typeFlag = 3;
188						break;
189					case "function":
190						typeFlag = 4;
191						break;
192				}
193				this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
194				this.mem.setUint32(addr, id, true);
195			}
196
197			const loadSlice = (addr) => {
198				const array = getInt64(addr + 0);
199				const len = getInt64(addr + 8);
200				return new Uint8Array(this._inst.exports.mem.buffer, array, len);
201			}
202
203			const loadSliceOfValues = (addr) => {
204				const array = getInt64(addr + 0);
205				const len = getInt64(addr + 8);
206				const a = new Array(len);
207				for (let i = 0; i < len; i++) {
208					a[i] = loadValue(array + i * 8);
209				}
210				return a;
211			}
212
213			const loadString = (addr) => {
214				const saddr = getInt64(addr + 0);
215				const len = getInt64(addr + 8);
216				return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
217			}
218
219			const testCallExport = (a, b) => {
220				this._inst.exports.testExport0();
221				return this._inst.exports.testExport(a, b);
222			}
223
224			const timeOrigin = Date.now() - performance.now();
225			this.importObject = {
226				_gotest: {
227					add: (a, b) => a + b,
228					callExport: testCallExport,
229				},
230				gojs: {
231					// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
232					// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
233					// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
234					// This changes the SP, thus we have to update the SP used by the imported function.
235
236					// func wasmExit(code int32)
237					"runtime.wasmExit": (sp) => {
238						sp >>>= 0;
239						const code = this.mem.getInt32(sp + 8, true);
240						this.exited = true;
241						delete this._inst;
242						delete this._values;
243						delete this._goRefCounts;
244						delete this._ids;
245						delete this._idPool;
246						this.exit(code);
247					},
248
249					// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
250					"runtime.wasmWrite": (sp) => {
251						sp >>>= 0;
252						const fd = getInt64(sp + 8);
253						const p = getInt64(sp + 16);
254						const n = this.mem.getInt32(sp + 24, true);
255						fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
256					},
257
258					// func resetMemoryDataView()
259					"runtime.resetMemoryDataView": (sp) => {
260						sp >>>= 0;
261						this.mem = new DataView(this._inst.exports.mem.buffer);
262					},
263
264					// func nanotime1() int64
265					"runtime.nanotime1": (sp) => {
266						sp >>>= 0;
267						setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
268					},
269
270					// func walltime() (sec int64, nsec int32)
271					"runtime.walltime": (sp) => {
272						sp >>>= 0;
273						const msec = (new Date).getTime();
274						setInt64(sp + 8, msec / 1000);
275						this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
276					},
277
278					// func scheduleTimeoutEvent(delay int64) int32
279					"runtime.scheduleTimeoutEvent": (sp) => {
280						sp >>>= 0;
281						const id = this._nextCallbackTimeoutID;
282						this._nextCallbackTimeoutID++;
283						this._scheduledTimeouts.set(id, setTimeout(
284							() => {
285								this._resume();
286								while (this._scheduledTimeouts.has(id)) {
287									// for some reason Go failed to register the timeout event, log and try again
288									// (temporary workaround for https://github.com/golang/go/issues/28975)
289									console.warn("scheduleTimeoutEvent: missed timeout event");
290									this._resume();
291								}
292							},
293							getInt64(sp + 8),
294						));
295						this.mem.setInt32(sp + 16, id, true);
296					},
297
298					// func clearTimeoutEvent(id int32)
299					"runtime.clearTimeoutEvent": (sp) => {
300						sp >>>= 0;
301						const id = this.mem.getInt32(sp + 8, true);
302						clearTimeout(this._scheduledTimeouts.get(id));
303						this._scheduledTimeouts.delete(id);
304					},
305
306					// func getRandomData(r []byte)
307					"runtime.getRandomData": (sp) => {
308						sp >>>= 0;
309						crypto.getRandomValues(loadSlice(sp + 8));
310					},
311
312					// func finalizeRef(v ref)
313					"syscall/js.finalizeRef": (sp) => {
314						sp >>>= 0;
315						const id = this.mem.getUint32(sp + 8, true);
316						this._goRefCounts[id]--;
317						if (this._goRefCounts[id] === 0) {
318							const v = this._values[id];
319							this._values[id] = null;
320							this._ids.delete(v);
321							this._idPool.push(id);
322						}
323					},
324
325					// func stringVal(value string) ref
326					"syscall/js.stringVal": (sp) => {
327						sp >>>= 0;
328						storeValue(sp + 24, loadString(sp + 8));
329					},
330
331					// func valueGet(v ref, p string) ref
332					"syscall/js.valueGet": (sp) => {
333						sp >>>= 0;
334						const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
335						sp = this._inst.exports.getsp() >>> 0; // see comment above
336						storeValue(sp + 32, result);
337					},
338
339					// func valueSet(v ref, p string, x ref)
340					"syscall/js.valueSet": (sp) => {
341						sp >>>= 0;
342						Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
343					},
344
345					// func valueDelete(v ref, p string)
346					"syscall/js.valueDelete": (sp) => {
347						sp >>>= 0;
348						Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
349					},
350
351					// func valueIndex(v ref, i int) ref
352					"syscall/js.valueIndex": (sp) => {
353						sp >>>= 0;
354						storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
355					},
356
357					// valueSetIndex(v ref, i int, x ref)
358					"syscall/js.valueSetIndex": (sp) => {
359						sp >>>= 0;
360						Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
361					},
362
363					// func valueCall(v ref, m string, args []ref) (ref, bool)
364					"syscall/js.valueCall": (sp) => {
365						sp >>>= 0;
366						try {
367							const v = loadValue(sp + 8);
368							const m = Reflect.get(v, loadString(sp + 16));
369							const args = loadSliceOfValues(sp + 32);
370							const result = Reflect.apply(m, v, args);
371							sp = this._inst.exports.getsp() >>> 0; // see comment above
372							storeValue(sp + 56, result);
373							this.mem.setUint8(sp + 64, 1);
374						} catch (err) {
375							sp = this._inst.exports.getsp() >>> 0; // see comment above
376							storeValue(sp + 56, err);
377							this.mem.setUint8(sp + 64, 0);
378						}
379					},
380
381					// func valueInvoke(v ref, args []ref) (ref, bool)
382					"syscall/js.valueInvoke": (sp) => {
383						sp >>>= 0;
384						try {
385							const v = loadValue(sp + 8);
386							const args = loadSliceOfValues(sp + 16);
387							const result = Reflect.apply(v, undefined, args);
388							sp = this._inst.exports.getsp() >>> 0; // see comment above
389							storeValue(sp + 40, result);
390							this.mem.setUint8(sp + 48, 1);
391						} catch (err) {
392							sp = this._inst.exports.getsp() >>> 0; // see comment above
393							storeValue(sp + 40, err);
394							this.mem.setUint8(sp + 48, 0);
395						}
396					},
397
398					// func valueNew(v ref, args []ref) (ref, bool)
399					"syscall/js.valueNew": (sp) => {
400						sp >>>= 0;
401						try {
402							const v = loadValue(sp + 8);
403							const args = loadSliceOfValues(sp + 16);
404							const result = Reflect.construct(v, args);
405							sp = this._inst.exports.getsp() >>> 0; // see comment above
406							storeValue(sp + 40, result);
407							this.mem.setUint8(sp + 48, 1);
408						} catch (err) {
409							sp = this._inst.exports.getsp() >>> 0; // see comment above
410							storeValue(sp + 40, err);
411							this.mem.setUint8(sp + 48, 0);
412						}
413					},
414
415					// func valueLength(v ref) int
416					"syscall/js.valueLength": (sp) => {
417						sp >>>= 0;
418						setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
419					},
420
421					// valuePrepareString(v ref) (ref, int)
422					"syscall/js.valuePrepareString": (sp) => {
423						sp >>>= 0;
424						const str = encoder.encode(String(loadValue(sp + 8)));
425						storeValue(sp + 16, str);
426						setInt64(sp + 24, str.length);
427					},
428
429					// valueLoadString(v ref, b []byte)
430					"syscall/js.valueLoadString": (sp) => {
431						sp >>>= 0;
432						const str = loadValue(sp + 8);
433						loadSlice(sp + 16).set(str);
434					},
435
436					// func valueInstanceOf(v ref, t ref) bool
437					"syscall/js.valueInstanceOf": (sp) => {
438						sp >>>= 0;
439						this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
440					},
441
442					// func copyBytesToGo(dst []byte, src ref) (int, bool)
443					"syscall/js.copyBytesToGo": (sp) => {
444						sp >>>= 0;
445						const dst = loadSlice(sp + 8);
446						const src = loadValue(sp + 32);
447						if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
448							this.mem.setUint8(sp + 48, 0);
449							return;
450						}
451						const toCopy = src.subarray(0, dst.length);
452						dst.set(toCopy);
453						setInt64(sp + 40, toCopy.length);
454						this.mem.setUint8(sp + 48, 1);
455					},
456
457					// func copyBytesToJS(dst ref, src []byte) (int, bool)
458					"syscall/js.copyBytesToJS": (sp) => {
459						sp >>>= 0;
460						const dst = loadValue(sp + 8);
461						const src = loadSlice(sp + 16);
462						if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
463							this.mem.setUint8(sp + 48, 0);
464							return;
465						}
466						const toCopy = src.subarray(0, dst.length);
467						dst.set(toCopy);
468						setInt64(sp + 40, toCopy.length);
469						this.mem.setUint8(sp + 48, 1);
470					},
471
472					"debug": (value) => {
473						console.log(value);
474					},
475				}
476			};
477		}
478
479		async run(instance) {
480			if (!(instance instanceof WebAssembly.Instance)) {
481				throw new Error("Go.run: WebAssembly.Instance expected");
482			}
483			this._inst = instance;
484			this.mem = new DataView(this._inst.exports.mem.buffer);
485			this._values = [ // JS values that Go currently has references to, indexed by reference id
486				NaN,
487				0,
488				null,
489				true,
490				false,
491				globalThis,
492				this,
493			];
494			this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
495			this._ids = new Map([ // mapping from JS values to reference ids
496				[0, 1],
497				[null, 2],
498				[true, 3],
499				[false, 4],
500				[globalThis, 5],
501				[this, 6],
502			]);
503			this._idPool = [];   // unused ids that have been garbage collected
504			this.exited = false; // whether the Go program has exited
505
506			// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
507			let offset = 4096;
508
509			const strPtr = (str) => {
510				const ptr = offset;
511				const bytes = encoder.encode(str + "\0");
512				new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
513				offset += bytes.length;
514				if (offset % 8 !== 0) {
515					offset += 8 - (offset % 8);
516				}
517				return ptr;
518			};
519
520			const argc = this.argv.length;
521
522			const argvPtrs = [];
523			this.argv.forEach((arg) => {
524				argvPtrs.push(strPtr(arg));
525			});
526			argvPtrs.push(0);
527
528			const keys = Object.keys(this.env).sort();
529			keys.forEach((key) => {
530				argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
531			});
532			argvPtrs.push(0);
533
534			const argv = offset;
535			argvPtrs.forEach((ptr) => {
536				this.mem.setUint32(offset, ptr, true);
537				this.mem.setUint32(offset + 4, 0, true);
538				offset += 8;
539			});
540
541			// The linker guarantees global data starts from at least wasmMinDataAddr.
542			// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
543			const wasmMinDataAddr = 4096 + 8192;
544			if (offset >= wasmMinDataAddr) {
545				throw new Error("total length of command line and environment variables exceeds limit");
546			}
547
548			this._inst.exports.run(argc, argv);
549			if (this.exited) {
550				this._resolveExitPromise();
551			}
552			await this._exitPromise;
553		}
554
555		_resume() {
556			if (this.exited) {
557				throw new Error("Go program has already exited");
558			}
559			this._inst.exports.resume();
560			if (this.exited) {
561				this._resolveExitPromise();
562			}
563		}
564
565		_makeFuncWrapper(id) {
566			const go = this;
567			return function () {
568				const event = { id: id, this: this, args: arguments };
569				go._pendingEvent = event;
570				go._resume();
571				return event.result;
572			};
573		}
574	}
575})();