github.com/vugu/vugu@v0.3.6-0.20240430171613-3f6f402e014b/tinygo-dev/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 web browser API and Node.js API to a single common API (preferring web standards over Node.js API).
     9  	const isNodeJS = typeof process !== "undefined";
    10  	if (isNodeJS) {
    11  		global.require = require;
    12  		global.fs = require("fs");
    13  
    14  		const nodeCrypto = require("crypto");
    15  		global.crypto = {
    16  			getRandomValues(b) {
    17  				nodeCrypto.randomFillSync(b);
    18  			},
    19  		};
    20  
    21  		global.performance = {
    22  			now() {
    23  				const [sec, nsec] = process.hrtime();
    24  				return sec * 1000 + nsec / 1000000;
    25  			},
    26  		};
    27  
    28  		const util = require("util");
    29  		global.TextEncoder = util.TextEncoder;
    30  		global.TextDecoder = util.TextDecoder;
    31  	} else {
    32  		if (typeof window !== "undefined") {
    33  			window.global = window;
    34  		} else if (typeof self !== "undefined") {
    35  			self.global = self;
    36  		} else {
    37  			throw new Error("cannot export Go (neither window nor self is defined)");
    38  		}
    39  
    40  		let outputBuf = "";
    41  		global.fs = {
    42  			constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
    43  			writeSync(fd, buf) {
    44  				outputBuf += decoder.decode(buf);
    45  				const nl = outputBuf.lastIndexOf("\n");
    46  				if (nl != -1) {
    47  					console.log(outputBuf.substr(0, nl));
    48  					outputBuf = outputBuf.substr(nl + 1);
    49  				}
    50  				return buf.length;
    51  			},
    52  			write(fd, buf, offset, length, position, callback) {
    53  				if (offset !== 0 || length !== buf.length || position !== null) {
    54  					throw new Error("not implemented");
    55  				}
    56  				const n = this.writeSync(fd, buf);
    57  				callback(null, n);
    58  			},
    59  			open(path, flags, mode, callback) {
    60  				const err = new Error("not implemented");
    61  				err.code = "ENOSYS";
    62  				callback(err);
    63  			},
    64  			fsync(fd, callback) {
    65  				callback(null);
    66  			},
    67  		};
    68  	}
    69  
    70  	const encoder = new TextEncoder("utf-8");
    71  	const decoder = new TextDecoder("utf-8");
    72  	var logLine = [];
    73  
    74  	global.Go = class {
    75  		constructor() {
    76  			this._callbackTimeouts = new Map();
    77  			this._nextCallbackTimeoutID = 1;
    78  
    79  			const mem = () => {
    80  				// The buffer may change when requesting more memory.
    81  				return new DataView(this._inst.exports.memory.buffer);
    82  			}
    83  
    84  			const setInt64 = (addr, v) => {
    85  				mem().setUint32(addr + 0, v, true);
    86  				mem().setUint32(addr + 4, Math.floor(v / 4294967296), true);
    87  			}
    88  
    89  			const getInt64 = (addr) => {
    90  				const low = mem().getUint32(addr + 0, true);
    91  				const high = mem().getInt32(addr + 4, true);
    92  				return low + high * 4294967296;
    93  			}
    94  
    95  			const loadValue = (addr) => {
    96  				const f = mem().getFloat64(addr, true);
    97  				if (f === 0) {
    98  					return undefined;
    99  				}
   100  				if (!isNaN(f)) {
   101  					return f;
   102  				}
   103  
   104  				const id = mem().getUint32(addr, true);
   105  				return this._values[id];
   106  			}
   107  
   108  			const storeValue = (addr, v) => {
   109  				const nanHead = 0x7FF80000;
   110  
   111  				if (typeof v === "number") {
   112  					if (isNaN(v)) {
   113  						mem().setUint32(addr + 4, nanHead, true);
   114  						mem().setUint32(addr, 0, true);
   115  						return;
   116  					}
   117  					if (v === 0) {
   118  						mem().setUint32(addr + 4, nanHead, true);
   119  						mem().setUint32(addr, 1, true);
   120  						return;
   121  					}
   122  					mem().setFloat64(addr, v, true);
   123  					return;
   124  				}
   125  
   126  				switch (v) {
   127  					case undefined:
   128  						mem().setFloat64(addr, 0, true);
   129  						return;
   130  					case null:
   131  						mem().setUint32(addr + 4, nanHead, true);
   132  						mem().setUint32(addr, 2, true);
   133  						return;
   134  					case true:
   135  						mem().setUint32(addr + 4, nanHead, true);
   136  						mem().setUint32(addr, 3, true);
   137  						return;
   138  					case false:
   139  						mem().setUint32(addr + 4, nanHead, true);
   140  						mem().setUint32(addr, 4, true);
   141  						return;
   142  				}
   143  
   144  				let ref = this._refs.get(v);
   145  				if (ref === undefined) {
   146  					ref = this._values.length;
   147  					this._values.push(v);
   148  					this._refs.set(v, ref);
   149  				}
   150  				let typeFlag = 0;
   151  				switch (typeof v) {
   152  					case "string":
   153  						typeFlag = 1;
   154  						break;
   155  					case "symbol":
   156  						typeFlag = 2;
   157  						break;
   158  					case "function":
   159  						typeFlag = 3;
   160  						break;
   161  				}
   162  				mem().setUint32(addr + 4, nanHead | typeFlag, true);
   163  				mem().setUint32(addr, ref, true);
   164  			}
   165  
   166  			const loadSlice = (array, len, cap) => {
   167  				return new Uint8Array(this._inst.exports.memory.buffer, array, len);
   168  			}
   169  
   170  			const loadSliceOfValues = (array, len, cap) => {
   171  				const a = new Array(len);
   172  				for (let i = 0; i < len; i++) {
   173  					a[i] = loadValue(array + i * 8);
   174  				}
   175  				return a;
   176  			}
   177  
   178  			const loadString = (ptr, len) => {
   179  				return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len));
   180  			}
   181  
   182  			const timeOrigin = Date.now() - performance.now();
   183  			this.importObject = {
   184  				env: {
   185  					io_get_stdout: function() {
   186  						return 1;
   187  					},
   188  
   189  					resource_write: function(fd, ptr, len) {
   190  						if (fd == 1) {
   191  							for (let i=0; i<len; i++) {
   192  								let c = mem().getUint8(ptr+i);
   193  								if (c == 13) { // CR
   194  									// ignore
   195  								} else if (c == 10) { // LF
   196  									// write line
   197  									let line = decoder.decode(new Uint8Array(logLine));
   198  									logLine = [];
   199  									console.log(line);
   200  								} else {
   201  									logLine.push(c);
   202  								}
   203  							}
   204  						} else {
   205  							console.error('invalid file descriptor:', fd);
   206  						}
   207  					},
   208  
   209  					// func ticks() float64
   210  					"runtime.ticks": () => {
   211  						return timeOrigin + performance.now();
   212  					},
   213  
   214  					// func sleepTicks(timeout float64)
   215  					"runtime.sleepTicks": (timeout) => {
   216  						// Do not sleep, only reactivate scheduler after the given timeout.
   217  						setTimeout(this._inst.exports.go_scheduler, timeout);
   218  					},
   219  
   220  					// func stringVal(value string) ref
   221  					"syscall/js.stringVal": (ret_ptr, value_ptr, value_len) => {
   222  						const s = loadString(value_ptr, value_len);
   223  						storeValue(ret_ptr, s);
   224  					},
   225  
   226  					// func valueGet(v ref, p string) ref
   227  					"syscall/js.valueGet": (retval, v_addr, p_ptr, p_len) => {
   228  						let prop = loadString(p_ptr, p_len);
   229  						let value = loadValue(v_addr);
   230  						let result = Reflect.get(value, prop);
   231  						storeValue(retval, result);
   232  					},
   233  
   234  					// func valueSet(v ref, p string, x ref)
   235  					"syscall/js.valueSet": (v_addr, p_ptr, p_len, x_addr) => {
   236  						const v = loadValue(v_addr);
   237  						const p = loadString(p_ptr, p_len);
   238  						const x = loadValue(x_addr);
   239  						Reflect.set(v, p, x);
   240  					},
   241  
   242  					// func valueIndex(v ref, i int) ref
   243  					"syscall/js.valueIndex": (ret_addr, v_addr, i) => {
   244  						storeValue(ret_addr, Reflect.get(loadValue(v_addr), i));
   245  					},
   246  
   247  					// valueSetIndex(v ref, i int, x ref)
   248  					"syscall/js.valueSetIndex": (v_addr, i, x_addr) => {
   249  						Reflect.set(loadValue(v_addr), i, loadValue(x_addr));
   250  					},
   251  
   252  					// func valueCall(v ref, m string, args []ref) (ref, bool)
   253  					"syscall/js.valueCall": (ret_addr, v_addr, m_ptr, m_len, args_ptr, args_len, args_cap) => {
   254  						const v = loadValue(v_addr);
   255  						const name = loadString(m_ptr, m_len);
   256  						const args = loadSliceOfValues(args_ptr, args_len, args_cap);
   257  						try {
   258  							const m = Reflect.get(v, name);
   259  							storeValue(ret_addr, Reflect.apply(m, v, args));
   260  							mem().setUint8(ret_addr + 8, 1);
   261  						} catch (err) {
   262  							storeValue(ret_addr, err);
   263  							mem().setUint8(ret_addr + 8, 0);
   264  						}
   265  					},
   266  
   267  					// func valueInvoke(v ref, args []ref) (ref, bool)
   268  					"syscall/js.valueInvoke": (ret_addr, v_addr, args_ptr, args_len, args_cap) => {
   269  						try {
   270  							const v = loadValue(v_addr);
   271  							const args = loadSliceOfValues(args_ptr, args_len, args_cap);
   272  							storeValue(ret_addr, Reflect.apply(v, undefined, args));
   273  							mem().setUint8(ret_addr + 8, 1);
   274  						} catch (err) {
   275  							storeValue(ret_addr, err);
   276  							mem().setUint8(ret_addr + 8, 0);
   277  						}
   278  					},
   279  
   280  					// func valueNew(v ref, args []ref) (ref, bool)
   281  					"syscall/js.valueNew": (ret_addr, v_addr, args_ptr, args_len, args_cap) => {
   282  						const v = loadValue(v_addr);
   283  						const args = loadSliceOfValues(args_ptr, args_len, args_cap);
   284  						try {
   285  							storeValue(ret_addr, Reflect.construct(v, args));
   286  							mem().setUint8(ret_addr + 8, 1);
   287  						} catch (err) {
   288  							storeValue(ret_addr, err);
   289  							mem().setUint8(ret_addr+ 8, 0);
   290  						}
   291  					},
   292  
   293  					// func valueLength(v ref) int
   294  					"syscall/js.valueLength": (v_addr) => {
   295  						return loadValue(v_addr).length;
   296  					},
   297  
   298  					// valuePrepareString(v ref) (ref, int)
   299  					"syscall/js.valuePrepareString": (ret_addr, v_addr) => {
   300  						const s = String(loadValue(v_addr));
   301  						const str = encoder.encode(s);
   302  						storeValue(ret_addr, str);
   303  						setInt64(ret_addr + 8, str.length);
   304  					},
   305  
   306  					// valueLoadString(v ref, b []byte)
   307  					"syscall/js.valueLoadString": (v_addr, slice_ptr, slice_len, slice_cap) => {
   308  						const str = loadValue(v_addr);
   309  						loadSlice(slice_ptr, slice_len, slice_cap).set(str);
   310  					},
   311  
   312  					// func valueInstanceOf(v ref, t ref) bool
   313  					//"syscall/js.valueInstanceOf": (sp) => {
   314  					//	mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16));
   315  					//},
   316  				}
   317  			};
   318  		}
   319  
   320  		async run(instance) {
   321  			this._inst = instance;
   322  			this._values = [ // TODO: garbage collection
   323  				NaN,
   324  				0,
   325  				null,
   326  				true,
   327  				false,
   328  				global,
   329  				this._inst.exports.memory,
   330  				this,
   331  			];
   332  			this._refs = new Map();
   333  			this._callbackShutdown = false;
   334  			this.exited = false;
   335  
   336  			const mem = new DataView(this._inst.exports.memory.buffer)
   337  
   338  			while (true) {
   339  				const callbackPromise = new Promise((resolve) => {
   340  					this._resolveCallbackPromise = () => {
   341  						if (this.exited) {
   342  							throw new Error("bad callback: Go program has already exited");
   343  						}
   344  						setTimeout(resolve, 0); // make sure it is asynchronous
   345  					};
   346  				});
   347  				this._inst.exports.cwa_main();
   348  				if (this.exited) {
   349  					break;
   350  				}
   351  				await callbackPromise;
   352  			}
   353  		}
   354  
   355  		_resume() {
   356  			if (this.exited) {
   357  				throw new Error("Go program has already exited");
   358  			}
   359  			this._inst.exports.resume();
   360  			if (this.exited) {
   361  				this._resolveExitPromise();
   362  			}
   363  		}
   364  
   365  		_makeFuncWrapper(id) {
   366  			const go = this;
   367  			return function () {
   368  				const event = { id: id, this: this, args: arguments };
   369  				go._pendingEvent = event;
   370  				go._resume();
   371  				return event.result;
   372  			};
   373  		}
   374  	}
   375  
   376  	if (isNodeJS) {
   377  		if (process.argv.length != 3) {
   378  			process.stderr.write("usage: go_js_wasm_exec [wasm binary] [arguments]\n");
   379  			process.exit(1);
   380  		}
   381  
   382  		const go = new Go();
   383  		WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
   384  			process.on("exit", (code) => { // Node.js exits if no callback is pending
   385  				if (code === 0 && !go.exited) {
   386  					// deadlock, make Go print error and stack traces
   387  					go._callbackShutdown = true;
   388  				}
   389  			});
   390  			return go.run(result.instance);
   391  		}).catch((err) => {
   392  			throw err;
   393  		});
   394  	}
   395  })();