github.com/vugu/vugu@v0.3.5/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 })();