12.11 闭环:二十用例点亮 + 单步观察。

- 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]
This commit is contained in:
2026-08-21 20:31:14 +08:00
parent 00565f035d
commit 66d9c6c548
6 changed files with 308 additions and 41 deletions
+2 -1
View File
@@ -7,11 +7,12 @@ BytecodeExecutor 模块:可执行入口。CMake 目标:`BytecodeExecutor`
## 用法
```text
BytecodeExecutor <name>.stb [--cycles N] [--replay <file>]
BytecodeExecutor <name>.stb [--cycles N] [--replay <file>] [--step]
```
- `--cycles N`:跑 N 个周期(缺省 10
- `--replay <file>`:读录制的 I 序列(每行空格分隔的 0/1,按 sidecar `io.input` 绑定顺序),逐周期喂入;读尽后保持最后一行
- `--step`:单步——采样 I 后逐指令打印(pc/fn/反汇编 + 执行后非零寄存器),跑 1 个周期,最后打印 I/Q
- 每周期打印 `cycle N: I=[...] Q=[...]`(按 sidecar 绑定顺序);故障打印 `FAULT(n)` 并退出码 1
## 职责
+100 -42
View File
@@ -20,16 +20,18 @@
#include <string>
#include <vector>
#include "isa/Encode.h"
#include "isa/Image.h"
#include "vm/Machine.h"
namespace {
void usage() {
std::printf("usage: BytecodeExecutor <name>.stb [--cycles N] [--replay <file>]\n"
std::printf("usage: BytecodeExecutor <name>.stb [--cycles N] [--replay <file>] [--step]\n"
" 加载 .stb + <name>.runtime.toml,按扫描周期执行\n"
" --cycles N 跑 N 个周期(缺省 10)\n"
" --replay <file> 读录制的 I 序列(每行按 io.input 顺序的 0/1\n"
" --step 单步:逐指令打印 pc/fn/反汇编/寄存器(跑 1 个周期)\n"
" --help 打印本帮助\n");
}
@@ -116,6 +118,58 @@ namespace {
m.data()[static_cast<size_t>(slot) * 8] = v ? 1 : 0;
}
// 采样:回放行按 io.input 顺序填,无回放则全 0;文件读尽后保持最后一行
void sample_inputs(vm::Machine& m, const std::vector<Binding>& bindings,
std::ifstream& replay_in, std::vector<int>& 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<unsigned char>(line[pos]))) {
++pos;
}
if (pos >= line.size()) break;
const size_t s = pos;
while (pos < line.size() &&
!std::isspace(static_cast<unsigned char>(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<Binding>& 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<uint32_t>(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);
// 单步模式:采样 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<uint8_t>(i));
if (v != 0) {
if (!regs.empty()) regs += " ";
regs += "r" + std::to_string(i) + "=" + std::to_string(v);
}
}
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<int>(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
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<unsigned char>(line[pos]))) {
++pos;
}
if (pos >= line.size()) break;
const size_t s = pos;
while (pos < line.size() &&
!std::isspace(static_cast<unsigned char>(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(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);
}
}
}
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<int>(f));
+11
View File
@@ -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)
+173
View File
@@ -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 <cstdio>
#include <string>
#include <vector>
#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<SourceUnit> 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<uint8_t> 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<int>(f));
return false;
}
if (f != vm::Fault::None) {
std::printf("FAIL %s: fault %d\n", dir, static_cast<int>(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;
}
+10
View File
@@ -20,6 +20,7 @@
#include <vector>
#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; // 本帧函数
+14
View File
@@ -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<uint32_t>(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<const uint32_t*>(base)[pc_];
}
Machine::Frame& Machine::cur_frame() { return frames_.back(); }
const Machine::Frame& Machine::cur_frame() const { return frames_.back(); }