github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/misc/wasm/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  (() => {
     6  	// Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API).
     7  	const isNodeJS = typeof process !== "undefined";
     8  	if (isNodeJS) {
     9  		global.require = require;
    10  		global.fs = require("fs");
    11  
    12  		const nodeCrypto = require("crypto");
    13  		global.crypto = {
    14  			getRandomValues(b) {
    15  				nodeCrypto.randomFillSync(b);
    16  			},
    17  		};
    18  
    19  		global.performance = {
    20  			now() {
    21  				const [sec, nsec] = process.hrtime();
    22  				return sec * 1000 + nsec / 1000000;
    23  			},
    24  		};
    25  
    26  		const util = require("util");
    27  		global.TextEncoder = util.TextEncoder;
    28  		global.TextDecoder = util.TextDecoder;
    29  	} else {
    30  		if (typeof window !== "undefined") {
    31  			window.global = window;
    32  		} else if (typeof self !== "undefined") {
    33  			self.global = self;
    34  		} else {
    35  			throw new Error("cannot export Go (neither window nor self is defined)");
    36  		}
    37  
    38  		let outputBuf = "";
    39  		global.fs = {
    40  			constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
    41  			writeSync(fd, buf) {
    42  				outputBuf += decoder.decode(buf);
    43  				const nl = outputBuf.lastIndexOf("\n");
    44  				if (nl != -1) {
    45  					console.log(outputBuf.substr(0, nl));
    46  					outputBuf = outputBuf.substr(nl + 1);
    47  				}
    48  				return buf.length;
    49  			},
    50  			write(fd, buf, offset, length, position, callback) {
    51  				if (offset !== 0 || length !== buf.length || position !== null) {
    52  					throw new Error("not implemented");
    53  				}
    54  				const n = this.writeSync(fd, buf);
    55  				callback(null, n);
    56  			},
    57  			open(path, flags, mode, callback) {
    58  				const err = new Error("not implemented");
    59  				err.code = "ENOSYS";
    60  				callback(err);
    61  			},
    62  			fsync(fd, callback) {
    63  				callback(null);
    64  			},
    65  		};
    66  	}
    67  
    68  	const encoder = new TextEncoder("utf-8");
    69  	const decoder = new TextDecoder("utf-8");
    70  
    71  	global.Go = class {
    72  		constructor() {
    73  			this.argv = ["js"];
    74  			this.env = {};
    75  			this.exit = (code) => {
    76  				if (code !== 0) {
    77  					console.warn("exit code:", code);
    78  				}
    79  			};
    80  			this._callbackTimeouts = new Map();
    81  			this._nextCallbackTimeoutID = 1;
    82  
    83  			const mem = () => {
    84  				// The buffer may change when requesting more memory.
    85  				return new DataView(this._inst.exports.mem.buffer);
    86  			}
    87  
    88  			const setInt64 = (addr, v) => {
    89  				mem().setUint32(addr + 0, v, true);
    90  				mem().setUint32(addr + 4, Math.floor(v / 4294967296), true);
    91  			}
    92  
    93  			const getInt64 = (addr) => {
    94  				const low = mem().getUint32(addr + 0, true);
    95  				const high = mem().getInt32(addr + 4, true);
    96  				return low + high * 4294967296;
    97  			}
    98  
    99  			const loadValue = (addr) => {
   100  				const f = mem().getFloat64(addr, true);
   101  				if (f === 0) {
   102  					return undefined;
   103  				}
   104  				if (!isNaN(f)) {
   105  					return f;
   106  				}
   107  
   108  				const id = mem().getUint32(addr, true);
   109  				return this._values[id];
   110  			}
   111  
   112  			const storeValue = (addr, v) => {
   113  				const nanHead = 0x7FF80000;
   114  
   115  				if (typeof v === "number") {
   116  					if (isNaN(v)) {
   117  						mem().setUint32(addr + 4, nanHead, true);
   118  						mem().setUint32(addr, 0, true);
   119  						return;
   120  					}
   121  					if (v === 0) {
   122  						mem().setUint32(addr + 4, nanHead, true);
   123  						mem().setUint32(addr, 1, true);
   124  						return;
   125  					}
   126  					mem().setFloat64(addr, v, true);
   127  					return;
   128  				}
   129  
   130  				switch (v) {
   131  					case undefined:
   132  						mem().setFloat64(addr, 0, true);
   133  						return;
   134  					case null:
   135  						mem().setUint32(addr + 4, nanHead, true);
   136  						mem().setUint32(addr, 2, true);
   137  						return;
   138  					case true:
   139  						mem().setUint32(addr + 4, nanHead, true);
   140  						mem().setUint32(addr, 3, true);
   141  						return;
   142  					case false:
   143  						mem().setUint32(addr + 4, nanHead, true);
   144  						mem().setUint32(addr, 4, true);
   145  						return;
   146  				}
   147  
   148  				let ref = this._refs.get(v);
   149  				if (ref === undefined) {
   150  					ref = this._values.length;
   151  					this._values.push(v);
   152  					this._refs.set(v, ref);
   153  				}
   154  				let typeFlag = 0;
   155  				switch (typeof v) {
   156  					case "string":
   157  						typeFlag = 1;
   158  						break;
   159  					case "symbol":
   160  						typeFlag = 2;
   161  						break;
   162  					case "function":
   163  						typeFlag = 3;
   164  						break;
   165  				}
   166  				mem().setUint32(addr + 4, nanHead | typeFlag, true);
   167  				mem().setUint32(addr, ref, true);
   168  			}
   169  
   170  			const loadSlice = (addr) => {
   171  				const array = getInt64(addr + 0);
   172  				const len = getInt64(addr + 8);
   173  				return new Uint8Array(this._inst.exports.mem.buffer, array, len);
   174  			}
   175  
   176  			const loadSliceOfValues = (addr) => {
   177  				const array = getInt64(addr + 0);
   178  				const len = getInt64(addr + 8);
   179  				const a = new Array(len);
   180  				for (let i = 0; i < len; i++) {
   181  					a[i] = loadValue(array + i * 8);
   182  				}
   183  				return a;
   184  			}
   185  
   186  			const loadString = (addr) => {
   187  				const saddr = getInt64(addr + 0);
   188  				const len = getInt64(addr + 8);
   189  				return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
   190  			}
   191  
   192  			const timeOrigin = Date.now() - performance.now();
   193  			this.importObject = {
   194  				go: {
   195  					// func wasmExit(code int32)
   196  					"runtime.wasmExit": (sp) => {
   197  						const code = mem().getInt32(sp + 8, true);
   198  						this.exited = true;
   199  						delete this._inst;
   200  						delete this._values;
   201  						delete this._refs;
   202  						this.exit(code);
   203  					},
   204  
   205  					// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
   206  					"runtime.wasmWrite": (sp) => {
   207  						const fd = getInt64(sp + 8);
   208  						const p = getInt64(sp + 16);
   209  						const n = mem().getInt32(sp + 24, true);
   210  						fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
   211  					},
   212  
   213  					// func nanotime() int64
   214  					"runtime.nanotime": (sp) => {
   215  						setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
   216  					},
   217  
   218  					// func walltime() (sec int64, nsec int32)
   219  					"runtime.walltime": (sp) => {
   220  						const msec = (new Date).getTime();
   221  						setInt64(sp + 8, msec / 1000);
   222  						mem().setInt32(sp + 16, (msec % 1000) * 1000000, true);
   223  					},
   224  
   225  					// func scheduleCallback(delay int64) int32
   226  					"runtime.scheduleCallback": (sp) => {
   227  						const id = this._nextCallbackTimeoutID;
   228  						this._nextCallbackTimeoutID++;
   229  						this._callbackTimeouts.set(id, setTimeout(
   230  							() => { this._resolveCallbackPromise(); },
   231  							getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
   232  						));
   233  						mem().setInt32(sp + 16, id, true);
   234  					},
   235  
   236  					// func clearScheduledCallback(id int32)
   237  					"runtime.clearScheduledCallback": (sp) => {
   238  						const id = mem().getInt32(sp + 8, true);
   239  						clearTimeout(this._callbackTimeouts.get(id));
   240  						this._callbackTimeouts.delete(id);
   241  					},
   242  
   243  					// func getRandomData(r []byte)
   244  					"runtime.getRandomData": (sp) => {
   245  						crypto.getRandomValues(loadSlice(sp + 8));
   246  					},
   247  
   248  					// func stringVal(value string) ref
   249  					"syscall/js.stringVal": (sp) => {
   250  						storeValue(sp + 24, loadString(sp + 8));
   251  					},
   252  
   253  					// func valueGet(v ref, p string) ref
   254  					"syscall/js.valueGet": (sp) => {
   255  						storeValue(sp + 32, Reflect.get(loadValue(sp + 8), loadString(sp + 16)));
   256  					},
   257  
   258  					// func valueSet(v ref, p string, x ref)
   259  					"syscall/js.valueSet": (sp) => {
   260  						Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
   261  					},
   262  
   263  					// func valueIndex(v ref, i int) ref
   264  					"syscall/js.valueIndex": (sp) => {
   265  						storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
   266  					},
   267  
   268  					// valueSetIndex(v ref, i int, x ref)
   269  					"syscall/js.valueSetIndex": (sp) => {
   270  						Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
   271  					},
   272  
   273  					// func valueCall(v ref, m string, args []ref) (ref, bool)
   274  					"syscall/js.valueCall": (sp) => {
   275  						try {
   276  							const v = loadValue(sp + 8);
   277  							const m = Reflect.get(v, loadString(sp + 16));
   278  							const args = loadSliceOfValues(sp + 32);
   279  							storeValue(sp + 56, Reflect.apply(m, v, args));
   280  							mem().setUint8(sp + 64, 1);
   281  						} catch (err) {
   282  							storeValue(sp + 56, err);
   283  							mem().setUint8(sp + 64, 0);
   284  						}
   285  					},
   286  
   287  					// func valueInvoke(v ref, args []ref) (ref, bool)
   288  					"syscall/js.valueInvoke": (sp) => {
   289  						try {
   290  							const v = loadValue(sp + 8);
   291  							const args = loadSliceOfValues(sp + 16);
   292  							storeValue(sp + 40, Reflect.apply(v, undefined, args));
   293  							mem().setUint8(sp + 48, 1);
   294  						} catch (err) {
   295  							storeValue(sp + 40, err);
   296  							mem().setUint8(sp + 48, 0);
   297  						}
   298  					},
   299  
   300  					// func valueNew(v ref, args []ref) (ref, bool)
   301  					"syscall/js.valueNew": (sp) => {
   302  						try {
   303  							const v = loadValue(sp + 8);
   304  							const args = loadSliceOfValues(sp + 16);
   305  							storeValue(sp + 40, Reflect.construct(v, args));
   306  							mem().setUint8(sp + 48, 1);
   307  						} catch (err) {
   308  							storeValue(sp + 40, err);
   309  							mem().setUint8(sp + 48, 0);
   310  						}
   311  					},
   312  
   313  					// func valueLength(v ref) int
   314  					"syscall/js.valueLength": (sp) => {
   315  						setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
   316  					},
   317  
   318  					// valuePrepareString(v ref) (ref, int)
   319  					"syscall/js.valuePrepareString": (sp) => {
   320  						const str = encoder.encode(String(loadValue(sp + 8)));
   321  						storeValue(sp + 16, str);
   322  						setInt64(sp + 24, str.length);
   323  					},
   324  
   325  					// valueLoadString(v ref, b []byte)
   326  					"syscall/js.valueLoadString": (sp) => {
   327  						const str = loadValue(sp + 8);
   328  						loadSlice(sp + 16).set(str);
   329  					},
   330  
   331  					// func valueInstanceOf(v ref, t ref) bool
   332  					"syscall/js.valueInstanceOf": (sp) => {
   333  						mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16));
   334  					},
   335  
   336  					"debug": (value) => {
   337  						console.log(value);
   338  					},
   339  				}
   340  			};
   341  		}
   342  
   343  		async run(instance) {
   344  			this._inst = instance;
   345  			this._values = [ // TODO: garbage collection
   346  				NaN,
   347  				0,
   348  				null,
   349  				true,
   350  				false,
   351  				global,
   352  				this._inst.exports.mem,
   353  				this,
   354  			];
   355  			this._refs = new Map();
   356  			this._callbackShutdown = false;
   357  			this.exited = false;
   358  
   359  			const mem = new DataView(this._inst.exports.mem.buffer)
   360  
   361  			// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
   362  			let offset = 4096;
   363  
   364  			const strPtr = (str) => {
   365  				let ptr = offset;
   366  				new Uint8Array(mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0"));
   367  				offset += str.length + (8 - (str.length % 8));
   368  				return ptr;
   369  			};
   370  
   371  			const argc = this.argv.length;
   372  
   373  			const argvPtrs = [];
   374  			this.argv.forEach((arg) => {
   375  				argvPtrs.push(strPtr(arg));
   376  			});
   377  
   378  			const keys = Object.keys(this.env).sort();
   379  			argvPtrs.push(keys.length);
   380  			keys.forEach((key) => {
   381  				argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
   382  			});
   383  
   384  			const argv = offset;
   385  			argvPtrs.forEach((ptr) => {
   386  				mem.setUint32(offset, ptr, true);
   387  				mem.setUint32(offset + 4, 0, true);
   388  				offset += 8;
   389  			});
   390  
   391  			while (true) {
   392  				const callbackPromise = new Promise((resolve) => {
   393  					this._resolveCallbackPromise = () => {
   394  						if (this.exited) {
   395  							throw new Error("bad callback: Go program has already exited");
   396  						}
   397  						setTimeout(resolve, 0); // make sure it is asynchronous
   398  					};
   399  				});
   400  				this._inst.exports.run(argc, argv);
   401  				if (this.exited) {
   402  					break;
   403  				}
   404  				await callbackPromise;
   405  			}
   406  		}
   407  
   408  		static _makeCallbackHelper(id, pendingCallbacks, go) {
   409  			return function () {
   410  				pendingCallbacks.push({ id: id, args: arguments });
   411  				go._resolveCallbackPromise();
   412  			};
   413  		}
   414  
   415  		static _makeEventCallbackHelper(preventDefault, stopPropagation, stopImmediatePropagation, fn) {
   416  			return function (event) {
   417  				if (preventDefault) {
   418  					event.preventDefault();
   419  				}
   420  				if (stopPropagation) {
   421  					event.stopPropagation();
   422  				}
   423  				if (stopImmediatePropagation) {
   424  					event.stopImmediatePropagation();
   425  				}
   426  				fn(event);
   427  			};
   428  		}
   429  	}
   430  
   431  	if (isNodeJS) {
   432  		if (process.argv.length < 3) {
   433  			process.stderr.write("usage: go_js_wasm_exec [wasm binary] [arguments]\n");
   434  			process.exit(1);
   435  		}
   436  
   437  		const go = new Go();
   438  		go.argv = process.argv.slice(2);
   439  		go.env = process.env;
   440  		go.exit = process.exit;
   441  		WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
   442  			process.on("exit", (code) => { // Node.js exits if no callback is pending
   443  				if (code === 0 && !go.exited) {
   444  					// deadlock, make Go print error and stack traces
   445  					go._callbackShutdown = true;
   446  					go._inst.exports.run();
   447  				}
   448  			});
   449  			return go.run(result.instance);
   450  		}).catch((err) => {
   451  			throw err;
   452  		});
   453  	}
   454  })();