12.9 步骤 6:vm_test 固化(手工映像 + 编译器产物 + 确定性)。

- 手工映像:标量/跳转/数据区、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 断言)
This commit is contained in:
2026-08-21 20:22:36 +08:00
parent ada9e5f99e
commit 8c0fc4fe98
2 changed files with 451 additions and 0 deletions
+11
View File
@@ -78,3 +78,14 @@ target_compile_definitions(codegen_test PRIVATE
add_test(NAME codegen_slice1 add_test(NAME codegen_slice1
COMMAND codegen_test) 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)
+440
View File
@@ -0,0 +1,440 @@
/**
* @file vm_test.cpp
* @brief VM 测试(12.9):手工映像 + 编译器产物 + 定时器 + 确定性
* @author
* @date 2026-08-21
*/
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#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<int64_t>(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<uint8_t>((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<SourceUnit> 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<uint8_t> img;
if (!codegen_project(p, units, link, &img, err)) {
return false;
}
return vm::Machine::create(img, m, err);
}
// ---- 手工拼映像 ----
struct HFunc {
std::vector<isa::Instr> code;
uint32_t nregs = 8;
};
static std::vector<uint8_t> hand_image(const std::vector<isa::ConstEntry>& consts,
const std::vector<HFunc>& funcs,
uint32_t entry = 0,
uint32_t cycle_limit = 100000,
const std::vector<uint8_t>& 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<uint32_t>(consts.size()) * 12;
const uint32_t off_code = off_funcs + static_cast<uint32_t>(funcs.size()) * 12;
const uint32_t off_data = off_code + static_cast<uint32_t>(code_total);
const uint32_t off_end = off_data + static_cast<uint32_t>(data.size());
std::vector<uint8_t> b(off_end, 0);
auto put32 = [&](size_t o, uint32_t v) {
b[o + 0] = static_cast<uint8_t>(v & 0xFFu);
b[o + 1] = static_cast<uint8_t>((v >> 8) & 0xFFu);
b[o + 2] = static_cast<uint8_t>((v >> 16) & 0xFFu);
b[o + 3] = static_cast<uint8_t>((v >> 24) & 0xFFu);
};
auto put64 = [&](size_t o, uint64_t v) {
for (int i = 0; i < 8; ++i) {
b[o + i] = static_cast<uint8_t>((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<uint32_t>(consts.size()));
put32(48, static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(acc));
put32(o + 8, static_cast<uint32_t>(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 := 5r9 := r8 + 3;槽 0 ← r9;槽 1 → r10;JT 跳过一条
const std::vector<isa::ConstEntry> consts = {
{isa::types::Int, 5},
{isa::types::Int, 3},
{isa::types::Int, 1},
};
const std::vector<HFunc> 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<uint8_t> img = hand_image(consts, funcs, 0, 100000,
std::vector<uint8_t>(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←r8CALL 1;结果 r0 → r8
// fn1r0 = r1 + r2r1/r2 来自调用约定区复制)
const std::vector<isa::ConstEntry> consts = {
{isa::types::Int, 5},
{isa::types::Int, 7},
};
const std::vector<HFunc> 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<uint8_t> 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<HFunc> funcs = {{
{
isa::enc_call(0),
isa::enc_ret(),
},
8,
}};
const std::vector<uint8_t> 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<HFunc> funcs = {{
{
isa::enc_slot(isa::Op::STORE_GLOBAL, 8, 60000), // 数据区 0 字节
isa::enc_ret(),
},
12,
}};
const std::vector<uint8_t> 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 IFsel=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 WHILEn=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 + EXTERNALx = 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 FUNCTIONx = 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 TONdt=10、pt=30in 恒真 → 第 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 line1I 组合 → 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. 用例 06cycle_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<HFunc> funcs = {{
{
isa::enc_imm(isa::Op::LOADK, 8, 99), // 常量表为空
isa::enc_ret(),
},
12,
}};
const std::vector<uint8_t> 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;
}