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