lib.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. "use strict";
  2. /** @const */
  3. var ASYNC_SAFE = false;
  4. (function()
  5. {
  6. if(typeof XMLHttpRequest === "undefined")
  7. {
  8. v86util.load_file = load_file_nodejs;
  9. }
  10. else
  11. {
  12. v86util.load_file = load_file;
  13. }
  14. v86util.AsyncXHRBuffer = AsyncXHRBuffer;
  15. v86util.AsyncFileBuffer = AsyncFileBuffer;
  16. v86util.SyncFileBuffer = SyncFileBuffer;
  17. /**
  18. * Decode a buffer into an unsigned LEB-128 integer
  19. * @param {Uint8Array} view Byte-stream of encoded integer
  20. * @param {number=} max_bits Maximum number of bits that are represented; see
  21. * https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#varuintn
  22. */
  23. v86util.decode_leb128_u = function(view, max_bits=256)
  24. {
  25. dbg_assert(view instanceof Uint8Array);
  26. const result = {
  27. value: 0,
  28. next_index: 0,
  29. };
  30. let shift = 0;
  31. const max_bytes = Math.ceil(max_bits / 7);
  32. while(result.next_index < view.length && result.next_index < max_bytes)
  33. {
  34. let byte = view[result.next_index++];
  35. result.value |= (byte & 127) << shift;
  36. if((byte & 128) === 0)
  37. {
  38. break;
  39. }
  40. shift += 7;
  41. }
  42. return result;
  43. };
  44. v86util.decode_dylink = function(module)
  45. {
  46. // Details on dylink section:
  47. // https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md
  48. const dylink_sections = WebAssembly.Module.customSections(module, "dylink");
  49. dbg_assert(dylink_sections && dylink_sections.length === 1);
  50. const dylink_section = dylink_sections[0];
  51. const view = new Uint8Array(dylink_section);
  52. const { value: memory_size, next_index: table_size_start } =
  53. v86util.decode_leb128_u(view, 32);
  54. const table_size = v86util.decode_leb128_u(view.subarray(table_size_start), 32).value;
  55. return {
  56. memory_size,
  57. table_size,
  58. };
  59. };
  60. // Reads len characters at offset from Memory object mem as a JS string
  61. v86util.read_sized_string_from_mem = function read_sized_string_from_mem(mem, offset, len)
  62. {
  63. return String.fromCharCode(...new Uint8Array(mem.buffer, offset, len));
  64. };
  65. //XXX: figure out a better way to handle dylink issue than duplicating above function
  66. v86util.minimal_load_wasm = function minimal_load_wasm(filename, imports, cb)
  67. {
  68. function load_cb(bytes)
  69. {
  70. WebAssembly
  71. .instantiate(bytes, imports)
  72. .then(function({ instance }) {
  73. cb({
  74. memory: imports["env"]["memory"],
  75. exports: instance["exports"],
  76. instance,
  77. imports,
  78. filename,
  79. });
  80. });
  81. }
  82. v86util.load_file(filename, { done: load_cb });
  83. };
  84. /**
  85. * Fetches, compiles, and instantiates a wasm file
  86. * @param {string} filename
  87. * @param {Object} imports Object used for WebAssembly module's imports
  88. * @param {number} memory_size Bytes of memory the module wants for itself, excluding the space
  89. * the dylink section requests.
  90. * @param {number} table_size Number of table entries the module wants for itself, excluding
  91. * what the dylink section requests.
  92. * @param {function(Object)} cb Callback function that receives custom object with instance, memory,
  93. * exported functions, imports, and the filename.
  94. */
  95. v86util.load_wasm = function load_wasm(filename, imports, memory_size, table_size, cb)
  96. {
  97. dbg_assert(memory_size > 0);
  98. dbg_assert(typeof imports["env"] === "object");
  99. function load_cb(buffer)
  100. {
  101. WebAssembly.compile(buffer)
  102. .then(module => {
  103. const dylink = v86util.decode_dylink(module);
  104. let total_mem_pages = Math.ceil(
  105. (dylink.memory_size + memory_size) / WASM_PAGE_SIZE
  106. );
  107. // emscripten seems to require a minimum of 256 pages (16 MB)
  108. total_mem_pages = Math.max(256, total_mem_pages);
  109. try
  110. {
  111. imports["env"]["memory"] = new WebAssembly.Memory({
  112. "initial": total_mem_pages,
  113. "maximum": total_mem_pages,
  114. });
  115. }
  116. catch(e)
  117. {
  118. console.error(
  119. "Failed to allocate WASM memory of %d pages",
  120. total_mem_pages
  121. );
  122. throw e;
  123. }
  124. imports["env"]["memoryBase"] = memory_size;
  125. // XXX: Emscripten forces EMULATED_FUNCTION_POINTERS when
  126. // using SIDE_MODULE=1, which we use. Newer versions of emscripten add
  127. // all exported functions to the WebAssembly.Table, so we need extra space
  128. // here
  129. const EXTRA_TABLE_SPACE_FOR_EMULATED_FP = 10000;
  130. imports["env"][WASM_EXPORT_TABLE_NAME] = new WebAssembly.Table({
  131. "initial": dylink.table_size + table_size + EXTRA_TABLE_SPACE_FOR_EMULATED_FP,
  132. "element": "anyfunc",
  133. });
  134. imports["env"]["tableBase"] = table_size;
  135. return WebAssembly.instantiate(module, imports)
  136. .then(instance => ({ instance, module }));
  137. })
  138. .then(({ instance, module }) => {
  139. cb({
  140. memory: imports["env"]["memory"],
  141. exports: instance["exports"],
  142. instance,
  143. imports,
  144. filename,
  145. });
  146. });
  147. }
  148. v86util.load_file(filename, { done: load_cb });
  149. };
  150. /**
  151. * @param {string} filename
  152. * @param {Object} options
  153. */
  154. function load_file(filename, options)
  155. {
  156. var http = new XMLHttpRequest();
  157. http.open(options.method || "get", filename, true);
  158. if(!options.as_text)
  159. {
  160. http.responseType = "arraybuffer";
  161. }
  162. if(options.headers)
  163. {
  164. var header_names = Object.keys(options.headers);
  165. for(var i = 0; i < header_names.length; i++)
  166. {
  167. var name = header_names[i];
  168. http.setRequestHeader(name, options.headers[name]);
  169. }
  170. }
  171. if(options.range)
  172. {
  173. let start = options.range.start;
  174. let end = start + options.range.length - 1;
  175. http.setRequestHeader("Range", "bytes=" + start + "-" + end);
  176. }
  177. http.onload = function(e)
  178. {
  179. if(http.readyState === 4)
  180. {
  181. if(http.status !== 200 && http.status !== 206)
  182. {
  183. console.error("Loading the image `" + filename + "` failed (status %d)", http.status);
  184. }
  185. else if(http.response)
  186. {
  187. options.done && options.done(http.response, http);
  188. }
  189. }
  190. };
  191. if(options.progress)
  192. {
  193. http.onprogress = function(e)
  194. {
  195. options.progress(e);
  196. };
  197. }
  198. http.send(null);
  199. }
  200. function load_file_nodejs(filename, options)
  201. {
  202. let fs = require("fs");
  203. if(options.range)
  204. {
  205. dbg_assert(!options.as_text);
  206. fs["open"](filename, "r", (err, fd) =>
  207. {
  208. if(err) throw err;
  209. let length = options.range.length;
  210. var buffer = Buffer.allocUnsafe(length);
  211. fs["read"](fd, buffer, 0, length, options.range.start, (err, bytes_read) =>
  212. {
  213. if(err) throw err;
  214. dbg_assert(bytes_read === length);
  215. options.done && options.done(new Uint8Array(buffer));
  216. fs["close"](fd, (err) => {
  217. if(err) throw err;
  218. });
  219. });
  220. });
  221. }
  222. else
  223. {
  224. var o = {
  225. encoding: options.as_text ? "utf-8" : null,
  226. };
  227. fs["readFile"](filename, o, function(err, data)
  228. {
  229. if(err)
  230. {
  231. console.log("Could not read file:", filename, err);
  232. }
  233. else
  234. {
  235. var result = data;
  236. if(!options.as_text)
  237. {
  238. result = new Uint8Array(result).buffer;
  239. }
  240. options.done(result);
  241. }
  242. });
  243. }
  244. }
  245. if(typeof XMLHttpRequest === "undefined")
  246. {
  247. var determine_size = function(path, cb)
  248. {
  249. require("fs")["stat"](path, (err, stats) =>
  250. {
  251. if(err)
  252. {
  253. cb(err);
  254. }
  255. else
  256. {
  257. cb(null, stats.size);
  258. }
  259. });
  260. };
  261. }
  262. else
  263. {
  264. var determine_size = function(url, cb)
  265. {
  266. v86util.load_file(url, {
  267. done: (buffer, http) =>
  268. {
  269. var header = http.getResponseHeader("Content-Range") || "";
  270. var match = header.match(/\/(\d+)\s*$/);
  271. if(match)
  272. {
  273. cb(null, +match[1]);
  274. }
  275. else
  276. {
  277. const error = "`Range: bytes=...` header not supported (Got `" + header + "`)";
  278. cb(error);
  279. }
  280. },
  281. headers: {
  282. Range: "bytes=0-0",
  283. //"Accept-Encoding": "",
  284. // Added by Chromium, but can cause the whole file to be sent
  285. // Settings this to empty also causes problems and Chromium
  286. // doesn't seem to create this header any more
  287. //"If-Range": "",
  288. }
  289. });
  290. };
  291. }
  292. /**
  293. * Asynchronous access to ArrayBuffer, loading blocks lazily as needed,
  294. * using the `Range: bytes=...` header
  295. *
  296. * @constructor
  297. * @param {string} filename Name of the file to download
  298. * @param {number|undefined} size
  299. */
  300. function AsyncXHRBuffer(filename, size)
  301. {
  302. this.filename = filename;
  303. /** @const */
  304. this.block_size = 256;
  305. this.byteLength = size;
  306. this.loaded_blocks = Object.create(null);
  307. this.onload = undefined;
  308. this.onprogress = undefined;
  309. }
  310. AsyncXHRBuffer.prototype.load = function()
  311. {
  312. if(this.byteLength !== undefined)
  313. {
  314. this.onload && this.onload(Object.create(null));
  315. return;
  316. }
  317. // Determine the size using a request
  318. determine_size(this.filename, (error, size) =>
  319. {
  320. if(error)
  321. {
  322. console.assert(false, "Cannot use: " + this.filename + ". " + error);
  323. }
  324. else
  325. {
  326. dbg_assert(size >= 0);
  327. this.byteLength = size;
  328. this.onload && this.onload(Object.create(null));
  329. }
  330. });
  331. };
  332. /**
  333. * @param {number} offset
  334. * @param {number} len
  335. * @param {function(!Uint8Array)} fn
  336. */
  337. AsyncXHRBuffer.prototype.get_from_cache = function(offset, len, fn)
  338. {
  339. var number_of_blocks = len / this.block_size;
  340. var block_index = offset / this.block_size;
  341. for(var i = 0; i < number_of_blocks; i++)
  342. {
  343. var block = this.loaded_blocks[block_index + i];
  344. if(!block)
  345. {
  346. return;
  347. }
  348. }
  349. if(number_of_blocks === 1)
  350. {
  351. return this.loaded_blocks[block_index];
  352. }
  353. else
  354. {
  355. var result = new Uint8Array(len);
  356. for(var i = 0; i < number_of_blocks; i++)
  357. {
  358. result.set(this.loaded_blocks[block_index + i], i * this.block_size);
  359. }
  360. return result;
  361. }
  362. };
  363. /**
  364. * @param {number} offset
  365. * @param {number} len
  366. * @param {function(!Uint8Array)} fn
  367. */
  368. AsyncXHRBuffer.prototype.get = function(offset, len, fn)
  369. {
  370. console.assert(offset + len <= this.byteLength);
  371. console.assert(offset % this.block_size === 0);
  372. console.assert(len % this.block_size === 0);
  373. console.assert(len);
  374. var block = this.get_from_cache(offset, len, fn);
  375. if(block)
  376. {
  377. if(ASYNC_SAFE)
  378. {
  379. setTimeout(fn.bind(this, block), 0);
  380. }
  381. else
  382. {
  383. fn(block);
  384. }
  385. return;
  386. }
  387. v86util.load_file(this.filename, {
  388. done: function done(buffer)
  389. {
  390. var block = new Uint8Array(buffer);
  391. this.handle_read(offset, len, block);
  392. fn(block);
  393. }.bind(this),
  394. range: { start: offset, length: len },
  395. });
  396. };
  397. /**
  398. * Relies on this.byteLength, this.loaded_blocks and this.block_size
  399. *
  400. * @this {AsyncFileBuffer|AsyncXHRBuffer}
  401. *
  402. * @param {number} start
  403. * @param {!Uint8Array} data
  404. * @param {function()} fn
  405. */
  406. AsyncXHRBuffer.prototype.set = function(start, data, fn)
  407. {
  408. console.assert(start + data.byteLength <= this.byteLength);
  409. var len = data.length;
  410. console.assert(start % this.block_size === 0);
  411. console.assert(len % this.block_size === 0);
  412. console.assert(len);
  413. var start_block = start / this.block_size;
  414. var block_count = len / this.block_size;
  415. for(var i = 0; i < block_count; i++)
  416. {
  417. var block = this.loaded_blocks[start_block + i];
  418. if(block === undefined)
  419. {
  420. block = this.loaded_blocks[start_block + i] = new Uint8Array(this.block_size);
  421. }
  422. var data_slice = data.subarray(i * this.block_size, (i + 1) * this.block_size);
  423. block.set(data_slice);
  424. console.assert(block.byteLength === data_slice.length);
  425. }
  426. fn();
  427. };
  428. /**
  429. * @this {AsyncFileBuffer|AsyncXHRBuffer}
  430. * @param {number} offset
  431. * @param {number} len
  432. * @param {!Uint8Array} block
  433. */
  434. AsyncXHRBuffer.prototype.handle_read = function(offset, len, block)
  435. {
  436. // Used by AsyncXHRBuffer and AsyncFileBuffer
  437. // Overwrites blocks from the original source that have been written since
  438. var start_block = offset / this.block_size;
  439. var block_count = len / this.block_size;
  440. for(var i = 0; i < block_count; i++)
  441. {
  442. var written_block = this.loaded_blocks[start_block + i];
  443. if(written_block)
  444. {
  445. block.set(written_block, i * this.block_size);
  446. }
  447. //else
  448. //{
  449. // var cached = this.loaded_blocks[start_block + i] = new Uint8Array(this.block_size);
  450. // cached.set(block.subarray(i * this.block_size, (i + 1) * this.block_size));
  451. //}
  452. }
  453. };
  454. AsyncXHRBuffer.prototype.get_buffer = function(fn)
  455. {
  456. // We must download all parts, unlikely a good idea for big files
  457. fn();
  458. };
  459. AsyncXHRBuffer.prototype.get_written_blocks = function()
  460. {
  461. var count = 0;
  462. for(var _ in this.loaded_blocks)
  463. {
  464. count++;
  465. }
  466. var buffer = new Uint8Array(count * this.block_size);
  467. var indices = [];
  468. var i = 0;
  469. for(var index in this.loaded_blocks)
  470. {
  471. var block = this.loaded_blocks[index];
  472. dbg_assert(block.length === this.block_size);
  473. index = +index;
  474. indices.push(index);
  475. buffer.set(
  476. block,
  477. i * this.block_size
  478. );
  479. i++;
  480. }
  481. return {
  482. buffer,
  483. indices,
  484. block_size: this.block_size,
  485. };
  486. };
  487. AsyncXHRBuffer.prototype.get_state = function()
  488. {
  489. const state = [];
  490. const loaded_blocks = [];
  491. for(let [index, block] in Object.values(this.loaded_blocks))
  492. {
  493. dbg_assert(isFinite(+index));
  494. loaded_blocks.push([+index, block]);
  495. }
  496. state[0] = loaded_blocks;
  497. return state;
  498. };
  499. AsyncXHRBuffer.prototype.set_state = function(state)
  500. {
  501. const loaded_blocks = state[0];
  502. this.loaded_blocks = Object.create(null);
  503. for(let [index, block] of loaded_blocks)
  504. {
  505. this.loaded_blocks[index] = block;
  506. }
  507. };
  508. /**
  509. * Synchronous access to File, loading blocks from the input type=file
  510. * The whole file is loaded into memory during initialisation
  511. *
  512. * @constructor
  513. */
  514. function SyncFileBuffer(file)
  515. {
  516. this.file = file;
  517. this.byteLength = file.size;
  518. if(file.size > (1 << 30))
  519. {
  520. console.warn("SyncFileBuffer: Allocating buffer of " + (file.size >> 20) + " MB ...");
  521. }
  522. this.buffer = new ArrayBuffer(file.size);
  523. this.onload = undefined;
  524. this.onprogress = undefined;
  525. }
  526. SyncFileBuffer.prototype.load = function()
  527. {
  528. this.load_next(0);
  529. };
  530. /**
  531. * @param {number} start
  532. */
  533. SyncFileBuffer.prototype.load_next = function(start)
  534. {
  535. /** @const */
  536. var PART_SIZE = 4 << 20;
  537. var filereader = new FileReader();
  538. filereader.onload = function(e)
  539. {
  540. var buffer = new Uint8Array(e.target.result);
  541. new Uint8Array(this.buffer, start).set(buffer);
  542. this.load_next(start + PART_SIZE);
  543. }.bind(this);
  544. if(this.onprogress)
  545. {
  546. this.onprogress({
  547. loaded: start,
  548. total: this.byteLength,
  549. lengthComputable: true,
  550. });
  551. }
  552. if(start < this.byteLength)
  553. {
  554. var end = Math.min(start + PART_SIZE, this.byteLength);
  555. var slice = this.file.slice(start, end);
  556. filereader.readAsArrayBuffer(slice);
  557. }
  558. else
  559. {
  560. this.file = undefined;
  561. this.onload && this.onload({ buffer: this.buffer });
  562. }
  563. };
  564. /**
  565. * @param {number} start
  566. * @param {number} len
  567. * @param {function(!Uint8Array)} fn
  568. */
  569. SyncFileBuffer.prototype.get = function(start, len, fn)
  570. {
  571. console.assert(start + len <= this.byteLength);
  572. fn(new Uint8Array(this.buffer, start, len));
  573. };
  574. /**
  575. * @param {number} offset
  576. * @param {!Uint8Array} slice
  577. * @param {function()} fn
  578. */
  579. SyncFileBuffer.prototype.set = function(offset, slice, fn)
  580. {
  581. console.assert(offset + slice.byteLength <= this.byteLength);
  582. new Uint8Array(this.buffer, offset, slice.byteLength).set(slice);
  583. fn();
  584. };
  585. SyncFileBuffer.prototype.get_buffer = function(fn)
  586. {
  587. fn(this.buffer);
  588. };
  589. /**
  590. * Asynchronous access to File, loading blocks from the input type=file
  591. *
  592. * @constructor
  593. */
  594. function AsyncFileBuffer(file)
  595. {
  596. this.file = file;
  597. this.byteLength = file.size;
  598. /** @const */
  599. this.block_size = 256;
  600. this.loaded_blocks = Object.create(null);
  601. this.onload = undefined;
  602. this.onprogress = undefined;
  603. }
  604. AsyncFileBuffer.prototype.load = function()
  605. {
  606. this.onload && this.onload(Object.create(null));
  607. };
  608. /**
  609. * @param {number} offset
  610. * @param {number} len
  611. * @param {function(!Uint8Array)} fn
  612. */
  613. AsyncFileBuffer.prototype.get = function(offset, len, fn)
  614. {
  615. console.assert(offset % this.block_size === 0);
  616. console.assert(len % this.block_size === 0);
  617. console.assert(len);
  618. var block = this.get_from_cache(offset, len, fn);
  619. if(block)
  620. {
  621. fn(block);
  622. return;
  623. }
  624. var fr = new FileReader();
  625. fr.onload = function(e)
  626. {
  627. var buffer = e.target.result;
  628. var block = new Uint8Array(buffer);
  629. this.handle_read(offset, len, block);
  630. fn(block);
  631. }.bind(this);
  632. fr.readAsArrayBuffer(this.file.slice(offset, offset + len));
  633. };
  634. AsyncFileBuffer.prototype.get_from_cache = AsyncXHRBuffer.prototype.get_from_cache;
  635. AsyncFileBuffer.prototype.set = AsyncXHRBuffer.prototype.set;
  636. AsyncFileBuffer.prototype.handle_read = AsyncXHRBuffer.prototype.handle_read;
  637. AsyncFileBuffer.prototype.get_buffer = function(fn)
  638. {
  639. // We must load all parts, unlikely a good idea for big files
  640. fn();
  641. };
  642. AsyncFileBuffer.prototype.get_as_file = function(name)
  643. {
  644. var parts = [];
  645. var existing_blocks = Object.keys(this.loaded_blocks)
  646. .map(Number)
  647. .sort(function(x, y) { return x - y; });
  648. var current_offset = 0;
  649. for(var i = 0; i < existing_blocks.length; i++)
  650. {
  651. var block_index = existing_blocks[i];
  652. var block = this.loaded_blocks[block_index];
  653. var start = block_index * this.block_size;
  654. console.assert(start >= current_offset);
  655. if(start !== current_offset)
  656. {
  657. parts.push(this.file.slice(current_offset, start));
  658. current_offset = start;
  659. }
  660. parts.push(block);
  661. current_offset += block.length;
  662. }
  663. if(current_offset !== this.file.size)
  664. {
  665. parts.push(this.file.slice(current_offset));
  666. }
  667. var file = new File(parts, name);
  668. console.assert(file.size === this.file.size);
  669. return file;
  670. };
  671. })();