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