12.8 切片 4:CMP_* 与 IF/ELSIF/ELSE 跳转。

- CMP_xx:两侧求值到临时后比较;IF 编成 <cond→t> JF t, L_next <body> JMP L_end 链
- 分支尾部 JMP 与条件 JF 双回填;无 else 时末分支 JF 直接落 L_end
- codegen_test:89 断言(用例 04 完整跳转序列),ctest 9/9
This commit is contained in:
2026-08-21 11:43:23 +08:00
parent e7dee50cde
commit 6f1bf61c7f
2 changed files with 148 additions and 13 deletions
+97 -13
View File
@@ -1,35 +1,34 @@
/**
* @file Codegen.cpp
* @brief 寄存器码生成(12.8,切片 3+ NOT、短路 AND/OR
* @brief 寄存器码生成(12.8,切片 4+ CMP_*、IF/ELSIF/ELSE
* @author
* @date 2026-08-21
*
* @details 设计说明(详见 Doc/compiler/寄存器码.md):
* - 切片 1:PROGRAM 标量变量帧、字面量 LOADK、变量 MOVERET。
* - 切片 2:全局数据区(对齐 + 初值)、LOAD_I/STORE_Q/LOAD_GLOBAL/STORE_GLOBAL。
* - 切片 3NOTenc_rr);AND/OR **短路编跳转**(禁止两边都算):
* <lhs → rd> JF(AND)/JT(OR) rd, L_end <rhs → t> MOVE rd, t L_end:
* - 切片 1~3:帧/字面量/MOVE/RET;全局数据区;NOT 与短路 AND/OR
* - 切片 4:比较 CMP_xx(两侧求值到临时);IF 编成
* <cond→t> JF t, L_next <body> JMP L_end L_next: ... L_end:
* 跳转偏移相对下一条指令(目标 = 当前 + 1 + off,见 Doc/isa/指令与映像.md
* - 其余构造报 codegen error(后续切片展开)
*
* 函数清单:
* - put_le32 / put_le64 小端写入映像缓冲
* - align_up 按 2 的幂宽度向上对齐
* - width_of 类型名 → 槽宽(bool 1 / int 2 / time 8
* - align_up / width_of 对齐与槽宽
* - Builder::Builder (构造)存工程/源文件/链接结果/输出,收集 io 绑定分类
* - Builder::run 布局数据区 → 逐 POU 建函数 → 拼映像
* - Builder::fail 组装 "codegen error: <msg>" 返回 false
* - find_pou 按名查 POU AST
* - build_function 编译一个 POU 的语句体(帧寄存器分配)
* - begin_stmt / alloc_temp 临时寄存器:语句内递增、语句结束复用基址
* - compile_stmt 语句编译(切片 3:仅赋值,左值可帧寄存器或全局
* - compile_stmt 语句编译(切片 4:赋值 / IF
* - compile_if IF/ELSIF/ELSE:条件链 + 分支尾部 JMPJF/JMP 回填
* - store_target 赋值左值:STORE_* 到全局槽
* - compile_expr 表达式编译到寄存器(字面量/变量读/NOT/短路 AND/OR
* - compile_expr 表达式编译到寄存器(含 CMP_xx / NOT / 短路 AND/OR
* - cmp_op 比较符 → CMP_xx 操作码
* - load_op / store_op 按 io 绑定选读取/写入操作码
* - global_offset 逻辑槽号 → 数据区字节偏移
* - patch_jump 回填跳转偏移(相对下一条指令)
* - layout_data 布局全局数据区(对齐 + 初值字节)
* - init_of 查全局声明的初值(AST)
* - layout_data / init_of 全局数据区布局与初值
* - const_id 取常量表 id(无则追加)
* - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + 数据段
* - codegen_project 对外入口
@@ -238,14 +237,17 @@ namespace {
}
/**
* @brief 编译一条语句(切片 2:仅赋值,左值可为帧寄存器或全局
* @brief 编译一条语句(切片 4:赋值 / IF
* @param f 当前函数
* @param st 语句 AST
* @return true 成功;falseerr 已写)
*/
bool compile_stmt(FuncCtx& f, const Stmt& st) {
if (st.kind == StmtKind::If) {
return compile_if(f, st);
}
if (st.kind != StmtKind::Assign) {
return fail("statement not supported in slice 2");
return fail("statement not supported in slice 4");
}
const auto it = f.regs.find(st.target);
if (it == f.regs.end()) {
@@ -256,6 +258,59 @@ namespace {
return compile_expr(f, *st.value, rd);
}
/**
* @brief 编译 IF / ELSIF / ELSE
* @details 每分支:<cond→t> JF t, L_next → <body> → JMP L_end
* 最后一个分支无 else 时 JF 直接落 L_end(不补 JMP
* @param f 当前函数
* @param st IF 语句 AST
* @return true 成功;falseerr 已写)
*/
bool compile_if(FuncCtx& f, const Stmt& st) {
// 条件链:IF 体 + ELSIF 各体;ELSE 体可选
std::vector<std::pair<const Expr*, const std::vector<Stmt>*>> branches;
branches.push_back({st.cond.get(), &st.body});
for (const IfBranch& b : st.elsifs) {
branches.push_back({b.cond.get(), &b.body});
}
const bool has_else = !st.else_body.empty();
std::vector<size_t> end_jmps; // 各分支尾部 JMP(回填到 L_end)
for (size_t i = 0; i < branches.size(); ++i) {
const uint8_t t = alloc_temp(f);
if (!compile_expr(f, *branches[i].first, t)) {
return false;
}
const size_t jf_idx = f.code.size();
f.code.push_back(isa::enc_jc(isa::Op::JF, t, 0)); // 假 → 下一分支
for (const Stmt& s : *branches[i].second) {
begin_stmt(f);
if (!compile_stmt(f, s)) {
return false;
}
}
if (i + 1 < branches.size() || has_else) {
const size_t jmp_idx = f.code.size();
f.code.push_back(isa::enc_jmp(0)); // 分支尾部 → L_end
end_jmps.push_back(jmp_idx);
}
patch_jump(f, jf_idx, f.code.size()); // 回填 JF 到下一分支起点
}
if (has_else) {
for (const Stmt& s : st.else_body) {
begin_stmt(f);
if (!compile_stmt(f, s)) {
return false;
}
}
}
const size_t end = f.code.size();
for (const size_t j : end_jmps) {
patch_jump(f, j, end);
}
return true;
}
/**
* @brief 赋值左值:帧寄存器直写或 STORE_* 到全局槽
* @param f 当前函数
@@ -314,6 +369,18 @@ namespace {
}
return fail("no register or slot for '" + e.name + "'");
}
if (e.kind == ExprKind::Cmp) {
const uint8_t l = alloc_temp(f);
if (!compile_expr(f, *e.lhs, l)) {
return false;
}
const uint8_t r = alloc_temp(f);
if (!compile_expr(f, *e.rhs, r)) {
return false;
}
f.code.push_back(isa::enc_rrr(cmp_op(e.op), rd, l, r));
return true;
}
if (e.kind == ExprKind::Not) {
const uint8_t t = alloc_temp(f);
if (!compile_expr(f, *e.operand, t)) {
@@ -356,6 +423,23 @@ namespace {
static_cast<uint8_t>((static_cast<uint16_t>(off) >> 8) & 0xFFu));
}
/**
* @brief 比较符 → CMP_xx 操作码
* @param op 比较运算
* @return 对应操作码(CMP_EQ / CMP_NE / CMP_LT / CMP_LE / CMP_GT / CMP_GE
*/
isa::Op cmp_op(BinOp op) const {
switch (op) {
case BinOp::Eq: return isa::Op::CMP_EQ;
case BinOp::Ne: return isa::Op::CMP_NE;
case BinOp::Lt: return isa::Op::CMP_LT;
case BinOp::Le: return isa::Op::CMP_LE;
case BinOp::Gt: return isa::Op::CMP_GT;
case BinOp::Ge: return isa::Op::CMP_GE;
}
return isa::Op::CMP_EQ;
}
/**
* @brief 按 io 绑定选读取操作码
* @param name 变量名(小写)
+51
View File
@@ -347,6 +347,56 @@ static bool test_not() {
return true;
}
// ---- 9. 用例 04IF / ELSIF / ELSECMP + JF + JMP 回填)----
static bool test_case04() {
std::vector<uint8_t> img;
std::string err;
CHECK(compile_case("04_if_elsif_else", &img, &err));
const isa::ImageView v = isa::ImageView::from(img);
CHECK(v.ok());
const isa::FuncRow r = v.func_row(0);
CHECK(r.nregs == 5); // sel out + 3 临时(CMP 目的 + 两操作数)
CHECK(r.code_len == 14);
char buf[64];
const uint8_t* base = v.code_bytes();
const uint32_t* ins = reinterpret_cast<const uint32_t*>(base);
// IF sel = 0 THENsel→r3、0→r4、CMP_EQ r2(常量序:0、10、1、20、30
isa::disasm(ins[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r3, r0") == 0);
isa::disasm(ins[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r4, 0") == 0);
isa::disasm(ins[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "CMP_EQ r2, r3, r4") == 0);
isa::disasm(ins[3], buf, sizeof buf);
CHECK(std::strcmp(buf, "JF r2, +2") == 0); // 假 → elsif[6]
isa::disasm(ins[4], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r1, 1") == 0); // out := 10(常量 1
isa::disasm(ins[5], buf, sizeof buf);
CHECK(std::strcmp(buf, "JMP +7") == 0); // → end[13]
// ELSIF sel = 1
isa::disasm(ins[6], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r3, r0") == 0);
isa::disasm(ins[7], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r4, 2") == 0);
isa::disasm(ins[8], buf, sizeof buf);
CHECK(std::strcmp(buf, "CMP_EQ r2, r3, r4") == 0);
isa::disasm(ins[9], buf, sizeof buf);
CHECK(std::strcmp(buf, "JF r2, +2") == 0); // 假 → else[12]
isa::disasm(ins[10], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r1, 3") == 0);
isa::disasm(ins[11], buf, sizeof buf);
CHECK(std::strcmp(buf, "JMP +1") == 0); // → end[13]
// ELSE
isa::disasm(ins[12], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r1, 4") == 0);
isa::disasm(ins[13], buf, sizeof buf);
CHECK(std::strcmp(buf, "RET") == 0);
return true;
}
int main() {
if (!test_case01()) return 1;
if (!test_case02()) return 1;
@@ -356,6 +406,7 @@ int main() {
if (!test_write_input()) return 1;
if (!test_case03()) return 1;
if (!test_not()) return 1;
if (!test_case04()) return 1;
std::printf("codegen_test: %d checks passed\n", g_checks);
return 0;
}