miki

Miki: personal wiki with the fmd markup format
git clone https://wtenison.com/repo/miki.git
Log | Files | Refs | README | LICENSE

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 }