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() int64 287 "runtime.ticks": () => { 288 return BigInt((timeOrigin + performance.now()) * 1e6); 289 }, 290 291 // func sleepTicks(timeout int64) 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 }, Number(timeout)/1e6); 302 }, 303 304 // func finalizeRef(v ref) 305 "syscall/js.finalizeRef": (v_ref) => { 306 // Note: TinyGo does not support finalizers so this is only called 307 // for one specific case, by js.go:jsString. and can/might leak memory. 308 const id = v_ref & 0xffffffffn; 309 if (this._goRefCounts?.[id] !== undefined) { 310 this._goRefCounts[id]--; 311 if (this._goRefCounts[id] === 0) { 312 const v = this._values[id]; 313 this._values[id] = null; 314 this._ids.delete(v); 315 this._idPool.push(id); 316 } 317 } else { 318 console.error("syscall/js.finalizeRef: unknown id", id); 319 } 320 }, 321 322 // func stringVal(value string) ref 323 "syscall/js.stringVal": (value_ptr, value_len) => { 324 value_ptr >>>= 0; 325 const s = loadString(value_ptr, value_len); 326 return boxValue(s); 327 }, 328 329 // func valueGet(v ref, p string) ref 330 "syscall/js.valueGet": (v_ref, p_ptr, p_len) => { 331 let prop = loadString(p_ptr, p_len); 332 let v = unboxValue(v_ref); 333 let result = Reflect.get(v, prop); 334 return boxValue(result); 335 }, 336 337 // func valueSet(v ref, p string, x ref) 338 "syscall/js.valueSet": (v_ref, p_ptr, p_len, x_ref) => { 339 const v = unboxValue(v_ref); 340 const p = loadString(p_ptr, p_len); 341 const x = unboxValue(x_ref); 342 Reflect.set(v, p, x); 343 }, 344 345 // func valueDelete(v ref, p string) 346 "syscall/js.valueDelete": (v_ref, p_ptr, p_len) => { 347 const v = unboxValue(v_ref); 348 const p = loadString(p_ptr, p_len); 349 Reflect.deleteProperty(v, p); 350 }, 351 352 // func valueIndex(v ref, i int) ref 353 "syscall/js.valueIndex": (v_ref, i) => { 354 return boxValue(Reflect.get(unboxValue(v_ref), i)); 355 }, 356 357 // valueSetIndex(v ref, i int, x ref) 358 "syscall/js.valueSetIndex": (v_ref, i, x_ref) => { 359 Reflect.set(unboxValue(v_ref), i, unboxValue(x_ref)); 360 }, 361 362 // func valueCall(v ref, m string, args []ref) (ref, bool) 363 "syscall/js.valueCall": (ret_addr, v_ref, m_ptr, m_len, args_ptr, args_len, args_cap) => { 364 const v = unboxValue(v_ref); 365 const name = loadString(m_ptr, m_len); 366 const args = loadSliceOfValues(args_ptr, args_len, args_cap); 367 try { 368 const m = Reflect.get(v, name);
369 storeValue(ret_addr, Reflect.apply(m, v, args)); 370 mem().setUint8(ret_addr + 8, 1); 371 } catch (err) { 372 storeValue(ret_addr, err); 373 mem().setUint8(ret_addr + 8, 0); 374 } 375 }, 376 377 // func valueInvoke(v ref, args []ref) (ref, bool) 378 "syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { 379 try { 380 const v = unboxValue(v_ref); 381 const args = loadSliceOfValues(args_ptr, args_len, args_cap); 382 storeValue(ret_addr, Reflect.apply(v, undefined, args)); 383 mem().setUint8(ret_addr + 8, 1); 384 } catch (err) { 385 storeValue(ret_addr, err); 386 mem().setUint8(ret_addr + 8, 0); 387 } 388 }, 389 390 // func valueNew(v ref, args []ref) (ref, bool) 391 "syscall/js.valueNew": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { 392 const v = unboxValue(v_ref); 393 const args = loadSliceOfValues(args_ptr, args_len, args_cap); 394 try { 395 storeValue(ret_addr, Reflect.construct(v, args)); 396 mem().setUint8(ret_addr + 8, 1); 397 } catch (err) { 398 storeValue(ret_addr, err); 399 mem().setUint8(ret_addr+ 8, 0); 400 } 401 }, 402 403 // func valueLength(v ref) int 404 "syscall/js.valueLength": (v_ref) => { 405 return unboxValue(v_ref).length; 406 }, 407 408 // valuePrepareString(v ref) (ref, int) 409 "syscall/js.valuePrepareString": (ret_addr, v_ref) => { 410 const s = String(unboxValue(v_ref)); 411 const str = encoder.encode(s); 412 storeValue(ret_addr, str); 413 mem().setInt32(ret_addr + 8, str.length, true); 414 }, 415 416 // valueLoadString(v ref, b []byte) 417 "syscall/js.valueLoadString": (v_ref, slice_ptr, slice_len, slice_cap) => { 418 const str = unboxValue(v_ref); 419 loadSlice(slice_ptr, slice_len, slice_cap).set(str); 420 }, 421 422 // func valueInstanceOf(v ref, t ref) bool 423 "syscall/js.valueInstanceOf": (v_ref, t_ref) => { 424 return unboxValue(v_ref) instanceof unboxValue(t_ref); 425 }, 426 427 // func copyBytesToGo(dst []byte, src ref) (int, bool) 428 "syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, src_ref) => { 429 let num_bytes_copied_addr = ret_addr; 430 let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable 431 432 const dst = loadSlice(dest_addr, dest_len); 433 const src = unboxValue(src_ref); 434 if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { 435 mem().setUint8(returned_status_addr, 0); // Return "not ok" status 436 return; 437 } 438 const toCopy = src.subarray(0, dst.length); 439 dst.set(toCopy); 440 mem().setUint32(num_bytes_copied_addr, toCopy.length, true); 441 mem().setUint8(returned_status_addr, 1); // Return "ok" status 442 }, 443 444 // copyBytesToJS(dst ref, src []byte) (int, bool) 445 // Originally copied from upstream Go project, then modified: 446 // https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416 447 "syscall/js.copyBytesToJS": (ret_addr, dst_ref, src_addr, src_len, src_cap) => { 448 let num_bytes_copied_addr = ret_addr; 449 let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable 450 451 const dst = unboxValue(dst_ref); 452 const src = loadSlice(src_addr, src_len); 453 if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { 454 mem().setUint8(returned_status_addr, 0); // Return "not ok" status 455 return; 456 } 457 const toCopy = src.subarray(0, dst.length); 458 dst.set(toCopy); 459 mem().setUint32(num_bytes_copied_addr, toCopy.length, true); 460 mem().setUint8(returned_status_addr, 1); // Return "ok" status 461 }, 462 } 463 }; 464 465 // Go 1.20 uses 'env'. Go 1.21 uses 'gojs'. 466 // For compatibility, we use both as long as Go 1.20 is supported. 467 this.importObject.env = this.importObject.gojs; 468 } 469 470 async run(instance) { 471 this._inst = instance; 472 this._values = [ // JS values that Go currently has references to, indexed by reference id 473 NaN, 474 0, 475 null, 476 true, 477 false, 478 global, 479 this, 480 ]; 481 this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id 482 this._ids = new Map(); // mapping from JS values to reference ids 483 this._idPool = []; // unused ids that have been garbage collected 484 this.exited = false; // whether the Go program has exited 485 this.exitCode = 0; 486 487 if (this._inst.exports._start) { 488 let exitPromise = new Promise((resolve, reject) => { 489 this._resolveExitPromise = resolve; 490 }); 491 492 // Run program, but catch the wasmExit exception that's thrown 493 // to return back here.
494 try { 495 this._inst.exports._start(); 496 } catch (e) { 497 if (e !== wasmExit) throw e; 498 } 499 500 await exitPromise; 501 return this.exitCode; 502 } else { 503 this._inst.exports._initialize(); 504 } 505 } 506 507 _resume() { 508 if (this.exited) { 509 throw new Error("Go program has already exited"); 510 } 511 try { 512 this._inst.exports.resume(); 513 } catch (e) { 514 if (e !== wasmExit) throw e; 515 } 516 if (this.exited) { 517 this._resolveExitPromise(); 518 } 519 } 520 521 _makeFuncWrapper(id) { 522 const go = this; 523 return function () { 524 const event = { id: id, this: this, args: arguments }; 525 go._pendingEvent = event; 526 go._resume(); 527 return event.result; 528 }; 529 } 530 } 531 532 if ( 533 global.require && 534 global.require.main === module && 535 global.process && 536 global.process.versions && 537 !global.process.versions.electron 538 ) { 539 if (process.argv.length != 3) { 540 console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); 541 process.exit(1); 542 } 543 544 const go = new Go(); 545 WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => { 546 let exitCode = await go.run(result.instance); 547 process.exit(exitCode); 548 }).catch((err) => { 549 console.error(err); 550 process.exit(1); 551 }); 552 } 553})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.