#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include "doctest.h" #include #include #include #include "webmetal/engine.h" using nlohmann::json; using webmetal::Engine; #ifndef WEBMETAL_CONFORMANCE_PATH #error "WEBMETAL_CONFORMANCE_PATH must be defined by the build" #endif namespace { json loadVectorFile() { std::ifstream in(WEBMETAL_CONFORMANCE_PATH); REQUIRE_MESSAGE(in.good(), "cannot open " WEBMETAL_CONFORMANCE_PATH); std::stringstream buffer; buffer << in.rdbuf(); json j = json::parse(buffer.str(), nullptr, false); REQUIRE_FALSE(j.is_discarded()); return j; } struct Layout { std::uint32_t pc = 0; std::uint32_t status = 1; std::uint32_t flagsOffset = 4; std::vector flagNames; struct Entry { std::uint32_t offset = 0; std::uint32_t count = 0; }; std::map banks; std::map memories; }; Layout parseLayout(const std::string& text) { const json j = json::parse(text, nullptr, false); REQUIRE_FALSE(j.is_discarded()); Layout layout; layout.pc = j["pc"].get(); layout.status = j["status"].get(); layout.flagsOffset = j["flags"]["offset"].get(); for (const auto& name : j["flags"]["names"]) { layout.flagNames.push_back(name.get()); } for (const auto& b : j["banks"]) { layout.banks[b["name"].get()] = { b["offset"].get(), b["count"].get()}; } for (const auto& m : j["memories"]) { layout.memories[m["name"].get()] = { m["offset"].get(), m["size"].get()}; } return layout; } int flagIndexOf(const Layout& layout, const std::string& name) { for (std::size_t i = 0; i < layout.flagNames.size(); i++) { if (layout.flagNames[i] == name) return static_cast(i); } return -1; } void applySetup(Engine& engine, const Layout& layout, const json& setup) { auto* state = reinterpret_cast(engine.stateBufferPtr()); if (setup.contains("banks")) { for (const auto& [name, values] : setup["banks"].items()) { REQUIRE(layout.banks.count(name) == 1); const auto entry = layout.banks.at(name); std::uint32_t i = 0; for (const auto& v : values) { REQUIRE(i < entry.count); state[entry.offset + i++] = v.get(); } } } if (setup.contains("memories")) { for (const auto& [name, cells] : setup["memories"].items()) { REQUIRE(layout.memories.count(name) == 1); const auto entry = layout.memories.at(name); for (const auto& [addr, v] : cells.items()) { state[entry.offset + std::stoul(addr)] = v.get(); } } } if (setup.contains("flags")) { for (const auto& [name, v] : setup["flags"].items()) { const int index = flagIndexOf(layout, name); REQUIRE(index >= 0); state[layout.flagsOffset + index] = v.get() ? 1 : 0; } } if (setup.contains("pc")) { state[layout.pc] = setup["pc"].get(); } } std::vector compareExpect(const Engine& engine, const Layout& layout, const json& expect) { std::vector problems; const auto* state = reinterpret_cast(engine.stateBufferPtr()); const auto mismatch = [&](const std::string& what, std::uint32_t got, std::uint32_t want) { problems.push_back(what + " = " + std::to_string(got) + ", want " + std::to_string(want)); }; if (expect.contains("banks")) { for (const auto& [name, values] : expect["banks"].items()) { const auto entry = layout.banks.at(name); std::uint32_t i = 0; for (const auto& v : values) { const auto want = v.get(); const auto got = state[entry.offset + i]; if (got != want) { mismatch(name + "[" + std::to_string(i) + "]", got, want); } i++; } } } if (expect.contains("memories")) { for (const auto& [name, cells] : expect["memories"].items()) { const auto entry = layout.memories.at(name); for (const auto& [addr, v] : cells.items()) { const auto want = v.get(); const auto got = state[entry.offset + std::stoul(addr)]; if (got != want) mismatch(name + "[" + addr + "]", got, want); } } } if (expect.contains("flags")) { for (const auto& [name, v] : expect["flags"].items()) { const int index = flagIndexOf(layout, name); const bool want = v.get(); const bool got = state[layout.flagsOffset + index] != 0; if (got != want) { problems.push_back("flag " + name + " = " + (got ? "true" : "false")); } } } if (expect.contains("pc")) { const auto want = expect["pc"].get(); if (state[layout.pc] != want) mismatch("pc", state[layout.pc], want); } if (expect.contains("halted")) { const bool want = expect["halted"].get(); const bool got = (state[layout.status] & 1u) != 0; if (got != want) { problems.push_back(std::string("halted = ") + (got ? "true" : "false")); } } return problems; } } // namespace TEST_CASE("C++ engine passes every shared conformance vector") { const json file = loadVectorFile(); const json defaultModel = file["defaultModel"]; for (const auto& vector : file["vectors"]) { const std::string name = vector["name"].get(); CAPTURE(name); Engine engine; const json model = vector.contains("model") ? vector["model"] : defaultModel; REQUIRE_MESSAGE(engine.loadModel(model.dump()), name); const Layout layout = parseLayout(engine.stateLayout()); if (vector.contains("setup")) { applySetup(engine, layout, vector["setup"]); } std::string error; for (const auto& step : vector["run"]) { const std::string operands = step.contains("operands") ? step["operands"].dump() : "{}"; error = engine.execSequence(step["microOps"].dump(), operands); if (!error.empty()) break; } if (vector.contains("expectError")) { const auto substring = vector["expectError"].get(); CHECK_MESSAGE(!error.empty(), name, ": expected an error"); CHECK_MESSAGE(error.find(substring) != std::string::npos, name, ": error \"", error, "\" lacks \"", substring, "\""); continue; } CHECK_MESSAGE(error.empty(), name, ": unexpected error: ", error); if (vector.contains("expect")) { const auto problems = compareExpect(engine, layout, vector["expect"]); for (const auto& problem : problems) { FAIL_CHECK(name << ": " << problem); } } } }