From 66d9c6c5481d2a6b3d2d85253c0ba449f98e49c2 Mon Sep 17 00:00:00 2001 From: chentianya Date: Fri, 21 Aug 2026 20:31:14 +0800 Subject: [PATCH] =?UTF-8?q?12.11=20=E9=97=AD=E7=8E=AF=EF=BC=9A=E4=BA=8C?= =?UTF-8?q?=E5=8D=81=E7=94=A8=E4=BE=8B=E7=82=B9=E4=BA=AE=20+=20=E5=8D=95?= =?UTF-8?q?=E6=AD=A5=E8=A7=82=E5=AF=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cases_test:tests/cases/01..20 逐一跑完整管线(解析→链接→类型→码→VM 周期), 按 EXPECTED.md 期望断言:13 正例运行通过(含双周期可重复)、06 CycleLimit、6 负例报错类别 - BytecodeExecutor --step:采样 I → 逐指令打印 pc/fn/反汇编 + 执行后非零寄存器 → I/Q - Machine 补 header()/ended()/nregs()/cur_instr() 观察接口 - ctest 11/11(cases_all 20 用例点亮);line1 回放 0/1 组合 Q 正确、单步 I=[0 1]→Q=[1] --- Doc/executor/执行器入口.md | 3 +- executor/src/main.cpp | 138 ++++++++++++++++++++--------- tests/CMakeLists.txt | 11 +++ tests/src/cases_test.cpp | 173 +++++++++++++++++++++++++++++++++++++ vm/include/vm/Machine.h | 10 +++ vm/src/Machine.cpp | 14 +++ 6 files changed, 308 insertions(+), 41 deletions(-) create mode 100644 tests/src/cases_test.cpp diff --git a/Doc/executor/执行器入口.md b/Doc/executor/执行器入口.md index e48d545..76de4af 100644 --- a/Doc/executor/执行器入口.md +++ b/Doc/executor/执行器入口.md @@ -7,11 +7,12 @@ BytecodeExecutor 模块:可执行入口。CMake 目标:`BytecodeExecutor`( ## 用法 ```text -BytecodeExecutor .stb [--cycles N] [--replay ] +BytecodeExecutor .stb [--cycles N] [--replay ] [--step] ``` - `--cycles N`:跑 N 个周期(缺省 10) - `--replay `:读录制的 I 序列(每行空格分隔的 0/1,按 sidecar `io.input` 绑定顺序),逐周期喂入;读尽后保持最后一行 +- `--step`:单步——采样 I 后逐指令打印(pc/fn/反汇编 + 执行后非零寄存器),跑 1 个周期,最后打印 I/Q - 每周期打印 `cycle N: I=[...] Q=[...]`(按 sidecar 绑定顺序);故障打印 `FAULT(n)` 并退出码 1 ## 职责 diff --git a/executor/src/main.cpp b/executor/src/main.cpp index ed9e2bb..a11e14d 100644 --- a/executor/src/main.cpp +++ b/executor/src/main.cpp @@ -20,16 +20,18 @@ #include #include +#include "isa/Encode.h" #include "isa/Image.h" #include "vm/Machine.h" namespace { void usage() { - std::printf("usage: BytecodeExecutor .stb [--cycles N] [--replay ]\n" + std::printf("usage: BytecodeExecutor .stb [--cycles N] [--replay ] [--step]\n" " 加载 .stb + .runtime.toml,按扫描周期执行\n" " --cycles N 跑 N 个周期(缺省 10)\n" " --replay 读录制的 I 序列(每行按 io.input 顺序的 0/1)\n" + " --step 单步:逐指令打印 pc/fn/反汇编/寄存器(跑 1 个周期)\n" " --help 打印本帮助\n"); } @@ -116,6 +118,58 @@ namespace { m.data()[static_cast(slot) * 8] = v ? 1 : 0; } + // 采样:回放行按 io.input 顺序填,无回放则全 0;文件读尽后保持最后一行 + void sample_inputs(vm::Machine& m, const std::vector& bindings, + std::ifstream& replay_in, std::vector& replay_line) { + if (replay_in.is_open()) { + std::string line; + if (std::getline(replay_in, line)) { + replay_line.clear(); + size_t pos = 0; + while (pos < line.size()) { + while (pos < line.size() && + std::isspace(static_cast(line[pos]))) { + ++pos; + } + if (pos >= line.size()) break; + const size_t s = pos; + while (pos < line.size() && + !std::isspace(static_cast(line[pos]))) { + ++pos; + } + replay_line.push_back(std::stoi(line.substr(s, pos - s))); + } + } + size_t idx = 0; + for (const Binding& b : bindings) { + if (!b.is_input) continue; + put_bool(m, b.slot, idx < replay_line.size() ? replay_line[idx] : 0); + ++idx; + } + } else { + for (const Binding& b : bindings) { + if (b.is_input) { + put_bool(m, b.slot, 0); + } + } + } + } + + // 按 sidecar 绑定顺序打印 I/Q + std::string iq_string(vm::Machine& m, const std::vector& bindings, + std::string* is, std::string* qs) { + for (const Binding& b : bindings) { + if (b.is_input) { + if (!is->empty()) *is += " "; + *is += std::to_string(slot_bool(m, b.slot)); + } else { + if (!qs->empty()) *qs += " "; + *qs += std::to_string(slot_bool(m, b.slot)); + } + } + return *is; + } + } // namespace int main(int argc, char** argv) { @@ -131,12 +185,15 @@ int main(int argc, char** argv) { const std::string stb_path = argv[1]; uint32_t cycles = 10; + bool step_mode = false; std::string replay; for (int i = 2; i < argc; ++i) { if (std::strcmp(argv[i], "--cycles") == 0 && i + 1 < argc) { cycles = static_cast(std::stoul(argv[i + 1])); } else if (std::strcmp(argv[i], "--replay") == 0 && i + 1 < argc) { replay = argv[i + 1]; + } else if (std::strcmp(argv[i], "--step") == 0) { + step_mode = true; } } @@ -180,54 +237,55 @@ int main(int argc, char** argv) { std::printf("image: %s (%zu bytes, dt_ms=%u, cycle_limit=%u)\n", stb_path.c_str(), bytes.size(), h.dt_ms, h.cycle_limit); - for (uint32_t c = 1; c <= cycles; ++c) { - // 采样:回放行按 io.input 顺序填,无回放则全 0 - if (replay_in.is_open()) { - std::string line; - if (std::getline(replay_in, line)) { - replay_line.clear(); - size_t pos = 0; - while (pos < line.size()) { - while (pos < line.size() && - std::isspace(static_cast(line[pos]))) { - ++pos; - } - if (pos >= line.size()) break; - const size_t s = pos; - while (pos < line.size() && - !std::isspace(static_cast(line[pos]))) { - ++pos; - } - replay_line.push_back(std::stoi(line.substr(s, pos - s))); + // 单步模式:采样 I → 逐指令打印(指令 + 执行后寄存器)→ I/Q + if (step_mode) { + std::printf("-- single step (cycle 1) --\n"); + sample_inputs(machine, bindings, replay_in, replay_line); + uint32_t guard = 0; + while (machine.fault() == vm::Fault::None && !machine.ended()) { + char buf[64]; + isa::disasm(machine.cur_instr(), buf, sizeof buf); + const uint32_t pc = machine.pc(); + const uint32_t fn = machine.fn_id(); + if (!machine.step()) { + break; + } + // 执行后状态:指令 + 非零寄存器 + std::printf(" pc=%u fn=%u %s", pc, fn, buf); + std::string regs; + const uint32_t n = machine.nregs() < 32 ? machine.nregs() : 32; + for (uint32_t i = 0; i < n; ++i) { + const int64_t v = machine.reg(static_cast(i)); + if (v != 0) { + if (!regs.empty()) regs += " "; + regs += "r" + std::to_string(i) + "=" + std::to_string(v); } } - size_t idx = 0; - for (const Binding& b : bindings) { - if (!b.is_input) continue; - put_bool(machine, b.slot, idx < replay_line.size() ? replay_line[idx] : 0); - ++idx; - } - } else { - for (const Binding& b : bindings) { - if (b.is_input) { - put_bool(machine, b.slot, 0); - } + std::printf(" [%s]\n", regs.c_str()); + if (++guard > 1000000) { + std::printf(" ...step limit\n"); + break; } } + if (machine.fault() != vm::Fault::None) { + std::printf(" FAULT(%d)\n", static_cast(machine.fault())); + return 1; + } + std::string is, qs; + iq_string(machine, bindings, &is, &qs); + std::printf("cycle 1: I=[%s] Q=[%s]\n", is.c_str(), qs.c_str()); + return 0; + } + + for (uint32_t c = 1; c <= cycles; ++c) { + // 采样:回放行按 io.input 顺序填,无回放则全 0 + sample_inputs(machine, bindings, replay_in, replay_line); const vm::Fault f = machine.run_cycle(); // 打印 I/Q(按 sidecar 绑定顺序) std::string is, qs; - for (const Binding& b : bindings) { - if (b.is_input) { - if (!is.empty()) is += " "; - is += std::to_string(slot_bool(machine, b.slot)); - } else { - if (!qs.empty()) qs += " "; - qs += std::to_string(slot_bool(machine, b.slot)); - } - } + iq_string(machine, bindings, &is, &qs); std::printf("cycle %u: I=[%s] Q=[%s]", c, is.c_str(), qs.c_str()); if (f != vm::Fault::None) { std::printf(" FAULT(%d)", static_cast(f)); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e06da6d..7f96223 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -89,3 +89,14 @@ target_compile_definitions(vm_test PRIVATE add_test(NAME vm_cycles COMMAND vm_test) + +# 12.2 二十用例点亮(12.11):按 EXPECTED.md 期望跑完整管线(REPO_ROOT 注入源目录绝对路径) +add_executable(cases_test + ./src/cases_test.cpp) + +target_link_libraries(cases_test PRIVATE compiler vm) +target_compile_definitions(cases_test PRIVATE + REPO_ROOT="${CMAKE_SOURCE_DIR}") + +add_test(NAME cases_all + COMMAND cases_test) diff --git a/tests/src/cases_test.cpp b/tests/src/cases_test.cpp new file mode 100644 index 0000000..0374248 --- /dev/null +++ b/tests/src/cases_test.cpp @@ -0,0 +1,173 @@ +/** + * @file cases_test.cpp + * @brief 12.2 二十用例点亮(12.11):每例按 EXPECTED.md 期望跑完整管线 + * @author + * @date 2026-08-21 + * + * @details 对 tests/cases/01..20 逐一执行:解析 → 链接 → 类型检查 → 寄存器码 → VM 周期, + * 断言期望结果(编译运行通过 / 报错类别 / cycle_limit 故障),与各用例 EXPECTED.md 一致。 + */ + +#include +#include +#include + +#include "compiler/Codegen.h" +#include "compiler/Linker.h" +#include "compiler/Project.h" +#include "compiler/Typecheck.h" +#include "vm/Machine.h" + +#ifndef REPO_ROOT +#define REPO_ROOT "." +#endif + +static int g_checks = 0; + +#define CHECK(cond) \ + do { \ + if (!(cond)) { \ + std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + return false; \ + } \ + ++g_checks; \ + } while (0) + +namespace { + + enum class Outcome { Ok, CompileError, CycleLimit }; + + // 期望表(与 tests/cases/*/EXPECTED.md 一致;负例断言错误类别关键词) + struct CaseSpec { + const char* dir; + Outcome out; + const char* err_keyword; // CompileError 时须出现在错误消息里 + }; + + const CaseSpec kCases[] = { + {"01_empty_main", Outcome::Ok, ""}, + {"02_bool_assign", Outcome::Ok, ""}, + {"03_short_circuit", Outcome::Ok, ""}, + {"04_if_elsif_else", Outcome::Ok, ""}, + {"05_while_normal", Outcome::Ok, ""}, + {"06_while_cycle_limit", Outcome::CycleLimit, ""}, + {"07_int_arith", Outcome::Ok, ""}, + {"08_time_literal", Outcome::Ok, ""}, + {"09_gvl_external", Outcome::Ok, ""}, + {"10_gvl_wrong_file", Outcome::CompileError, "VAR_GLOBAL only allowed in gvl file"}, + {"11_duplicate_global", Outcome::CompileError, "duplicate global"}, + {"12_io_unknown_var", Outcome::CompileError, "io.var"}, + {"13_function_call", Outcome::Ok, ""}, + {"14_function_write_global", Outcome::CompileError, "function cannot write global"}, + {"15_fb_instance", Outcome::Ok, ""}, + {"16_fb_undeclared", Outcome::CompileError, "undeclared FB instance"}, + {"17_ton", Outcome::Ok, ""}, + {"18_tof_ctu", Outcome::Ok, ""}, + {"19_recursive_call", Outcome::CompileError, "recursive call"}, + {"20_line1", Outcome::Ok, ""}, + }; + + bool run_case(const char* dir, Outcome want, const char* keyword) { + using namespace compiler; + const std::string toml = std::string(REPO_ROOT) + "/tests/cases/" + dir + "/project.toml"; + std::string err; + + Project p; + if (!parse_project(toml, &p, &err)) { + if (want == Outcome::CompileError) { + ++g_checks; + return true; + } + std::printf("FAIL %s parse: %s\n", dir, err.c_str()); + return false; + } + std::vector units; + for (const std::string& f : compile_files(p)) { + SourceUnit u; + if (!load_unit(p.base_dir + "/" + f, &u, &err)) { + if (want == Outcome::CompileError) { + ++g_checks; + return true; + } + std::printf("FAIL %s load: %s\n", dir, err.c_str()); + return false; + } + units.push_back(std::move(u)); + } + LinkResult link; + if (!link_project(p, units, &link, &err)) { + if (want == Outcome::CompileError) { + if (err.find(keyword) == std::string::npos) { + std::printf("FAIL %s: want '%s', got '%s'\n", dir, keyword, err.c_str()); + return false; + } + ++g_checks; + return true; + } + std::printf("FAIL %s link: %s\n", dir, err.c_str()); + return false; + } + if (!check_project(p, units, link, &err)) { + if (want == Outcome::CompileError) { + if (err.find(keyword) == std::string::npos) { + std::printf("FAIL %s: want '%s', got '%s'\n", dir, keyword, err.c_str()); + return false; + } + ++g_checks; + return true; + } + std::printf("FAIL %s type: %s\n", dir, err.c_str()); + return false; + } + std::vector img; + if (!codegen_project(p, units, link, &img, &err)) { + if (want == Outcome::CompileError) { + ++g_checks; + return true; + } + std::printf("FAIL %s codegen: %s\n", dir, err.c_str()); + return false; + } + if (want == Outcome::CompileError) { + std::printf("FAIL %s: expected compile error\n", dir); + return false; + } + + vm::Machine m; + if (!vm::Machine::create(img, &m, &err)) { + std::printf("FAIL %s vm create: %s\n", dir, err.c_str()); + return false; + } + const vm::Fault f = m.run_cycle(); + if (want == Outcome::CycleLimit) { + if (f == vm::Fault::CycleLimit) { + ++g_checks; + return true; + } + std::printf("FAIL %s: want CycleLimit, got %d\n", dir, static_cast(f)); + return false; + } + if (f != vm::Fault::None) { + std::printf("FAIL %s: fault %d\n", dir, static_cast(f)); + return false; + } + // 可重复性:再跑一个周期 + if (m.run_cycle() != vm::Fault::None) { + std::printf("FAIL %s: 2nd cycle fault\n", dir); + return false; + } + ++g_checks; + return true; + } + +} // namespace + +int main() { + for (const CaseSpec& c : kCases) { + if (!run_case(c.dir, c.out, c.err_keyword)) { + return 1; + } + } + std::printf("cases_test: %d cases lit up\n", g_checks); + return 0; +} diff --git a/vm/include/vm/Machine.h b/vm/include/vm/Machine.h index 9864bd6..4ffdc1e 100644 --- a/vm/include/vm/Machine.h +++ b/vm/include/vm/Machine.h @@ -20,6 +20,7 @@ #include #include "isa/Image.h" +#include "isa/Instr.h" namespace vm { @@ -62,6 +63,9 @@ namespace vm { /** @brief 最近一次故障(step 返回 false 后查询) */ Fault fault() const; + /** @brief 周期是否已结束(MAIN 的 RET 后为 true) */ + bool ended() const; + /** @brief 映像头(dt_ms / cycle_limit 等) */ const isa::ImageHeader& header() const; @@ -87,6 +91,12 @@ namespace vm { /** @brief 当前帧寄存器值(i < nregs) */ int64_t reg(uint8_t i) const; + /** @brief 当前帧寄存器数(函数头 nregs) */ + uint32_t nregs() const; + + /** @brief 当前指令(pc_ 处;单步/观察用) */ + isa::Instr cur_instr() const; + private: struct Frame { uint32_t fn_id = 0; // 本帧函数 diff --git a/vm/src/Machine.cpp b/vm/src/Machine.cpp index 24e82a9..24a61a2 100644 --- a/vm/src/Machine.cpp +++ b/vm/src/Machine.cpp @@ -78,6 +78,7 @@ bool Machine::step() { } Fault Machine::fault() const { return fault_; } +bool Machine::ended() const { return ended_; } const isa::ImageHeader& Machine::header() const { return image_.header(); } uint32_t Machine::pc() const { return pc_; } uint32_t Machine::fn_id() const { return cur_frame().fn_id; } @@ -88,6 +89,19 @@ const uint8_t* Machine::data() const { return data_.data(); } size_t Machine::data_len() const { return data_.size(); } int64_t Machine::reg(uint8_t i) const { return cur_frame().regs[i]; } +uint32_t Machine::nregs() const { + return static_cast(cur_frame().regs.size()); +} + +isa::Instr Machine::cur_instr() const { + const isa::FuncRow row = image_.func_row(cur_frame().fn_id); + const uint8_t* base = image_.code_bytes() + row.code_offset; + if (pc_ >= row.code_len) { + return isa::pack(isa::Op::RET, 0, 0, 0); + } + return reinterpret_cast(base)[pc_]; +} + Machine::Frame& Machine::cur_frame() { return frames_.back(); } const Machine::Frame& Machine::cur_frame() const { return frames_.back(); }