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.substring(0, nl)); 23 outputBuf = outputBuf.substring(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 setInt32 = (addr, v) => { 117 this.mem.setUint32(addr + 0, v, true); 118 } 119 120 const getInt64 = (addr) => { 121 const low = this.mem.getUint32(addr + 0, true); 122 const high = this.mem.getInt32(addr + 4, true); 123 return low + high * 4294967296; 124 } 125 126 const loadValue = (addr) => { 127 const f = this.mem.getFloat64(addr, true); 128 if (f === 0) { 129 return undefined; 130 } 131 if (!isNaN(f)) { 132 return f; 133 } 134 135 const id = this.mem.getUint32(addr, true); 136 return this._values[id]; 137 } 138 139 const storeValue = (addr, v) => { 140 const nanHead = 0x7FF80000; 141 142 if (typeof v === "number" && v !== 0) { 143 if (isNaN(v)) { 144 this.mem.setUint32(addr + 4, nanHead, true); 145 this.mem.setUint32(addr, 0, true); 146 return; 147 } 148 this.mem.setFloat64(addr, v, true); 149 return; 150 } 151 152 if (v === undefined) { 153 this.mem.setFloat64(addr, 0, true); 154 return; 155 } 156 157 let id = this._ids.get(v); 158 if (id === undefined) { 159 id = this._idPool.pop(); 160 if (id === undefined) { 161 id = this._values.length; 162 } 163 this._values[id] = v; 164 this._goRefCounts[id] = 0; 165 this._ids.set(v, id); 166 } 167 this._goRefCounts[id]++; 168 let typeFlag = 0; 169 switch (typeof v) { 170 case "object": 171 if (v !== null) { 172 typeFlag = 1; 173 } 174 break; 175 case "string": 176 typeFlag = 2; 177 break; 178 case "symbol": 179 typeFlag = 3; 180 break; 181 case "function": 182 typeFlag = 4; 183 break; 184 } 185 this.mem.setUint32(addr + 4, nanHead | typeFlag, true); 186 this.mem.setUint32(addr, id, true); 187 } 188 189 const loadSlice = (addr) => { 190 const array = getInt64(addr + 0); 191 const len = getInt64(addr + 8); 192 return new Uint8Array(this._inst.exports.mem.buffer, array, len); 193 } 194 195 const loadSliceOfValues = (addr) => { 196 const array = getInt64(addr + 0); 197 const len = getInt64(addr + 8); 198 const a = new Array(len); 199 for (let i = 0; i < len; i++) { 200 a[i] = loadValue(array + i * 8); 201 } 202 return a; 203 } 204 205 const loadString = (addr) => { 206 const saddr = getInt64(addr + 0); 207 const len = getInt64(addr + 8); 208 return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); 209 } 210 211 const timeOrigin = Date.now() - performance.now(); 212 this.importObject = { 213 _gotest: { 214 add: (a, b) => a + b, 215 }, 216 gojs: { 217 // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) 218 // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported 219 // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). 220 // This changes the SP, thus we have to update the SP used by the imported function. 221 222 // func wasmExit(code int32) 223 "runtime.wasmExit": (sp) => { 224 sp >>>= 0; 225 const code = this.mem.getInt32(sp + 8, true); 226 this.exited = true; 227 delete this._inst; 228 delete this._values; 229 delete this._goRefCounts; 230 delete this._ids; 231 delete this._idPool; 232 this.exit(code); 233 }, 234 235 // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) 236 "runtime.wasmWrite": (sp) => { 237 sp >>>= 0; 238 const fd = getInt64(sp + 8); 239 const p = getInt64(sp + 16); 240 const n = this.mem.getInt32(sp + 24, true); 241 fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); 242 }, 243 244 // func resetMemoryDataView() 245 "runtime.resetMemoryDataView": (sp) => { 246 sp >>>= 0; 247 this.mem = new DataView(this._inst.exports.mem.buffer); 248 }, 249 250 // func nanotime1() int64 251 "runtime.nanotime1": (sp) => { 252 sp >>>= 0; 253 setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); 254 }, 255 256 // func walltime() (sec int64, nsec int32) 257 "runtime.walltime": (sp) => { 258 sp >>>= 0; 259 const msec = (new Date).getTime(); 260 setInt64(sp + 8, msec / 1000); 261 this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); 262 }, 263 264 // func scheduleTimeoutEvent(delay int64) int32 265 "runtime.scheduleTimeoutEvent": (sp) => { 266 sp >>>= 0; 267 const id = this._nextCallbackTimeoutID; 268 this._nextCallbackTimeoutID++; 269 this._scheduledTimeouts.set(id, setTimeout( 270 () => { 271 this._resume(); 272 while (this._scheduledTimeouts.has(id)) { 273 // for some reason Go failed to register the timeout event, log and try again 274 // (temporary workaround for https://github.com/golang/go/issues/28975) 275 console.warn("scheduleTimeoutEvent: missed timeout event"); 276 this._resume(); 277 } 278 }, 279 getInt64(sp + 8), 280 )); 281 this.mem.setInt32(sp + 16, id, true); 282 }, 283 284 // func clearTimeoutEvent(id int32) 285 "runtime.clearTimeoutEvent": (sp) => { 286 sp >>>= 0; 287 const id = this.mem.getInt32(sp + 8, true); 288 clearTimeout(this._scheduledTimeouts.get(id)); 289 this._scheduledTimeouts.delete(id); 290 }, 291 292 // func getRandomData(r []byte) 293 "runtime.getRandomData": (sp) => { 294 sp >>>= 0; 295 crypto.getRandomValues(loadSlice(sp + 8)); 296 }, 297 298 // func finalizeRef(v ref) 299 "syscall/js.finalizeRef": (sp) => { 300 sp >>>= 0; 301 const id = this.mem.getUint32(sp + 8, true); 302 this._goRefCounts[id]--; 303 if (this._goRefCounts[id] === 0) { 304 const v = this._values[id]; 305 this._values[id] = null; 306 this._ids.delete(v); 307 this._idPool.push(id); 308 } 309 }, 310 311 // func stringVal(value string) ref 312 "syscall/js.stringVal": (sp) => { 313 sp >>>= 0; 314 storeValue(sp + 24, loadString(sp + 8)); 315 }, 316 317 // func valueGet(v ref, p string) ref 318 "syscall/js.valueGet": (sp) => { 319 sp >>>= 0; 320 const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); 321 sp = this._inst.exports.getsp() >>> 0; // see comment above 322 storeValue(sp + 32, result); 323 }, 324 325 // func valueSet(v ref, p string, x ref) 326 "syscall/js.valueSet": (sp) => { 327 sp >>>= 0; 328 Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); 329 }, 330 331 // func valueDelete(v ref, p string) 332 "syscall/js.valueDelete": (sp) => { 333 sp >>>= 0; 334 Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); 335 }, 336 337 // func valueIndex(v ref, i int) ref 338 "syscall/js.valueIndex": (sp) => { 339 sp >>>= 0; 340 storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); 341 }, 342 343 // valueSetIndex(v ref, i int, x ref) 344 "syscall/js.valueSetIndex": (sp) => { 345 sp >>>= 0; 346 Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); 347 }, 348 349 // func valueCall(v ref, m string, args []ref) (ref, bool) 350 "syscall/js.valueCall": (sp) => { 351 sp >>>= 0; 352 try { 353 const v = loadValue(sp + 8); 354 const m = Reflect.get(v, loadString(sp + 16)); 355 const args = loadSliceOfValues(sp + 32); 356 const result = Reflect.apply(m, v, args); 357 sp = this._inst.exports.getsp() >>> 0; // see comment above 358 storeValue(sp + 56, result); 359 this.mem.setUint8(sp + 64, 1); 360 } catch (err) { 361 sp = this._inst.exports.getsp() >>> 0; // see comment above 362 storeValue(sp + 56, err); 363 this.mem.setUint8(sp + 64, 0); 364 } 365 }, 366 367 // func valueInvoke(v ref, args []ref) (ref, bool) 368 "syscall/js.valueInvoke": (sp) => { 369 sp >>>= 0; 370 try { 371 const v = loadValue(sp + 8); 372 const args = loadSliceOfValues(sp + 16); 373 const result = Reflect.apply(v, undefined, args); 374 sp = this._inst.exports.getsp() >>> 0; // see comment above 375 storeValue(sp + 40, result); 376 this.mem.setUint8(sp + 48, 1); 377 } catch (err) { 378 sp = this._inst.exports.getsp() >>> 0; // see comment above 379 storeValue(sp + 40, err); 380 this.mem.setUint8(sp + 48, 0); 381 } 382 }, 383 384 // func valueNew(v ref, args []ref) (ref, bool) 385 "syscall/js.valueNew": (sp) => { 386 sp >>>= 0; 387 try { 388 const v = loadValue(sp + 8); 389 const args = loadSliceOfValues(sp + 16); 390 const result = Reflect.construct(v, args); 391 sp = this._inst.exports.getsp() >>> 0; // see comment above 392 storeValue(sp + 40, result); 393 this.mem.setUint8(sp + 48, 1); 394 } catch (err) { 395 sp = this._inst.exports.getsp() >>> 0; // see comment above 396 storeValue(sp + 40, err); 397 this.mem.setUint8(sp + 48, 0); 398 } 399 }, 400 401 // func valueLength(v ref) int 402 "syscall/js.valueLength": (sp) => { 403 sp >>>= 0; 404 setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); 405 }, 406 407 // valuePrepareString(v ref) (ref, int) 408 "syscall/js.valuePrepareString": (sp) => { 409 sp >>>= 0; 410 const str = encoder.encode(String(loadValue(sp + 8))); 411 storeValue(sp + 16, str); 412 setInt64(sp + 24, str.length); 413 }, 414 415 // valueLoadString(v ref, b []byte) 416 "syscall/js.valueLoadString": (sp) => { 417 sp >>>= 0; 418 const str = loadValue(sp + 8); 419 loadSlice(sp + 16).set(str); 420 }, 421 422 // func valueInstanceOf(v ref, t ref) bool 423 "syscall/js.valueInstanceOf": (sp) => { 424 sp >>>= 0; 425 this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); 426 }, 427 428 // func copyBytesToGo(dst []byte, src ref) (int, bool) 429 "syscall/js.copyBytesToGo": (sp) => { 430 sp >>>= 0; 431 const dst = loadSlice(sp + 8); 432 const src = loadValue(sp + 32); 433 if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { 434 this.mem.setUint8(sp + 48, 0); 435 return; 436 } 437 const toCopy = src.subarray(0, dst.length); 438 dst.set(toCopy); 439 setInt64(sp + 40, toCopy.length); 440 this.mem.setUint8(sp + 48, 1); 441 }, 442 443 // func copyBytesToJS(dst ref, src []byte) (int, bool) 444 "syscall/js.copyBytesToJS": (sp) => { 445 sp >>>= 0; 446 const dst = loadValue(sp + 8); 447 const src = loadSlice(sp + 16); 448 if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { 449 this.mem.setUint8(sp + 48, 0); 450 return; 451 } 452 const toCopy = src.subarray(0, dst.length); 453 dst.set(toCopy); 454 setInt64(sp + 40, toCopy.length); 455 this.mem.setUint8(sp + 48, 1); 456 }, 457 458 "debug": (value) => { 459 console.log(value); 460 }, 461 } 462 }; 463 } 464 465 async run(instance) { 466 if (!(instance instanceof WebAssembly.Instance)) { 467 throw new Error("Go.run: WebAssembly.Instance expected"); 468 } 469 this._inst = instance; 470 this.mem = new DataView(this._inst.exports.mem.buffer); 471 this._values = [ // JS values that Go currently has references to, indexed by reference id 472 NaN, 473 0, 474 null, 475 true, 476 false, 477 globalThis, 478 this, 479 ]; 480 this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id 481 this._ids = new Map([ // mapping from JS values to reference ids 482 [0, 1], 483 [null, 2], 484 [true, 3], 485 [false, 4], 486 [globalThis, 5], 487 [this, 6], 488 ]); 489 this._idPool = []; // unused ids that have been garbage collected 490 this.exited = false; // whether the Go program has exited 491 492 // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. 493 let offset = 4096; 494 495 const strPtr = (str) => { 496 const ptr = offset; 497 const bytes = encoder.encode(str + "\0"); 498 new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); 499 offset += bytes.length; 500 if (offset % 8 !== 0) { 501 offset += 8 - (offset % 8); 502 } 503 return ptr; 504 }; 505 506 const argc = this.argv.length; 507 508 const argvPtrs = []; 509 this.argv.forEach((arg) => { 510 argvPtrs.push(strPtr(arg)); 511 }); 512 argvPtrs.push(0); 513 514 const keys = Object.keys(this.env).sort(); 515 keys.forEach((key) => { 516 argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); 517 }); 518 argvPtrs.push(0); 519 520 const argv = offset; 521 argvPtrs.forEach((ptr) => { 522 this.mem.setUint32(offset, ptr, true); 523 this.mem.setUint32(offset + 4, 0, true); 524 offset += 8; 525 }); 526 527 // The linker guarantees global data starts from at least wasmMinDataAddr. 528 // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. 529 const wasmMinDataAddr = 4096 + 8192; 530 if (offset >= wasmMinDataAddr) { 531 throw new Error("total length of command line and environment variables exceeds limit"); 532 } 533 534 this._inst.exports.run(argc, argv); 535 if (this.exited) { 536 this._resolveExitPromise(); 537 } 538 await this._exitPromise; 539 } 540 541 _resume() { 542 if (this.exited) { 543 throw new Error("Go program has already exited"); 544 } 545 this._inst.exports.resume(); 546 if (this.exited) { 547 this._resolveExitPromise(); 548 } 549 } 550 551 _makeFuncWrapper(id) { 552 const go = this; 553 return function () { 554 const event = { id: id, this: this, args: arguments }; 555 go._pendingEvent = event; 556 go._resume(); 557 return event.result; 558 }; 559 } 560 } 561})(); 562