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