Files
Interpreter/tests/src/machine_test.cpp
T
Admin 246fe09646 阶段2-3:Codec V2 化(变长指令字编解码 + 配置驱动 disasm 类型后缀)
- instr_len 前缀定长(1/01/001/0001 → 4/8/16/32B)
- prefix_of/op_of/func_of + 全部位号 get/set(rd/rs1/rs2/imm32/slot32/off32/
  fn_id32/fb_id16/cal_slot32/off32_hi/imm64)
- set_byte0 按前缀写(11/10/011/010xxx 形态位/0010/0011/0001)
- disasm:find_op_by_key 查配置;full/float/dst 类显示 .TYPE 后缀
  (U8/I8/.../F64);CAL 打印 fb 名(CAL.TON s5);未知输出 ??? 0x hex
- main.cpp --disasm 调用适配(TODO V2 待 Stb 重写)
- 状态:编译仅剩 Codegen.cpp(84 处 V1 残留,下一步阶段2-5 重写)
2026-08-25 20:08:37 +08:00

387 lines
19 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file machine_test.cpp
* @brief machine.toml V2 加载与强校验测试(阶段 2)
* @author
* @date 2026-08-21
*/
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <string>
#include "compiler/MachineConfig.h"
#include "compiler/Stb.h"
#include "compiler/TypeInfo.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)
// ---- 写临时 toml ----
static std::string write_tmp(const char* name, const std::string& content) {
const std::filesystem::path dir = std::filesystem::temp_directory_path();
const std::filesystem::path path = dir / name;
std::FILE* f = std::fopen(path.string().c_str(), "w");
std::fputs(content.c_str(), f);
std::fclose(f);
return path.string();
}
static bool expect_load_err(const char* name, const std::string& content, const char* keyword) {
const std::string path = write_tmp(name, content);
compiler::MachineConfig cfg;
std::string err;
if (cfg.load(path, &err)) {
std::printf("FAIL %s: loaded ok\n", name);
std::remove(path.c_str());
return false;
}
if (err.find("machine error") != 0) {
std::printf("FAIL %s: want 'machine error', got '%s'\n", name, err.c_str());
std::remove(path.c_str());
return false;
}
if (err.find(keyword) == std::string::npos) {
std::printf("FAIL %s: want '%s', got '%s'\n", name, keyword, err.c_str());
std::remove(path.c_str());
return false;
}
++g_checks;
std::remove(path.c_str());
return true;
}
// 合法 type 模板(19 条全集,可注入损坏;缺 DT 时用于"缺全集"负例)
static const char* kTypesAll =
"[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nkind = \"bool\"\nfunc = 0\n"
"[[type]]\nname = \"BYTE\"\nbase = \"uint8\"\nkind = \"bit\"\nfunc = 0\n"
"[[type]]\nname = \"WORD\"\nbase = \"uint16\"\nkind = \"bit\"\nfunc = 2\n"
"[[type]]\nname = \"DWORD\"\nbase = \"uint32\"\nkind = \"bit\"\nfunc = 4\n"
"[[type]]\nname = \"LWORD\"\nbase = \"uint64\"\nkind = \"bit\"\nfunc = 6\n"
"[[type]]\nname = \"SINT\"\nbase = \"int8\"\nkind = \"int\"\nfunc = 1\n"
"[[type]]\nname = \"INT\"\nbase = \"int16\"\nkind = \"int\"\nfunc = 3\n"
"[[type]]\nname = \"DINT\"\nbase = \"int32\"\nkind = \"int\"\nfunc = 5\n"
"[[type]]\nname = \"LINT\"\nbase = \"int64\"\nkind = \"int\"\nfunc = 7\n"
"[[type]]\nname = \"USINT\"\nbase = \"uint8\"\nkind = \"uint\"\nfunc = 0\n"
"[[type]]\nname = \"UINT\"\nbase = \"uint16\"\nkind = \"uint\"\nfunc = 2\n"
"[[type]]\nname = \"UDINT\"\nbase = \"uint32\"\nkind = \"uint\"\nfunc = 4\n"
"[[type]]\nname = \"ULINT\"\nbase = \"uint64\"\nkind = \"uint\"\nfunc = 6\n"
"[[type]]\nname = \"TIME\"\nbase = \"int64\"\nkind = \"time\"\nfunc = 7\n"
"[[type]]\nname = \"REAL\"\nbase = \"float32\"\nkind = \"float\"\nfunc = 8\n"
"[[type]]\nname = \"LREAL\"\nbase = \"float64\"\nkind = \"float\"\nfunc = 9\n"
"[[type]]\nname = \"DATE\"\nbase = \"uint32\"\nkind = \"date\"\nfunc = 4\n"
"[[type]]\nname = \"TOD\"\nbase = \"uint32\"\nkind = \"date\"\nfunc = 4\n"
"[[type]]\nname = \"DT\"\nbase = \"uint64\"\nkind = \"date\"\nfunc = 6\n";
// 合法 op 模板(含 CAL 与 RET,可注入损坏)
static const char* kOpsBasic =
"[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n"
"[[op]]\nname = \"CAL\"\nprefix = \"010101\"\nop = 0\nfmt = \"CAL\"\nfunc = \"none\"\nparams = [\"fb_id\", \"slot\"]\nenabled = true\n"
"[[op]]\nname = \"RET\"\nprefix = \"10\"\nop = 1\nfmt = \"NONE\"\nfunc = \"none\"\nparams = []\nenabled = true\n";
// 合法 fb 模板(15 条全集,可注入损坏)
static const char* kFbsAll =
"[[fb]]\nname = \"TON\"\nfb_id = 0\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n"
"[[fb]]\nname = \"TOF\"\nfb_id = 1\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n"
"[[fb]]\nname = \"TP\"\nfb_id = 2\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n"
"[[fb]]\nname = \"CTU\"\nfb_id = 3\nfields = [[\"cu\", \"BOOL\"], [\"r\", \"BOOL\"], [\"pv\", \"INT\"], [\"q\", \"BOOL\"], [\"cv\", \"INT\"]]\n"
"[[fb]]\nname = \"DCTU\"\nfb_id = 4\nfields = [[\"cu\", \"BOOL\"], [\"r\", \"BOOL\"], [\"pv\", \"DINT\"], [\"q\", \"BOOL\"], [\"cv\", \"DINT\"]]\n"
"[[fb]]\nname = \"CTD\"\nfb_id = 5\nfields = [[\"cd\", \"BOOL\"], [\"ld\", \"BOOL\"], [\"pv\", \"INT\"], [\"q\", \"BOOL\"], [\"cv\", \"INT\"]]\n"
"[[fb]]\nname = \"DCTD\"\nfb_id = 6\nfields = [[\"cd\", \"BOOL\"], [\"ld\", \"BOOL\"], [\"pv\", \"DINT\"], [\"q\", \"BOOL\"], [\"cv\", \"DINT\"]]\n"
"[[fb]]\nname = \"CTUD\"\nfb_id = 7\nfields = [[\"cu\", \"BOOL\"], [\"cd\", \"BOOL\"], [\"r\", \"BOOL\"], [\"lu\", \"BOOL\"], [\"pv\", \"INT\"], [\"qu\", \"BOOL\"], [\"qd\", \"BOOL\"], [\"cv\", \"INT\"]]\n"
"[[fb]]\nname = \"DCTUD\"\nfb_id = 8\nfields = [[\"cu\", \"BOOL\"], [\"cd\", \"BOOL\"], [\"r\", \"BOOL\"], [\"lu\", \"BOOL\"], [\"pv\", \"DINT\"], [\"qu\", \"BOOL\"], [\"qd\", \"BOOL\"], [\"cv\", \"DINT\"]]\n"
"[[fb]]\nname = \"R_TRIG\"\nfb_id = 9\nfields = [[\"clk\", \"BOOL\"], [\"q\", \"BOOL\"]]\n"
"[[fb]]\nname = \"F_TRIG\"\nfb_id = 10\nfields = [[\"clk\", \"BOOL\"], [\"q\", \"BOOL\"]]\n"
"[[fb]]\nname = \"SR\"\nfb_id = 11\nfields = [[\"S1\", \"BOOL\"], [\"R\", \"BOOL\"], [\"Q1\", \"BOOL\"]]\n"
"[[fb]]\nname = \"RS\"\nfb_id = 12\nfields = [[\"SET\", \"BOOL\"], [\"RESET1\", \"BOOL\"], [\"Q1\", \"BOOL\"]]\n"
"[[fb]]\nname = \"PWM\"\nfb_id = 13\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"duty\", \"REAL\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n"
"[[fb]]\nname = \"RTC\"\nfb_id = 14\nfields = [[\"enable\", \"BOOL\"], [\"date\", \"DATE\"], [\"tod\", \"TOD\"]]\n";
// ---- 1. 正例:仓库 machine.toml ----
static bool test_positive() {
compiler::MachineConfig cfg;
std::string err;
CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err));
CHECK(cfg.ok());
CHECK(cfg.model_name() == "STATOR");
CHECK(cfg.version() == 2);
CHECK(cfg.types().size() == 19);
const compiler::ConfigType* bt = cfg.find_type("BOOL");
CHECK(bt != nullptr && bt->base == "uint8" && bt->kind == "bool" && bt->func == 0);
CHECK(cfg.find_type("INT") != nullptr && cfg.find_type("INT")->base == "int16" &&
cfg.find_type("INT")->kind == "int" && cfg.find_type("INT")->func == 3);
CHECK(cfg.find_type("REAL") != nullptr && cfg.find_type("REAL")->func == 8);
CHECK(cfg.find_type("LREAL") != nullptr && cfg.find_type("LREAL")->func == 9);
CHECK(cfg.find_type("DATE") != nullptr && cfg.find_type("DATE")->func == 4);
CHECK(cfg.find_type("TOD") != nullptr && cfg.find_type("TOD")->func == 4);
CHECK(cfg.find_type("DT") != nullptr && cfg.find_type("DT")->func == 6);
CHECK(cfg.find_type("NOPE") == nullptr);
CHECK(cfg.ops().size() == 68);
const compiler::ConfigOp* move = cfg.find_op("MOVE");
CHECK(move != nullptr && move->prefix == "11" && move->op == 0 && move->fmt == "RR" &&
move->func_mode == "width" && move->params.size() == 2);
const compiler::ConfigOp* load = cfg.find_op("LOAD");
CHECK(load != nullptr && load->prefix == "010001" && load->fmt == "SLOT" &&
load->func_mode == "full");
const compiler::ConfigOp* cal = cfg.find_op("CAL");
CHECK(cal != nullptr && cal->prefix == "010101" && cal->fmt == "CAL" &&
cal->func_mode == "none" && cal->params.size() == 2);
CHECK(cfg.find_op_by_key("11", 0) != nullptr &&
cfg.find_op_by_key("11", 0)->name == "MOVE");
CHECK(cfg.find_op_by_key("010001", 1) != nullptr &&
cfg.find_op_by_key("010001", 1)->name == "STORE");
CHECK(cfg.op_enabled("11", 0) && cfg.op_enabled("010101", 0));
CHECK(!cfg.op_enabled("11", 63));
CHECK(cfg.find_op("NOPE") == nullptr);
CHECK(cfg.fbs().size() == 15);
const compiler::ConfigFb* ton = cfg.find_fb("TON");
CHECK(ton != nullptr && ton->fb_id == 0 && ton->fields.size() == 4);
CHECK(cfg.find_fb("DCTU") != nullptr && cfg.find_fb("DCTU")->fb_id == 4 &&
cfg.find_fb("DCTU")->fields[2].type == "DINT");
CHECK(cfg.find_fb_by_id(14) != nullptr && cfg.find_fb_by_id(14)->name == "RTC");
CHECK(cfg.find_fb_by_id(15) == nullptr);
// 基元表
CHECK(compiler::MachineConfig::find_prim("int16") != nullptr);
const compiler::PrimType* p = compiler::MachineConfig::find_prim("uint8");
CHECK(p != nullptr && p->width == 1 && !p->is_signed && !p->is_float);
CHECK(compiler::MachineConfig::find_prim("float64") != nullptr);
CHECK(compiler::MachineConfig::find_prim("bfloat16") == nullptr);
return true;
}
// ---- 2. 负例:V2 校验项 ----
static bool test_negative() {
const std::string m = "[meta]\nname = \"STATOR\"\nversion = 2\n";
// version != 2
if (!expect_load_err("mc_v2_bad1.toml",
"[meta]\nname = \"STATOR\"\nversion = 1\n" + std::string(kTypesAll) +
kOpsBasic + kFbsAll,
"version must be 2")) {
return false;
}
// type 缺全集(去掉 DT
{
std::string types = kTypesAll;
const std::string dt = "[[type]]\nname = \"DT\"\nbase = \"uint64\"\nkind = \"date\"\nfunc = 6\n";
const size_t pos = types.find(dt);
types.erase(pos, dt.size());
if (!expect_load_err("mc_v2_bad2.toml", m + types + kOpsBasic + kFbsAll,
"missing 'DT'")) {
return false;
}
}
// base 未命中基元
{
std::string types = kTypesAll;
const std::string old = "[[type]]\nname = \"BYTE\"\nbase = \"uint8\"\nkind = \"bit\"\nfunc = 0\n";
const std::string bad = "[[type]]\nname = \"BYTE\"\nbase = \"bigint\"\nkind = \"bit\"\nfunc = 0\n";
types.replace(types.find(old), old.size(), bad);
if (!expect_load_err("mc_v2_bad3.toml", m + types + kOpsBasic + kFbsAll,
"unknown base")) {
return false;
}
}
// func 与 base 互锁破坏(INT func=0
{
std::string types = kTypesAll;
const std::string old = "[[type]]\nname = \"INT\"\nbase = \"int16\"\nkind = \"int\"\nfunc = 3\n";
const std::string bad = "[[type]]\nname = \"INT\"\nbase = \"int16\"\nkind = \"int\"\nfunc = 0\n";
types.replace(types.find(old), old.size(), bad);
if (!expect_load_err("mc_v2_bad4.toml", m + types + kOpsBasic + kFbsAll,
"func 0 mismatches base")) {
return false;
}
}
// kind 非法
{
std::string types = kTypesAll;
const std::string old = "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nkind = \"bool\"\nfunc = 0\n";
const std::string bad = "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nkind = \"weird\"\nfunc = 0\n";
types.replace(types.find(old), old.size(), bad);
if (!expect_load_err("mc_v2_bad5.toml", m + types + kOpsBasic + kFbsAll,
"bad kind")) {
return false;
}
}
// prefix 非法
{
std::string ops = kOpsBasic;
const std::string old = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n";
const std::string bad = "[[op]]\nname = \"MOVE\"\nprefix = \"99\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n";
ops.replace(ops.find(old), old.size(), bad);
if (!expect_load_err("mc_v2_bad6.toml", m + kTypesAll + ops + kFbsAll,
"bad prefix")) {
return false;
}
}
// prefix-fmt 互锁破坏(11 + RRR
{
std::string ops = kOpsBasic;
const std::string old = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n";
const std::string bad = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RRR\"\nfunc = \"width\"\nparams = [\"rd\", \"ra\", \"rb\"]\nenabled = true\n";
ops.replace(ops.find(old), old.size(), bad);
if (!expect_load_err("mc_v2_bad7.toml", m + kTypesAll + ops + kFbsAll,
"must pair with fmt")) {
return false;
}
}
// (prefix, op) 分区内重复
{
std::string ops = kOpsBasic;
ops += "[[op]]\nname = \"DUP\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"a\", \"b\"]\nenabled = true\n";
if (!expect_load_err("mc_v2_bad8.toml", m + kTypesAll + ops + kFbsAll,
"duplicate (prefix, op)")) {
return false;
}
}
// func 类别非法
{
std::string ops = kOpsBasic;
const std::string old = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n";
const std::string bad = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"magic\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n";
ops.replace(ops.find(old), old.size(), bad);
if (!expect_load_err("mc_v2_bad9.toml", m + kTypesAll + ops + kFbsAll,
"bad func mode")) {
return false;
}
}
// params 数量与 fmt 不符(CAL 应为 2)
{
std::string ops = kOpsBasic;
const std::string old = "[[op]]\nname = \"CAL\"\nprefix = \"010101\"\nop = 0\nfmt = \"CAL\"\nfunc = \"none\"\nparams = [\"fb_id\", \"slot\"]\nenabled = true\n";
const std::string bad = "[[op]]\nname = \"CAL\"\nprefix = \"010101\"\nop = 0\nfmt = \"CAL\"\nfunc = \"none\"\nparams = [\"fb_id\"]\nenabled = true\n";
ops.replace(ops.find(old), old.size(), bad);
if (!expect_load_err("mc_v2_bad10.toml", m + kTypesAll + ops + kFbsAll,
"params count")) {
return false;
}
}
// fb 缺全集(去掉 RTC
{
std::string fbs = kFbsAll;
const std::string rtc = "[[fb]]\nname = \"RTC\"\nfb_id = 14\nfields = [[\"enable\", \"BOOL\"], [\"date\", \"DATE\"], [\"tod\", \"TOD\"]]\n";
fbs.erase(fbs.find(rtc), rtc.size());
if (!expect_load_err("mc_v2_bad11.toml", m + kTypesAll + kOpsBasic + fbs,
"missing fb_id 14")) {
return false;
}
}
// fb 字段类型未知
{
std::string fbs = kFbsAll;
const std::string old = "[[\"in\", \"BOOL\"]], [\"pt\", \"TIME\"]";
const std::string bad = "[[\"in\", \"REAL\"]], [\"pt\", \"TIME\"]";
fbs.replace(fbs.find(old), old.size(), bad);
if (!expect_load_err("mc_v2_bad12.toml", m + kTypesAll + kOpsBasic + fbs,
"unknown field type")) {
return false;
}
}
// 缺 CALop-fb 互锁)
{
std::string ops = kOpsBasic;
const std::string cal = "[[op]]\nname = \"CAL\"\nprefix = \"010101\"\nop = 0\nfmt = \"CAL\"\nfunc = \"none\"\nparams = [\"fb_id\", \"slot\"]\nenabled = true\n";
ops.erase(ops.find(cal), cal.size());
if (!expect_load_err("mc_v2_bad13.toml", m + kTypesAll + ops + kFbsAll,
"op-fb interlock")) {
return false;
}
}
// 语法错误
if (!expect_load_err("mc_v2_bad14.toml", "[meta\nname = \"S\"\n", "parse error")) {
return false;
}
return true;
}
// ---- 3. 类型元数据(TypeInfoV2----
static bool test_type_meta() {
compiler::MachineConfig cfg;
std::string err;
CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err));
// BOOL1 字节、无符号、kind bool、func 0
const compiler::TypeMeta b = compiler::type_meta(cfg, "BOOL");
CHECK(b && b.width() == 1 && !b.is_signed() && !b.is_float() && b.func() == 0);
CHECK(b.kind() == "bool");
// 大小写不敏感(Linker type_name 是小写)
const compiler::TypeMeta b2 = compiler::type_meta(cfg, "bool");
CHECK(b2 && b2.func() == 0 && b2.width() == 1);
// INT2 字节、有符号、kind int、func 3
const compiler::TypeMeta i = compiler::type_meta(cfg, "int");
CHECK(i && i.width() == 2 && i.is_signed() && !i.is_float() && i.func() == 3);
CHECK(i.kind() == "int");
// REAL4 字节、浮点、func 8TOD4 字节、func 4
const compiler::TypeMeta r = compiler::type_meta(cfg, "REAL");
CHECK(r && r.width() == 4 && r.is_float() && r.func() == 8);
const compiler::TypeMeta tod = compiler::type_meta(cfg, "TOD");
CHECK(tod && tod.width() == 4 && tod.func() == 4);
// 未定义类型 → 空
CHECK(!compiler::type_meta(cfg, "NOPE"));
CHECK(!compiler::type_meta(cfg, ""));
return true;
}
// ---- 4. SHA-256 已知向量 + 型号标识 ----
static bool test_sha256() {
{
uint8_t out[compiler::kSha256Size];
compiler::sha256(nullptr, 0, out);
const uint8_t want[32] = {0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14,
0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c,
0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55};
CHECK(std::memcmp(out, want, 32) == 0);
}
{
const uint8_t abc[] = {'a', 'b', 'c'};
uint8_t out[compiler::kSha256Size];
compiler::sha256(abc, 3, out);
const uint8_t want[32] = {0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea,
0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c,
0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad};
CHECK(std::memcmp(out, want, 32) == 0);
}
// 型号标识:STATOR + 2 → "STATOR2" 补 '\0'
char mid[compiler::kModelIdSize];
compiler::fill_model_id("STATOR", 2, mid);
CHECK(std::strncmp(mid, "STATOR2", 7) == 0);
CHECK(mid[7] == '\0');
CHECK(mid[31] == '\0');
return true;
}
int main() {
if (!test_positive()) return 1;
if (!test_negative()) return 1;
if (!test_type_meta()) return 1;
if (!test_sha256()) return 1;
std::printf("machine_test: %d checks passed\n", g_checks);
return 0;
}