github.com/mattn/anko@v0.1.10/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 window.global = window; 31 32 let outputBuf = ""; 33 global.fs = { 34 constants: {}, 35 writeSync(fd, buf) { 36 outputBuf += decoder.decode(buf); 37 const nl = outputBuf.lastIndexOf("\n"); 38 if (nl != -1) { 39 console.log(outputBuf.substr(0, nl)); 40 outputBuf = outputBuf.substr(nl + 1); 41 } 42 return buf.length; 43 }, 44 openSync(path, flags, mode) { 45 const err = new Error("not implemented"); 46 err.code = "ENOSYS"; 47 throw err; 48 }, 49 }; 50 } 51 52 const encoder = new TextEncoder("utf-8"); 53 const decoder = new TextDecoder("utf-8"); 54 55 global.Go = class { 56 constructor() { 57 this.argv = ["js"]; 58 this.env = {}; 59 this.exit = (code) => { 60 if (code !== 0) { 61 console.warn("exit code:", code); 62 } 63 }; 64 this._callbackTimeouts = new Map(); 65 this._nextCallbackTimeoutID = 1; 66 67 const mem = () => { 68 // The buffer may change when requesting more memory. 69 return new DataView(this._inst.exports.mem.buffer); 70 } 71 72 const setInt64 = (addr, v) => { 73 mem().setUint32(addr + 0, v, true); 74 mem().setUint32(addr + 4, Math.floor(v / 4294967296), true); 75 } 76 77 const getInt64 = (addr) => { 78 const low = mem().getUint32(addr + 0, true); 79 const high = mem().getInt32(addr + 4, true); 80 return low + high * 4294967296; 81 } 82 83 const loadValue = (addr) => { 84 const id = mem().getUint32(addr, true); 85 return this._values[id]; 86 } 87 88 const storeValue = (addr, v) => { 89 if (v === undefined) { 90 mem().setUint32(addr, 0, true); 91 return; 92 } 93 if (v === null) { 94 mem().setUint32(addr, 1, true); 95 return; 96 } 97 this._values.push(v); 98 mem().setUint32(addr, this._values.length - 1, true); 99 } 100 101 const loadSlice = (addr) => { 102 const array = getInt64(addr + 0); 103 const len = getInt64(addr + 8); 104 return new Uint8Array(this._inst.exports.mem.buffer, array, len); 105 } 106 107 const loadSliceOfValues = (addr) => { 108 const array = getInt64(addr + 0); 109 const len = getInt64(addr + 8); 110 const a = new Array(len); 111 for (let i = 0; i < len; i++) { 112 const id = mem().getUint32(array + i * 4, true); 113 a[i] = this._values[id]; 114 } 115 return a; 116 } 117 118 const loadString = (addr) => { 119 const saddr = getInt64(addr + 0); 120 const len = getInt64(addr + 8); 121 return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); 122 } 123 124 const timeOrigin = Date.now() - performance.now(); 125 this.importObject = { 126 go: { 127 // func wasmExit(code int32) 128 "runtime.wasmExit": (sp) => { 129 this.exited = true; 130 this.exit(mem().getInt32(sp + 8, true)); 131 }, 132 133 // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) 134 "runtime.wasmWrite": (sp) => { 135 const fd = getInt64(sp + 8); 136 const p = getInt64(sp + 16); 137 const n = mem().getInt32(sp + 24, true); 138 fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); 139 }, 140 141 // func nanotime() int64 142 "runtime.nanotime": (sp) => { 143 setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); 144 }, 145 146 // func walltime() (sec int64, nsec int32) 147 "runtime.walltime": (sp) => { 148 const msec = (new Date).getTime(); 149 setInt64(sp + 8, msec / 1000); 150 mem().setInt32(sp + 16, (msec % 1000) * 1000000, true); 151 }, 152 153 // func scheduleCallback(delay int64) int32 154 "runtime.scheduleCallback": (sp) => { 155 const id = this._nextCallbackTimeoutID; 156 this._nextCallbackTimeoutID++; 157 this._callbackTimeouts.set(id, setTimeout( 158 () => { this._resolveCallbackPromise(); }, 159 getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early 160 )); 161 mem().setInt32(sp + 16, id, true); 162 }, 163 164 // func clearScheduledCallback(id int32) 165 "runtime.clearScheduledCallback": (sp) => { 166 const id = mem().getInt32(sp + 8, true); 167 clearTimeout(this._callbackTimeouts.get(id)); 168 this._callbackTimeouts.delete(id); 169 }, 170 171 // func getRandomData(r []byte) 172 "runtime.getRandomData": (sp) => { 173 crypto.getRandomValues(loadSlice(sp + 8)); 174 }, 175 176 // func boolVal(value bool) ref 177 "syscall/js.boolVal": (sp) => { 178 storeValue(sp + 16, mem().getUint8(sp + 8) !== 0); 179 }, 180 181 // func intVal(value int) ref 182 "syscall/js.intVal": (sp) => { 183 storeValue(sp + 16, getInt64(sp + 8)); 184 }, 185 186 // func floatVal(value float64) ref 187 "syscall/js.floatVal": (sp) => { 188 storeValue(sp + 16, mem().getFloat64(sp + 8, true)); 189 }, 190 191 // func stringVal(value string) ref 192 "syscall/js.stringVal": (sp) => { 193 storeValue(sp + 24, loadString(sp + 8)); 194 }, 195 196 // func valueGet(v ref, p string) ref 197 "syscall/js.valueGet": (sp) => { 198 storeValue(sp + 32, Reflect.get(loadValue(sp + 8), loadString(sp + 16))); 199 }, 200 201 // func valueSet(v ref, p string, x ref) 202 "syscall/js.valueSet": (sp) => { 203 Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); 204 }, 205 206 // func valueIndex(v ref, i int) ref 207 "syscall/js.valueIndex": (sp) => { 208 storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); 209 }, 210 211 // valueSetIndex(v ref, i int, x ref) 212 "syscall/js.valueSetIndex": (sp) => { 213 Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); 214 }, 215 216 // func valueCall(v ref, m string, args []ref) (ref, bool) 217 "syscall/js.valueCall": (sp) => { 218 try { 219 const v = loadValue(sp + 8); 220 const m = Reflect.get(v, loadString(sp + 16)); 221 const args = loadSliceOfValues(sp + 32); 222 storeValue(sp + 56, Reflect.apply(m, v, args)); 223 mem().setUint8(sp + 60, 1); 224 } catch (err) { 225 storeValue(sp + 56, err); 226 mem().setUint8(sp + 60, 0); 227 } 228 }, 229 230 // func valueInvoke(v ref, args []ref) (ref, bool) 231 "syscall/js.valueInvoke": (sp) => { 232 try { 233 const v = loadValue(sp + 8); 234 const args = loadSliceOfValues(sp + 16); 235 storeValue(sp + 40, Reflect.apply(v, undefined, args)); 236 mem().setUint8(sp + 44, 1); 237 } catch (err) { 238 storeValue(sp + 40, err); 239 mem().setUint8(sp + 44, 0); 240 } 241 }, 242 243 // func valueNew(v ref, args []ref) (ref, bool) 244 "syscall/js.valueNew": (sp) => { 245 try { 246 const v = loadValue(sp + 8); 247 const args = loadSliceOfValues(sp + 16); 248 storeValue(sp + 40, Reflect.construct(v, args)); 249 mem().setUint8(sp + 44, 1); 250 } catch (err) { 251 storeValue(sp + 40, err); 252 mem().setUint8(sp + 44, 0); 253 } 254 }, 255 256 // func valueFloat(v ref) float64 257 "syscall/js.valueFloat": (sp) => { 258 mem().setFloat64(sp + 16, parseFloat(loadValue(sp + 8)), true); 259 }, 260 261 // func valueInt(v ref) int 262 "syscall/js.valueInt": (sp) => { 263 setInt64(sp + 16, parseInt(loadValue(sp + 8))); 264 }, 265 266 // func valueBool(v ref) bool 267 "syscall/js.valueBool": (sp) => { 268 mem().setUint8(sp + 16, !!loadValue(sp + 8)); 269 }, 270 271 // func valueLength(v ref) int 272 "syscall/js.valueLength": (sp) => { 273 setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); 274 }, 275 276 // valuePrepareString(v ref) (ref, int) 277 "syscall/js.valuePrepareString": (sp) => { 278 const str = encoder.encode(String(loadValue(sp + 8))); 279 storeValue(sp + 16, str); 280 setInt64(sp + 24, str.length); 281 }, 282 283 // valueLoadString(v ref, b []byte) 284 "syscall/js.valueLoadString": (sp) => { 285 const str = loadValue(sp + 8); 286 loadSlice(sp + 16).set(str); 287 }, 288 289 "debug": (value) => { 290 console.log(value); 291 }, 292 } 293 }; 294 } 295 296 async run(instance) { 297 this._inst = instance; 298 this._values = [ // TODO: garbage collection 299 undefined, 300 null, 301 global, 302 this._inst.exports.mem, 303 () => { // resolveCallbackPromise 304 if (this.exited) { 305 throw new Error("bad callback: Go program has already exited"); 306 } 307 setTimeout(this._resolveCallbackPromise, 0); // make sure it is asynchronous 308 }, 309 ]; 310 this.exited = false; 311 312 const mem = new DataView(this._inst.exports.mem.buffer) 313 314 // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. 315 let offset = 4096; 316 317 const strPtr = (str) => { 318 let ptr = offset; 319 new Uint8Array(mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0")); 320 offset += str.length + (8 - (str.length % 8)); 321 return ptr; 322 }; 323 324 const argc = this.argv.length; 325 326 const argvPtrs = []; 327 this.argv.forEach((arg) => { 328 argvPtrs.push(strPtr(arg)); 329 }); 330 331 const keys = Object.keys(this.env).sort(); 332 argvPtrs.push(keys.length); 333 keys.forEach((key) => { 334 argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); 335 }); 336 337 const argv = offset; 338 argvPtrs.forEach((ptr) => { 339 mem.setUint32(offset, ptr, true); 340 mem.setUint32(offset + 4, 0, true); 341 offset += 8; 342 }); 343 344 while (true) { 345 const callbackPromise = new Promise((resolve) => { 346 this._resolveCallbackPromise = resolve; 347 }); 348 this._inst.exports.run(argc, argv); 349 if (this.exited) { 350 break; 351 } 352 await callbackPromise; 353 } 354 } 355 } 356 357 if (isNodeJS) { 358 if (process.argv.length < 3) { 359 process.stderr.write("usage: go_js_wasm_exec [wasm binary] [arguments]\n"); 360 process.exit(1); 361 } 362 363 const go = new Go(); 364 go.argv = process.argv.slice(2); 365 go.env = process.env; 366 go.exit = process.exit; 367 WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { 368 process.on("exit", () => { // Node.js exits if no callback is pending 369 if (!go.exited) { 370 console.error("error: all goroutines asleep and no JavaScript callback pending - deadlock!"); 371 process.exit(1); 372 } 373 }); 374 return go.run(result.instance); 375 }).catch((err) => { 376 console.error(err); 377 go.exited = true; 378 process.exit(1); 379 }); 380 } 381 })();