Make machine configuration authoritative and restore native checkpoints exactly
This commit is contained in:
55
web/worker/native-checkpoint.mjs
Normal file
55
web/worker/native-checkpoint.mjs
Normal file
@@ -0,0 +1,55 @@
|
||||
import { fnv1a64 } from "./replay.mjs";
|
||||
|
||||
function buildId(core) {
|
||||
if (typeof core._sim_checkpoint_build_id !== "function") throw new Error("native_checkpoint_build_id_missing");
|
||||
const pointer = core._sim_checkpoint_build_id();
|
||||
const id = new TextDecoder().decode(core.HEAPU8.subarray(pointer, pointer + 64));
|
||||
if (!/^[a-f0-9]{64}$/.test(id)) throw new Error("native_checkpoint_unversioned_build");
|
||||
return id;
|
||||
}
|
||||
|
||||
// Copy opaque native state with zero-filled blocks omitted. No arithmetic or
|
||||
// state reconstruction occurs here; each C adapter owns its layout/validation.
|
||||
export function exportNativeCheckpoint(core, prefix, owner = null) {
|
||||
const length = core[`_${prefix}_checkpoint_size`]();
|
||||
const pointer = core._malloc(length);
|
||||
if (!pointer) throw new Error(`${prefix}_checkpoint_allocation`);
|
||||
try {
|
||||
const args = owner === null ? [pointer, length] : [owner, pointer, length];
|
||||
const code = core[`_${prefix}_checkpoint_export`](...args);
|
||||
if (code !== 0) throw new Error(`${prefix}_checkpoint_export_${code}`);
|
||||
const bytes = core.HEAPU8.subarray(pointer, pointer + length), blocks = [];
|
||||
for (let offset = 0; offset < length; offset += 256) {
|
||||
const block = bytes.subarray(offset, Math.min(offset + 256, length));
|
||||
if (block.some((value) => value !== 0)) blocks.push([offset, Array.from(block, (value) => value.toString(16).padStart(2, "0")).join("")]);
|
||||
}
|
||||
const value = { schema: "native-checkpoint.v1", component: prefix, build_id: buildId(core), byte_length: length, blocks };
|
||||
return { ...value, state_hash: fnv1a64(value) };
|
||||
} finally { core._free(pointer); }
|
||||
}
|
||||
|
||||
export function validateNativeCheckpoint(core, prefix, value) {
|
||||
if (value?.build_id !== buildId(core)) throw new Error(`${prefix}_checkpoint_build_mismatch`);
|
||||
const length = core[`_${prefix}_checkpoint_size`]();
|
||||
if (!value || value.schema !== "native-checkpoint.v1" || value.component !== prefix || value.byte_length !== length || !Array.isArray(value.blocks) || fnv1a64(Object.fromEntries(Object.entries(value).filter(([key]) => key !== "state_hash"))) !== value.state_hash) throw new Error(`${prefix}_checkpoint_invalid`);
|
||||
const bytes = new Uint8Array(length); let end = 0;
|
||||
for (const [offset, hex] of value.blocks) {
|
||||
if (!Number.isSafeInteger(offset) || offset < end || typeof hex !== "string" || !/^(?:[0-9a-f]{2})+$/.test(hex) || offset + hex.length / 2 > length) throw new Error(`${prefix}_checkpoint_blocks`);
|
||||
for (let index = 0; index < hex.length; index += 2) bytes[offset + index / 2] = Number.parseInt(hex.slice(index, index + 2), 16);
|
||||
end = offset + hex.length / 2;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
export function restoreNativeCheckpoint(core, prefix, value, owner = null) {
|
||||
const bytes = validateNativeCheckpoint(core, prefix, value);
|
||||
const length = bytes.length;
|
||||
const pointer = core._malloc(length);
|
||||
if (!pointer) throw new Error(`${prefix}_checkpoint_allocation`);
|
||||
try {
|
||||
core.HEAPU8.set(bytes, pointer);
|
||||
const args = owner === null ? [pointer, length] : [owner, pointer, length];
|
||||
const code = core[`_${prefix}_checkpoint_restore`](...args);
|
||||
if (code !== 0) throw new Error(`${prefix}_checkpoint_restore_${code}`);
|
||||
} finally { core._free(pointer); }
|
||||
}
|
||||
Reference in New Issue
Block a user