Files
WebMetal/scripts/run-conformance-node.mjs
2026-07-19 18:05:04 +02:00

111 lines
3.8 KiB
JavaScript

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`)