github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/development/wasm/example/wasm_exec.js (about)

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