Spaces:
Runtime error
Runtime error
| import { h } from "./lib.js"; | |
| import { dbg_assert, dbg_log } from "./log.js"; | |
| import { CPU } from "./cpu.js"; | |
| const STATE_VERSION = 6; | |
| const STATE_MAGIC = 0x86768676|0; | |
| const STATE_INDEX_MAGIC = 0; | |
| const STATE_INDEX_VERSION = 1; | |
| const STATE_INDEX_TOTAL_LEN = 2; | |
| const STATE_INDEX_INFO_LEN = 3; | |
| const STATE_INFO_BLOCK_START = 16; | |
| const ZSTD_MAGIC = 0xFD2FB528; | |
| /** @constructor */ | |
| function StateLoadError(msg) | |
| { | |
| this.message = msg; | |
| } | |
| StateLoadError.prototype = new Error; | |
| const CONSTRUCTOR_TABLE = { | |
| "Uint8Array": Uint8Array, | |
| "Int8Array": Int8Array, | |
| "Uint16Array": Uint16Array, | |
| "Int16Array": Int16Array, | |
| "Uint32Array": Uint32Array, | |
| "Int32Array": Int32Array, | |
| "Float32Array": Float32Array, | |
| "Float64Array": Float64Array, | |
| }; | |
| function save_object(obj, saved_buffers) | |
| { | |
| if(typeof obj !== "object" || obj === null) | |
| { | |
| dbg_assert(typeof obj !== "function"); | |
| return obj; | |
| } | |
| if(Array.isArray(obj)) | |
| { | |
| return obj.map(x => save_object(x, saved_buffers)); | |
| } | |
| if(obj.constructor === Object) | |
| { | |
| console.log(obj); | |
| dbg_assert(obj.constructor !== Object, "Expected non-object"); | |
| } | |
| if(obj.BYTES_PER_ELEMENT) | |
| { | |
| // Uint8Array, etc. | |
| var buffer = new Uint8Array(obj.buffer, obj.byteOffset, obj.length * obj.BYTES_PER_ELEMENT); | |
| const constructor = obj.constructor.name.replace("bound ", ""); | |
| dbg_assert(CONSTRUCTOR_TABLE[constructor]); | |
| return { | |
| "__state_type__": constructor, | |
| "buffer_id": saved_buffers.push(buffer) - 1, | |
| }; | |
| } | |
| if(DEBUG && !obj.get_state) | |
| { | |
| console.log("Object without get_state: ", obj); | |
| } | |
| var state = obj.get_state(); | |
| var result = []; | |
| for(var i = 0; i < state.length; i++) | |
| { | |
| var value = state[i]; | |
| dbg_assert(typeof value !== "function"); | |
| result[i] = save_object(value, saved_buffers); | |
| } | |
| return result; | |
| } | |
| function restore_buffers(obj, buffers) | |
| { | |
| if(typeof obj !== "object" || obj === null) | |
| { | |
| dbg_assert(typeof obj !== "function"); | |
| return obj; | |
| } | |
| if(Array.isArray(obj)) | |
| { | |
| for(let i = 0; i < obj.length; i++) | |
| { | |
| obj[i] = restore_buffers(obj[i], buffers); | |
| } | |
| return obj; | |
| } | |
| const type = obj["__state_type__"]; | |
| dbg_assert(type !== undefined); | |
| const constructor = CONSTRUCTOR_TABLE[type]; | |
| dbg_assert(constructor, "Unkown type: " + type); | |
| const buffer = buffers[obj["buffer_id"]]; | |
| return new constructor(buffer); | |
| } | |
| /* @param {CPU} cpu */ | |
| export function save_state(cpu) | |
| { | |
| var saved_buffers = []; | |
| var state = save_object(cpu, saved_buffers); | |
| var buffer_infos = []; | |
| var total_buffer_size = 0; | |
| for(var i = 0; i < saved_buffers.length; i++) | |
| { | |
| var len = saved_buffers[i].byteLength; | |
| buffer_infos[i] = { | |
| offset: total_buffer_size, | |
| length: len, | |
| }; | |
| total_buffer_size += len; | |
| // align | |
| total_buffer_size = total_buffer_size + 3 & ~3; | |
| } | |
| var info_object = JSON.stringify({ | |
| "buffer_infos": buffer_infos, | |
| "state": state, | |
| }); | |
| var info_block = new TextEncoder().encode(info_object); | |
| var buffer_block_start = STATE_INFO_BLOCK_START + info_block.length; | |
| buffer_block_start = buffer_block_start + 3 & ~3; | |
| var total_size = buffer_block_start + total_buffer_size; | |
| //console.log("State: json_size=" + Math.ceil(buffer_block_start / 1024 / 1024) + "MB " + | |
| // "buffer_size=" + Math.ceil(total_buffer_size / 1024 / 1024) + "MB"); | |
| var result = new ArrayBuffer(total_size); | |
| var header_block = new Int32Array( | |
| result, | |
| 0, | |
| STATE_INFO_BLOCK_START / 4 | |
| ); | |
| new Uint8Array(result, STATE_INFO_BLOCK_START, info_block.length).set(info_block); | |
| var buffer_block = new Uint8Array( | |
| result, | |
| buffer_block_start | |
| ); | |
| header_block[STATE_INDEX_MAGIC] = STATE_MAGIC; | |
| header_block[STATE_INDEX_VERSION] = STATE_VERSION; | |
| header_block[STATE_INDEX_TOTAL_LEN] = total_size; | |
| header_block[STATE_INDEX_INFO_LEN] = info_block.length; | |
| for(var i = 0; i < saved_buffers.length; i++) | |
| { | |
| var buffer = saved_buffers[i]; | |
| dbg_assert(buffer.constructor === Uint8Array); | |
| buffer_block.set(buffer, buffer_infos[i].offset); | |
| } | |
| dbg_log("State: json size " + (info_block.byteLength >> 10) + "k"); | |
| dbg_log("State: Total buffers size " + (buffer_block.byteLength >> 10) + "k"); | |
| return result; | |
| } | |
| /* @param {CPU} cpu */ | |
| export function restore_state(cpu, state) | |
| { | |
| state = new Uint8Array(state); | |
| function read_state_header(state, check_length) | |
| { | |
| const len = state.length; | |
| if(len < STATE_INFO_BLOCK_START) | |
| { | |
| throw new StateLoadError("Invalid length: " + len); | |
| } | |
| const header_block = new Int32Array(state.buffer, state.byteOffset, 4); | |
| if(header_block[STATE_INDEX_MAGIC] !== STATE_MAGIC) | |
| { | |
| throw new StateLoadError("Invalid header: " + h(header_block[STATE_INDEX_MAGIC] >>> 0)); | |
| } | |
| if(header_block[STATE_INDEX_VERSION] !== STATE_VERSION) | |
| { | |
| throw new StateLoadError( | |
| "Version mismatch: dump=" + header_block[STATE_INDEX_VERSION] + | |
| " we=" + STATE_VERSION); | |
| } | |
| if(check_length && header_block[STATE_INDEX_TOTAL_LEN] !== len) | |
| { | |
| throw new StateLoadError( | |
| "Length doesn't match header: " + | |
| "real=" + len + " header=" + header_block[STATE_INDEX_TOTAL_LEN]); | |
| } | |
| return header_block[STATE_INDEX_INFO_LEN]; | |
| } | |
| function read_info_block(info_block_buffer) | |
| { | |
| const info_block = new TextDecoder().decode(info_block_buffer); | |
| return JSON.parse(info_block); | |
| } | |
| if(new Uint32Array(state.buffer, 0, 1)[0] === ZSTD_MAGIC) | |
| { | |
| const ctx = cpu.zstd_create_ctx(state.length); | |
| new Uint8Array(cpu.wasm_memory.buffer, cpu.zstd_get_src_ptr(ctx), state.length).set(state); | |
| let ptr = cpu.zstd_read(ctx, 16); | |
| const header_block = new Uint8Array(cpu.wasm_memory.buffer, ptr, 16); | |
| const info_block_len = read_state_header(header_block, false); | |
| cpu.zstd_read_free(ptr, 16); | |
| ptr = cpu.zstd_read(ctx, info_block_len); | |
| const info_block_buffer = new Uint8Array(cpu.wasm_memory.buffer, ptr, info_block_len); | |
| const info_block_obj = read_info_block(info_block_buffer); | |
| cpu.zstd_read_free(ptr, info_block_len); | |
| let state_object = info_block_obj["state"]; | |
| const buffer_infos = info_block_obj["buffer_infos"]; | |
| const buffers = []; | |
| let position = STATE_INFO_BLOCK_START + info_block_len; | |
| for(const buffer_info of buffer_infos) | |
| { | |
| const front_padding = (position + 3 & ~3) - position; | |
| const CHUNK_SIZE = 1 * 1024 * 1024; | |
| if(buffer_info.length > CHUNK_SIZE) | |
| { | |
| const ptr = cpu.zstd_read(ctx, front_padding); | |
| cpu.zstd_read_free(ptr, front_padding); | |
| const buffer = new Uint8Array(buffer_info.length); | |
| buffers.push(buffer.buffer); | |
| let have = 0; | |
| while(have < buffer_info.length) | |
| { | |
| const remaining = buffer_info.length - have; | |
| dbg_assert(remaining >= 0); | |
| const to_read = Math.min(remaining, CHUNK_SIZE); | |
| const ptr = cpu.zstd_read(ctx, to_read); | |
| buffer.set(new Uint8Array(cpu.wasm_memory.buffer, ptr, to_read), have); | |
| cpu.zstd_read_free(ptr, to_read); | |
| have += to_read; | |
| } | |
| } | |
| else | |
| { | |
| const ptr = cpu.zstd_read(ctx, front_padding + buffer_info.length); | |
| const offset = ptr + front_padding; | |
| buffers.push(cpu.wasm_memory.buffer.slice(offset, offset + buffer_info.length)); | |
| cpu.zstd_read_free(ptr, front_padding + buffer_info.length); | |
| } | |
| position += front_padding + buffer_info.length; | |
| } | |
| state_object = restore_buffers(state_object, buffers); | |
| cpu.set_state(state_object); | |
| cpu.zstd_free_ctx(ctx); | |
| } | |
| else | |
| { | |
| const info_block_len = read_state_header(state, true); | |
| if(info_block_len < 0 || info_block_len + 12 >= state.length) | |
| { | |
| throw new StateLoadError("Invalid info block length: " + info_block_len); | |
| } | |
| const info_block_buffer = state.subarray(STATE_INFO_BLOCK_START, STATE_INFO_BLOCK_START + info_block_len); | |
| const info_block_obj = read_info_block(info_block_buffer); | |
| let state_object = info_block_obj["state"]; | |
| const buffer_infos = info_block_obj["buffer_infos"]; | |
| let buffer_block_start = STATE_INFO_BLOCK_START + info_block_len; | |
| buffer_block_start = buffer_block_start + 3 & ~3; | |
| const buffers = buffer_infos.map(buffer_info => { | |
| const offset = buffer_block_start + buffer_info.offset; | |
| return state.buffer.slice(offset, offset + buffer_info.length); | |
| }); | |
| state_object = restore_buffers(state_object, buffers); | |
| cpu.set_state(state_object); | |
| } | |
| } | |