diff --git a/compiler/src/Codegen.cpp b/compiler/src/Codegen.cpp index 8aed05f..29bb880 100644 --- a/compiler/src/Codegen.cpp +++ b/compiler/src/Codegen.cpp @@ -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: " 返回 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 code; // 字节码(函数表 code_offset 相对此段) std::map regs; // 变量名 → 帧寄存器 uint8_t nlocals = 0; // 变量区终点 = 临时寄存器起始 @@ -184,6 +200,13 @@ namespace { std::string result_name; // FUNCTION 名(结果寄存器映射) }; + // FB 实例:字段名 → 数据区字节地址 + struct InstFields { + std::map 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: JF t, L_end 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(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 io_input_; // io.input 绑定名(小写) std::map io_output_; // io.output 绑定名(小写) std::vector global_offsets_; // 逻辑槽号 → 数据区字节偏移 - std::vector data_; // 数据区(全局初值字节) + std::vector data_; // 数据区(全局初值 + FB 实例块) + std::map instances_; // "POU/实例名" → 字段地址 + const InstFields* inline_fields_ = nullptr; // 内联 FB 体字段上下文(可空) }; } // namespace diff --git a/tests/src/codegen_test.cpp b/tests/src/codegen_test.cpp index 2418128..aab5a73 100644 --- a/tests/src/codegen_test.cpp +++ b/tests/src/codegen_test.cpp @@ -541,6 +541,151 @@ static bool test_case13() { return true; } +// ---- 14. 用例 15:用户 FB 内联展开 ---- + +static bool test_case15() { + std::vector 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(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 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(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 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(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 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 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(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; }