import { readFileSync } from 'node:fs' import { fileURLToPath, pathToFileURL } from 'node:url' import { dirname, join } from 'node:path' const repoRoot = dirname(dirname(fileURLToPath(import.meta.url))) const modulePath = process.argv[2] ?? join(repoRoot, 'wasm/build-wasm/webmetal-engine-node.mjs') const vectorsPath = join(repoRoot, 'conformance/microop-vectors.json') const { default: createModule } = await import(pathToFileURL(modulePath).href) const module_ = await createModule() const file = JSON.parse(readFileSync(vectorsPath, 'utf-8')) const heap = () => module_.HEAPU32 let failures = 0 const fail = (name, message) => { failures++ console.error(` ✗ ${name}: ${message}`) } for (const vector of file.vectors) { const engine = new module_.Engine() const model = vector.model ?? file.defaultModel if (!engine.loadModel(JSON.stringify(model))) { fail(vector.name, `loadModel failed: ${engine.lastError()}`) engine.delete() continue } const layout = JSON.parse(engine.stateLayout()) const base = engine.stateBufferPtr() >> 2 const flagIndex = (name) => layout.flags.names.indexOf(name) const bankOf = (name) => layout.banks.find((b) => b.name === name) const memOf = (name) => layout.memories.find((m) => m.name === name) // setup const setup = vector.setup ?? {} for (const [bank, values] of Object.entries(setup.banks ?? {})) { values.forEach((v, i) => { heap()[base + bankOf(bank).offset + i] = v }) } for (const [mem, cells] of Object.entries(setup.memories ?? {})) { for (const [addr, v] of Object.entries(cells)) { heap()[base + memOf(mem).offset + Number(addr)] = v } } for (const [flag, v] of Object.entries(setup.flags ?? {})) { heap()[base + layout.flags.offset + flagIndex(flag)] = v ? 1 : 0 } if (setup.pc !== undefined) heap()[base + layout.pc] = setup.pc // run let error = '' for (const step of vector.run) { error = engine.execSequence( JSON.stringify(step.microOps), JSON.stringify(step.operands ?? {}), ) if (error !== '') break } if (vector.expectError !== undefined) { if (error === '') fail(vector.name, 'expected an error, got none') else if (!error.includes(vector.expectError)) fail(vector.name, `error "${error}" lacks "${vector.expectError}"`) engine.delete() continue } if (error !== '') { fail(vector.name, `unexpected error: ${error}`) engine.delete() continue } // expect (partial) const expect = vector.expect ?? {} const problems = [] for (const [bank, values] of Object.entries(expect.banks ?? {})) { values.forEach((want, i) => { const got = heap()[base + bankOf(bank).offset + i] if (got !== want) problems.push(`${bank}[${i}] = ${got}, want ${want}`) }) } for (const [mem, cells] of Object.entries(expect.memories ?? {})) { for (const [addr, want] of Object.entries(cells)) { const got = heap()[base + memOf(mem).offset + Number(addr)] if (got !== want) problems.push(`${mem}[${addr}] = ${got}, want ${want}`) } } for (const [flag, want] of Object.entries(expect.flags ?? {})) { const got = heap()[base + layout.flags.offset + flagIndex(flag)] !== 0 if (got !== want) problems.push(`flag ${flag} = ${got}, want ${want}`) } if (expect.pc !== undefined && heap()[base + layout.pc] !== expect.pc) { problems.push(`pc = ${heap()[base + layout.pc]}, want ${expect.pc}`) } if (expect.halted !== undefined) { const got = (heap()[base + layout.status] & 1) !== 0 if (got !== expect.halted) problems.push(`halted = ${got}`) } for (const problem of problems) fail(vector.name, problem) engine.delete() } const total = file.vectors.length if (failures > 0) { console.error(`\n${failures} failure(s) across ${total} vectors (wasm build)`) process.exit(1) } console.log(`all ${total} conformance vectors pass against the wasm build`)