wasm_exec.js (16988B)
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 // This file has been modified for use by the TinyGo compiler. 6 7 (() => { 8 // Map multiple JavaScript environments to a single common API, 9 // preferring web standards over Node.js API. 10 // 11 // Environments considered: 12 // - Browsers 13 // - Node.js 14 // - Electron 15 // - Parcel 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 global.fs = require("node:fs"); 33 } 34 35 const enosys = () => { 36 const err = new Error("not implemented"); 37 err.code = "ENOSYS"; 38 return err; 39 }; 40 41 if (!global.fs) { 42 let outputBuf = ""; 43 global.fs = { 44 constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused 45 writeSync(fd, buf) { 46 outputBuf += decoder.decode(buf); 47 const nl = outputBuf.lastIndexOf("\n"); 48 if (nl != -1) { 49 console.log(outputBuf.substr(0, nl)); 50 outputBuf = outputBuf.substr(nl + 1); 51 } 52 return buf.length; 53 }, 54 write(fd, buf, offset, length, position, callback) { 55 if (offset !== 0 || length !== buf.length || position !== null) { 56 callback(enosys()); 57 return; 58 } 59 const n = this.writeSync(fd, buf); 60 callback(null, n); 61 }, 62 chmod(path, mode, callback) { callback(enosys()); }, 63 chown(path, uid, gid, callback) { callback(enosys()); }, 64 close(fd, callback) { callback(enosys()); }, 65 fchmod(fd, mode, callback) { callback(enosys()); }, 66 fchown(fd, uid, gid, callback) { callback(enosys()); }, 67 fstat(fd, callback) { callback(enosys()); }, 68 fsync(fd, callback) { callback(null); }, 69 ftruncate(fd, length, callback) { callback(enosys()); }, 70 lchown(path, uid, gid, callback) { callback(enosys()); }, 71 link(path, link, callback) { callback(enosys()); }, 72 lstat(path, callback) { callback(enosys()); }, 73 mkdir(path, perm, callback) { callback(enosys()); }, 74 open(path, flags, mode, callback) { callback(enosys()); }, 75 read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, 76 readdir(path, callback) { callback(enosys()); }, 77 readlink(path, callback) { callback(enosys()); }, 78 rename(from, to, callback) { callback(enosys()); }, 79 rmdir(path, callback) { callback(enosys()); }, 80 stat(path, callback) { callback(enosys()); }, 81 symlink(path, link, callback) { callback(enosys()); }, 82 truncate(path, length, callback) { callback(enosys()); }, 83 unlink(path, callback) { callback(enosys()); }, 84 utimes(path, atime, mtime, callback) { callback(enosys()); }, 85 }; 86 } 87 88 if (!global.process) { 89 global.process = { 90 getuid() { return -1; }, 91 getgid() { return -1; }, 92 geteuid() { return -1; }, 93 getegid() { return -1; }, 94 getgroups() { throw enosys(); }, 95 pid: -1, 96 ppid: -1, 97 umask() { throw enosys(); }, 98 cwd() { throw enosys(); }, 99 chdir() { throw enosys(); }, 100 } 101 } 102 103 if (!global.crypto) { 104 const nodeCrypto = require("node:crypto"); 105 global.crypto = { 106 getRandomValues(b) { 107 nodeCrypto.randomFillSync(b); 108 }, 109 }; 110 } 111 112 if (!global.performance) { 113 global.performance = { 114 now() { 115 const [sec, nsec] = process.hrtime(); 116 return sec * 1000 + nsec / 1000000; 117 }, 118 }; 119 } 120 121 if (!global.TextEncoder) { 122 global.TextEncoder = require("node:util").TextEncoder; 123 } 124 125 if (!global.TextDecoder) { 126 global.TextDecoder = require("node:util").TextDecoder; 127 } 128 129 // End of polyfills for common API. 130 131 const encoder = new TextEncoder("utf-8"); 132 const decoder = new TextDecoder("utf-8"); 133 let reinterpretBuf = new DataView(new ArrayBuffer(8)); 134 var logLine = []; 135 const wasmExit = {}; // thrown to exit via proc_exit (not an error) 136 137 global.Go = class { 138 constructor() { 139 this._callbackTimeouts = new Map(); 140 this._nextCallbackTimeoutID = 1; 141 142 const mem = () => { 143 // The buffer may change when requesting more memory. 144 return new DataView(this._inst.exports.memory.buffer); 145 } 146 147 const unboxValue = (v_ref) => { 148 reinterpretBuf.setBigInt64(0, v_ref, true); 149 const f = reinterpretBuf.getFloat64(0, true); 150 if (f === 0) { 151 return undefined; 152 } 153 if (!isNaN(f)) { 154 return f; 155 } 156 157 const id = v_ref & 0xffffffffn; 158 return this._values[id]; 159 } 160 161 162 const loadValue = (addr) => { 163 let v_ref = mem().getBigUint64(addr, true); 164 return unboxValue(v_ref); 165 } 166 167 const boxValue = (v) => { 168 const nanHead = 0x7FF80000n; 169 170 if (typeof v === "number") { 171 if (isNaN(v)) { 172 return nanHead << 32n; 173 } 174 if (v === 0) { 175 return (nanHead << 32n) | 1n; 176 } 177 reinterpretBuf.setFloat64(0, v, true); 178 return reinterpretBuf.getBigInt64(0, true); 179 } 180 181 switch (v) { 182 case undefined: 183 return 0n; 184 case null: 185 return (nanHead << 32n) | 2n; 186 case true: 187 return (nanHead << 32n) | 3n; 188 case false: 189 return (nanHead << 32n) | 4n; 190 } 191 192 let id = this._ids.get(v); 193 if (id === undefined) { 194 id = this._idPool.pop(); 195 if (id === undefined) { 196 id = BigInt(this._values.length); 197 } 198 this._values[id] = v; 199 this._goRefCounts[id] = 0; 200 this._ids.set(v, id); 201 } 202 this._goRefCounts[id]++; 203 let typeFlag = 1n; 204 switch (typeof v) { 205 case "string": 206 typeFlag = 2n; 207 break; 208 case "symbol": 209 typeFlag = 3n; 210 break; 211 case "function": 212 typeFlag = 4n; 213 break; 214 } 215 return id | ((nanHead | typeFlag) << 32n); 216 } 217 218 const storeValue = (addr, v) => { 219 let v_ref = boxValue(v); 220 mem().setBigUint64(addr, v_ref, true); 221 } 222 223 const loadSlice = (array, len, cap) => { 224 return new Uint8Array(this._inst.exports.memory.buffer, array, len); 225 } 226 227 const loadSliceOfValues = (array, len, cap) => { 228 const a = new Array(len); 229 for (let i = 0; i < len; i++) { 230 a[i] = loadValue(array + i * 8); 231 } 232 return a; 233 } 234 235 const loadString = (ptr, len) => { 236 return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len)); 237 } 238 239 const timeOrigin = Date.now() - performance.now(); 240 this.importObject = { 241 wasi_snapshot_preview1: { 242 // https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write 243 fd_write: function (fd, iovs_ptr, iovs_len, nwritten_ptr) { 244 let nwritten = 0; 245 if (fd == 1) { 246 for (let iovs_i = 0; iovs_i < iovs_len; iovs_i++) { 247 let iov_ptr = iovs_ptr + iovs_i * 8; // assuming wasm32 248 let ptr = mem().getUint32(iov_ptr + 0, true); 249 let len = mem().getUint32(iov_ptr + 4, true); 250 nwritten += len; 251 for (let i = 0; i < len; i++) { 252 let c = mem().getUint8(ptr + i); 253 if (c == 13) { // CR 254 // ignore 255 } else if (c == 10) { // LF 256 // write line 257 let line = decoder.decode(new Uint8Array(logLine)); 258 logLine = []; 259 console.log(line); 260 } else { 261 logLine.push(c); 262 } 263 } 264 } 265 } else { 266 console.error('invalid file descriptor:', fd); 267 } 268 mem().setUint32(nwritten_ptr, nwritten, true); 269 return 0; 270 }, 271 fd_close: () => 0, // dummy 272 fd_fdstat_get: () => 0, // dummy 273 fd_seek: () => 0, // dummy 274 proc_exit: (code) => { 275 this.exited = true; 276 this.exitCode = code; 277 this._resolveExitPromise(); 278 throw wasmExit; 279 }, 280 random_get: (bufPtr, bufLen) => { 281 crypto.getRandomValues(loadSlice(bufPtr, bufLen)); 282 return 0; 283 }, 284 }, 285 gojs: { 286 // func ticks() float64 287 "runtime.ticks": () => { 288 return timeOrigin + performance.now(); 289 }, 290 291 // func sleepTicks(timeout float64) 292 "runtime.sleepTicks": (timeout) => { 293 // Do not sleep, only reactivate scheduler after the given timeout. 294 setTimeout(() => { 295 if (this.exited) return; 296 try { 297 this._inst.exports.go_scheduler(); 298 } catch (e) { 299 if (e !== wasmExit) throw e; 300 } 301 }, timeout); 302 }, 303 304 // func finalizeRef(v ref) 305 "syscall/js.finalizeRef": (v_ref) => { 306 // Note: TinyGo does not support finalizers so this should never be 307 // called. 308 // console.error('syscall/js.finalizeRef not implemented'); 309 }, 310 311 // func stringVal(value string) ref 312 "syscall/js.stringVal": (value_ptr, value_len) => { 313 const s = loadString(value_ptr, value_len); 314 return boxValue(s); 315 }, 316 317 // func valueGet(v ref, p string) ref 318 "syscall/js.valueGet": (v_ref, p_ptr, p_len) => { 319 let prop = loadString(p_ptr, p_len); 320 let v = unboxValue(v_ref); 321 let result = Reflect.get(v, prop); 322 return boxValue(result); 323 }, 324 325 // func valueSet(v ref, p string, x ref) 326 "syscall/js.valueSet": (v_ref, p_ptr, p_len, x_ref) => { 327 const v = unboxValue(v_ref); 328 const p = loadString(p_ptr, p_len); 329 const x = unboxValue(x_ref); 330 Reflect.set(v, p, x); 331 }, 332 333 // func valueDelete(v ref, p string) 334 "syscall/js.valueDelete": (v_ref, p_ptr, p_len) => { 335 const v = unboxValue(v_ref); 336 const p = loadString(p_ptr, p_len); 337 Reflect.deleteProperty(v, p); 338 }, 339 340 // func valueIndex(v ref, i int) ref 341 "syscall/js.valueIndex": (v_ref, i) => { 342 return boxValue(Reflect.get(unboxValue(v_ref), i)); 343 }, 344 345 // valueSetIndex(v ref, i int, x ref) 346 "syscall/js.valueSetIndex": (v_ref, i, x_ref) => { 347 Reflect.set(unboxValue(v_ref), i, unboxValue(x_ref)); 348 }, 349 350 // func valueCall(v ref, m string, args []ref) (ref, bool) 351 "syscall/js.valueCall": (ret_addr, v_ref, m_ptr, m_len, args_ptr, args_len, args_cap) => { 352 const v = unboxValue(v_ref); 353 const name = loadString(m_ptr, m_len); 354 const args = loadSliceOfValues(args_ptr, args_len, args_cap); 355 try { 356 const m = Reflect.get(v, name); 357 storeValue(ret_addr, Reflect.apply(m, v, args)); 358 mem().setUint8(ret_addr + 8, 1); 359 } catch (err) { 360 storeValue(ret_addr, err); 361 mem().setUint8(ret_addr + 8, 0); 362 } 363 }, 364 365 // func valueInvoke(v ref, args []ref) (ref, bool) 366 "syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { 367 try { 368 const v = unboxValue(v_ref); 369 const args = loadSliceOfValues(args_ptr, args_len, args_cap); 370 storeValue(ret_addr, Reflect.apply(v, undefined, args)); 371 mem().setUint8(ret_addr + 8, 1); 372 } catch (err) { 373 storeValue(ret_addr, err); 374 mem().setUint8(ret_addr + 8, 0); 375 } 376 }, 377 378 // func valueNew(v ref, args []ref) (ref, bool) 379 "syscall/js.valueNew": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { 380 const v = unboxValue(v_ref); 381 const args = loadSliceOfValues(args_ptr, args_len, args_cap); 382 try { 383 storeValue(ret_addr, Reflect.construct(v, args)); 384 mem().setUint8(ret_addr + 8, 1); 385 } catch (err) { 386 storeValue(ret_addr, err); 387 mem().setUint8(ret_addr + 8, 0); 388 } 389 }, 390 391 // func valueLength(v ref) int 392 "syscall/js.valueLength": (v_ref) => { 393 return unboxValue(v_ref).length; 394 }, 395 396 // valuePrepareString(v ref) (ref, int) 397 "syscall/js.valuePrepareString": (ret_addr, v_ref) => { 398 const s = String(unboxValue(v_ref)); 399 const str = encoder.encode(s); 400 storeValue(ret_addr, str); 401 mem().setInt32(ret_addr + 8, str.length, true); 402 }, 403 404 // valueLoadString(v ref, b []byte) 405 "syscall/js.valueLoadString": (v_ref, slice_ptr, slice_len, slice_cap) => { 406 const str = unboxValue(v_ref); 407 loadSlice(slice_ptr, slice_len, slice_cap).set(str); 408 }, 409 410 // func valueInstanceOf(v ref, t ref) bool 411 "syscall/js.valueInstanceOf": (v_ref, t_ref) => { 412 return unboxValue(v_ref) instanceof unboxValue(t_ref); 413 }, 414 415 // func copyBytesToGo(dst []byte, src ref) (int, bool) 416 "syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, src_ref) => { 417 let num_bytes_copied_addr = ret_addr; 418 let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable 419 420 const dst = loadSlice(dest_addr, dest_len); 421 const src = unboxValue(src_ref); 422 if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { 423 mem().setUint8(returned_status_addr, 0); // Return "not ok" status 424 return; 425 } 426 const toCopy = src.subarray(0, dst.length); 427 dst.set(toCopy); 428 mem().setUint32(num_bytes_copied_addr, toCopy.length, true); 429 mem().setUint8(returned_status_addr, 1); // Return "ok" status 430 }, 431 432 // copyBytesToJS(dst ref, src []byte) (int, bool) 433 // Originally copied from upstream Go project, then modified: 434 // https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416 435 "syscall/js.copyBytesToJS": (ret_addr, dst_ref, src_addr, src_len, src_cap) => { 436 let num_bytes_copied_addr = ret_addr; 437 let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable 438 439 const dst = unboxValue(dst_ref); 440 const src = loadSlice(src_addr, src_len); 441 if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { 442 mem().setUint8(returned_status_addr, 0); // Return "not ok" status 443 return; 444 } 445 const toCopy = src.subarray(0, dst.length); 446 dst.set(toCopy); 447 mem().setUint32(num_bytes_copied_addr, toCopy.length, true); 448 mem().setUint8(returned_status_addr, 1); // Return "ok" status 449 }, 450 } 451 }; 452 453 // Go 1.20 uses 'env'. Go 1.21 uses 'gojs'. 454 // For compatibility, we use both as long as Go 1.20 is supported. 455 this.importObject.env = this.importObject.gojs; 456 } 457 458 async run(instance) { 459 this._inst = instance; 460 this._values = [ // JS values that Go currently has references to, indexed by reference id 461 NaN, 462 0, 463 null, 464 true, 465 false, 466 global, 467 this, 468 ]; 469 this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id 470 this._ids = new Map(); // mapping from JS values to reference ids 471 this._idPool = []; // unused ids that have been garbage collected 472 this.exited = false; // whether the Go program has exited 473 this.exitCode = 0; 474 475 if (this._inst.exports._start) { 476 let exitPromise = new Promise((resolve, reject) => { 477 this._resolveExitPromise = resolve; 478 }); 479 480 // Run program, but catch the wasmExit exception that's thrown 481 // to return back here. 482 try { 483 this._inst.exports._start(); 484 } catch (e) { 485 if (e !== wasmExit) throw e; 486 } 487 488 await exitPromise; 489 return this.exitCode; 490 } else { 491 this._inst.exports._initialize(); 492 } 493 } 494 495 _resume() { 496 if (this.exited) { 497 throw new Error("Go program has already exited"); 498 } 499 try { 500 this._inst.exports.resume(); 501 } catch (e) { 502 if (e !== wasmExit) throw e; 503 } 504 if (this.exited) { 505 this._resolveExitPromise(); 506 } 507 } 508 509 _makeFuncWrapper(id) { 510 const go = this; 511 return function () { 512 const event = { id: id, this: this, args: arguments }; 513 go._pendingEvent = event; 514 go._resume(); 515 return event.result; 516 }; 517 } 518 } 519 520 if ( 521 global.require && 522 global.require.main === module && 523 global.process && 524 global.process.versions && 525 !global.process.versions.electron 526 ) { 527 if (process.argv.length != 3) { 528 console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); 529 process.exit(1); 530 } 531 532 const go = new Go(); 533 WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => { 534 let exitCode = await go.run(result.instance); 535 process.exit(exitCode); 536 }).catch((err) => { 537 console.error(err); 538 process.exit(1); 539 }); 540 } 541 })(); 542 543 function loadFMDFileRaw(url, container) { 544 return fetch(url) 545 .then(response => response.text()) 546 .then(text => { 547 // Load the text into the text editor & buffer 548 _textBUffer = text 549 }) 550 .then(() => { 551 // If FMD is valid, render to HTML 552 container.innerHTML = _textBUffer 553 }) 554 } 555 556 function loadFMDFileHTML(url, container) { 557 _retrieved = true 558 return fetch(url) 559 .then(response => { 560 if (!response.ok) { 561 _retrieved = false 562 } 563 564 return response.text() 565 }) 566 .then(text => { 567 // Load the text into the text editor & buffer 568 _textBUffer = text 569 }) 570 .then(() => { 571 // If FMD is valid, render to HTML 572 if (!_retrieved) { 573 container.innerHTML = _textBUffer 574 return "invalid" 575 } else if (isFMDValid(0, _textBUffer)) { 576 container.innerHTML = interpretFMD(0, _textBUffer) 577 return "valid" 578 } else { 579 container.innerHTML = "FMD Parsing Failed" 580 return "invalid" 581 } 582 }) 583 }