From 8c0fc4fe98fc4a93387fbac18f2c95b11c6a08ed Mon Sep 17 00:00:00 2001 From: chentianya Date: Fri, 21 Aug 2026 20:22:36 +0800 Subject: [PATCH] =?UTF-8?q?12.9=20=E6=AD=A5=E9=AA=A4=206=EF=BC=9Avm=5Ftest?= =?UTF-8?q?=20=E5=9B=BA=E5=8C=96=EF=BC=88=E6=89=8B=E5=B7=A5=E6=98=A0?= =?UTF-8?q?=E5=83=8F=20+=20=E7=BC=96=E8=AF=91=E5=99=A8=E4=BA=A7=E7=89=A9?= =?UTF-8?q?=20+=20=E7=A1=AE=E5=AE=9A=E6=80=A7=EF=BC=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 手工映像:标量/跳转/数据区、CALL/RET 帧复制(r1/r2 实参 → r0 结果)、自调用 StackOverflow、越界槽 BadSlot、坏常量 BadConst - 编译器产物:用例 01~09/13/15/17/18/20 跑周期断言(TON 第 3 周期 q=1、line1 真值表 4 组) - 用例 06 CycleLimit;确定性:同一 I 序列两遍数据区逐字节一致 - step 单步:停在指令边界、cycle_count/regs 逐步核对 - ctest 10/10(vm_cycles 84 断言) --- tests/CMakeLists.txt | 11 ++ tests/src/vm_test.cpp | 440 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 451 insertions(+) create mode 100644 tests/src/vm_test.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5ab5034..e06da6d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -78,3 +78,14 @@ target_compile_definitions(codegen_test PRIVATE add_test(NAME codegen_slice1 COMMAND codegen_test) + +# VM 测试:手工映像 + 编译器产物 + 定时器 + 确定性(REPO_ROOT 注入源目录绝对路径) +add_executable(vm_test + ./src/vm_test.cpp) + +target_link_libraries(vm_test PRIVATE compiler vm) +target_compile_definitions(vm_test PRIVATE + REPO_ROOT="${CMAKE_SOURCE_DIR}") + +add_test(NAME vm_cycles + COMMAND vm_test) diff --git a/tests/src/vm_test.cpp b/tests/src/vm_test.cpp new file mode 100644 index 0000000..01cec55 --- /dev/null +++ b/tests/src/vm_test.cpp @@ -0,0 +1,440 @@ +/** + * @file vm_test.cpp + * @brief VM 测试(12.9):手工映像 + 编译器产物 + 定时器 + 确定性 + * @author + * @date 2026-08-21 + */ + +#include +#include +#include +#include + +#include "compiler/Codegen.h" +#include "compiler/Linker.h" +#include "compiler/Project.h" +#include "compiler/Typecheck.h" +#include "isa/Encode.h" +#include "isa/Image.h" +#include "isa/Instr.h" +#include "isa/Op.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) + +// ---- 数据区槽访问 ---- + +static int64_t slot(const vm::Machine& m, int s) { + int64_t v = 0; + for (int i = 0; i < 8; ++i) { + v |= static_cast(m.data()[s * 8 + i]) << (8 * i); + } + return v; +} + +static void wslot(vm::Machine& m, int s, int64_t v) { + for (int i = 0; i < 8; ++i) { + m.data()[s * 8 + i] = static_cast((v >> (8 * i)) & 0xFF); + } +} + +// ---- 编译器产物全链路(编译 → 建机 → 跑周期)---- + +static bool make_machine(const char* dir, vm::Machine* m, std::string* err) { + using namespace compiler; + const std::string toml = std::string(REPO_ROOT) + "/" + dir + "/project.toml"; + Project p; + if (!parse_project(toml, &p, err)) { + return false; + } + std::vector units; + for (const std::string& f : compile_files(p)) { + SourceUnit u; + if (!load_unit(p.base_dir + "/" + f, &u, err)) { + return false; + } + units.push_back(std::move(u)); + } + LinkResult link; + if (!link_project(p, units, &link, err)) { + return false; + } + if (!check_project(p, units, link, err)) { + return false; + } + std::vector img; + if (!codegen_project(p, units, link, &img, err)) { + return false; + } + return vm::Machine::create(img, m, err); +} + +// ---- 手工拼映像 ---- + +struct HFunc { + std::vector code; + uint32_t nregs = 8; +}; + +static std::vector hand_image(const std::vector& consts, + const std::vector& funcs, + uint32_t entry = 0, + uint32_t cycle_limit = 100000, + const std::vector& data = {}) { + size_t code_total = 0; + for (const HFunc& f : funcs) { + code_total += f.code.size() * 4; + } + const uint32_t off_const = 72; + const uint32_t off_funcs = off_const + static_cast(consts.size()) * 12; + const uint32_t off_code = off_funcs + static_cast(funcs.size()) * 12; + const uint32_t off_data = off_code + static_cast(code_total); + const uint32_t off_end = off_data + static_cast(data.size()); + + std::vector b(off_end, 0); + auto put32 = [&](size_t o, uint32_t v) { + b[o + 0] = static_cast(v & 0xFFu); + b[o + 1] = static_cast((v >> 8) & 0xFFu); + b[o + 2] = static_cast((v >> 16) & 0xFFu); + b[o + 3] = static_cast((v >> 24) & 0xFFu); + }; + auto put64 = [&](size_t o, uint64_t v) { + for (int i = 0; i < 8; ++i) { + b[o + i] = static_cast((v >> (8 * i)) & 0xFFu); + } + }; + put32(0, isa::kMagic); + put32(4, isa::kVersion); + put32(8, cycle_limit); + put32(12, 10); // dt_ms + put32(24, entry); + put32(44, static_cast(consts.size())); + put32(48, static_cast(funcs.size())); + put32(52, off_const); + put32(56, off_funcs); + put32(60, off_code); + put32(64, off_data); + put32(68, off_data); + + for (size_t i = 0; i < consts.size(); ++i) { + const size_t o = off_const + i * 12; + put32(o, static_cast(consts[i].tag)); + put64(o + 4, consts[i].value); + } + size_t acc = 0; + for (size_t i = 0; i < funcs.size(); ++i) { + const size_t o = off_funcs + i * 12; + put32(o, funcs[i].nregs); + put32(o + 4, static_cast(acc)); + put32(o + 8, static_cast(funcs[i].code.size())); + acc += funcs[i].code.size() * 4; + } + size_t c = off_code; + for (const HFunc& f : funcs) { + for (const isa::Instr in : f.code) { + put32(c, in); + c += 4; + } + } + for (size_t i = 0; i < data.size(); ++i) { + b[off_data + i] = data[i]; + } + return b; +} + +// ---- 1. 手工映像:标量 + 跳转 + 数据区 ---- + +static bool test_hand_scalar() { + // r8 := 5;r9 := r8 + 3;槽 0 ← r9;槽 1 → r10;JT 跳过一条 + const std::vector consts = { + {isa::types::Int, 5}, + {isa::types::Int, 3}, + {isa::types::Int, 1}, + }; + const std::vector funcs = {{ + { + isa::enc_imm(isa::Op::LOADK, 8, 0), + isa::enc_rr(isa::Op::MOVE, 9, 8), + isa::enc_imm(isa::Op::LOADK, 11, 1), + isa::enc_rrr(isa::Op::ADD, 9, 9, 11), + isa::enc_slot(isa::Op::STORE_GLOBAL, 9, 0), + isa::enc_slot(isa::Op::LOAD_GLOBAL, 10, 0), + isa::enc_jc(isa::Op::JT, 9, 1), // r9≠0 → 跳过下一条 + isa::enc_imm(isa::Op::LOADK, 10, 2), + isa::enc_ret(), + }, + 12, + }}; + const std::vector img = hand_image(consts, funcs, 0, 100000, + std::vector(16, 0)); // 2 槽 + vm::Machine m; + std::string err; + CHECK(vm::Machine::create(img, &m, &err)); + CHECK(m.run_cycle() == vm::Fault::None); + CHECK(m.reg(8) == 5); + CHECK(m.reg(9) == 8); // 5 + 3 + CHECK(slot(m, 0) == 8); // 槽 0 收到 8 + CHECK(m.reg(10) == 8); // 槽 1 读回(JT 跳过覆盖) + CHECK(m.cycle_count() == 8); + return true; +} + +// ---- 2. 手工映像:CALL/RET 帧复制 ---- + +static bool test_hand_call() { + // fn0(入口):r8=5、r2=7、实参 r1←r8;CALL 1;结果 r0 → r8 + // fn1:r0 = r1 + r2(r1/r2 来自调用约定区复制) + const std::vector consts = { + {isa::types::Int, 5}, + {isa::types::Int, 7}, + }; + const std::vector funcs = { + {{ + isa::enc_imm(isa::Op::LOADK, 8, 0), + isa::enc_imm(isa::Op::LOADK, 2, 1), + isa::enc_rr(isa::Op::MOVE, 1, 8), + isa::enc_call(1), + isa::enc_rr(isa::Op::MOVE, 8, 0), + isa::enc_ret(), + }, + 12}, + {{ + isa::enc_rrr(isa::Op::ADD, 0, 1, 2), + isa::enc_ret(), + }, + 10}, + }; + const std::vector img = hand_image(consts, funcs); + vm::Machine m; + std::string err; + CHECK(vm::Machine::create(img, &m, &err)); + CHECK(m.run_cycle() == vm::Fault::None); + CHECK(m.reg(8) == 12); // 5 + 7 + CHECK(m.call_depth() == 1); + return true; +} + +// ---- 3. 手工映像:自调用 → StackOverflow ---- + +static bool test_stack_overflow() { + const std::vector funcs = {{ + { + isa::enc_call(0), + isa::enc_ret(), + }, + 8, + }}; + const std::vector img = hand_image({}, funcs); + vm::Machine m; + std::string err; + CHECK(vm::Machine::create(img, &m, &err)); + CHECK(m.run_cycle() == vm::Fault::StackOverflow); + CHECK(m.call_depth() == 1); // 周期结束已清栈(留 MAIN) + return true; +} + +// ---- 4. 手工映像:越界槽 → BadSlot ---- + +static bool test_bad_slot() { + const std::vector funcs = {{ + { + isa::enc_slot(isa::Op::STORE_GLOBAL, 8, 60000), // 数据区 0 字节 + isa::enc_ret(), + }, + 12, + }}; + const std::vector img = hand_image({}, funcs); + vm::Machine m; + std::string err; + CHECK(vm::Machine::create(img, &m, &err)); + CHECK(m.run_cycle() == vm::Fault::BadSlot); + return true; +} + +// ---- 5. 编译器产物正例 ---- + +static bool test_cases_positive() { + std::string err; + vm::Machine m; + + // 01 空 MAIN + CHECK(make_machine("tests/cases/01_empty_main", &m, &err)); + CHECK(m.run_cycle() == vm::Fault::None); + + // 02 BOOL 赋值:a=TRUE(r8) b=FALSE(r9) + CHECK(make_machine("tests/cases/02_bool_assign", &m, &err)); + CHECK(m.run_cycle() == vm::Fault::None); + CHECK(m.reg(8) == 1 && m.reg(9) == 0); + + // 03 短路:a=0 b=0 → x=0 + CHECK(make_machine("tests/cases/03_short_circuit", &m, &err)); + CHECK(m.run_cycle() == vm::Fault::None); + CHECK(m.reg(10) == 0); + + // 04 IF:sel=0 → out=10 + CHECK(make_machine("tests/cases/04_if_elsif_else", &m, &err)); + CHECK(m.run_cycle() == vm::Fault::None); + CHECK(m.reg(9) == 10); + + // 05 WHILE:n=10 + CHECK(make_machine("tests/cases/05_while_normal", &m, &err)); + CHECK(m.run_cycle() == vm::Fault::None); + CHECK(m.reg(8) == 10); + + // 07 算术:a=42 b=10 c=32 eq=1 + CHECK(make_machine("tests/cases/07_int_arith", &m, &err)); + CHECK(m.run_cycle() == vm::Fault::None); + CHECK(m.reg(8) == 42 && m.reg(9) == 10 && m.reg(10) == 32 && m.reg(11) == 1); + + // 08 TIME 字面量:t1=10 t2=1250 + CHECK(make_machine("tests/cases/08_time_literal", &m, &err)); + CHECK(m.run_cycle() == vm::Fault::None); + CHECK(m.reg(8) == 10 && m.reg(9) == 1250); + + // 09 GVL + EXTERNAL:x = G1 初值 5(槽 0) + CHECK(make_machine("tests/cases/09_gvl_external", &m, &err)); + CHECK(m.run_cycle() == vm::Fault::None); + CHECK(m.reg(8) == 5); + + // 13 FUNCTION:x = Add(3,4) = 7 + CHECK(make_machine("tests/cases/13_function_call", &m, &err)); + CHECK(m.run_cycle() == vm::Fault::None); + CHECK(m.reg(8) == 7); + + // 15 FB 内联:starter(start=TRUE, stop=FALSE) → q=TRUE + CHECK(make_machine("tests/cases/15_fb_instance", &m, &err)); + CHECK(m.run_cycle() == vm::Fault::None); + CHECK(m.reg(8) == 1); + + // 17 TON:dt=10、pt=30,in 恒真 → 第 3 周期 q=1(槽 2) + CHECK(make_machine("tests/cases/17_ton", &m, &err)); + m.run_cycle(); + m.run_cycle(); + CHECK(m.run_cycle() == vm::Fault::None); + CHECK(slot(m, 2) == 1); // t.Q + CHECK(slot(m, 3) == 30); // t.ET + + // 18 TOF/CTU:跑一个周期无故障 + CHECK(make_machine("tests/cases/18_tof_ctu", &m, &err)); + CHECK(m.run_cycle() == vm::Fault::None); + + // 20 line1:I 组合 → Q0_0 真值表(槽 0=急停 1=I0_0 2=I0_1 3=Q0_0) + CHECK(make_machine("tests/cases/20_line1", &m, &err)); + struct { + int start, stop, es, expect; + } table[] = { + {1, 0, 0, 1}, + {1, 1, 0, 0}, + {0, 0, 0, 0}, + {1, 0, 1, 0}, + }; + for (const auto& t : table) { + wslot(m, 0, t.es); + wslot(m, 1, t.start); + wslot(m, 2, t.stop); + CHECK(m.run_cycle() == vm::Fault::None); + CHECK(slot(m, 3) == t.expect); + } + return true; +} + +// ---- 6. 用例 06:cycle_limit 打满 ---- + +static bool test_cycle_limit() { + std::string err; + vm::Machine m; + CHECK(make_machine("tests/cases/06_while_cycle_limit", &m, &err)); + CHECK(m.run_cycle() == vm::Fault::CycleLimit); + return true; +} + +// ---- 7. 确定性:同一 I 序列跑两遍,数据区一致 ---- + +static bool test_determinism() { + std::string err; + vm::Machine a, b; + CHECK(make_machine("tests/cases/20_line1", &a, &err)); + CHECK(make_machine("tests/cases/20_line1", &b, &err)); + const int seq[4][3] = {{1, 0, 0}, {1, 1, 0}, {0, 0, 0}, {1, 0, 1}}; + for (int rep = 0; rep < 2; ++rep) { + vm::Machine& m = rep ? b : a; + for (const auto& s : seq) { + wslot(m, 0, s[2]); + wslot(m, 1, s[0]); + wslot(m, 2, s[1]); + CHECK(m.run_cycle() == vm::Fault::None); + } + } + // 两遍结束:数据区逐字节一致(含 starter 实例状态) + CHECK(a.data_len() == b.data_len()); + for (size_t i = 0; i < a.data_len(); ++i) { + if (a.data()[i] != b.data()[i]) { + std::printf("FAIL determinism byte %zu: %d vs %d\n", i, a.data()[i], b.data()[i]); + return false; + } + } + ++g_checks; + return true; +} + +// ---- 8. step 单步:停在指令边界 ---- + +static bool test_step() { + std::string err; + vm::Machine m; + CHECK(make_machine("tests/cases/02_bool_assign", &m, &err)); + // 3 条指令:LOADK ×2 + RET + CHECK(m.step()); + CHECK(m.cycle_count() == 1 && m.reg(8) == 1); + CHECK(m.step()); + CHECK(m.cycle_count() == 2 && m.reg(9) == 0); + CHECK(!m.step()); // RET → 周期结束 + CHECK(m.cycle_count() == 3); + CHECK(!m.step()); // 结束后不再执行 + return true; +} + +// ---- 9. 手工映像:坏常量 id → BadConst ---- + +static bool test_bad_const() { + const std::vector funcs = {{ + { + isa::enc_imm(isa::Op::LOADK, 8, 99), // 常量表为空 + isa::enc_ret(), + }, + 12, + }}; + const std::vector img = hand_image({}, funcs); + vm::Machine m; + std::string err; + CHECK(vm::Machine::create(img, &m, &err)); + CHECK(m.run_cycle() == vm::Fault::BadConst); + return true; +} + +int main() { + if (!test_hand_scalar()) return 1; + if (!test_hand_call()) return 1; + if (!test_stack_overflow()) return 1; + if (!test_bad_slot()) return 1; + if (!test_cases_positive()) return 1; + if (!test_cycle_limit()) return 1; + if (!test_determinism()) return 1; + if (!test_step()) return 1; + if (!test_bad_const()) return 1; + std::printf("vm_test: %d checks passed\n", g_checks); + return 0; +}