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