github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/targets/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 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("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("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("util").TextEncoder; 123 } 124 125 if (!global.TextDecoder) { 126 global.TextDecoder = require("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 136 global.Go = class { 137 constructor() { 138 this._callbackTimeouts = new Map(); 139 this._nextCallbackTimeoutID = 1; 140 141 const mem = () => { 142 // The buffer may change when requesting more memory. 143 return new DataView(this._inst.exports.memory.buffer); 144 } 145 146 const unboxValue = (v_ref) => { 147 reinterpretBuf.setBigInt64(0, v_ref, true); 148 const f = reinterpretBuf.getFloat64(0, true); 149 if (f === 0) { 150 return undefined; 151 } 152 if (!isNaN(f)) { 153 return f; 154 } 155 156 const id = v_ref & 0xffffffffn; 157 return this._values[id]; 158 } 159 160 161 const loadValue = (addr) => { 162 let v_ref = mem().getBigUint64(addr, true); 163 return unboxValue(v_ref); 164 } 165 166 const boxValue = (v) => { 167 const nanHead = 0x7FF80000n; 168 169 if (typeof v === "number") { 170 if (isNaN(v)) { 171 return nanHead << 32n; 172 } 173 if (v === 0) { 174 return (nanHead << 32n) | 1n; 175 } 176 reinterpretBuf.setFloat64(0, v, true); 177 return reinterpretBuf.getBigInt64(0, true); 178 } 179 180 switch (v) { 181 case undefined: 182 return 0n; 183 case null: 184 return (nanHead << 32n) | 2n; 185 case true: 186 return (nanHead << 32n) | 3n; 187 case false: 188 return (nanHead << 32n) | 4n; 189 } 190 191 let id = this._ids.get(v); 192 if (id === undefined) { 193 id = this._idPool.pop(); 194 if (id === undefined) { 195 id = BigInt(this._values.length); 196 } 197 this._values[id] = v; 198 this._goRefCounts[id] = 0; 199 this._ids.set(v, id); 200 } 201 this._goRefCounts[id]++; 202 let typeFlag = 1n; 203 switch (typeof v) { 204 case "string": 205 typeFlag = 2n; 206 break; 207 case "symbol": 208 typeFlag = 3n; 209 break; 210 case "function": 211 typeFlag = 4n; 212 break; 213 } 214 return id | ((nanHead | typeFlag) << 32n); 215 } 216 217 const storeValue = (addr, v) => { 218 let v_ref = boxValue(v); 219 mem().setBigUint64(addr, v_ref, true); 220 } 221 222 const loadSlice = (array, len, cap) => { 223 return new Uint8Array(this._inst.exports.memory.buffer, array, len); 224 } 225 226 const loadSliceOfValues = (array, len, cap) => { 227 const a = new Array(len); 228 for (let i = 0; i < len; i++) { 229 a[i] = loadValue(array + i * 8); 230 } 231 return a; 232 } 233 234 const loadString = (ptr, len) => { 235 return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len)); 236 } 237 238 const timeOrigin = Date.now() - performance.now(); 239 this.importObject = { 240 wasi_snapshot_preview1: { 241 // https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write 242 fd_write: function(fd, iovs_ptr, iovs_len, nwritten_ptr) { 243 let nwritten = 0; 244 if (fd == 1) { 245 for (let iovs_i=0; iovs_i<iovs_len;iovs_i++) { 246 let iov_ptr = iovs_ptr+iovs_i*8; // assuming wasm32 247 let ptr = mem().getUint32(iov_ptr + 0, true); 248 let len = mem().getUint32(iov_ptr + 4, true); 249 nwritten += len; 250 for (let i=0; i<len; i++) { 251 let c = mem().getUint8(ptr+i); 252 if (c == 13) { // CR 253 // ignore 254 } else if (c == 10) { // LF 255 // write line 256 let line = decoder.decode(new Uint8Array(logLine)); 257 logLine = []; 258 console.log(line); 259 } else { 260 logLine.push(c); 261 } 262 } 263 } 264 } else { 265 console.error('invalid file descriptor:', fd); 266 } 267 mem().setUint32(nwritten_ptr, nwritten, true); 268 return 0; 269 }, 270 fd_close: () => 0, // dummy 271 fd_fdstat_get: () => 0, // dummy 272 fd_seek: () => 0, // dummy 273 "proc_exit": (code) => { 274 if (global.process) { 275 // Node.js 276 process.exit(code); 277 } else { 278 // Can't exit in a browser. 279 throw 'trying to exit with code ' + code; 280 } 281 }, 282 random_get: (bufPtr, bufLen) => { 283 crypto.getRandomValues(loadSlice(bufPtr, bufLen)); 284 return 0; 285 }, 286 }, 287 gojs: { 288 // func ticks() float64 289 "runtime.ticks": () => { 290 return timeOrigin + performance.now(); 291 }, 292 293 // func sleepTicks(timeout float64) 294 "runtime.sleepTicks": (timeout) => { 295 // Do not sleep, only reactivate scheduler after the given timeout. 296 setTimeout(this._inst.exports.go_scheduler, timeout); 297 }, 298 299 // func finalizeRef(v ref) 300 "syscall/js.finalizeRef": (v_ref) => { 301 // Note: TinyGo does not support finalizers so this should never be 302 // called. 303 console.error('syscall/js.finalizeRef not implemented'); 304 }, 305 306 // func stringVal(value string) ref 307 "syscall/js.stringVal": (value_ptr, value_len) => { 308 const s = loadString(value_ptr, value_len); 309 return boxValue(s); 310 }, 311 312 // func valueGet(v ref, p string) ref 313 "syscall/js.valueGet": (v_ref, p_ptr, p_len) => { 314 let prop = loadString(p_ptr, p_len); 315 let v = unboxValue(v_ref); 316 let result = Reflect.get(v, prop); 317 return boxValue(result); 318 }, 319 320 // func valueSet(v ref, p string, x ref) 321 "syscall/js.valueSet": (v_ref, p_ptr, p_len, x_ref) => { 322 const v = unboxValue(v_ref); 323 const p = loadString(p_ptr, p_len); 324 const x = unboxValue(x_ref); 325 Reflect.set(v, p, x); 326 }, 327 328 // func valueDelete(v ref, p string) 329 "syscall/js.valueDelete": (v_ref, p_ptr, p_len) => { 330 const v = unboxValue(v_ref); 331 const p = loadString(p_ptr, p_len); 332 Reflect.deleteProperty(v, p); 333 }, 334 335 // func valueIndex(v ref, i int) ref 336 "syscall/js.valueIndex": (v_ref, i) => { 337 return boxValue(Reflect.get(unboxValue(v_ref), i)); 338 }, 339 340 // valueSetIndex(v ref, i int, x ref) 341 "syscall/js.valueSetIndex": (v_ref, i, x_ref) => { 342 Reflect.set(unboxValue(v_ref), i, unboxValue(x_ref)); 343 }, 344 345 // func valueCall(v ref, m string, args []ref) (ref, bool) 346 "syscall/js.valueCall": (ret_addr, v_ref, m_ptr, m_len, args_ptr, args_len, args_cap) => { 347 const v = unboxValue(v_ref); 348 const name = loadString(m_ptr, m_len); 349 const args = loadSliceOfValues(args_ptr, args_len, args_cap); 350 try { 351 const m = Reflect.get(v, name); 352 storeValue(ret_addr, Reflect.apply(m, v, args)); 353 mem().setUint8(ret_addr + 8, 1); 354 } catch (err) { 355 storeValue(ret_addr, err); 356 mem().setUint8(ret_addr + 8, 0); 357 } 358 }, 359 360 // func valueInvoke(v ref, args []ref) (ref, bool) 361 "syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { 362 try { 363 const v = unboxValue(v_ref); 364 const args = loadSliceOfValues(args_ptr, args_len, args_cap); 365 storeValue(ret_addr, Reflect.apply(v, undefined, args)); 366 mem().setUint8(ret_addr + 8, 1); 367 } catch (err) { 368 storeValue(ret_addr, err); 369 mem().setUint8(ret_addr + 8, 0); 370 } 371 }, 372 373 // func valueNew(v ref, args []ref) (ref, bool) 374 "syscall/js.valueNew": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { 375 const v = unboxValue(v_ref); 376 const args = loadSliceOfValues(args_ptr, args_len, args_cap); 377 try { 378 storeValue(ret_addr, Reflect.construct(v, args)); 379 mem().setUint8(ret_addr + 8, 1); 380 } catch (err) { 381 storeValue(ret_addr, err); 382 mem().setUint8(ret_addr+ 8, 0); 383 } 384 }, 385 386 // func valueLength(v ref) int 387 "syscall/js.valueLength": (v_ref) => { 388 return unboxValue(v_ref).length; 389 }, 390 391 // valuePrepareString(v ref) (ref, int) 392 "syscall/js.valuePrepareString": (ret_addr, v_ref) => { 393 const s = String(unboxValue(v_ref)); 394 const str = encoder.encode(s); 395 storeValue(ret_addr, str); 396 mem().setInt32(ret_addr + 8, str.length, true); 397 }, 398 399 // valueLoadString(v ref, b []byte) 400 "syscall/js.valueLoadString": (v_ref, slice_ptr, slice_len, slice_cap) => { 401 const str = unboxValue(v_ref); 402 loadSlice(slice_ptr, slice_len, slice_cap).set(str); 403 }, 404 405 // func valueInstanceOf(v ref, t ref) bool 406 "syscall/js.valueInstanceOf": (v_ref, t_ref) => { 407 return unboxValue(v_ref) instanceof unboxValue(t_ref); 408 }, 409 410 // func copyBytesToGo(dst []byte, src ref) (int, bool) 411 "syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, src_ref) => { 412 let num_bytes_copied_addr = ret_addr; 413 let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable 414 415 const dst = loadSlice(dest_addr, dest_len); 416 const src = unboxValue(src_ref); 417 if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { 418 mem().setUint8(returned_status_addr, 0); // Return "not ok" status 419 return; 420 } 421 const toCopy = src.subarray(0, dst.length); 422 dst.set(toCopy); 423 mem().setUint32(num_bytes_copied_addr, toCopy.length, true); 424 mem().setUint8(returned_status_addr, 1); // Return "ok" status 425 }, 426 427 // copyBytesToJS(dst ref, src []byte) (int, bool) 428 // Originally copied from upstream Go project, then modified: 429 // https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416 430 "syscall/js.copyBytesToJS": (ret_addr, dst_ref, src_addr, src_len, src_cap) => { 431 let num_bytes_copied_addr = ret_addr; 432 let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable 433 434 const dst = unboxValue(dst_ref); 435 const src = loadSlice(src_addr, src_len); 436 if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { 437 mem().setUint8(returned_status_addr, 0); // Return "not ok" status 438 return; 439 } 440 const toCopy = src.subarray(0, dst.length); 441 dst.set(toCopy); 442 mem().setUint32(num_bytes_copied_addr, toCopy.length, true); 443 mem().setUint8(returned_status_addr, 1); // Return "ok" status 444 }, 445 } 446 }; 447 448 // Go 1.20 uses 'env'. Go 1.21 uses 'gojs'. 449 // For compatibility, we use both as long as Go 1.20 is supported. 450 this.importObject.env = this.importObject.gojs; 451 } 452 453 async run(instance) { 454 this._inst = instance; 455 this._values = [ // JS values that Go currently has references to, indexed by reference id 456 NaN, 457 0, 458 null, 459 true, 460 false, 461 global, 462 this, 463 ]; 464 this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id 465 this._ids = new Map(); // mapping from JS values to reference ids 466 this._idPool = []; // unused ids that have been garbage collected 467 this.exited = false; // whether the Go program has exited 468 469 while (true) { 470 const callbackPromise = new Promise((resolve) => { 471 this._resolveCallbackPromise = () => { 472 if (this.exited) { 473 throw new Error("bad callback: Go program has already exited"); 474 } 475 setTimeout(resolve, 0); // make sure it is asynchronous 476 }; 477 }); 478 this._inst.exports._start(); 479 if (this.exited) { 480 break; 481 } 482 await callbackPromise; 483 } 484 } 485 486 _resume() { 487 if (this.exited) { 488 throw new Error("Go program has already exited"); 489 } 490 this._inst.exports.resume(); 491 if (this.exited) { 492 this._resolveExitPromise(); 493 } 494 } 495 496 _makeFuncWrapper(id) { 497 const go = this; 498 return function () { 499 const event = { id: id, this: this, args: arguments }; 500 go._pendingEvent = event; 501 go._resume(); 502 return event.result; 503 }; 504 } 505 } 506 507 if ( 508 global.require && 509 global.require.main === module && 510 global.process && 511 global.process.versions && 512 !global.process.versions.electron 513 ) { 514 if (process.argv.length != 3) { 515 console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); 516 process.exit(1); 517 } 518 519 const go = new Go(); 520 WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { 521 return go.run(result.instance); 522 }).catch((err) => { 523 console.error(err); 524 process.exit(1); 525 }); 526 } 527 })();