wasm_exec.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  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. // Map multiple JavaScript environments to a single common API,
  8. // preferring web standards over Node.js API.
  9. //
  10. // Environments considered:
  11. // - Browsers
  12. // - Node.js
  13. // - Electron
  14. // - Parcel
  15. if (typeof global !== 'undefined') {
  16. // global already exists
  17. } else if (typeof window !== 'undefined') {
  18. window.global = window
  19. } else if (typeof self !== 'undefined') {
  20. self.global = self
  21. } else {
  22. throw new Error('cannot export Go (neither global, window nor self is defined)')
  23. }
  24. if (!global.require && typeof require !== 'undefined') {
  25. global.require = require
  26. }
  27. if (!global.fs && global.require) {
  28. global.fs = require('fs')
  29. }
  30. const enosys = () => {
  31. const err = new Error('not implemented')
  32. err.code = 'ENOSYS'
  33. return err
  34. }
  35. if (!global.fs) {
  36. let outputBuf = ''
  37. global.fs = {
  38. constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
  39. writeSync(fd, buf) {
  40. outputBuf += decoder.decode(buf)
  41. const nl = outputBuf.lastIndexOf('\n')
  42. if (nl != -1) {
  43. console.log(outputBuf.substr(0, nl))
  44. outputBuf = outputBuf.substr(nl + 1)
  45. }
  46. return buf.length
  47. },
  48. write(fd, buf, offset, length, position, callback) {
  49. if (offset !== 0 || length !== buf.length || position !== null) {
  50. callback(enosys())
  51. return
  52. }
  53. const n = this.writeSync(fd, buf)
  54. callback(null, n)
  55. },
  56. chmod(path, mode, callback) {
  57. callback(enosys())
  58. },
  59. chown(path, uid, gid, callback) {
  60. callback(enosys())
  61. },
  62. close(fd, callback) {
  63. callback(enosys())
  64. },
  65. fchmod(fd, mode, callback) {
  66. callback(enosys())
  67. },
  68. fchown(fd, uid, gid, callback) {
  69. callback(enosys())
  70. },
  71. fstat(fd, callback) {
  72. callback(enosys())
  73. },
  74. fsync(fd, callback) {
  75. callback(null)
  76. },
  77. ftruncate(fd, length, callback) {
  78. callback(enosys())
  79. },
  80. lchown(path, uid, gid, callback) {
  81. callback(enosys())
  82. },
  83. link(path, link, callback) {
  84. callback(enosys())
  85. },
  86. lstat(path, callback) {
  87. callback(enosys())
  88. },
  89. mkdir(path, perm, callback) {
  90. callback(enosys())
  91. },
  92. open(path, flags, mode, callback) {
  93. callback(enosys())
  94. },
  95. read(fd, buffer, offset, length, position, callback) {
  96. callback(enosys())
  97. },
  98. readdir(path, callback) {
  99. callback(enosys())
  100. },
  101. readlink(path, callback) {
  102. callback(enosys())
  103. },
  104. rename(from, to, callback) {
  105. callback(enosys())
  106. },
  107. rmdir(path, callback) {
  108. callback(enosys())
  109. },
  110. stat(path, callback) {
  111. callback(enosys())
  112. },
  113. symlink(path, link, callback) {
  114. callback(enosys())
  115. },
  116. truncate(path, length, callback) {
  117. callback(enosys())
  118. },
  119. unlink(path, callback) {
  120. callback(enosys())
  121. },
  122. utimes(path, atime, mtime, callback) {
  123. callback(enosys())
  124. }
  125. }
  126. }
  127. if (!global.process) {
  128. global.process = {
  129. getuid() {
  130. return -1
  131. },
  132. getgid() {
  133. return -1
  134. },
  135. geteuid() {
  136. return -1
  137. },
  138. getegid() {
  139. return -1
  140. },
  141. getgroups() {
  142. throw enosys()
  143. },
  144. pid: -1,
  145. ppid: -1,
  146. umask() {
  147. throw enosys()
  148. },
  149. cwd() {
  150. throw enosys()
  151. },
  152. chdir() {
  153. throw enosys()
  154. }
  155. }
  156. }
  157. if (!global.crypto) {
  158. const nodeCrypto = require('crypto')
  159. global.crypto = {
  160. getRandomValues(b) {
  161. nodeCrypto.randomFillSync(b)
  162. }
  163. }
  164. }
  165. if (!global.performance) {
  166. global.performance = {
  167. now() {
  168. const [sec, nsec] = process.hrtime()
  169. return sec * 1000 + nsec / 1000000
  170. }
  171. }
  172. }
  173. if (!global.TextEncoder) {
  174. global.TextEncoder = require('util').TextEncoder
  175. }
  176. if (!global.TextDecoder) {
  177. global.TextDecoder = require('util').TextDecoder
  178. }
  179. // End of polyfills for common API.
  180. const encoder = new TextEncoder('utf-8')
  181. const decoder = new TextDecoder('utf-8')
  182. var logLine = []
  183. global.Go = class {
  184. constructor() {
  185. this._callbackTimeouts = new Map()
  186. this._nextCallbackTimeoutID = 1
  187. const mem = () => {
  188. // The buffer may change when requesting more memory.
  189. return new DataView(this._inst.exports.memory.buffer)
  190. }
  191. const setInt64 = (addr, v) => {
  192. mem().setUint32(addr + 0, v, true)
  193. mem().setUint32(addr + 4, Math.floor(v / 4294967296), true)
  194. }
  195. const getInt64 = (addr) => {
  196. const low = mem().getUint32(addr + 0, true)
  197. const high = mem().getInt32(addr + 4, true)
  198. return low + high * 4294967296
  199. }
  200. const loadValue = (addr) => {
  201. const f = mem().getFloat64(addr, true)
  202. if (f === 0) {
  203. return undefined
  204. }
  205. if (!isNaN(f)) {
  206. return f
  207. }
  208. const id = mem().getUint32(addr, true)
  209. return this._values[id]
  210. }
  211. const storeValue = (addr, v) => {
  212. const nanHead = 0x7ff80000
  213. if (typeof v === 'number') {
  214. if (isNaN(v)) {
  215. mem().setUint32(addr + 4, nanHead, true)
  216. mem().setUint32(addr, 0, true)
  217. return
  218. }
  219. if (v === 0) {
  220. mem().setUint32(addr + 4, nanHead, true)
  221. mem().setUint32(addr, 1, true)
  222. return
  223. }
  224. mem().setFloat64(addr, v, true)
  225. return
  226. }
  227. switch (v) {
  228. case undefined:
  229. mem().setFloat64(addr, 0, true)
  230. return
  231. case null:
  232. mem().setUint32(addr + 4, nanHead, true)
  233. mem().setUint32(addr, 2, true)
  234. return
  235. case true:
  236. mem().setUint32(addr + 4, nanHead, true)
  237. mem().setUint32(addr, 3, true)
  238. return
  239. case false:
  240. mem().setUint32(addr + 4, nanHead, true)
  241. mem().setUint32(addr, 4, true)
  242. return
  243. }
  244. let id = this._ids.get(v)
  245. if (id === undefined) {
  246. id = this._idPool.pop()
  247. if (id === undefined) {
  248. id = this._values.length
  249. }
  250. this._values[id] = v
  251. this._goRefCounts[id] = 0
  252. this._ids.set(v, id)
  253. }
  254. this._goRefCounts[id]++
  255. let typeFlag = 1
  256. switch (typeof v) {
  257. case 'string':
  258. typeFlag = 2
  259. break
  260. case 'symbol':
  261. typeFlag = 3
  262. break
  263. case 'function':
  264. typeFlag = 4
  265. break
  266. }
  267. mem().setUint32(addr + 4, nanHead | typeFlag, true)
  268. mem().setUint32(addr, id, true)
  269. }
  270. const loadSlice = (array, len, cap) => {
  271. return new Uint8Array(this._inst.exports.memory.buffer, array, len)
  272. }
  273. const loadSliceOfValues = (array, len, cap) => {
  274. const a = new Array(len)
  275. for (let i = 0; i < len; i++) {
  276. a[i] = loadValue(array + i * 8)
  277. }
  278. return a
  279. }
  280. const loadString = (ptr, len) => {
  281. return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len))
  282. }
  283. const timeOrigin = Date.now() - performance.now()
  284. this.importObject = {
  285. wasi_snapshot_preview1: {
  286. // https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write
  287. fd_write: function (fd, iovs_ptr, iovs_len, nwritten_ptr) {
  288. let nwritten = 0
  289. if (fd == 1) {
  290. for (let iovs_i = 0; iovs_i < iovs_len; iovs_i++) {
  291. let iov_ptr = iovs_ptr + iovs_i * 8 // assuming wasm32
  292. let ptr = mem().getUint32(iov_ptr + 0, true)
  293. let len = mem().getUint32(iov_ptr + 4, true)
  294. nwritten += len
  295. for (let i = 0; i < len; i++) {
  296. let c = mem().getUint8(ptr + i)
  297. if (c == 13) {
  298. // CR
  299. // ignore
  300. } else if (c == 10) {
  301. // LF
  302. // write line
  303. let line = decoder.decode(new Uint8Array(logLine))
  304. logLine = []
  305. console.log(line)
  306. } else {
  307. logLine.push(c)
  308. }
  309. }
  310. }
  311. } else {
  312. console.error('invalid file descriptor:', fd)
  313. }
  314. mem().setUint32(nwritten_ptr, nwritten, true)
  315. return 0
  316. },
  317. fd_close: () => 0, // dummy
  318. fd_fdstat_get: () => 0, // dummy
  319. fd_seek: () => 0, // dummy
  320. proc_exit: (code) => {
  321. if (global.process) {
  322. // Node.js
  323. process.exit(code)
  324. } else {
  325. // Can't exit in a browser.
  326. throw 'trying to exit with code ' + code
  327. }
  328. },
  329. random_get: (bufPtr, bufLen) => {
  330. crypto.getRandomValues(loadSlice(bufPtr, bufLen))
  331. return 0
  332. }
  333. },
  334. env: {
  335. // func ticks() float64
  336. 'runtime.ticks': () => {
  337. return timeOrigin + performance.now()
  338. },
  339. // func sleepTicks(timeout float64)
  340. 'runtime.sleepTicks': (timeout) => {
  341. // Do not sleep, only reactivate scheduler after the given timeout.
  342. setTimeout(this._inst.exports.go_scheduler, timeout)
  343. },
  344. // func finalizeRef(v ref)
  345. 'syscall/js.finalizeRef': (v_addr) => {
  346. // Note: TinyGo does not support finalizers so this is only called
  347. // for one specific case, by js.go:jsString.
  348. const id = mem().getUint32(v_addr, true)
  349. this._goRefCounts[id]--
  350. if (this._goRefCounts[id] === 0) {
  351. const v = this._values[id]
  352. this._values[id] = null
  353. this._ids.delete(v)
  354. this._idPool.push(id)
  355. }
  356. },
  357. // func stringVal(value string) ref
  358. 'syscall/js.stringVal': (ret_ptr, value_ptr, value_len) => {
  359. const s = loadString(value_ptr, value_len)
  360. storeValue(ret_ptr, s)
  361. },
  362. // func valueGet(v ref, p string) ref
  363. 'syscall/js.valueGet': (retval, v_addr, p_ptr, p_len) => {
  364. let prop = loadString(p_ptr, p_len)
  365. let value = loadValue(v_addr)
  366. let result = Reflect.get(value, prop)
  367. storeValue(retval, result)
  368. },
  369. // func valueSet(v ref, p string, x ref)
  370. 'syscall/js.valueSet': (v_addr, p_ptr, p_len, x_addr) => {
  371. const v = loadValue(v_addr)
  372. const p = loadString(p_ptr, p_len)
  373. const x = loadValue(x_addr)
  374. Reflect.set(v, p, x)
  375. },
  376. // func valueDelete(v ref, p string)
  377. 'syscall/js.valueDelete': (v_addr, p_ptr, p_len) => {
  378. const v = loadValue(v_addr)
  379. const p = loadString(p_ptr, p_len)
  380. Reflect.deleteProperty(v, p)
  381. },
  382. // func valueIndex(v ref, i int) ref
  383. 'syscall/js.valueIndex': (ret_addr, v_addr, i) => {
  384. storeValue(ret_addr, Reflect.get(loadValue(v_addr), i))
  385. },
  386. // valueSetIndex(v ref, i int, x ref)
  387. 'syscall/js.valueSetIndex': (v_addr, i, x_addr) => {
  388. Reflect.set(loadValue(v_addr), i, loadValue(x_addr))
  389. },
  390. // func valueCall(v ref, m string, args []ref) (ref, bool)
  391. 'syscall/js.valueCall': (ret_addr, v_addr, m_ptr, m_len, args_ptr, args_len, args_cap) => {
  392. const v = loadValue(v_addr)
  393. const name = loadString(m_ptr, m_len)
  394. const args = loadSliceOfValues(args_ptr, args_len, args_cap)
  395. try {
  396. const m = Reflect.get(v, name)
  397. storeValue(ret_addr, Reflect.apply(m, v, args))
  398. mem().setUint8(ret_addr + 8, 1)
  399. } catch (err) {
  400. storeValue(ret_addr, err)
  401. mem().setUint8(ret_addr + 8, 0)
  402. }
  403. },
  404. // func valueInvoke(v ref, args []ref) (ref, bool)
  405. 'syscall/js.valueInvoke': (ret_addr, v_addr, args_ptr, args_len, args_cap) => {
  406. try {
  407. const v = loadValue(v_addr)
  408. const args = loadSliceOfValues(args_ptr, args_len, args_cap)
  409. storeValue(ret_addr, Reflect.apply(v, undefined, args))
  410. mem().setUint8(ret_addr + 8, 1)
  411. } catch (err) {
  412. storeValue(ret_addr, err)
  413. mem().setUint8(ret_addr + 8, 0)
  414. }
  415. },
  416. // func valueNew(v ref, args []ref) (ref, bool)
  417. 'syscall/js.valueNew': (ret_addr, v_addr, args_ptr, args_len, args_cap) => {
  418. const v = loadValue(v_addr)
  419. const args = loadSliceOfValues(args_ptr, args_len, args_cap)
  420. try {
  421. storeValue(ret_addr, Reflect.construct(v, args))
  422. mem().setUint8(ret_addr + 8, 1)
  423. } catch (err) {
  424. storeValue(ret_addr, err)
  425. mem().setUint8(ret_addr + 8, 0)
  426. }
  427. },
  428. // func valueLength(v ref) int
  429. 'syscall/js.valueLength': (v_addr) => {
  430. return loadValue(v_addr).length
  431. },
  432. // valuePrepareString(v ref) (ref, int)
  433. 'syscall/js.valuePrepareString': (ret_addr, v_addr) => {
  434. const s = String(loadValue(v_addr))
  435. const str = encoder.encode(s)
  436. storeValue(ret_addr, str)
  437. setInt64(ret_addr + 8, str.length)
  438. },
  439. // valueLoadString(v ref, b []byte)
  440. 'syscall/js.valueLoadString': (v_addr, slice_ptr, slice_len, slice_cap) => {
  441. const str = loadValue(v_addr)
  442. loadSlice(slice_ptr, slice_len, slice_cap).set(str)
  443. },
  444. // func valueInstanceOf(v ref, t ref) bool
  445. 'syscall/js.valueInstanceOf': (v_addr, t_addr) => {
  446. return loadValue(v_addr) instanceof loadValue(t_addr)
  447. },
  448. // func copyBytesToGo(dst []byte, src ref) (int, bool)
  449. 'syscall/js.copyBytesToGo': (ret_addr, dest_addr, dest_len, dest_cap, source_addr) => {
  450. let num_bytes_copied_addr = ret_addr
  451. let returned_status_addr = ret_addr + 4 // Address of returned boolean status variable
  452. const dst = loadSlice(dest_addr, dest_len)
  453. const src = loadValue(source_addr)
  454. if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
  455. mem().setUint8(returned_status_addr, 0) // Return "not ok" status
  456. return
  457. }
  458. const toCopy = src.subarray(0, dst.length)
  459. dst.set(toCopy)
  460. setInt64(num_bytes_copied_addr, toCopy.length)
  461. mem().setUint8(returned_status_addr, 1) // Return "ok" status
  462. },
  463. // copyBytesToJS(dst ref, src []byte) (int, bool)
  464. // Originally copied from upstream Go project, then modified:
  465. // https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416
  466. 'syscall/js.copyBytesToJS': (ret_addr, dest_addr, source_addr, source_len, source_cap) => {
  467. let num_bytes_copied_addr = ret_addr
  468. let returned_status_addr = ret_addr + 4 // Address of returned boolean status variable
  469. const dst = loadValue(dest_addr)
  470. const src = loadSlice(source_addr, source_len)
  471. if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
  472. mem().setUint8(returned_status_addr, 0) // Return "not ok" status
  473. return
  474. }
  475. const toCopy = src.subarray(0, dst.length)
  476. dst.set(toCopy)
  477. setInt64(num_bytes_copied_addr, toCopy.length)
  478. mem().setUint8(returned_status_addr, 1) // Return "ok" status
  479. }
  480. }
  481. }
  482. }
  483. async run(instance) {
  484. this._inst = instance
  485. this._values = [
  486. // JS values that Go currently has references to, indexed by reference id
  487. NaN,
  488. 0,
  489. null,
  490. true,
  491. false,
  492. global,
  493. this
  494. ]
  495. this._goRefCounts = [] // number of references that Go has to a JS value, indexed by reference id
  496. this._ids = new Map() // mapping from JS values to reference ids
  497. this._idPool = [] // unused ids that have been garbage collected
  498. this.exited = false // whether the Go program has exited
  499. const mem = new DataView(this._inst.exports.memory.buffer)
  500. while (true) {
  501. const callbackPromise = new Promise((resolve) => {
  502. this._resolveCallbackPromise = () => {
  503. if (this.exited) {
  504. throw new Error('bad callback: Go program has already exited')
  505. }
  506. setTimeout(resolve, 0) // make sure it is asynchronous
  507. }
  508. })
  509. this._inst.exports._start()
  510. if (this.exited) {
  511. break
  512. }
  513. await callbackPromise
  514. }
  515. }
  516. _resume() {
  517. if (this.exited) {
  518. throw new Error('Go program has already exited')
  519. }
  520. this._inst.exports.resume()
  521. if (this.exited) {
  522. this._resolveExitPromise()
  523. }
  524. }
  525. _makeFuncWrapper(id) {
  526. const go = this
  527. return function () {
  528. const event = { id: id, this: this, args: arguments }
  529. go._pendingEvent = event
  530. go._resume()
  531. return event.result
  532. }
  533. }
  534. }
  535. if (
  536. global.require &&
  537. global.require.main === module &&
  538. global.process &&
  539. global.process.versions &&
  540. !global.process.versions.electron
  541. ) {
  542. if (process.argv.length != 3) {
  543. console.error('usage: go_js_wasm_exec [wasm binary] [arguments]')
  544. process.exit(1)
  545. }
  546. const go = new Go()
  547. WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject)
  548. .then((result) => {
  549. return go.run(result.instance)
  550. })
  551. .catch((err) => {
  552. console.error(err)
  553. process.exit(1)
  554. })
  555. }
  556. })()