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