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 })();