12.8 切片 7:FB 实例块、用户 FB 内联、CAL_TON/TOF/CTU。
- FB 实例 → 数据区实例块(字段地址 = 基址+偏移,编译期算死;跨周期持久) - 用户 FB 调用点内联展开(实参写字段 → FB 体以实例字段为变量编译) - 内建 TON/TOF/CTU:实参写字段 → CAL_* <实例偏移>;FB 生成空函数占位保持 fn_id - codegen_test:189 断言(用例 15 内联序列、17 CAL_TON 字段偏移、18 TOF/CTU、line1 全链路 LOAD_I/STORE_Q),ctest 9/9 - 20 用例扫描:14 正例全部出映像,6 负例按期望在前层拒绝
This commit is contained in:
+210
-27
@@ -1,37 +1,37 @@
|
||||
/**
|
||||
* @file Codegen.cpp
|
||||
* @brief 寄存器码生成(12.8,切片 6:+ FUNCTION 与 CALL)
|
||||
* @brief 寄存器码生成(12.8,切片 7:+ FB 实例、内联展开、CAL_*)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 设计说明(详见 Doc/compiler/寄存器码.md):
|
||||
* - 切片 1~5:帧/字面量/MOVE/RET;全局数据区;NOT 与短路 AND/OR;CMP 与 IF;WHILE 与四则。
|
||||
* - 切片 6:调用约定(冻结)——结果 r0、参数 r1..r7(最多 7 输入、函数内只读)、
|
||||
* 变量与临时全部从 r8 起(调用约定区不受覆盖);
|
||||
* CALL 时 VM 复制 r0..r7 到新帧、RET 复制回调用方(r0=结果);
|
||||
* 调用点:实参求值到 r8+ 临时 → MOVE r(1+i) → CALL fn_id → MOVE rd, r0
|
||||
* - 其余构造报 codegen error(后续切片展开)
|
||||
* - 切片 1~6:帧/字面量/MOVE/RET;全局数据区;短路 AND/OR;CMP 与 IF;WHILE 与四则;
|
||||
* FUNCTION 与 CALL(调用约定 r0/r1..r7/r8+)。
|
||||
* - 切片 7:FB 实例 → 数据区实例块(跨周期持久);字段地址 = 实例基址 + 字段偏移
|
||||
* (编译期算死);字段读写走 LOAD_GLOBAL / STORE_GLOBAL;
|
||||
* 内建 TON/TOF/CTU → 实参写字段后 CAL_* <实例偏移>;
|
||||
* 用户 FB → 调用点内联展开(实参写字段 → FB 体以实例字段为变量编译)
|
||||
* - 其余构造报 codegen error
|
||||
*
|
||||
* 函数清单:
|
||||
* - put_le32 / put_le64 小端写入映像缓冲
|
||||
* - align_up / width_of 对齐与槽宽
|
||||
* - Builder::Builder (构造)存工程/源文件/链接结果/输出,收集 io 绑定分类
|
||||
* - Builder::run 布局数据区 → 逐 POU 建函数 → 拼映像
|
||||
* - Builder::run 布局数据区(全局 + 实例)→ 逐 POU 建函数 → 拼映像
|
||||
* - Builder::fail 组装 "codegen error: <msg>" 返回 false
|
||||
* - find_pou 按名查 POU AST
|
||||
* - find_fn_id 按名查 fn_id(POU 收集序)
|
||||
* - build_function 编译一个 POU(PROGRAM/FUNCTION;帧约定分配)
|
||||
* - find_pou / find_fn_id 按名查 POU AST / fn_id
|
||||
* - build_function 编译一个 POU(PROGRAM/FUNCTION/FB 骨架;帧约定分配)
|
||||
* - begin_stmt / alloc_temp 临时寄存器:语句内递增、语句结束复用基址
|
||||
* - compile_stmt 语句编译(赋值 / IF / WHILE;函数输入只读)
|
||||
* - compile_if IF/ELSIF/ELSE:条件链 + 分支尾部 JMP,JF/JMP 回填
|
||||
* - compile_while WHILE:L_loop + 条件 JF + 体 + JMP 回环
|
||||
* - store_target 赋值左值:STORE_* 到全局槽
|
||||
* - compile_expr 表达式编译到寄存器(含 CMP/算术/NOT/短路/CALL)
|
||||
* - cmp_op / arith_op 比较符/算术符 → 操作码
|
||||
* - load_op / store_op 按 io 绑定选读取/写入操作码
|
||||
* - global_offset 逻辑槽号 → 数据区字节偏移
|
||||
* - compile_stmt 语句编译(赋值 / IF / WHILE / FB 调用)
|
||||
* - compile_if / compile_while IF 链 / WHILE 回环
|
||||
* - compile_fb_call FB 调用:内建 CAL_* 或用户内联展开
|
||||
* - compile_fb_inline 内联编译用户 FB 体(实例字段为变量)
|
||||
* - store_target 赋值左值:STORE_* 到全局槽(或内联模式实例字段)
|
||||
* - compile_expr 表达式编译到寄存器(含 Field 字段读)
|
||||
* - cmp_op / arith_op / load_op / store_op 操作码选择
|
||||
* - global_offset / instance_of 槽号 → 偏移 / 实例 → 字段地址
|
||||
* - patch_jump 回填跳转偏移(相对下一条指令)
|
||||
* - layout_data / init_of 全局数据区布局与初值
|
||||
* - layout_data / layout_fb_instances / init_of 数据区布局
|
||||
* - const_id 取常量表 id(无则追加)
|
||||
* - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + 数据段
|
||||
* - codegen_project 对外入口
|
||||
@@ -89,6 +89,17 @@ namespace {
|
||||
return 1; // bool 及未知
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief TypeKind → 槽宽(FB 字段布局用)
|
||||
* @param k 类型种类
|
||||
* @return 1 / 2 / 8;未知返回 1
|
||||
*/
|
||||
uint32_t width_of(TypeKind k) {
|
||||
if (k == TypeKind::Int) return 2;
|
||||
if (k == TypeKind::Time) return 8;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 代码生成器(切片 1)
|
||||
*/
|
||||
@@ -120,14 +131,17 @@ namespace {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 布局数据区 → 逐 POU 建函数 → 拼映像
|
||||
* @details 数据区偏移先定(函数编译引用 global_offset)
|
||||
* @brief 布局数据区 → 布局 FB 实例 → 逐 POU 建函数 → 拼映像
|
||||
* @details 偏移先定(函数编译引用 global_offset / instance_of)
|
||||
* @return true 成功;false(err 已写,前缀 "codegen error")
|
||||
*/
|
||||
bool run() {
|
||||
if (!layout_data()) {
|
||||
return false;
|
||||
}
|
||||
if (!layout_fb_instances()) {
|
||||
return false;
|
||||
}
|
||||
for (const LinkResult::PouScope& sc : link_.scopes) {
|
||||
const POU* pou = find_pou(sc.name);
|
||||
if (pou == nullptr) {
|
||||
@@ -135,6 +149,7 @@ namespace {
|
||||
}
|
||||
FuncCtx f;
|
||||
f.name = sc.name;
|
||||
f.pou_name = sc.name;
|
||||
if (!build_function(*pou, &f)) {
|
||||
return false;
|
||||
}
|
||||
@@ -175,6 +190,7 @@ namespace {
|
||||
// 每函数的编译态
|
||||
struct FuncCtx {
|
||||
std::string name;
|
||||
std::string pou_name; // 所属 POU 名(实例查找键)
|
||||
std::vector<isa::Instr> code; // 字节码(函数表 code_offset 相对此段)
|
||||
std::map<std::string, uint8_t> regs; // 变量名 → 帧寄存器
|
||||
uint8_t nlocals = 0; // 变量区终点 = 临时寄存器起始
|
||||
@@ -184,6 +200,13 @@ namespace {
|
||||
std::string result_name; // FUNCTION 名(结果寄存器映射)
|
||||
};
|
||||
|
||||
// FB 实例:字段名 → 数据区字节地址
|
||||
struct InstFields {
|
||||
std::map<std::string, uint32_t> field_addr;
|
||||
uint32_t base = 0;
|
||||
std::string type_name; // 实例的 FB 类型名(内建 / 用户)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 语句开始:临时寄存器基址复用
|
||||
* @param f 当前函数
|
||||
@@ -206,7 +229,8 @@ namespace {
|
||||
|
||||
/**
|
||||
* @brief 编译一个 POU
|
||||
* @details 切片 6:支持 PROGRAM 与 FUNCTION(FB 留切片 8)
|
||||
* @details 支持 PROGRAM / FUNCTION / FUNCTION_BLOCK:
|
||||
* FB 不生成业务字节码(调用点内联),只出空函数占位保持 fn_id 一致;
|
||||
* 帧约定(见 Doc/compiler/寄存器码.md):结果 r0、输入 r1..r7(只读)、
|
||||
* 变量与临时全部从 r8 起
|
||||
* @param pou POU AST
|
||||
@@ -215,7 +239,11 @@ namespace {
|
||||
*/
|
||||
bool build_function(const POU& pou, FuncCtx* f) {
|
||||
if (pou.kind == PouKind::FunctionBlock) {
|
||||
return fail("FB not supported in slice 6 ('" + pou.name + "')");
|
||||
// FB 内联展开(见 compile_fb_call),此处只占 fn_id
|
||||
f->nlocals = 8;
|
||||
f->nregs = 8;
|
||||
f->code.push_back(isa::enc_ret());
|
||||
return true;
|
||||
}
|
||||
f->is_function = pou.kind == PouKind::Function;
|
||||
f->result_name = pou.name;
|
||||
@@ -242,8 +270,9 @@ namespace {
|
||||
continue;
|
||||
}
|
||||
for (const VarDecl& d : b.vars) {
|
||||
// FB 实例走数据区(layout_fb_instances),不占帧寄存器
|
||||
if (d.type.kind == TypeKind::FbUser || d.type.kind == TypeKind::FbBuiltin) {
|
||||
return fail("FB instance not supported in slice 6 ('" + d.name + "')");
|
||||
continue;
|
||||
}
|
||||
if (r > 255) {
|
||||
return fail("register overflow in function '" + pou.name + "'");
|
||||
@@ -276,8 +305,23 @@ namespace {
|
||||
if (st.kind == StmtKind::While) {
|
||||
return compile_while(f, st);
|
||||
}
|
||||
if (st.kind == StmtKind::FbCall) {
|
||||
return compile_fb_call(f, st);
|
||||
}
|
||||
if (st.kind != StmtKind::Assign) {
|
||||
return fail("statement not supported in slice 5");
|
||||
return fail("statement not supported in slice 7");
|
||||
}
|
||||
// 内联 FB 体内:左值可能是实例字段(STORE_GLOBAL)
|
||||
if (inline_fields_) {
|
||||
const auto fit = inline_fields_->field_addr.find(st.target);
|
||||
if (fit != inline_fields_->field_addr.end()) {
|
||||
const uint8_t t = alloc_temp(f);
|
||||
if (!compile_expr(f, *st.value, t)) {
|
||||
return false;
|
||||
}
|
||||
f.code.push_back(isa::enc_slot(isa::Op::STORE_GLOBAL, t, fit->second));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const auto it = f.regs.find(st.target);
|
||||
if (it == f.regs.end()) {
|
||||
@@ -292,6 +336,68 @@ namespace {
|
||||
return compile_expr(f, *st.value, rd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 编译 FB 调用
|
||||
* @details 实参求值到临时 → STORE_GLOBAL 到实例字段;
|
||||
* 内建 TON/TOF/CTU → CAL_* <实例偏移>;
|
||||
* 用户 FB → compile_fb_inline 内联展开
|
||||
* @param f 当前函数
|
||||
* @param st FbCall 语句 AST
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool compile_fb_call(FuncCtx& f, const Stmt& st) {
|
||||
const InstFields* inst = instance_of(f.pou_name, st.instance);
|
||||
if (inst == nullptr) {
|
||||
return fail("no instance '" + st.instance + "' in '" + f.pou_name + "'");
|
||||
}
|
||||
for (const FbArg& a : st.args) {
|
||||
const auto it = inst->field_addr.find(a.name);
|
||||
if (it == inst->field_addr.end()) {
|
||||
return fail("unknown input '" + a.name + "' for '" + st.instance + "'");
|
||||
}
|
||||
const uint8_t t = alloc_temp(f);
|
||||
if (!compile_expr(f, *a.value, t)) {
|
||||
return false;
|
||||
}
|
||||
f.code.push_back(isa::enc_slot(isa::Op::STORE_GLOBAL, t, it->second));
|
||||
}
|
||||
if (inst->type_name == "ton" || inst->type_name == "tof" ||
|
||||
inst->type_name == "ctu") {
|
||||
const isa::Op op = inst->type_name == "ton" ? isa::Op::CAL_TON
|
||||
: inst->type_name == "tof" ? isa::Op::CAL_TOF
|
||||
: isa::Op::CAL_CTU;
|
||||
f.code.push_back(isa::enc_slot(op, 0, inst->base));
|
||||
return true;
|
||||
}
|
||||
const POU* fb = find_pou(inst->type_name);
|
||||
if (fb == nullptr) {
|
||||
return fail("no FB type '" + inst->type_name + "'");
|
||||
}
|
||||
return compile_fb_inline(f, *fb, *inst);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 内联编译用户 FB 体
|
||||
* @details 切到实例字段上下文(VarRef/赋值左值映射到字段地址);
|
||||
* 表达式临时与调用方共用 r8+;字段读写全走 LOAD/STORE_GLOBAL
|
||||
* @param f 当前函数
|
||||
* @param fb FB 类型 POU
|
||||
* @param inst 本实例字段表
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool compile_fb_inline(FuncCtx& f, const POU& fb, const InstFields& inst) {
|
||||
const InstFields* saved = inline_fields_;
|
||||
inline_fields_ = &inst;
|
||||
for (const Stmt& s : fb.body) {
|
||||
begin_stmt(f);
|
||||
if (!compile_stmt(f, s)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
inline_fields_ = saved;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 编译 WHILE
|
||||
* @details L_loop: <cond→t> JF t, L_end <body> JMP L_loop L_end:
|
||||
@@ -419,6 +525,14 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
if (e.kind == ExprKind::VarRef) {
|
||||
// 内联 FB 体内:字段优先(实例字段为变量)
|
||||
if (inline_fields_) {
|
||||
const auto fit = inline_fields_->field_addr.find(e.name);
|
||||
if (fit != inline_fields_->field_addr.end()) {
|
||||
f.code.push_back(isa::enc_slot(isa::Op::LOAD_GLOBAL, rd, fit->second));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const auto sit = f.regs.find(e.name);
|
||||
if (sit != f.regs.end()) {
|
||||
f.code.push_back(isa::enc_rr(isa::Op::MOVE, rd, sit->second));
|
||||
@@ -432,6 +546,18 @@ namespace {
|
||||
}
|
||||
return fail("no register or slot for '" + e.name + "'");
|
||||
}
|
||||
if (e.kind == ExprKind::Field) {
|
||||
const InstFields* inst = instance_of(f.pou_name, e.name);
|
||||
if (inst == nullptr) {
|
||||
return fail("no instance '" + e.name + "' in '" + f.pou_name + "'");
|
||||
}
|
||||
const auto fit = inst->field_addr.find(e.field);
|
||||
if (fit == inst->field_addr.end()) {
|
||||
return fail("unknown field '" + e.field + "' for '" + e.name + "'");
|
||||
}
|
||||
f.code.push_back(isa::enc_slot(isa::Op::LOAD_GLOBAL, rd, fit->second));
|
||||
return true;
|
||||
}
|
||||
if (e.kind == ExprKind::Add || e.kind == ExprKind::Sub ||
|
||||
e.kind == ExprKind::Mul || e.kind == ExprKind::Div) {
|
||||
const uint8_t l = alloc_temp(f);
|
||||
@@ -633,6 +759,61 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 布局 FB 实例块:数据区全局之后,按 POU 收集序 / 实例声明序
|
||||
* @details 每实例:字段按布局段序对齐排布(BOOL 1 / INT 2 / TIME 8),
|
||||
* 字段地址 = 实例基址 + 字段偏移;实例块间 8 字节对齐,块内容初值 0
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool layout_fb_instances() {
|
||||
uint32_t cur = align_up(static_cast<uint32_t>(data_.size()), 8);
|
||||
bool any = false;
|
||||
for (const LinkResult::PouScope& sc : link_.scopes) {
|
||||
for (const Symbol& s : sc.syms) {
|
||||
if (s.kind != SymbolKind::FbInstance) {
|
||||
continue;
|
||||
}
|
||||
const auto lay = sc.fb_instances.find(s.name);
|
||||
if (lay == sc.fb_instances.end()) {
|
||||
return fail("no layout for instance '" + s.name + "'");
|
||||
}
|
||||
cur = align_up(cur, 8);
|
||||
InstFields inst;
|
||||
inst.base = cur;
|
||||
inst.type_name = s.type_name;
|
||||
uint32_t off = 0;
|
||||
for (const FbField& fd : lay->second.fields) {
|
||||
const uint32_t w = width_of(fd.type);
|
||||
off = align_up(off, w);
|
||||
inst.field_addr[fd.name] = cur + off;
|
||||
off += w;
|
||||
}
|
||||
cur += align_up(off, 8);
|
||||
instances_[sc.name + "/" + s.name] = inst;
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
// 仅在有实例时把数据区补到实例区终点(无实例则保持全局区原样)
|
||||
if (any) {
|
||||
while (data_.size() < cur) {
|
||||
data_.push_back(0);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 POU + 实例名查实例字段表
|
||||
* @param pou POU 名
|
||||
* @param name 实例名
|
||||
* @return 实例字段表指针;未找到返回 nullptr
|
||||
*/
|
||||
const InstFields* instance_of(const std::string& pou,
|
||||
const std::string& name) const {
|
||||
const auto it = instances_.find(pou + "/" + name);
|
||||
return it == instances_.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查全局声明的初值(AST;gvl 文件顶层段,声明序 = 槽号序)
|
||||
* @param name 全局名(小写)
|
||||
@@ -765,7 +946,9 @@ namespace {
|
||||
std::map<std::string, bool> io_input_; // io.input 绑定名(小写)
|
||||
std::map<std::string, bool> io_output_; // io.output 绑定名(小写)
|
||||
std::vector<uint32_t> global_offsets_; // 逻辑槽号 → 数据区字节偏移
|
||||
std::vector<uint8_t> data_; // 数据区(全局初值字节)
|
||||
std::vector<uint8_t> data_; // 数据区(全局初值 + FB 实例块)
|
||||
std::map<std::string, InstFields> instances_; // "POU/实例名" → 字段地址
|
||||
const InstFields* inline_fields_ = nullptr; // 内联 FB 体字段上下文(可空)
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -541,6 +541,151 @@ static bool test_case13() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 14. 用例 15:用户 FB 内联展开 ----
|
||||
|
||||
static bool test_case15() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
CHECK(compile_case("15_fb_instance", &img, &err));
|
||||
|
||||
const isa::ImageView v = isa::ImageView::from(img);
|
||||
CHECK(v.ok());
|
||||
CHECK(v.header().n_funcs == 2); // FB 占位 + MAIN
|
||||
CHECK(v.header().entry_fn_id == 1);
|
||||
CHECK(v.data_len() == 8); // 实例 3B(start@0 stop@1 q@2),8 对齐
|
||||
|
||||
const isa::FuncRow fm = v.func_row(1);
|
||||
CHECK(fm.nregs == 12);
|
||||
CHECK(fm.code_len == 12);
|
||||
const uint32_t* im = reinterpret_cast<const uint32_t*>(v.code_bytes()) + fm.code_offset / 4;
|
||||
char buf[64];
|
||||
// 实参写字段:start := TRUE / stop := FALSE
|
||||
isa::disasm(im[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r9, 0") == 0);
|
||||
isa::disasm(im[1], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 0") == 0);
|
||||
isa::disasm(im[2], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r10, 1") == 0);
|
||||
isa::disasm(im[3], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_GLOBAL r10, 1") == 0);
|
||||
// 内联体:Q := start AND NOT stop(短路跳转)
|
||||
isa::disasm(im[4], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 0") == 0);
|
||||
isa::disasm(im[5], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "JF r9, +3") == 0);
|
||||
isa::disasm(im[9], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 2") == 0); // Q 字段
|
||||
// q := starter.Q
|
||||
isa::disasm(im[10], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 2") == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 15. 用例 17:TON(CAL_TON + 字段偏移)----
|
||||
|
||||
static bool test_case17() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
CHECK(compile_case("17_ton", &img, &err));
|
||||
|
||||
const isa::ImageView v = isa::ImageView::from(img);
|
||||
CHECK(v.ok());
|
||||
CHECK(v.data_len() == 32); // in@0 pt@8 q@16 et@24
|
||||
const isa::FuncRow r = v.func_row(0);
|
||||
CHECK(r.nregs == 11);
|
||||
const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes());
|
||||
char buf[64];
|
||||
isa::disasm(ins[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r9, 0") == 0);
|
||||
isa::disasm(ins[1], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 0") == 0); // in
|
||||
isa::disasm(ins[2], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r10, 1") == 0);
|
||||
isa::disasm(ins[3], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_GLOBAL r10, 8") == 0); // pt(TIME 8 对齐)
|
||||
isa::disasm(ins[4], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "CAL_TON 0") == 0);
|
||||
isa::disasm(ins[5], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 16") == 0); // t.Q
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 16. 用例 18:TOF / CTU ----
|
||||
|
||||
static bool test_case18() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
CHECK(compile_case("18_tof_ctu", &img, &err));
|
||||
|
||||
const isa::ImageView v = isa::ImageView::from(img);
|
||||
CHECK(v.ok());
|
||||
CHECK(v.data_len() == 40); // tf 块 0..31(in@0 pt@8 q@16 et@24)、c 块 32..
|
||||
const isa::FuncRow r = v.func_row(0);
|
||||
CHECK(r.nregs == 14);
|
||||
const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes());
|
||||
char buf[64];
|
||||
isa::disasm(ins[4], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "CAL_TOF 0") == 0);
|
||||
isa::disasm(ins[12], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "CAL_CTU 32") == 0);
|
||||
isa::disasm(ins[13], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 36") == 0); // c.Q
|
||||
isa::disasm(ins[14], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r10, 38") == 0); // c.CV(INT 2 对齐)
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 17. line1 全链路(MAIN + Motor FB + I/O + 全局)----
|
||||
|
||||
static bool test_line1() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
using namespace compiler;
|
||||
const std::string toml = std::string(REPO_ROOT) + "/examples/line1/project.toml";
|
||||
Project p;
|
||||
CHECK(parse_project(toml, &p, &err));
|
||||
std::vector<SourceUnit> units;
|
||||
for (const std::string& f : compile_files(p)) {
|
||||
SourceUnit u;
|
||||
CHECK(load_unit(p.base_dir + "/" + f, &u, &err));
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
LinkResult link;
|
||||
CHECK(link_project(p, units, &link, &err));
|
||||
CHECK(check_project(p, units, link, &err));
|
||||
CHECK(codegen_project(p, units, link, &img, &err));
|
||||
|
||||
const isa::ImageView v = isa::ImageView::from(img);
|
||||
CHECK(v.ok());
|
||||
CHECK(v.header().n_globals == 4);
|
||||
CHECK(v.header().n_funcs == 2);
|
||||
CHECK(v.header().entry_fn_id == 1);
|
||||
CHECK(v.data_len() == 16); // 全局 4B + 实例块 8..10(8 对齐)
|
||||
|
||||
const isa::FuncRow fm = v.func_row(1);
|
||||
CHECK(fm.nregs == 13);
|
||||
CHECK(fm.code_len == 17);
|
||||
const uint32_t* im = reinterpret_cast<const uint32_t*>(v.code_bytes()) + fm.code_offset / 4;
|
||||
char buf[64];
|
||||
isa::disasm(im[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_I r8, 1") == 0); // I0_0(io.input)
|
||||
isa::disasm(im[1], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_GLOBAL r8, 8") == 0); // starter.start
|
||||
isa::disasm(im[2], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 2") == 0); // I0_1
|
||||
isa::disasm(im[3], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 9") == 0); // starter.stop
|
||||
isa::disasm(im[13], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_GLOBAL r8, 10") == 0); // starter.Q
|
||||
isa::disasm(im[14], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 10") == 0); // starter.Q 读回
|
||||
isa::disasm(im[15], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_Q r8, 3") == 0); // Q0_0(io.output)
|
||||
isa::disasm(im[16], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "RET") == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
int main() {
|
||||
if (!test_case01()) return 1;
|
||||
if (!test_case02()) return 1;
|
||||
@@ -555,6 +700,10 @@ int main() {
|
||||
if (!test_case07()) return 1;
|
||||
if (!test_case08()) return 1;
|
||||
if (!test_case13()) return 1;
|
||||
if (!test_case15()) return 1;
|
||||
if (!test_case17()) return 1;
|
||||
if (!test_case18()) return 1;
|
||||
if (!test_line1()) return 1;
|
||||
std::printf("codegen_test: %d checks passed\n", g_checks);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user