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:
2026-08-21 11:52:25 +08:00
parent 0a36e36b16
commit 0263813a8f
2 changed files with 359 additions and 27 deletions
+210 -27
View File
@@ -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/ORCMP 与 IFWHILE 与四则
* - 切片 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/ORCMP 与 IFWHILE 与四则
* 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_idPOU 收集序
* - build_function 编译一个 POUPROGRAM/FUNCTION;帧约定分配)
* - find_pou / find_fn_id 按名查 POU AST / fn_id
* - build_function 编译一个 POUPROGRAM/FUNCTION/FB 骨架;帧约定分配
* - begin_stmt / alloc_temp 临时寄存器:语句内递增、语句结束复用基址
* - compile_stmt 语句编译(赋值 / IF / WHILE;函数输入只读
* - compile_if IF/ELSIF/ELSE:条件链 + 分支尾部 JMPJF/JMP
* - compile_while WHILEL_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 成功;falseerr 已写,前缀 "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 FUNCTIONFB 留切片 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 成功;falseerr 已写)
*/
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 成功;falseerr 已写)
*/
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 成功;falseerr 已写)
*/
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
+149
View File
@@ -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); // 实例 3Bstart@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. 用例 17TONCAL_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); // ptTIME 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. 用例 18TOF / 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..31in@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.CVINT 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..108 对齐)
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_0io.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_0io.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;
}