state.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. "use strict";
  2. /** @const */
  3. var STATE_VERSION = 6;
  4. /** @const */
  5. var STATE_MAGIC = 0x86768676|0;
  6. /** @const */
  7. var STATE_INDEX_MAGIC = 0;
  8. /** @const */
  9. var STATE_INDEX_VERSION = 1;
  10. /** @const */
  11. var STATE_INDEX_TOTAL_LEN = 2;
  12. /** @const */
  13. var STATE_INDEX_INFO_LEN = 3;
  14. /** @const */
  15. var STATE_INFO_BLOCK_START = 16;
  16. const ZSTD_MAGIC = 0xFD2FB528;
  17. /** @constructor */
  18. function StateLoadError(msg)
  19. {
  20. this.message = msg;
  21. }
  22. StateLoadError.prototype = new Error;
  23. const CONSTRUCTOR_TABLE = {
  24. "Uint8Array": Uint8Array,
  25. "Int8Array": Int8Array,
  26. "Uint16Array": Uint16Array,
  27. "Int16Array": Int16Array,
  28. "Uint32Array": Uint32Array,
  29. "Int32Array": Int32Array,
  30. "Float32Array": Float32Array,
  31. "Float64Array": Float64Array,
  32. };
  33. function save_object(obj, saved_buffers)
  34. {
  35. if(typeof obj !== "object" || obj === null)
  36. {
  37. dbg_assert(typeof obj !== "function");
  38. return obj;
  39. }
  40. if(obj instanceof Array)
  41. {
  42. return obj.map(x => save_object(x, saved_buffers));
  43. }
  44. if(obj.constructor === Object)
  45. {
  46. console.log(obj);
  47. dbg_assert(obj.constructor !== Object, "Expected non-object");
  48. }
  49. if(obj.BYTES_PER_ELEMENT)
  50. {
  51. // Uint8Array, etc.
  52. var buffer = new Uint8Array(obj.buffer, obj.byteOffset, obj.length * obj.BYTES_PER_ELEMENT);
  53. const constructor = obj.constructor.name.replace("bound ", "");
  54. dbg_assert(CONSTRUCTOR_TABLE[constructor]);
  55. return {
  56. "__state_type__": constructor,
  57. "buffer_id": saved_buffers.push(buffer) - 1,
  58. };
  59. }
  60. if(DEBUG && !obj.get_state)
  61. {
  62. console.log("Object without get_state: ", obj);
  63. }
  64. var state = obj.get_state();
  65. var result = [];
  66. for(var i = 0; i < state.length; i++)
  67. {
  68. var value = state[i];
  69. dbg_assert(typeof value !== "function");
  70. result[i] = save_object(value, saved_buffers);
  71. }
  72. return result;
  73. }
  74. function restore_buffers(obj, buffers)
  75. {
  76. if(typeof obj !== "object" || obj === null)
  77. {
  78. dbg_assert(typeof obj !== "function");
  79. return obj;
  80. }
  81. if(obj instanceof Array)
  82. {
  83. for(let i = 0; i < obj.length; i++)
  84. {
  85. obj[i] = restore_buffers(obj[i], buffers);
  86. }
  87. return obj;
  88. }
  89. const type = obj["__state_type__"];
  90. dbg_assert(type !== undefined);
  91. const constructor = CONSTRUCTOR_TABLE[type];
  92. dbg_assert(constructor, "Unkown type: " + type);
  93. const buffer = buffers[obj["buffer_id"]];
  94. return new constructor(buffer);
  95. }
  96. CPU.prototype.save_state = function()
  97. {
  98. var saved_buffers = [];
  99. var state = save_object(this, saved_buffers);
  100. var buffer_infos = [];
  101. var total_buffer_size = 0;
  102. for(var i = 0; i < saved_buffers.length; i++)
  103. {
  104. var len = saved_buffers[i].byteLength;
  105. buffer_infos[i] = {
  106. offset: total_buffer_size,
  107. length: len,
  108. };
  109. total_buffer_size += len;
  110. // align
  111. total_buffer_size = total_buffer_size + 3 & ~3;
  112. }
  113. var info_object = JSON.stringify({
  114. "buffer_infos": buffer_infos,
  115. "state": state,
  116. });
  117. var info_block = new TextEncoder().encode(info_object);
  118. var buffer_block_start = STATE_INFO_BLOCK_START + info_block.length;
  119. buffer_block_start = buffer_block_start + 3 & ~3;
  120. var total_size = buffer_block_start + total_buffer_size;
  121. //console.log("State: json_size=" + Math.ceil(buffer_block_start / 1024 / 1024) + "MB " +
  122. // "buffer_size=" + Math.ceil(total_buffer_size / 1024 / 1024) + "MB");
  123. var result = new ArrayBuffer(total_size);
  124. var header_block = new Int32Array(
  125. result,
  126. 0,
  127. STATE_INFO_BLOCK_START / 4
  128. );
  129. new Uint8Array(result, STATE_INFO_BLOCK_START, info_block.length).set(info_block);
  130. var buffer_block = new Uint8Array(
  131. result,
  132. buffer_block_start
  133. );
  134. header_block[STATE_INDEX_MAGIC] = STATE_MAGIC;
  135. header_block[STATE_INDEX_VERSION] = STATE_VERSION;
  136. header_block[STATE_INDEX_TOTAL_LEN] = total_size;
  137. header_block[STATE_INDEX_INFO_LEN] = info_block.length;
  138. for(var i = 0; i < saved_buffers.length; i++)
  139. {
  140. var buffer = saved_buffers[i];
  141. dbg_assert(buffer.constructor === Uint8Array);
  142. buffer_block.set(buffer, buffer_infos[i].offset);
  143. }
  144. dbg_log("State: json size " + (info_block.byteLength >> 10) + "k");
  145. dbg_log("State: Total buffers size " + (buffer_block.byteLength >> 10) + "k");
  146. return result;
  147. };
  148. CPU.prototype.restore_state = function(state)
  149. {
  150. state = new Uint8Array(state);
  151. function read_state_header(state, check_length)
  152. {
  153. const len = state.length;
  154. if(len < STATE_INFO_BLOCK_START)
  155. {
  156. throw new StateLoadError("Invalid length: " + len);
  157. }
  158. const header_block = new Int32Array(state.buffer, state.byteOffset, 4);
  159. if(header_block[STATE_INDEX_MAGIC] !== STATE_MAGIC)
  160. {
  161. throw new StateLoadError("Invalid header: " + h(header_block[STATE_INDEX_MAGIC] >>> 0));
  162. }
  163. if(header_block[STATE_INDEX_VERSION] !== STATE_VERSION)
  164. {
  165. throw new StateLoadError(
  166. "Version mismatch: dump=" + header_block[STATE_INDEX_VERSION] +
  167. " we=" + STATE_VERSION);
  168. }
  169. if(check_length && header_block[STATE_INDEX_TOTAL_LEN] !== len)
  170. {
  171. throw new StateLoadError(
  172. "Length doesn't match header: " +
  173. "real=" + len + " header=" + header_block[STATE_INDEX_TOTAL_LEN]);
  174. }
  175. return header_block[STATE_INDEX_INFO_LEN];
  176. }
  177. function read_info_block(info_block_buffer)
  178. {
  179. const info_block = new TextDecoder().decode(info_block_buffer);
  180. return JSON.parse(info_block);
  181. }
  182. if(new Uint32Array(state.buffer, 0, 1)[0] === ZSTD_MAGIC)
  183. {
  184. const ctx = this.zstd_create_ctx(state.length);
  185. new Uint8Array(this.wasm_memory.buffer, this.zstd_get_src_ptr(ctx), state.length).set(state);
  186. let ptr = this.zstd_read(ctx, 16);
  187. const header_block = new Uint8Array(this.wasm_memory.buffer, ptr, 16);
  188. const info_block_len = read_state_header(header_block, false);
  189. this.zstd_read_free(ptr, 16);
  190. ptr = this.zstd_read(ctx, info_block_len);
  191. const info_block_buffer = new Uint8Array(this.wasm_memory.buffer, ptr, info_block_len);
  192. const info_block_obj = read_info_block(info_block_buffer);
  193. this.zstd_read_free(ptr, info_block_len);
  194. let state_object = info_block_obj["state"];
  195. const buffer_infos = info_block_obj["buffer_infos"];
  196. const buffers = [];
  197. let position = STATE_INFO_BLOCK_START + info_block_len;
  198. for(const buffer_info of buffer_infos)
  199. {
  200. const front_padding = (position + 3 & ~3) - position;
  201. const CHUNK_SIZE = 1 * 1024 * 1024;
  202. if(buffer_info.length > CHUNK_SIZE)
  203. {
  204. const ptr = this.zstd_read(ctx, front_padding);
  205. this.zstd_read_free(ptr, front_padding);
  206. const buffer = new Uint8Array(buffer_info.length);
  207. buffers.push(buffer.buffer);
  208. let have = 0;
  209. while(have < buffer_info.length)
  210. {
  211. const remaining = buffer_info.length - have;
  212. dbg_assert(remaining >= 0);
  213. const to_read = Math.min(remaining, CHUNK_SIZE);
  214. const ptr = this.zstd_read(ctx, to_read);
  215. buffer.set(new Uint8Array(this.wasm_memory.buffer, ptr, to_read), have);
  216. this.zstd_read_free(ptr, to_read);
  217. have += to_read;
  218. }
  219. }
  220. else
  221. {
  222. const ptr = this.zstd_read(ctx, front_padding + buffer_info.length);
  223. const offset = ptr + front_padding;
  224. buffers.push(this.wasm_memory.buffer.slice(offset, offset + buffer_info.length));
  225. this.zstd_read_free(ptr, front_padding + buffer_info.length);
  226. }
  227. position += front_padding + buffer_info.length;
  228. }
  229. state_object = restore_buffers(state_object, buffers);
  230. this.set_state(state_object);
  231. this.zstd_free_ctx(ctx);
  232. }
  233. else
  234. {
  235. const info_block_len = read_state_header(state, true);
  236. if(info_block_len < 0 || info_block_len + 12 >= state.length)
  237. {
  238. throw new StateLoadError("Invalid info block length: " + info_block_len);
  239. }
  240. const info_block_buffer = state.subarray(STATE_INFO_BLOCK_START, STATE_INFO_BLOCK_START + info_block_len);
  241. const info_block_obj = read_info_block(info_block_buffer);
  242. let state_object = info_block_obj["state"];
  243. const buffer_infos = info_block_obj["buffer_infos"];
  244. let buffer_block_start = STATE_INFO_BLOCK_START + info_block_len;
  245. buffer_block_start = buffer_block_start + 3 & ~3;
  246. const buffers = buffer_infos.map(buffer_info => {
  247. const offset = buffer_block_start + buffer_info.offset;
  248. return state.buffer.slice(offset, offset + buffer_info.length);
  249. });
  250. state_object = restore_buffers(state_object, buffers);
  251. this.set_state(state_object);
  252. }
  253. };