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