From d296a9215e3d26030f47f199574a655fa838bdc1 Mon Sep 17 00:00:00 2001 From: chentianya Date: Fri, 21 Aug 2026 11:30:43 +0800 Subject: [PATCH 1/9] =?UTF-8?q?=E8=A7=84=E5=88=92=2012.8=20=E5=AF=84?= =?UTF-8?q?=E5=AD=98=E5=99=A8=E7=A0=81=EF=BC=9A=E5=AD=98=E5=82=A8=E5=B8=83?= =?UTF-8?q?=E5=B1=80=E3=80=81=E6=8C=87=E4=BB=A4=E6=A8=A1=E5=BC=8F=E3=80=81?= =?UTF-8?q?8=20=E7=89=87=E6=89=A7=E8=A1=8C=E8=AE=A1=E5=88=92=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 寄存器码.md:帧/数据区布局(FB 实例块持久化)、短路编跳转、函数帧约定(结果 r0) - 用户 FB 调用内联展开定案;内建 CAL_TON/TOF/CTU - 8 片计划:骨架→I/Q/M→短路→IF→WHILE→算术→CALL→FB+line1 全链路 - 索引.md 登记 --- Doc/compiler/寄存器码.md | 121 +++++++++++++++++++++++++++++++++++++++ Doc/索引.md | 2 + 2 files changed, 123 insertions(+) create mode 100644 Doc/compiler/寄存器码.md diff --git a/Doc/compiler/寄存器码.md b/Doc/compiler/寄存器码.md new file mode 100644 index 0000000..7e9cb80 --- /dev/null +++ b/Doc/compiler/寄存器码.md @@ -0,0 +1,121 @@ +# 寄存器码(12.8) + +compiler 模块的代码生成。输入:`Project` + `Unit`(AST)+ `LinkResult`;输出:`.stb` 映像字节(`std::vector`)。全工程定案见 [`初步计划.md`](../初步计划.md) 12.8,指令/映像布局见 [`指令与映像.md`](../isa/指令与映像.md)。 + +## 做 / 不做 + +**做** + +- 寄存器分配:局部/变量固定寄存器,表达式临时值往后编号,`nregs` 写进函数头 +- 跳转先留洞、后回填;`AND` / `OR` **必须编成跳转**(短路),禁止两边都算 +- 常量表(`LOADK`)、全局/实例槽(`LOAD_GLOBAL` / `STORE_GLOBAL` 等) +- `CALL fn_id` 有界帧;用户 FB 调用**内联展开**;内建 `CAL_TON` / `CAL_TOF` / `CAL_CTU` +- 拼装映像:函数表 / 常量表 / 数据段 / 头(`cycle_limit`、`dt_ms`、工程哈希、入口) + +**不做(第一版)** + +- 图染色、SSA、生命周期分析、优化、异常表、闭包 +- 函数指针、间接跳转、`REAL` + +## 存储布局(v1 定案) + +| 数据 | 放哪 | 说明 | +|---|---|---| +| PROGRAM / FUNCTION 标量变量与临时 | **寄存器**(函数帧) | 每周期帧保留由 VM 负责(12.9) | +| FB 实例(任意 POU 内声明) | **数据区实例块**(绝对地址) | 状态跨周期持久 | +| 全局(含 I/Q/M) | 数据区(声明序) | 12.6 已定槽号 | + +- **数据区** = 全局块(声明序,按对齐规则:BOOL 1B / INT 2B / TIME 8B,起点对齐自身宽度)→ FB 实例块(按实例声明序,各块按类型布局大小、8 字节对齐) +- **指令 slot = 数据区字节偏移**(u16);符号表 `address` 保持逻辑槽号(12.6 冻结),编译时做 槽号 → 偏移 映射 +- 字段访问 `fb.Q`:`LOAD_GLOBAL rd, <实例基址+字段偏移>`(编译期算死);I/Q/M 用 `LOAD_I` / `STORE_Q` / `LOAD_M` / `STORE_M` 同槽号 +- 函数帧寄存器:`FUNCTION` 结果固定 **r0**,`VAR_INPUT` 从 r1 起按声明序,`VAR` 续后;PROGRAM / FB 变量从 r0 起按声明序 + +## 寄存器分配 + +- 局部变量固定占用前段(0..k-1) +- 表达式临时从 k 起,**语句内递增、语句结束复用基址**(不分析生命周期) +- `nregs` = 全函数临时峰值 + 1 + +## 表达式与语句的指令模式 + +```text +字面量 LOADK rd, const_id +变量读 LOAD_GLOBAL/LOAD_I/LOAD_M rd, slot (或帧内 MOVE) +字段读 LOAD_GLOBAL rd, <基址+偏移> +函数调用 <实参求值 → 参数寄存器> CALL fn_id MOVE rd, r0 + +NOT NOT rd, t +AND/OR 短路(必须跳转): + JF(AND)/JT(OR) rd, L_end + MOVE rd, t + L_end: +比较 CMP_xx rd, t1, t2 +算术 ADD/SUB/MUL/DIV rd, t1, t2 +负号 SUB rd, r0, t (0 - t,r0 恒 0) + +IF JF t, L_else + JMP L_end + L_else: (ELSIF 逐级) JF t, L_next ... + L_end: +WHILE L_loop: JF t, L_end JMP L_loop L_end: + +FB 调用(用户,内联展开): + <每个实参求值 → 临时> STORE_GLOBAL <实例基址+字段偏移>, 临时 + <内联体:读输入字段、算输出、写输出字段> +内建 实参同左 → CAL_TON/CAL_TOF/CAL_CTU <实例偏移> +``` + +- 常量统一进常量表(BOOL / INT / TIME),`const_id` = 首次出现序 +- 空条件寄存器约定:`r0` 恒 0(`enc_jmp` 用),`SUB rd, r0, t` 实现取负 + +## 用户 FB 调用内联(v1 定案) + +FB 体共享会与「绝对字段地址」冲突(不同实例基址不同),v1 选择**调用点内联展开**: + +- 调用点:实参写入该实例字段 → 内联该 FB 的函数体(字段地址按本实例基址算死)→ 字段读取自然可用 +- 代价:字节码随调用点数膨胀;v1 规模可接受;以后可升级为「基址寄存器寻址」指令 +- FB 无递归、无动态实例,内联安全 + +## 映像拼装 + +```text +函数表:收集序 fn_id → { nregs, code_offset(相对字节码段), code_len } +常量表:const_id → { tag, value } +数据段:全局 + 实例块(见上) +头:cycle_limit / dt_ms / 工程哈希(12.3)/ entry=MAIN fn_id / n_globals / n_i=n_q=n_m=0 +``` + +- 用 `isa` 的 `enc_*` 编码指令、`ImageView` 校验回读 +- 工程哈希用 12.3 的 `compute_project_hash` + +## 错误 + +稳定前缀 `codegen error`: + +```text +codegen error: register overflow (>256) in function 'f' +codegen error: constant table overflow +codegen error: slot out of range +``` + +## 完成标准 + +1. `line1` 能写出映像(函数字节码 + 全局/FB 槽 + 工程哈希),`ImageView` 校验通过 +2. 跳转回填无悬空(disasm 抽查) +3. 用例 1~9、13、15、17、18、20 编译出映像;负例(10/11/12/14/16/19)仍在前层被拒 +4. 全部构建 + `ctest` 无回归 + +--- + +## 执行计划(8 片,每片验证编译产物) + +1. **`Codegen.h`(新建)+ `Codegen.cpp` 骨架**:帧/槽/常量表/指令缓冲/回填基础设施;`MOVE` `LOADK` `RET` → 用例 1、2 出映像 +2. **全局与 I/Q/M**:`LOAD_I` / `STORE_Q` / `LOAD_M` / `STORE_M` / `LOAD_GLOBAL` / `STORE_GLOBAL`(用例 9) +3. **`NOT` + 短路 `AND` / `OR`**(用例 3,断言产物含跳转) +4. **`CMP_*` + `JMP` / `JT` / `JF` → `IF`**(用例 4) +5. **`WHILE`**(用例 5;`cycle_limit` 进映像头) +6. **`ADD/SUB/MUL/DIV` + TIME 常量**(用例 7、8) +7. **`CALL fn_id` + 有界帧**(用例 13) +8. **FB 实例块 + 字段偏移;内联用户 FB;`CAL_TON` / `CAL_TOF` / `CAL_CTU`**(用例 15、17、18、20 line1 全链路) + +每片配 `codegen_test` 断言(nregs / 指令序列 disasm / 回填 / 常量表 / 数据区);最终 `ctest` 9/9。 diff --git a/Doc/索引.md b/Doc/索引.md index 4eaf3af..44cd671 100644 --- a/Doc/索引.md +++ b/Doc/索引.md @@ -12,6 +12,7 @@ doc/ compiler/语法.md compiler/符号表与链接.md compiler/类型检查.md + compiler/寄存器码.md vm/扫描周期.md executor/执行器入口.md ``` @@ -25,6 +26,7 @@ doc/ | [`compiler/语法.md`](compiler/语法.md) | 12.5 语法:AST 结构、文法、拒绝清单 | | [`compiler/符号表与链接.md`](compiler/符号表与链接.md) | 12.6 符号表与链接:数据区定址、FB 布局、错误类别 | | [`compiler/类型检查.md`](compiler/类型检查.md) | 12.7 类型检查:表达式类型、语句规则、错误类别 | +| [`compiler/寄存器码.md`](compiler/寄存器码.md) | 12.8 寄存器码:布局、指令模式、内联 FB、8 片计划 | | [`vm/扫描周期.md`](vm/扫描周期.md) | 扫描周期与 VM 边界 | | [`executor/执行器入口.md`](executor/执行器入口.md) | 可执行入口:加载 `.stb` + sidecar,跑扫描周期 | From 66359a0a26863c5aede7df00d38fb8a15ead6cbf Mon Sep 17 00:00:00 2001 From: chentianya Date: Fri, 21 Aug 2026 11:32:31 +0800 Subject: [PATCH 2/9] =?UTF-8?q?12.8=20=E5=88=87=E7=89=87=201=EF=BC=9A?= =?UTF-8?q?=E5=AF=84=E5=AD=98=E5=99=A8=E7=A0=81=E9=AA=A8=E6=9E=B6=EF=BC=88?= =?UTF-8?q?MOVE/LOADK/RET=EF=BC=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Codegen.h/cpp:帧寄存器(0 起声明序)、常量表、函数表、小端映像拼装 - 切片 1 范围:PROGRAM 标量 + 字面量 LOADK + 变量 MOVE + RET;其余构造报 codegen error - codegen_test:23 断言(用例 01 空 MAIN 出 RET、02 LOADK r0/r1 + 常量表),ctest 9/9 --- compiler/CMakeLists.txt | 3 +- compiler/include/compiler/Codegen.h | 25 +++ compiler/src/Codegen.cpp | 321 ++++++++++++++++++++++++++++ tests/CMakeLists.txt | 11 + tests/src/codegen_test.cpp | 127 +++++++++++ 5 files changed, 486 insertions(+), 1 deletion(-) create mode 100644 compiler/include/compiler/Codegen.h create mode 100644 compiler/src/Codegen.cpp create mode 100644 tests/src/codegen_test.cpp diff --git a/compiler/CMakeLists.txt b/compiler/CMakeLists.txt index efcffe0..eecfc8e 100644 --- a/compiler/CMakeLists.txt +++ b/compiler/CMakeLists.txt @@ -12,7 +12,8 @@ add_library(compiler STATIC ./src/Project.cpp ./src/Parser.cpp ./src/Linker.cpp - ./src/Typecheck.cpp) + ./src/Typecheck.cpp + ./src/Codegen.cpp) target_include_directories(compiler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) target_include_directories(compiler PRIVATE ${CMAKE_SOURCE_DIR}/third_party/tomlplusplus/include) diff --git a/compiler/include/compiler/Codegen.h b/compiler/include/compiler/Codegen.h new file mode 100644 index 0000000..ee72f53 --- /dev/null +++ b/compiler/include/compiler/Codegen.h @@ -0,0 +1,25 @@ +/** + * @file Codegen.h + * @brief 寄存器码生成(12.8,切片 1:MOVE / LOADK / RET) + * @author + * @date 2026-08-21 + */ + +#pragma once + +#include +#include +#include + +#include "compiler/Linker.h" +#include "compiler/Parser.h" +#include "compiler/Project.h" + +namespace compiler { + + // 编译工程为 .stb 映像字节(在链接 + 类型检查成功后调用)。 + // 失败返回 false,err 前缀 "codegen error"。 + bool codegen_project(const Project& proj, const std::vector& units, + const LinkResult& link, std::vector* image, + std::string* err); +} diff --git a/compiler/src/Codegen.cpp b/compiler/src/Codegen.cpp new file mode 100644 index 0000000..cc5edf3 --- /dev/null +++ b/compiler/src/Codegen.cpp @@ -0,0 +1,321 @@ +/** + * @file Codegen.cpp + * @brief 寄存器码生成(12.8,切片 1:MOVE / LOADK / RET) + * @author + * @date 2026-08-21 + * + * @details 设计说明(详见 Doc/compiler/寄存器码.md): + * - 切片 1 范围:PROGRAM 标量变量帧(r0 起按声明序)、字面量 LOADK、 + * 变量间 MOVE、RET;空 MAIN 出映像。其余构造报 codegen error(后续切片展开) + * - 常量统一进常量表(BOOL/INT/TIME),const_id = 首次出现序 + * - 映像:头(cycle_limit/dt_ms/工程哈希/入口)+ 常量表 + 函数表 + 字节码, + * 数据段为空(无全局/实例) + * + * 函数清单: + * - put_le32 / put_le64 小端写入映像缓冲 + * - Builder::Builder (构造)存工程/源文件/链接结果/输出 + * - Builder::run 逐 POU 建函数 → 拼映像 + * - Builder::fail 组装 "codegen error: " 返回 false + * - build_frame 给 POU 标量变量分配帧寄存器(0 起按声明序) + * - build_function 编译一个 POU 的语句体 + * - compile_stmt 语句编译(切片 1:仅赋值) + * - compile_expr 表达式编译(切片 1:仅字面量 / 变量读) + * - const_id 取常量表 id(无则追加) + * - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + * - codegen_project 对外入口 + */ + +#include "compiler/Codegen.h" + +#include +#include +#include +#include + +#include "isa/Encode.h" +#include "isa/Image.h" +#include "isa/Instr.h" +#include "isa/Types.h" + +namespace compiler { +namespace { + + // ---- 小端写入(Image.cpp 内部实现不可见,这里自带最小版)---- + + void put_le32(std::vector& b, size_t off, uint32_t v) { + b[off + 0] = static_cast(v & 0xFFu); + b[off + 1] = static_cast((v >> 8) & 0xFFu); + b[off + 2] = static_cast((v >> 16) & 0xFFu); + b[off + 3] = static_cast((v >> 24) & 0xFFu); + } + + void put_le64(std::vector& b, size_t off, uint64_t v) { + for (int i = 0; i < 8; ++i) { + b[off + i] = static_cast((v >> (8 * i)) & 0xFFu); + } + } + + /** + * @brief 代码生成器(切片 1) + */ + class Builder { + public: + /** + * @brief 构造生成器 + * @param proj 工程定义(cycle_limit / dt_ms / 哈希用) + * @param units 全部源文件的 AST + * @param link 链接结果(POU 顺序 / 符号) + * @param image 输出映像字节 + * @param err 错误输出;可为 nullptr(静默) + */ + Builder(const Project& proj, const std::vector& units, + const LinkResult& link, std::vector* image, std::string* err) + : proj_(proj), units_(units), link_(link), image_(image), err_(err) {} + + /** + * @brief 逐 POU 建函数后拼映像 + * @return true 成功;false(err 已写,前缀 "codegen error") + */ + bool run() { + for (const LinkResult::PouScope& sc : link_.scopes) { + const POU* pou = find_pou(sc.name); + if (pou == nullptr) { + continue; + } + FuncCtx f; + f.name = sc.name; + if (!build_function(*pou, &f)) { + return false; + } + funcs_.push_back(std::move(f)); + } + return assemble_image(); + } + + private: + /** + * @brief 组装错误消息并返回 false + * @param msg 错误描述(不含前缀) + * @return 恒 false + */ + bool fail(const std::string& msg) { + if (err_) { + *err_ = "codegen error: " + msg; + } + return false; + } + + /** + * @brief 按名查 POU AST + * @param name POU 名(小写) + * @return POU 指针;未找到返回 nullptr + */ + const POU* find_pou(const std::string& name) const { + for (const SourceUnit& u : units_) { + for (const POU& p : u.ast.pous) { + if (p.name == name) { + return &p; + } + } + } + return nullptr; + } + + // 每函数的编译态 + struct FuncCtx { + std::string name; + std::vector code; // 字节码(函数表 code_offset 相对此段) + std::map regs; // 变量名 → 帧寄存器 + uint8_t nregs = 0; // 变量数(切片 1 无临时) + }; + + /** + * @brief 编译一个 POU + * @details 切片 1:仅 PROGRAM + 标量变量(FB 实例/函数留后续切片) + * @param pou POU AST + * @param f 输出函数编译态 + * @return true 成功;false(err 已写) + */ + bool build_function(const POU& pou, FuncCtx* f) { + if (pou.kind != PouKind::Program) { + return fail("only PROGRAM supported in slice 1 ('" + pou.name + "')"); + } + for (const VarBlock& b : pou.blocks) { + for (const VarDecl& d : b.vars) { + if (d.type.kind == TypeKind::FbUser || d.type.kind == TypeKind::FbBuiltin) { + return fail("FB instance not supported in slice 1 ('" + d.name + "')"); + } + if (f->regs.size() > 255) { + return fail("register overflow in function '" + pou.name + "'"); + } + f->regs[d.name] = f->nregs++; + } + } + for (const Stmt& st : pou.body) { + if (!compile_stmt(*f, st)) { + return false; + } + } + f->code.push_back(isa::enc_ret()); + return true; + } + + /** + * @brief 编译一条语句(切片 1:仅赋值) + * @param f 当前函数 + * @param st 语句 AST + * @return true 成功;false(err 已写) + */ + bool compile_stmt(FuncCtx& f, const Stmt& st) { + if (st.kind != StmtKind::Assign) { + return fail("statement not supported in slice 1"); + } + const auto it = f.regs.find(st.target); + if (it == f.regs.end()) { + return fail("no register for '" + st.target + "'"); + } + const uint8_t rd = it->second; + uint8_t rs = 0; + const ExprKind k = st.value->kind; + if (k == ExprKind::LitBool || k == ExprKind::LitInt || + k == ExprKind::LitTime) { + const isa::types::TypeTag tag = + k == ExprKind::LitBool ? isa::types::Bool + : k == ExprKind::LitInt ? isa::types::Int + : isa::types::Time; + f.code.push_back( + isa::enc_imm(isa::Op::LOADK, rd, const_id(tag, st.value->int_value))); + return true; + } + if (k == ExprKind::VarRef) { + const auto sit = f.regs.find(st.value->name); + if (sit == f.regs.end()) { + return fail("no register for '" + st.value->name + "'"); + } + f.code.push_back(isa::enc_rr(isa::Op::MOVE, rd, sit->second)); + return true; + } + return fail("expression not supported in slice 1"); + } + + /** + * @brief 取常量表 id(无则追加) + * @param tag 类型标记(BOOL/INT/TIME) + * @param value 常量值 + * @return const_id(u16) + */ + uint16_t const_id(isa::types::TypeTag tag, int64_t value) { + for (size_t i = 0; i < consts_.size(); ++i) { + if (consts_[i].tag == tag && consts_[i].value == static_cast(value)) { + return static_cast(i); + } + } + consts_.push_back({tag, static_cast(value)}); + return static_cast(consts_.size() - 1); + } + + /** + * @brief 拼映像:头 + 常量表 + 函数表 + 字节码(数据段空) + * @return true 成功;false(err 已写) + */ + bool assemble_image() { + const uint32_t off_const = isa::kHeaderSize; + const uint32_t off_funcs = off_const + + static_cast(consts_.size()) * isa::kConstEntrySize; + uint32_t off_code = off_funcs + + static_cast(funcs_.size()) * isa::kFuncRowSize; + uint32_t code_total = 0; + for (const FuncCtx& f : funcs_) { + code_total += static_cast(f.code.size()) * 4; + } + const uint32_t off_end = off_code + code_total; + + std::vector& b = *image_; + b.assign(off_end, 0); + put_le32(b, 0, isa::kMagic); + put_le32(b, 4, isa::kVersion); + put_le32(b, 8, proj_.cycle_limit); + put_le32(b, 12, proj_.dt_ms); + + uint64_t hash = isa::kFnvBasis; + if (!compute_project_hash(proj_, &hash, err_)) { + return false; + } + put_le64(b, 16, hash); + + uint32_t entry = 0; + for (size_t i = 0; i < funcs_.size(); ++i) { + if (funcs_[i].name == "main") { + entry = static_cast(i); + } + } + put_le32(b, 24, entry); + put_le32(b, 28, 0); // n_globals(切片 1 无全局) + put_le32(b, 32, 0); // n_i + put_le32(b, 36, 0); // n_q + put_le32(b, 40, 0); // n_m + put_le32(b, 44, static_cast(consts_.size())); + put_le32(b, 48, static_cast(funcs_.size())); + put_le32(b, 52, off_const); + put_le32(b, 56, off_funcs); + put_le32(b, 60, off_code); + put_le32(b, 64, off_end); // offset_fb(空) + put_le32(b, 68, off_end); // offset_data(空) + + for (size_t i = 0; i < consts_.size(); ++i) { + const size_t o = off_const + i * isa::kConstEntrySize; + put_le32(b, o, static_cast(consts_[i].tag)); + put_le64(b, o + 4, consts_[i].value); + } + + uint32_t c = off_code; + for (size_t i = 0; i < funcs_.size(); ++i) { + const FuncCtx& f = funcs_[i]; + const size_t o = off_funcs + i * isa::kFuncRowSize; + put_le32(b, o, f.nregs); + put_le32(b, o + 4, 0); // code_offset 相对段起点 = 前序函数长度和 + put_le32(b, o + 8, static_cast(f.code.size())); + for (const isa::Instr in : f.code) { + put_le32(b, c, in); + c += 4; + } + } + // code_offset 回填(相对字节码段起点) + uint32_t acc = 0; + for (size_t i = 0; i < funcs_.size(); ++i) { + const size_t o = off_funcs + i * isa::kFuncRowSize; + put_le32(b, o + 4, acc); + acc += static_cast(funcs_[i].code.size()) * 4; + } + return true; + } + + // ---- 成员 ---- + const Project& proj_; + const std::vector& units_; + const LinkResult& link_; + std::vector* image_; + std::string* err_; + std::vector funcs_; + std::vector consts_; + }; + +} // namespace + +/** + * @brief 编译工程为 .stb 映像字节(对外入口) + * @param proj 工程定义 + * @param units 全部源文件的 AST + * @param link 链接结果(在类型检查成功后调用) + * @param image 输出映像字节 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false 失败(err 前缀 "codegen error") + */ +bool codegen_project(const Project& proj, const std::vector& units, + const LinkResult& link, std::vector* image, + std::string* err) { + Builder b(proj, units, link, image, err); + return b.run(); +} + +} // namespace compiler diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 204615e..5ab5034 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -67,3 +67,14 @@ target_compile_definitions(typecheck_test PRIVATE add_test(NAME typecheck_types COMMAND typecheck_test) + +# 寄存器码测试(12.8 切片 1):用例 01/02 出映像(REPO_ROOT 注入源目录绝对路径) +add_executable(codegen_test + ./src/codegen_test.cpp) + +target_link_libraries(codegen_test PRIVATE compiler) +target_compile_definitions(codegen_test PRIVATE + REPO_ROOT="${CMAKE_SOURCE_DIR}") + +add_test(NAME codegen_slice1 + COMMAND codegen_test) diff --git a/tests/src/codegen_test.cpp b/tests/src/codegen_test.cpp new file mode 100644 index 0000000..a0f6751 --- /dev/null +++ b/tests/src/codegen_test.cpp @@ -0,0 +1,127 @@ +/** + * @file codegen_test.cpp + * @brief 寄存器码测试(12.8 切片 1):用例 01/02 出映像 + * @author + * @date 2026-08-21 + */ + +#include +#include +#include +#include + +#include "compiler/Codegen.h" +#include "compiler/Linker.h" +#include "compiler/Project.h" +#include "compiler/Typecheck.h" +#include "isa/Encode.h" +#include "isa/Image.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) + +// 从用例目录走完整编译管线(解析 → 链接 → 类型检查 → 代码生成) +static bool compile_case(const char* dir, std::vector* image, std::string* err) { + using namespace compiler; + const std::string toml = std::string(REPO_ROOT) + "/tests/cases/" + dir + "/project.toml"; + Project p; + if (!parse_project(toml, &p, err)) { + return false; + } + std::vector units; + for (const std::string& f : compile_files(p)) { + SourceUnit u; + if (!load_unit(p.base_dir + "/" + f, &u, err)) { + return false; + } + units.push_back(std::move(u)); + } + LinkResult link; + if (!link_project(p, units, &link, err)) { + return false; + } + if (!check_project(p, units, link, err)) { + return false; + } + return codegen_project(p, units, link, image, err); +} + +// ---- 1. 用例 01:空 MAIN + RET ---- + +static bool test_case01() { + std::vector img; + std::string err; + CHECK(compile_case("01_empty_main", &img, &err)); + + const isa::ImageView v = isa::ImageView::from(img); + CHECK(v.ok()); + CHECK(v.header().cycle_limit == 1000); + CHECK(v.header().dt_ms == 10); + CHECK(v.header().entry_fn_id == 0); + CHECK(v.header().n_funcs == 1); + CHECK(v.header().n_consts == 0); + CHECK(v.header().n_globals == 0); + CHECK(v.header().project_hash != isa::kFnvBasis); + + const isa::FuncRow r = v.func_row(0); + CHECK(r.nregs == 0); + CHECK(r.code_len == 1); + + char buf[32]; + isa::disasm(v.code_bytes()[0], buf, sizeof buf); + CHECK(std::strcmp(buf, "RET") == 0); + return true; +} + +// ---- 2. 用例 02:BOOL 赋值(TRUE / FALSE → LOADK)---- + +static bool test_case02() { + std::vector img; + std::string err; + CHECK(compile_case("02_bool_assign", &img, &err)); + + const isa::ImageView v = isa::ImageView::from(img); + CHECK(v.ok()); + CHECK(v.header().n_funcs == 1); + CHECK(v.header().n_consts == 2); // TRUE 与 FALSE + + const isa::FuncRow r = v.func_row(0); + CHECK(r.nregs == 2); // a, b + CHECK(r.code_len == 3); + + // 从字节码段取指令(按函数行 code_offset 定位) + const uint8_t* base = v.code_bytes(); + char buf[64]; + isa::disasm(reinterpret_cast(base)[0], buf, sizeof buf); + CHECK(std::strcmp(buf, "LOADK r0, 0") == 0); // a := TRUE(常量 0) + isa::disasm(reinterpret_cast(base)[1], buf, sizeof buf); + CHECK(std::strcmp(buf, "LOADK r1, 1") == 0); // b := FALSE(常量 1) + isa::disasm(reinterpret_cast(base)[2], buf, sizeof buf); + CHECK(std::strcmp(buf, "RET") == 0); + + // 常量表:0 = BOOL 1,1 = BOOL 0 + const isa::ConstEntry c0 = v.const_entry(0); + CHECK(c0.tag == isa::types::Bool && c0.value == 1); + const isa::ConstEntry c1 = v.const_entry(1); + CHECK(c1.tag == isa::types::Bool && c1.value == 0); + return true; +} + +int main() { + if (!test_case01()) return 1; + if (!test_case02()) return 1; + std::printf("codegen_test: %d checks passed\n", g_checks); + return 0; +} From ac411e6a4a9e157bb664ad744088704c2641b5cc Mon Sep 17 00:00:00 2001 From: chentianya Date: Fri, 21 Aug 2026 11:38:57 +0800 Subject: [PATCH 3/9] =?UTF-8?q?12.8=20=E5=88=87=E7=89=87=202=EF=BC=9A?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E6=95=B0=E6=8D=AE=E5=8C=BA=E4=B8=8E=20I/Q/M?= =?UTF-8?q?=EF=BC=88LOAD/STORE=20=E6=8C=87=E4=BB=A4=EF=BC=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 数据区布局:声明序 + 对齐(BOOL 1B/INT 2B/TIME 8B),初值入数据段 - 读全局 LOAD_GLOBAL / io.input 绑定用 LOAD_I;写 STORE_GLOBAL / io.output 用 STORE_Q;写 io.input 拒绝 - 临时寄存器分配:语句内递增、语句结束复用基址 - codegen_test:51 断言(用例 09 初值+LOAD_GLOBAL、io 绑定 LOAD_I/STORE_Q、混合布局偏移、写输入负例),ctest 9/9 --- compiler/src/Codegen.cpp | 299 +++++++++++++++++++++++++++++++------ tests/src/codegen_test.cpp | 164 ++++++++++++++++++++ 2 files changed, 421 insertions(+), 42 deletions(-) diff --git a/compiler/src/Codegen.cpp b/compiler/src/Codegen.cpp index cc5edf3..da4f0c1 100644 --- a/compiler/src/Codegen.cpp +++ b/compiler/src/Codegen.cpp @@ -1,32 +1,43 @@ /** * @file Codegen.cpp - * @brief 寄存器码生成(12.8,切片 1:MOVE / LOADK / RET) + * @brief 寄存器码生成(12.8,切片 2:+ 全局 / I/Q/M) * @author * @date 2026-08-21 * * @details 设计说明(详见 Doc/compiler/寄存器码.md): - * - 切片 1 范围:PROGRAM 标量变量帧(r0 起按声明序)、字面量 LOADK、 - * 变量间 MOVE、RET;空 MAIN 出映像。其余构造报 codegen error(后续切片展开) + * - 切片 1:PROGRAM 标量变量帧(r0 起按声明序)、字面量 LOADK、 + * 变量间 MOVE、RET;空 MAIN 出映像。 + * - 切片 2:+ 全局数据区(声明序、对齐 BOOL 1B / INT 2B / TIME 8B、 + * 初值入数据段);读全局 LOAD_GLOBAL / io.input 绑定用 LOAD_I; + * 写全局 STORE_GLOBAL / io.output 绑定用 STORE_Q;写 io.input → 拒绝。 + * 其余构造报 codegen error(后续切片展开) * - 常量统一进常量表(BOOL/INT/TIME),const_id = 首次出现序 - * - 映像:头(cycle_limit/dt_ms/工程哈希/入口)+ 常量表 + 函数表 + 字节码, - * 数据段为空(无全局/实例) + * - 指令 slot = 数据区字节偏移;符号表 address 保持逻辑槽号(12.6 冻结) * * 函数清单: * - put_le32 / put_le64 小端写入映像缓冲 - * - Builder::Builder (构造)存工程/源文件/链接结果/输出 - * - Builder::run 逐 POU 建函数 → 拼映像 - * - Builder::fail 组装 "codegen error: " 返回 false - * - build_frame 给 POU 标量变量分配帧寄存器(0 起按声明序) - * - build_function 编译一个 POU 的语句体 - * - compile_stmt 语句编译(切片 1:仅赋值) - * - compile_expr 表达式编译(切片 1:仅字面量 / 变量读) - * - const_id 取常量表 id(无则追加) - * - assemble_image 拼头 + 常量表 + 函数表 + 字节码 - * - codegen_project 对外入口 + * - align_up 按 2 的幂宽度向上对齐 + * - width_of 类型名 → 槽宽(bool 1 / int 2 / time 8) + * - Builder::Builder (构造)存工程/源文件/链接结果/输出,收集 io 绑定分类 + * - Builder::run 逐 POU 建函数 → 布局数据区 → 拼映像 + * - Builder::fail 组装 "codegen error: " 返回 false + * - find_pou 按名查 POU AST + * - build_function 编译一个 POU 的语句体(帧寄存器分配) + * - compile_stmt 语句编译(切片 2:仅赋值,左值可帧寄存器或全局) + * - compile_expr 表达式编译到寄存器(字面量 / 变量读,含全局 LOAD_*) + * - store_target 赋值左值:帧寄存器直写或 STORE_* 到全局槽 + * - load_op / store_op 按 io 绑定选读取/写入操作码 + * - global_offset 逻辑槽号 → 数据区字节偏移 + * - layout_data 布局全局数据区(对齐 + 初值字节) + * - init_of 查全局声明的初值(AST) + * - const_id 取常量表 id(无则追加) + * - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + 数据段 + * - codegen_project 对外入口 */ #include "compiler/Codegen.h" +#include #include #include #include @@ -55,6 +66,27 @@ namespace { } } + /** + * @brief 按 2 的幂宽度向上对齐 + * @param v 当前偏移 + * @param w 对齐宽度(1/2/8) + * @return 对齐后的偏移 + */ + uint32_t align_up(uint32_t v, uint32_t w) { + return (v + w - 1) & ~(w - 1); + } + + /** + * @brief 类型名 → 槽宽(对齐规则见 Doc/isa/指令与映像.md) + * @param name "bool" / "int" / "time" + * @return 1 / 2 / 8;未知返回 1 + */ + uint32_t width_of(const std::string& name) { + if (name == "int") return 2; + if (name == "time") return 8; + return 1; // bool 及未知 + } + /** * @brief 代码生成器(切片 1) */ @@ -70,13 +102,30 @@ namespace { */ Builder(const Project& proj, const std::vector& units, const LinkResult& link, std::vector* image, std::string* err) - : proj_(proj), units_(units), link_(link), image_(image), err_(err) {} + : proj_(proj), units_(units), link_(link), image_(image), err_(err) { + // io 绑定分类(名已折小写;不创造变量,只影响操作码选择) + for (const isa::IoBinding& b : proj_.io) { + std::string key = b.var; + for (char& ch : key) { + ch = static_cast(std::tolower(static_cast(ch))); + } + if (b.is_input) { + io_input_[key] = true; + } else { + io_output_[key] = true; + } + } + } /** - * @brief 逐 POU 建函数后拼映像 + * @brief 布局数据区 → 逐 POU 建函数 → 拼映像 + * @details 数据区偏移先定(函数编译引用 global_offset) * @return true 成功;false(err 已写,前缀 "codegen error") */ bool run() { + if (!layout_data()) { + return false; + } for (const LinkResult::PouScope& sc : link_.scopes) { const POU* pou = find_pou(sc.name); if (pou == nullptr) { @@ -126,9 +175,31 @@ namespace { std::string name; std::vector code; // 字节码(函数表 code_offset 相对此段) std::map regs; // 变量名 → 帧寄存器 - uint8_t nregs = 0; // 变量数(切片 1 无临时) + uint8_t nlocals = 0; // 变量数 = 临时寄存器起始 + uint8_t nregs = 0; // 峰值(变量 + 临时) + uint8_t temp_used = 0; // 本语句已用临时数(语句结束清零) }; + /** + * @brief 语句开始:临时寄存器基址复用 + * @param f 当前函数 + */ + void begin_stmt(FuncCtx& f) { f.temp_used = 0; } + + /** + * @brief 分配一个临时寄存器(语句内递增) + * @param f 当前函数 + * @return 临时寄存器号(可能更新 nregs 峰值) + */ + uint8_t alloc_temp(FuncCtx& f) { + const uint8_t r = f.nlocals + f.temp_used; + ++f.temp_used; + if (static_cast(f.nlocals) + f.temp_used > f.nregs) { + f.nregs = f.nlocals + f.temp_used; + } + return r; + } + /** * @brief 编译一个 POU * @details 切片 1:仅 PROGRAM + 标量变量(FB 实例/函数留后续切片) @@ -141,6 +212,10 @@ namespace { return fail("only PROGRAM supported in slice 1 ('" + pou.name + "')"); } for (const VarBlock& b : pou.blocks) { + // External / Global 走数据区槽,不占帧寄存器 + if (b.section == VarSection::External || b.section == VarSection::Global) { + continue; + } for (const VarDecl& d : b.vars) { if (d.type.kind == TypeKind::FbUser || d.type.kind == TypeKind::FbBuiltin) { return fail("FB instance not supported in slice 1 ('" + d.name + "')"); @@ -151,7 +226,9 @@ namespace { f->regs[d.name] = f->nregs++; } } + f->nlocals = f->nregs; for (const Stmt& st : pou.body) { + begin_stmt(*f); if (!compile_stmt(*f, st)) { return false; } @@ -161,41 +238,168 @@ namespace { } /** - * @brief 编译一条语句(切片 1:仅赋值) + * @brief 编译一条语句(切片 2:仅赋值,左值可为帧寄存器或全局) * @param f 当前函数 * @param st 语句 AST * @return true 成功;false(err 已写) */ bool compile_stmt(FuncCtx& f, const Stmt& st) { if (st.kind != StmtKind::Assign) { - return fail("statement not supported in slice 1"); + return fail("statement not supported in slice 2"); } const auto it = f.regs.find(st.target); if (it == f.regs.end()) { - return fail("no register for '" + st.target + "'"); + // 全局 / 外部左值 → STORE_* 到数据区 + return store_target(f, st.target, *st.value); } const uint8_t rd = it->second; - uint8_t rs = 0; - const ExprKind k = st.value->kind; - if (k == ExprKind::LitBool || k == ExprKind::LitInt || - k == ExprKind::LitTime) { + return compile_expr(f, *st.value, rd); + } + + /** + * @brief 赋值左值:帧寄存器直写或 STORE_* 到全局槽 + * @param f 当前函数 + * @param target 左值名(全局 / 外部) + * @param value 右值表达式 + * @return true 成功;false(err 已写) + */ + bool store_target(FuncCtx& f, const std::string& target, const Expr& value) { + const auto git = link_.global_index.find(target); + if (git == link_.global_index.end()) { + return fail("no storage for '" + target + "'"); + } + if (io_input_.count(target)) { + return fail("cannot write to input '" + target + "'"); + } + const uint8_t tmp = alloc_temp(f); + if (!compile_expr(f, value, tmp)) { + return false; + } + const uint16_t slot = global_offset(git->second); + f.code.push_back(isa::enc_slot(store_op(target), tmp, slot)); + return true; + } + + /** + * @brief 表达式编译到目标寄存器 + * @details 字面量 → LOADK;帧变量 → MOVE; + * 全局/外部 → LOAD_GLOBAL(io.input 绑定用 LOAD_I) + * @param f 当前函数 + * @param e 表达式 AST + * @param rd 目标寄存器 + * @return true 成功;false(err 已写) + */ + bool compile_expr(FuncCtx& f, const Expr& e, uint8_t rd) { + if (e.kind == ExprKind::LitBool || e.kind == ExprKind::LitInt || + e.kind == ExprKind::LitTime) { const isa::types::TypeTag tag = - k == ExprKind::LitBool ? isa::types::Bool - : k == ExprKind::LitInt ? isa::types::Int - : isa::types::Time; + e.kind == ExprKind::LitBool ? isa::types::Bool + : e.kind == ExprKind::LitInt ? isa::types::Int + : isa::types::Time; f.code.push_back( - isa::enc_imm(isa::Op::LOADK, rd, const_id(tag, st.value->int_value))); + isa::enc_imm(isa::Op::LOADK, rd, const_id(tag, e.int_value))); return true; } - if (k == ExprKind::VarRef) { - const auto sit = f.regs.find(st.value->name); - if (sit == f.regs.end()) { - return fail("no register for '" + st.value->name + "'"); + if (e.kind == ExprKind::VarRef) { + 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)); + return true; } - f.code.push_back(isa::enc_rr(isa::Op::MOVE, rd, sit->second)); - return true; + const auto git = link_.global_index.find(e.name); + if (git != link_.global_index.end()) { + const uint16_t slot = global_offset(git->second); + f.code.push_back(isa::enc_slot(load_op(e.name), rd, slot)); + return true; + } + return fail("no register or slot for '" + e.name + "'"); } - return fail("expression not supported in slice 1"); + return fail("expression not supported in slice 2"); + } + + /** + * @brief 按 io 绑定选读取操作码 + * @param name 变量名(小写) + * @return io.input 绑定 → LOAD_I;否则 LOAD_GLOBAL + */ + isa::Op load_op(const std::string& name) const { + return io_input_.count(name) ? isa::Op::LOAD_I : isa::Op::LOAD_GLOBAL; + } + + /** + * @brief 按 io 绑定选写入操作码 + * @param name 变量名(小写) + * @return io.output 绑定 → STORE_Q;否则 STORE_GLOBAL + */ + isa::Op store_op(const std::string& name) const { + return io_output_.count(name) ? isa::Op::STORE_Q : isa::Op::STORE_GLOBAL; + } + + /** + * @brief 逻辑槽号 → 数据区字节偏移(layout_data 先行) + * @param slot 全局逻辑槽号(符号表 address) + * @return 数据区字节偏移(u16 可容) + */ + uint16_t global_offset(uint32_t slot) const { + return static_cast(global_offsets_[slot]); + } + + /** + * @brief 布局全局数据区:对齐 + 初值字节 + * @details 槽序 = 声明序(12.6);对齐规则 BOOL 1B / INT 2B / TIME 8B, + * 起点对齐自身宽度;初值取自 GVL 声明的 has_init(无则 0) + * @return true 成功;false(槽溢出等,err 已写) + */ + bool layout_data() { + uint32_t cur = 0; + for (const Symbol& s : link_.globals) { + const uint32_t w = width_of(s.type_name); + cur = align_up(cur, w); + if (cur + w > 0xFFFF) { + return fail("data area exceeds slot range"); + } + global_offsets_.push_back(cur); + bool has_init = false; + int64_t init = 0; + init_of(s.name, &has_init, &init); + data_.resize(cur + w, 0); + if (s.type_name == "bool") { + data_[cur] = static_cast(has_init ? init : 0); + } else if (s.type_name == "int") { + const uint16_t v = static_cast(has_init ? init : 0); + data_[cur] = static_cast(v & 0xFFu); + data_[cur + 1] = static_cast((v >> 8) & 0xFFu); + } else if (s.type_name == "time") { + const uint64_t v = static_cast(has_init ? init : 0); + for (int i = 0; i < 8; ++i) { + data_[cur + i] = static_cast((v >> (8 * i)) & 0xFFu); + } + } + cur += w; + } + return true; + } + + /** + * @brief 查全局声明的初值(AST;gvl 文件顶层段,声明序 = 槽号序) + * @param name 全局名(小写) + * @param has_init 输出是否有初值 + * @param init 输出初值 + */ + void init_of(const std::string& name, bool* has_init, int64_t* init) const { + for (const SourceUnit& u : units_) { + for (const VarBlock& b : u.ast.globals) { + for (const VarDecl& d : b.vars) { + if (d.name == name) { + *has_init = d.has_init; + *init = d.init_value; + return; + } + } + } + } + *has_init = false; + *init = 0; } /** @@ -215,7 +419,9 @@ namespace { } /** - * @brief 拼映像:头 + 常量表 + 函数表 + 字节码(数据段空) + * @brief 拼映像:头 + 常量表 + 函数表 + 字节码 + 数据段 + * @details 段序:const → funcs → code → fb(空)→ data; + * 数据段放全局初值(layout_data 已生成字节) * @return true 成功;false(err 已写) */ bool assemble_image() { @@ -228,7 +434,8 @@ namespace { for (const FuncCtx& f : funcs_) { code_total += static_cast(f.code.size()) * 4; } - const uint32_t off_end = off_code + code_total; + const uint32_t off_data = off_code + code_total; + const uint32_t off_end = off_data + static_cast(data_.size()); std::vector& b = *image_; b.assign(off_end, 0); @@ -250,7 +457,7 @@ namespace { } } put_le32(b, 24, entry); - put_le32(b, 28, 0); // n_globals(切片 1 无全局) + put_le32(b, 28, static_cast(link_.globals.size())); // n_globals put_le32(b, 32, 0); // n_i put_le32(b, 36, 0); // n_q put_le32(b, 40, 0); // n_m @@ -259,8 +466,8 @@ namespace { put_le32(b, 52, off_const); put_le32(b, 56, off_funcs); put_le32(b, 60, off_code); - put_le32(b, 64, off_end); // offset_fb(空) - put_le32(b, 68, off_end); // offset_data(空) + put_le32(b, 64, off_data); // offset_fb(空,与数据段起点相同) + put_le32(b, 68, off_data); // offset_data for (size_t i = 0; i < consts_.size(); ++i) { const size_t o = off_const + i * isa::kConstEntrySize; @@ -273,7 +480,6 @@ namespace { const FuncCtx& f = funcs_[i]; const size_t o = off_funcs + i * isa::kFuncRowSize; put_le32(b, o, f.nregs); - put_le32(b, o + 4, 0); // code_offset 相对段起点 = 前序函数长度和 put_le32(b, o + 8, static_cast(f.code.size())); for (const isa::Instr in : f.code) { put_le32(b, c, in); @@ -287,6 +493,11 @@ namespace { put_le32(b, o + 4, acc); acc += static_cast(funcs_[i].code.size()) * 4; } + + // 数据段:全局初值字节 + for (size_t i = 0; i < data_.size(); ++i) { + b[off_data + i] = data_[i]; + } return true; } @@ -298,6 +509,10 @@ namespace { std::string* err_; std::vector funcs_; std::vector consts_; + std::map io_input_; // io.input 绑定名(小写) + std::map io_output_; // io.output 绑定名(小写) + std::vector global_offsets_; // 逻辑槽号 → 数据区字节偏移 + std::vector data_; // 数据区(全局初值字节) }; } // namespace diff --git a/tests/src/codegen_test.cpp b/tests/src/codegen_test.cpp index a0f6751..820db67 100644 --- a/tests/src/codegen_test.cpp +++ b/tests/src/codegen_test.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -58,6 +59,50 @@ static bool compile_case(const char* dir, std::vector* image, std::stri return codegen_project(p, units, link, image, err); } +// 从临时工程(globals.st + main.st + 可含 io 绑定)走完整管线 +static bool compile_src(const char* name, const char* io_extra, const char* globals_st, + const char* main_st, std::vector* image, + std::string* err) { + using namespace compiler; + const std::string dir = std::string(REPO_ROOT) + "/build/cg_tmp_" + name; + std::filesystem::remove_all(dir); + std::filesystem::create_directories(dir); + const std::string toml_path = dir + "/project.toml"; + std::FILE* f = std::fopen(toml_path.c_str(), "w"); + std::fprintf(f, "[project]\nname = \"cg\"\nentry = \"program MAIN\"\n" + "cycle_limit = 1000\ndt_ms = 10\n" + "[files]\nst = [\"globals.st\", \"main.st\"]\n" + "[gvl]\nfile = \"globals.st\"\n%s", io_extra); + std::fclose(f); + f = std::fopen((dir + "/globals.st").c_str(), "w"); + std::fputs(globals_st, f); + std::fclose(f); + f = std::fopen((dir + "/main.st").c_str(), "w"); + std::fputs(main_st, f); + std::fclose(f); + + Project p; + if (!parse_project(toml_path, &p, err)) { + return false; + } + std::vector units; + for (const std::string& file : compile_files(p)) { + SourceUnit u; + if (!load_unit(p.base_dir + "/" + file, &u, err)) { + return false; + } + units.push_back(std::move(u)); + } + LinkResult link; + if (!link_project(p, units, &link, err)) { + return false; + } + if (!check_project(p, units, link, err)) { + return false; + } + return codegen_project(p, units, link, image, err); +} + // ---- 1. 用例 01:空 MAIN + RET ---- static bool test_case01() { @@ -119,9 +164,128 @@ static bool test_case02() { return true; } +// ---- 3. 用例 09:GVL + VAR_EXTERNAL(全局读取 LOAD_GLOBAL + 初值数据段)---- + +static bool test_case09() { + std::vector img; + std::string err; + CHECK(compile_case("09_gvl_external", &img, &err)); + + const isa::ImageView v = isa::ImageView::from(img); + CHECK(v.ok()); + CHECK(v.header().n_globals == 1); + CHECK(v.data_len() == 2); // INT 2 字节 + CHECK(v.data_bytes()[0] == 5); // G1 初值 5(小端低位) + CHECK(v.data_bytes()[1] == 0); + + const isa::FuncRow r = v.func_row(0); + CHECK(r.nregs == 1); // 仅局部 x + CHECK(r.code_len == 2); + + char buf[64]; + const uint8_t* base = v.code_bytes(); + isa::disasm(reinterpret_cast(base)[0], buf, sizeof buf); + CHECK(std::strcmp(buf, "LOAD_GLOBAL r0, 0") == 0); // x := G1(槽 0 偏移 0) + isa::disasm(reinterpret_cast(base)[1], buf, sizeof buf); + CHECK(std::strcmp(buf, "RET") == 0); + return true; +} + +// ---- 4. io 绑定:LOAD_I / STORE_Q ---- + +static bool test_io_ops() { + std::vector img; + std::string err; + const char* io_extra = + "[[io.input]]\nvar = \"I0_0\"\nchannel = 0\nbit = 0\n" + "[[io.output]]\nvar = \"Q0_0\"\nchannel = 0\nbit = 0\n"; + const char* globals_st = + "VAR_GLOBAL\n I0_0 : BOOL;\n Q0_0 : BOOL;\nEND_VAR\n"; + const char* main_st = + "PROGRAM MAIN\nVAR\n q : BOOL;\nEND_VAR\n" + " q := I0_0;\n" + " Q0_0 := q;\n" + "END_PROGRAM\n"; + CHECK(compile_src("io", io_extra, globals_st, main_st, &img, &err)); + + const isa::ImageView v = isa::ImageView::from(img); + CHECK(v.ok()); + CHECK(v.header().n_globals == 2); + + const isa::FuncRow r = v.func_row(0); + CHECK(r.nregs == 2); // q + 1 临时 + + char buf[64]; + const uint8_t* base = v.code_bytes(); + isa::disasm(reinterpret_cast(base)[0], buf, sizeof buf); + CHECK(std::strcmp(buf, "LOAD_I r0, 0") == 0); // q := I0_0(io.input → LOAD_I) + isa::disasm(reinterpret_cast(base)[1], buf, sizeof buf); + CHECK(std::strcmp(buf, "MOVE r1, r0") == 0); // 临时 ← q + isa::disasm(reinterpret_cast(base)[2], buf, sizeof buf); + CHECK(std::strcmp(buf, "STORE_Q r1, 1") == 0); // Q0_0 := 临时(io.output → STORE_Q) + isa::disasm(reinterpret_cast(base)[3], buf, sizeof buf); + CHECK(std::strcmp(buf, "RET") == 0); + return true; +} + +// ---- 5. 混合类型布局:BOOL@0 INT@2 TIME@8,初值入数据段 ---- + +static bool test_layout() { + std::vector img; + std::string err; + const char* globals_st = + "VAR_GLOBAL\n" + " B : BOOL;\n" + " I : INT := 300;\n" + " T : TIME := T#10ms;\n" + "END_VAR\n"; + const char* main_st = + "PROGRAM MAIN\nVAR\n x : INT;\nEND_VAR\n" + " x := I;\n" + "END_PROGRAM\n"; + CHECK(compile_src("layout", "", globals_st, main_st, &img, &err)); + + const isa::ImageView v = isa::ImageView::from(img); + CHECK(v.ok()); + CHECK(v.header().n_globals == 3); + CHECK(v.data_len() == 16); // 0 + 2 + 8(对齐)= 16 + CHECK(v.data_bytes()[0] == 0); // B 无初值 + CHECK(v.data_bytes()[2] == 0x2C && v.data_bytes()[3] == 0x01); // I = 300 + CHECK(v.data_bytes()[8] == 10); // T = 10ms + + char buf[64]; + isa::disasm(reinterpret_cast(v.code_bytes())[0], buf, sizeof buf); + CHECK(std::strcmp(buf, "LOAD_GLOBAL r0, 2") == 0); // x := I(INT 偏移 2) + return true; +} + +// ---- 6. 写 io.input → codegen error ---- + +static bool test_write_input() { + std::vector img; + std::string err; + const char* io_extra = + "[[io.input]]\nvar = \"I0_0\"\nchannel = 0\nbit = 0\n"; + const char* globals_st = + "VAR_GLOBAL\n I0_0 : BOOL;\nEND_VAR\n"; + const char* main_st = + "PROGRAM MAIN\n I0_0 := TRUE;\nEND_PROGRAM\n"; + if (compile_src("winput", io_extra, globals_st, main_st, &img, &err)) { + std::printf("FAIL write_input: codegen passed\n"); + return false; + } + CHECK(err.find("codegen error") == 0); + CHECK(err.find("cannot write to input") != std::string::npos); + return true; +} + int main() { if (!test_case01()) return 1; if (!test_case02()) return 1; + if (!test_case09()) return 1; + if (!test_io_ops()) return 1; + if (!test_layout()) return 1; + if (!test_write_input()) return 1; std::printf("codegen_test: %d checks passed\n", g_checks); return 0; } From e7dee50cde56a7bb28093db5ae7f629c7cd3c674 Mon Sep 17 00:00:00 2001 From: chentianya Date: Fri, 21 Aug 2026 11:40:53 +0800 Subject: [PATCH 4/9] =?UTF-8?q?12.8=20=E5=88=87=E7=89=87=203=EF=BC=9ANOT?= =?UTF-8?q?=20=E4=B8=8E=E7=9F=AD=E8=B7=AF=20AND/OR=EF=BC=88=E7=BC=96?= =?UTF-8?q?=E8=B7=B3=E8=BD=AC=EF=BC=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 短路模式: JF(AND)/JT(OR) rd, L_end MOVE rd, t L_end: - patch_jump 回填偏移;约定相对下一条指令(目标 = 当前 + 1 + off),冻结进 isa 规格 - codegen_test:71 断言(用例 03 跳转序列 JF/JT +2、NOT 序列),ctest 9/9 --- Doc/isa/指令与映像.md | 2 +- compiler/src/Codegen.cpp | 71 +++++++++++++++++++++++++++++--------- tests/src/codegen_test.cpp | 70 +++++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 17 deletions(-) diff --git a/Doc/isa/指令与映像.md b/Doc/isa/指令与映像.md index 7d2c0fb..2256c8a 100644 --- a/Doc/isa/指令与映像.md +++ b/Doc/isa/指令与映像.md @@ -82,7 +82,7 @@ C++20 定宽: - 三寄存器:`a`/`b` 为 `ra`/`rb`(`MOVE`/`NOT` 只用 `rd`+`a`) - `LOADK`:`a|b` 为 `const_id`(u16) -- `JMP` / `JT` / `JF`:`a|b` 为有符号相对 offset(指令条数) +- `JMP` / `JT` / `JF`:`a|b` 为有符号相对 offset(指令条数),**相对下一条指令**:目标下标 = 当前下标 + 1 + off - `CALL`:`a|b` 为 `fn_id`(u16) 操作码一次列全(`CMP_xx` 冻下面六种): diff --git a/compiler/src/Codegen.cpp b/compiler/src/Codegen.cpp index da4f0c1..c37302e 100644 --- a/compiler/src/Codegen.cpp +++ b/compiler/src/Codegen.cpp @@ -1,33 +1,33 @@ /** * @file Codegen.cpp - * @brief 寄存器码生成(12.8,切片 2:+ 全局 / I/Q/M) + * @brief 寄存器码生成(12.8,切片 3:+ NOT、短路 AND/OR) * @author * @date 2026-08-21 * * @details 设计说明(详见 Doc/compiler/寄存器码.md): - * - 切片 1:PROGRAM 标量变量帧(r0 起按声明序)、字面量 LOADK、 - * 变量间 MOVE、RET;空 MAIN 出映像。 - * - 切片 2:+ 全局数据区(声明序、对齐 BOOL 1B / INT 2B / TIME 8B、 - * 初值入数据段);读全局 LOAD_GLOBAL / io.input 绑定用 LOAD_I; - * 写全局 STORE_GLOBAL / io.output 绑定用 STORE_Q;写 io.input → 拒绝。 - * 其余构造报 codegen error(后续切片展开) - * - 常量统一进常量表(BOOL/INT/TIME),const_id = 首次出现序 - * - 指令 slot = 数据区字节偏移;符号表 address 保持逻辑槽号(12.6 冻结) + * - 切片 1:PROGRAM 标量变量帧、字面量 LOADK、变量 MOVE、RET。 + * - 切片 2:全局数据区(对齐 + 初值)、LOAD_I/STORE_Q/LOAD_GLOBAL/STORE_GLOBAL。 + * - 切片 3:NOT(enc_rr);AND/OR **短路编跳转**(禁止两边都算): + * JF(AND)/JT(OR) rd, L_end MOVE rd, t L_end: + * 跳转偏移相对下一条指令(目标 = 当前 + 1 + off,见 Doc/isa/指令与映像.md) + * - 其余构造报 codegen error(后续切片展开) * * 函数清单: * - put_le32 / put_le64 小端写入映像缓冲 * - align_up 按 2 的幂宽度向上对齐 * - width_of 类型名 → 槽宽(bool 1 / int 2 / time 8) * - Builder::Builder (构造)存工程/源文件/链接结果/输出,收集 io 绑定分类 - * - Builder::run 逐 POU 建函数 → 布局数据区 → 拼映像 + * - Builder::run 布局数据区 → 逐 POU 建函数 → 拼映像 * - Builder::fail 组装 "codegen error: " 返回 false * - find_pou 按名查 POU AST * - build_function 编译一个 POU 的语句体(帧寄存器分配) - * - compile_stmt 语句编译(切片 2:仅赋值,左值可帧寄存器或全局) - * - compile_expr 表达式编译到寄存器(字面量 / 变量读,含全局 LOAD_*) - * - store_target 赋值左值:帧寄存器直写或 STORE_* 到全局槽 + * - begin_stmt / alloc_temp 临时寄存器:语句内递增、语句结束复用基址 + * - compile_stmt 语句编译(切片 3:仅赋值,左值可帧寄存器或全局) + * - store_target 赋值左值:STORE_* 到全局槽 + * - compile_expr 表达式编译到寄存器(字面量/变量读/NOT/短路 AND/OR) * - load_op / store_op 按 io 绑定选读取/写入操作码 * - global_offset 逻辑槽号 → 数据区字节偏移 + * - patch_jump 回填跳转偏移(相对下一条指令) * - layout_data 布局全局数据区(对齐 + 初值字节) * - init_of 查全局声明的初值(AST) * - const_id 取常量表 id(无则追加) @@ -282,8 +282,8 @@ namespace { /** * @brief 表达式编译到目标寄存器 - * @details 字面量 → LOADK;帧变量 → MOVE; - * 全局/外部 → LOAD_GLOBAL(io.input 绑定用 LOAD_I) + * @details 字面量 → LOADK;帧变量 → MOVE;全局/外部 → LOAD_*(io.input 用 LOAD_I); + * NOT → 求值后 NOT rd, t;AND/OR → 短路跳转(JF/JT 跳过右侧) * @param f 当前函数 * @param e 表达式 AST * @param rd 目标寄存器 @@ -314,7 +314,46 @@ namespace { } return fail("no register or slot for '" + e.name + "'"); } - return fail("expression not supported in slice 2"); + if (e.kind == ExprKind::Not) { + const uint8_t t = alloc_temp(f); + if (!compile_expr(f, *e.operand, t)) { + return false; + } + f.code.push_back(isa::enc_rr(isa::Op::NOT, rd, t)); + return true; + } + if (e.kind == ExprKind::And || e.kind == ExprKind::Or) { + // 短路:左侧结果在 rd;AND 为假 / OR 为真时跳过右侧 + if (!compile_expr(f, *e.lhs, rd)) { + return false; + } + const isa::Op jop = (e.kind == ExprKind::And) ? isa::Op::JF : isa::Op::JT; + const size_t jmp_idx = f.code.size(); + f.code.push_back(isa::enc_jc(jop, rd, 0)); // 偏移留洞,稍后回填 + const uint8_t t = alloc_temp(f); + if (!compile_expr(f, *e.rhs, t)) { + return false; + } + f.code.push_back(isa::enc_rr(isa::Op::MOVE, rd, t)); + patch_jump(f, jmp_idx, f.code.size()); + return true; + } + return fail("expression not supported in slice 3"); + } + + /** + * @brief 回填跳转偏移(相对下一条指令) + * @param f 当前函数 + * @param idx 跳转指令下标(占位) + * @param target_idx 目标指令下标 + */ + void patch_jump(FuncCtx& f, size_t idx, size_t target_idx) { + const int16_t off = static_cast( + static_cast(target_idx) - (static_cast(idx) + 1)); + const isa::Instr w = f.code[idx]; + f.code[idx] = isa::pack(isa::op(w), isa::rd(w), + static_cast(static_cast(off) & 0xFFu), + static_cast((static_cast(off) >> 8) & 0xFFu)); } /** diff --git a/tests/src/codegen_test.cpp b/tests/src/codegen_test.cpp index 820db67..464a232 100644 --- a/tests/src/codegen_test.cpp +++ b/tests/src/codegen_test.cpp @@ -279,6 +279,74 @@ static bool test_write_input() { return true; } +// ---- 7. 用例 03:短路 AND / OR 必须编跳转 ---- + +static bool test_case03() { + std::vector img; + std::string err; + CHECK(compile_case("03_short_circuit", &img, &err)); + + const isa::ImageView v = isa::ImageView::from(img); + CHECK(v.ok()); + const isa::FuncRow r = v.func_row(0); + CHECK(r.nregs == 4); // a b x + 1 临时 + CHECK(r.code_len == 9); + + char buf[64]; + const uint8_t* base = v.code_bytes(); + const uint32_t* ins = reinterpret_cast(base); + // x := a AND b:MOVE r2,r0 / JF r2,+2 / MOVE r3,r1 / MOVE r2,r3 + isa::disasm(ins[0], buf, sizeof buf); + CHECK(std::strcmp(buf, "MOVE r2, r0") == 0); + isa::disasm(ins[1], buf, sizeof buf); + CHECK(std::strcmp(buf, "JF r2, +2") == 0); + isa::disasm(ins[2], buf, sizeof buf); + CHECK(std::strcmp(buf, "MOVE r3, r1") == 0); + isa::disasm(ins[3], buf, sizeof buf); + CHECK(std::strcmp(buf, "MOVE r2, r3") == 0); + // x := a OR b:MOVE r2,r0 / JT r2,+2 / MOVE r3,r1 / MOVE r2,r3 + isa::disasm(ins[4], buf, sizeof buf); + CHECK(std::strcmp(buf, "MOVE r2, r0") == 0); + isa::disasm(ins[5], buf, sizeof buf); + CHECK(std::strcmp(buf, "JT r2, +2") == 0); + isa::disasm(ins[6], buf, sizeof buf); + CHECK(std::strcmp(buf, "MOVE r3, r1") == 0); + isa::disasm(ins[7], buf, sizeof buf); + CHECK(std::strcmp(buf, "MOVE r2, r3") == 0); + isa::disasm(ins[8], buf, sizeof buf); + CHECK(std::strcmp(buf, "RET") == 0); + return true; +} + +// ---- 8. NOT ---- + +static bool test_not() { + std::vector img; + std::string err; + const char* main_st = + "PROGRAM MAIN\nVAR\n a, x : BOOL;\nEND_VAR\n" + " x := NOT a;\n" + "END_PROGRAM\n"; + CHECK(compile_src("not", "", "", main_st, &img, &err)); + + const isa::ImageView v = isa::ImageView::from(img); + CHECK(v.ok()); + const isa::FuncRow r = v.func_row(0); + CHECK(r.nregs == 3); // a x + 1 临时 + CHECK(r.code_len == 3); + + char buf[64]; + const uint8_t* base = v.code_bytes(); + const uint32_t* ins = reinterpret_cast(base); + isa::disasm(ins[0], buf, sizeof buf); + CHECK(std::strcmp(buf, "MOVE r2, r0") == 0); // 临时 ← a + isa::disasm(ins[1], buf, sizeof buf); + CHECK(std::strcmp(buf, "NOT r1, r2") == 0); // x := NOT 临时 + isa::disasm(ins[2], buf, sizeof buf); + CHECK(std::strcmp(buf, "RET") == 0); + return true; +} + int main() { if (!test_case01()) return 1; if (!test_case02()) return 1; @@ -286,6 +354,8 @@ int main() { if (!test_io_ops()) return 1; if (!test_layout()) return 1; if (!test_write_input()) return 1; + if (!test_case03()) return 1; + if (!test_not()) return 1; std::printf("codegen_test: %d checks passed\n", g_checks); return 0; } From 6f1bf61c7f9d691bd95538116ffc8de64eea7b4a Mon Sep 17 00:00:00 2001 From: chentianya Date: Fri, 21 Aug 2026 11:43:23 +0800 Subject: [PATCH 5/9] =?UTF-8?q?12.8=20=E5=88=87=E7=89=87=204=EF=BC=9ACMP?= =?UTF-8?q?=5F*=20=E4=B8=8E=20IF/ELSIF/ELSE=20=E8=B7=B3=E8=BD=AC=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CMP_xx:两侧求值到临时后比较;IF 编成 JF t, L_next JMP L_end 链 - 分支尾部 JMP 与条件 JF 双回填;无 else 时末分支 JF 直接落 L_end - codegen_test:89 断言(用例 04 完整跳转序列),ctest 9/9 --- compiler/src/Codegen.cpp | 110 ++++++++++++++++++++++++++++++++----- tests/src/codegen_test.cpp | 51 +++++++++++++++++ 2 files changed, 148 insertions(+), 13 deletions(-) diff --git a/compiler/src/Codegen.cpp b/compiler/src/Codegen.cpp index c37302e..7395d79 100644 --- a/compiler/src/Codegen.cpp +++ b/compiler/src/Codegen.cpp @@ -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、变量 MOVE、RET。 - * - 切片 2:全局数据区(对齐 + 初值)、LOAD_I/STORE_Q/LOAD_GLOBAL/STORE_GLOBAL。 - * - 切片 3:NOT(enc_rr);AND/OR **短路编跳转**(禁止两边都算): - * JF(AND)/JT(OR) rd, L_end MOVE rd, t L_end: + * - 切片 1~3:帧/字面量/MOVE/RET;全局数据区;NOT 与短路 AND/OR。 + * - 切片 4:比较 CMP_xx(两侧求值到临时);IF 编成 + * JF t, L_next 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: " 返回 false * - find_pou 按名查 POU AST * - build_function 编译一个 POU 的语句体(帧寄存器分配) * - begin_stmt / alloc_temp 临时寄存器:语句内递增、语句结束复用基址 - * - compile_stmt 语句编译(切片 3:仅赋值,左值可帧寄存器或全局) + * - compile_stmt 语句编译(切片 4:赋值 / IF) + * - compile_if IF/ELSIF/ELSE:条件链 + 分支尾部 JMP,JF/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 成功;false(err 已写) */ 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 每分支: JF t, L_next → → JMP L_end; + * 最后一个分支无 else 时 JF 直接落 L_end(不补 JMP) + * @param f 当前函数 + * @param st IF 语句 AST + * @return true 成功;false(err 已写) + */ + bool compile_if(FuncCtx& f, const Stmt& st) { + // 条件链:IF 体 + ELSIF 各体;ELSE 体可选 + std::vector*>> 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 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((static_cast(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 变量名(小写) diff --git a/tests/src/codegen_test.cpp b/tests/src/codegen_test.cpp index 464a232..c1022bd 100644 --- a/tests/src/codegen_test.cpp +++ b/tests/src/codegen_test.cpp @@ -347,6 +347,56 @@ static bool test_not() { return true; } +// ---- 9. 用例 04:IF / ELSIF / ELSE(CMP + JF + JMP 回填)---- + +static bool test_case04() { + std::vector 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(base); + // IF sel = 0 THEN:sel→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; } From 31ba557627e8314151ffd1c933c6b5b320b10d91 Mon Sep 17 00:00:00 2001 From: chentianya Date: Fri, 21 Aug 2026 11:45:32 +0800 Subject: [PATCH 6/9] =?UTF-8?q?12.8=20=E5=88=87=E7=89=87=205=EF=BC=9AWHILE?= =?UTF-8?q?=20=E5=9B=9E=E7=8E=AF=E4=B8=8E=E5=9B=9B=E5=88=99=E8=BF=90?= =?UTF-8?q?=E7=AE=97=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - WHILE 编成 L_loop: JF t, L_end JMP L_loop L_end:,JF/JMP 双回填 - 四则 ADD/SUB/MUL/DIV(用例 5 循环体自增需要,同模式实现,用例 7 顺带点亮) - codegen_test:115 断言(用例 05 回环 JMP -8、07 四则+比较、08 TIME 常量表),ctest 9/9 --- compiler/src/Codegen.cpp | 80 +++++++++++++++++++++++++++---- tests/src/codegen_test.cpp | 97 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 9 deletions(-) diff --git a/compiler/src/Codegen.cpp b/compiler/src/Codegen.cpp index 7395d79..796e036 100644 --- a/compiler/src/Codegen.cpp +++ b/compiler/src/Codegen.cpp @@ -1,14 +1,15 @@ /** * @file Codegen.cpp - * @brief 寄存器码生成(12.8,切片 4:+ CMP_*、IF/ELSIF/ELSE) + * @brief 寄存器码生成(12.8,切片 5:+ WHILE、四则运算) * @author * @date 2026-08-21 * * @details 设计说明(详见 Doc/compiler/寄存器码.md): - * - 切片 1~3:帧/字面量/MOVE/RET;全局数据区;NOT 与短路 AND/OR。 - * - 切片 4:比较 CMP_xx(两侧求值到临时);IF 编成 - * JF t, L_next JMP L_end L_next: ... L_end: - * 跳转偏移相对下一条指令(目标 = 当前 + 1 + off,见 Doc/isa/指令与映像.md) + * - 切片 1~4:帧/字面量/MOVE/RET;全局数据区;NOT 与短路 AND/OR;CMP 与 IF。 + * - 切片 5:WHILE 编成 + * L_loop: JF t, L_end JMP L_loop L_end: + * (用例 5 循环体含自增,四则 ADD/SUB/MUL/DIV 同模式一并实现,用例 7 可测) + * - 跳转偏移相对下一条指令(目标 = 当前 + 1 + off,见 Doc/isa/指令与映像.md) * - 其余构造报 codegen error(后续切片展开) * * 函数清单: @@ -20,11 +21,12 @@ * - find_pou 按名查 POU AST * - build_function 编译一个 POU 的语句体(帧寄存器分配) * - begin_stmt / alloc_temp 临时寄存器:语句内递增、语句结束复用基址 - * - compile_stmt 语句编译(切片 4:赋值 / IF) + * - compile_stmt 语句编译(切片 5:赋值 / IF / WHILE) * - compile_if IF/ELSIF/ELSE:条件链 + 分支尾部 JMP,JF/JMP 回填 + * - compile_while WHILE:L_loop + 条件 JF + 体 + JMP 回环 * - store_target 赋值左值:STORE_* 到全局槽 - * - compile_expr 表达式编译到寄存器(含 CMP_xx / NOT / 短路 AND/OR) - * - cmp_op 比较符 → CMP_xx 操作码 + * - compile_expr 表达式编译到寄存器(含 CMP/算术/NOT/短路 AND/OR) + * - cmp_op / arith_op 比较符/算术符 → 操作码 * - load_op / store_op 按 io 绑定选读取/写入操作码 * - global_offset 逻辑槽号 → 数据区字节偏移 * - patch_jump 回填跳转偏移(相对下一条指令) @@ -246,8 +248,11 @@ namespace { if (st.kind == StmtKind::If) { return compile_if(f, st); } + if (st.kind == StmtKind::While) { + return compile_while(f, st); + } if (st.kind != StmtKind::Assign) { - return fail("statement not supported in slice 4"); + return fail("statement not supported in slice 5"); } const auto it = f.regs.find(st.target); if (it == f.regs.end()) { @@ -258,6 +263,35 @@ namespace { return compile_expr(f, *st.value, rd); } + /** + * @brief 编译 WHILE + * @details L_loop: JF t, L_end JMP L_loop L_end: + * 条件 JF 与回环 JMP 双回填 + * @param f 当前函数 + * @param st WHILE 语句 AST + * @return true 成功;false(err 已写) + */ + bool compile_while(FuncCtx& f, const Stmt& st) { + const size_t loop = f.code.size(); + const uint8_t t = alloc_temp(f); + if (!compile_expr(f, *st.cond, t)) { + return false; + } + const size_t jf_idx = f.code.size(); + f.code.push_back(isa::enc_jc(isa::Op::JF, t, 0)); // 假 → L_end + for (const Stmt& s : st.body) { + begin_stmt(f); + if (!compile_stmt(f, s)) { + return false; + } + } + const size_t jmp_idx = f.code.size(); + f.code.push_back(isa::enc_jmp(0)); // 回环 + patch_jump(f, jmp_idx, loop); + patch_jump(f, jf_idx, f.code.size()); + return true; + } + /** * @brief 编译 IF / ELSIF / ELSE * @details 每分支: JF t, L_next → → JMP L_end; @@ -369,6 +403,19 @@ namespace { } return fail("no register or slot for '" + e.name + "'"); } + if (e.kind == ExprKind::Add || e.kind == ExprKind::Sub || + e.kind == ExprKind::Mul || e.kind == ExprKind::Div) { + 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(arith_op(e.kind), rd, l, r)); + return true; + } if (e.kind == ExprKind::Cmp) { const uint8_t l = alloc_temp(f); if (!compile_expr(f, *e.lhs, l)) { @@ -423,6 +470,21 @@ namespace { static_cast((static_cast(off) >> 8) & 0xFFu)); } + /** + * @brief 算术运算 → 操作码 + * @param k 表达式种类(Add/Sub/Mul/Div) + * @return 对应操作码 + */ + isa::Op arith_op(ExprKind k) const { + switch (k) { + case ExprKind::Add: return isa::Op::ADD; + case ExprKind::Sub: return isa::Op::SUB; + case ExprKind::Mul: return isa::Op::MUL; + case ExprKind::Div: return isa::Op::DIV; + default: return isa::Op::ADD; + } + } + /** * @brief 比较符 → CMP_xx 操作码 * @param op 比较运算 diff --git a/tests/src/codegen_test.cpp b/tests/src/codegen_test.cpp index c1022bd..8dac32a 100644 --- a/tests/src/codegen_test.cpp +++ b/tests/src/codegen_test.cpp @@ -397,6 +397,100 @@ static bool test_case04() { return true; } +// ---- 10. 用例 05:WHILE 回环(JF 出口 + JMP 回填)---- + +static bool test_case05() { + std::vector img; + std::string err; + CHECK(compile_case("05_while_normal", &img, &err)); + + const isa::ImageView v = isa::ImageView::from(img); + CHECK(v.ok()); + const isa::FuncRow r = v.func_row(0); + CHECK(r.nregs == 4); // n + 3 临时(CMP 目的 + 两操作数) + CHECK(r.code_len == 10); + + char buf[64]; + const uint8_t* base = v.code_bytes(); + const uint32_t* ins = reinterpret_cast(base); + isa::disasm(ins[0], buf, sizeof buf); + CHECK(std::strcmp(buf, "LOADK r0, 0") == 0); // n := 0 + // L_loop [1]:n < 10 → r1 + isa::disasm(ins[1], buf, sizeof buf); + CHECK(std::strcmp(buf, "MOVE r2, r0") == 0); + isa::disasm(ins[2], buf, sizeof buf); + CHECK(std::strcmp(buf, "LOADK r3, 1") == 0); // 常量 1 = 10 + isa::disasm(ins[3], buf, sizeof buf); + CHECK(std::strcmp(buf, "CMP_LT r1, r2, r3") == 0); + isa::disasm(ins[4], buf, sizeof buf); + CHECK(std::strcmp(buf, "JF r1, +4") == 0); // 假 → RET([9]) + // n := n + 1(体语句临时复用 r1/r2) + isa::disasm(ins[5], buf, sizeof buf); + CHECK(std::strcmp(buf, "MOVE r1, r0") == 0); + isa::disasm(ins[6], buf, sizeof buf); + CHECK(std::strcmp(buf, "LOADK r2, 2") == 0); // 常量 2 = 1 + isa::disasm(ins[7], buf, sizeof buf); + CHECK(std::strcmp(buf, "ADD r0, r1, r2") == 0); + isa::disasm(ins[8], buf, sizeof buf); + CHECK(std::strcmp(buf, "JMP -8") == 0); // 回 L_loop([1]) + isa::disasm(ins[9], buf, sizeof buf); + CHECK(std::strcmp(buf, "RET") == 0); + return true; +} + +// ---- 11. 用例 07:四则 + 比较 ---- + +static bool test_case07() { + std::vector img; + std::string err; + CHECK(compile_case("07_int_arith", &img, &err)); + + const isa::ImageView v = isa::ImageView::from(img); + CHECK(v.ok()); + const isa::FuncRow r = v.func_row(0); + CHECK(r.nregs == 6); // a b c eq + 2 临时(语句间复用) + + char buf[64]; + const uint8_t* base = v.code_bytes(); + const uint32_t* ins = reinterpret_cast(base); + bool saw_mul = false, saw_div = false, saw_sub = false, saw_gt = false; + for (uint32_t i = 0; i < r.code_len; ++i) { + isa::disasm(ins[i], buf, sizeof buf); + if (std::strstr(buf, "MUL r0, ")) saw_mul = true; + if (std::strstr(buf, "DIV r1, ")) saw_div = true; + if (std::strstr(buf, "SUB r2, ")) saw_sub = true; + if (std::strstr(buf, "CMP_GT r3, ")) saw_gt = true; + } + CHECK(saw_mul && saw_div && saw_sub && saw_gt); + return true; +} + +// ---- 12. 用例 08:TIME 字面量进常量表 ---- + +static bool test_case08() { + std::vector img; + std::string err; + CHECK(compile_case("08_time_literal", &img, &err)); + + const isa::ImageView v = isa::ImageView::from(img); + CHECK(v.ok()); + CHECK(v.header().n_consts == 2); + const isa::ConstEntry c0 = v.const_entry(0); + CHECK(c0.tag == isa::types::Time && c0.value == 10); // T#10ms + const isa::ConstEntry c1 = v.const_entry(1); + CHECK(c1.tag == isa::types::Time && c1.value == 1250); // T#1s250ms + + const isa::FuncRow r = v.func_row(0); + CHECK(r.nregs == 2); + char buf[64]; + const uint32_t* ins = reinterpret_cast(v.code_bytes()); + isa::disasm(ins[0], buf, sizeof buf); + CHECK(std::strcmp(buf, "LOADK r0, 0") == 0); + isa::disasm(ins[1], buf, sizeof buf); + CHECK(std::strcmp(buf, "LOADK r1, 1") == 0); + return true; +} + int main() { if (!test_case01()) return 1; if (!test_case02()) return 1; @@ -407,6 +501,9 @@ int main() { if (!test_case03()) return 1; if (!test_not()) return 1; if (!test_case04()) return 1; + if (!test_case05()) return 1; + if (!test_case07()) return 1; + if (!test_case08()) return 1; std::printf("codegen_test: %d checks passed\n", g_checks); return 0; } From 0a36e36b16fd3bdcdd0b34281840ababc52415c9 Mon Sep 17 00:00:00 2001 From: chentianya Date: Fri, 21 Aug 2026 11:48:33 +0800 Subject: [PATCH 7/9] =?UTF-8?q?12.8=20=E5=88=87=E7=89=87=206=EF=BC=9AFUNCT?= =?UTF-8?q?ION=20=E4=B8=8E=20CALL=EF=BC=8C=E5=86=BB=E7=BB=93=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E7=BA=A6=E5=AE=9A=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调用约定(冻结进 isa 规格与 12.8 文档):结果 r0、参数 r1..r7(最多 7 输入、函数内只读)、 变量与临时全部从 r8 起;CALL/RET 时 VM 复制 r0..r7(12.9 实现) - 调用点:实参求值到 r8+ 临时 → MOVE r(1+i) → CALL fn_id → MOVE rd, r0 - 函数名赋值 = 写 r0(结果);写函数输入 → codegen error - codegen_test:134 断言(用例 13 两函数 CALL 序列;既有用例帧基址迁移 r8 起),ctest 9/9 --- Doc/compiler/寄存器码.md | 14 ++++ Doc/isa/指令与映像.md | 8 ++ compiler/src/Codegen.cpp | 104 +++++++++++++++++++----- tests/src/codegen_test.cpp | 159 ++++++++++++++++++++++++------------- 4 files changed, 213 insertions(+), 72 deletions(-) diff --git a/Doc/compiler/寄存器码.md b/Doc/compiler/寄存器码.md index 7e9cb80..a50d4ed 100644 --- a/Doc/compiler/寄存器码.md +++ b/Doc/compiler/寄存器码.md @@ -30,6 +30,20 @@ compiler 模块的代码生成。输入:`Project` + `Unit`(AST)+ `LinkResu - 字段访问 `fb.Q`:`LOAD_GLOBAL rd, <实例基址+字段偏移>`(编译期算死);I/Q/M 用 `LOAD_I` / `STORE_Q` / `LOAD_M` / `STORE_M` 同槽号 - 函数帧寄存器:`FUNCTION` 结果固定 **r0**,`VAR_INPUT` 从 r1 起按声明序,`VAR` 续后;PROGRAM / FB 变量从 r0 起按声明序 +### 调用约定(v1 冻结,VM 12.9 按此实现) + +| 项 | 约定 | +|---|---| +| 结果寄存器 | **r0**(FUNCTION 结果写入处) | +| 参数寄存器 | **r1..r7**(最多 7 个输入,按声明序) | +| 变量 / 临时 | **全部从 r8 起**(调用约定区不受覆盖) | +| `CALL` | VM 复制当前帧 **r0..r7 → 新帧 r0..r7** | +| `RET` | VM 复制当前帧 **r0..r7 → 调用方帧 r0..r7**(r0=结果,r1..r7 原样返回) | +| 函数输入 | 函数体内**只读**(写输入 → codegen error) | + +- 调用点:实参求值到 r8+ 临时 → `MOVE r(1+i), t` → `CALL fn_id` → `MOVE rd, r0` +- 所有 POU 的变量/临时统一 r8 起,故 r1..r7 在调用点可安全覆盖 + ## 寄存器分配 - 局部变量固定占用前段(0..k-1) diff --git a/Doc/isa/指令与映像.md b/Doc/isa/指令与映像.md index 2256c8a..082d43f 100644 --- a/Doc/isa/指令与映像.md +++ b/Doc/isa/指令与映像.md @@ -47,6 +47,14 @@ CMake 目标:`isa`(`STATIC`),无依赖。公开头:`isa/include/isa/` | 寄存器 | 下标 0..255;每函数实际个数写在函数头 `nregs` | | 跳转 | 相对 offset,单位是**指令条数**,不是字节 | | `CALL` | 立即数 `fn_id`(u16),无函数指针 | + +### 调用约定(冻结,compiler 12.8 与 VM 12.9 共同遵守) + +- 结果寄存器 **r0**;参数寄存器 **r1..r7**(最多 7 个输入,按声明序) +- 变量与表达式临时**全部从 r8 起**(调用约定区 r0..r7 不受用户数据占用) +- `CALL`:VM 复制当前帧 r0..r7 到新帧 r0..r7 +- `RET`:VM 复制当前帧 r0..r7 回调用方帧 r0..r7(r0 = 结果;r1..r7 原样返回) +- 函数输入在函数体内只读(compiler 拒绝写入) | 映像魔数 | `STSC`,版本 `1` | | 工程哈希 | 源文件路径排序后对内容做 FNV-1a 64(非密码学) | | I/O 绑定 | **不进映像**;编译期把 `var` 收成槽号,channel/bit 由 STCompiler 写进 sidecar(`.runtime.toml`),BytecodeExecutor 读 sidecar 采样 | diff --git a/compiler/src/Codegen.cpp b/compiler/src/Codegen.cpp index 796e036..8aed05f 100644 --- a/compiler/src/Codegen.cpp +++ b/compiler/src/Codegen.cpp @@ -1,15 +1,15 @@ /** * @file Codegen.cpp - * @brief 寄存器码生成(12.8,切片 5:+ WHILE、四则运算) + * @brief 寄存器码生成(12.8,切片 6:+ FUNCTION 与 CALL) * @author * @date 2026-08-21 * * @details 设计说明(详见 Doc/compiler/寄存器码.md): - * - 切片 1~4:帧/字面量/MOVE/RET;全局数据区;NOT 与短路 AND/OR;CMP 与 IF。 - * - 切片 5:WHILE 编成 - * L_loop: JF t, L_end JMP L_loop L_end: - * (用例 5 循环体含自增,四则 ADD/SUB/MUL/DIV 同模式一并实现,用例 7 可测) - * - 跳转偏移相对下一条指令(目标 = 当前 + 1 + off,见 Doc/isa/指令与映像.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(后续切片展开) * * 函数清单: @@ -19,13 +19,14 @@ * - Builder::run 布局数据区 → 逐 POU 建函数 → 拼映像 * - Builder::fail 组装 "codegen error: " 返回 false * - find_pou 按名查 POU AST - * - build_function 编译一个 POU 的语句体(帧寄存器分配) + * - find_fn_id 按名查 fn_id(POU 收集序) + * - build_function 编译一个 POU(PROGRAM/FUNCTION;帧约定分配) * - begin_stmt / alloc_temp 临时寄存器:语句内递增、语句结束复用基址 - * - compile_stmt 语句编译(切片 5:赋值 / IF / WHILE) + * - 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/短路 AND/OR) + * - compile_expr 表达式编译到寄存器(含 CMP/算术/NOT/短路/CALL) * - cmp_op / arith_op 比较符/算术符 → 操作码 * - load_op / store_op 按 io 绑定选读取/写入操作码 * - global_offset 逻辑槽号 → 数据区字节偏移 @@ -176,9 +177,11 @@ namespace { std::string name; std::vector code; // 字节码(函数表 code_offset 相对此段) std::map regs; // 变量名 → 帧寄存器 - uint8_t nlocals = 0; // 变量数 = 临时寄存器起始 + uint8_t nlocals = 0; // 变量区终点 = 临时寄存器起始 uint8_t nregs = 0; // 峰值(变量 + 临时) uint8_t temp_used = 0; // 本语句已用临时数(语句结束清零) + bool is_function = false; // FUNCTION(结果 r0 / 输入只读) + std::string result_name; // FUNCTION 名(结果寄存器映射) }; /** @@ -203,31 +206,53 @@ namespace { /** * @brief 编译一个 POU - * @details 切片 1:仅 PROGRAM + 标量变量(FB 实例/函数留后续切片) + * @details 切片 6:支持 PROGRAM 与 FUNCTION(FB 留切片 8) + * 帧约定(见 Doc/compiler/寄存器码.md):结果 r0、输入 r1..r7(只读)、 + * 变量与临时全部从 r8 起 * @param pou POU AST * @param f 输出函数编译态 * @return true 成功;false(err 已写) */ bool build_function(const POU& pou, FuncCtx* f) { - if (pou.kind != PouKind::Program) { - return fail("only PROGRAM supported in slice 1 ('" + pou.name + "')"); + if (pou.kind == PouKind::FunctionBlock) { + return fail("FB not supported in slice 6 ('" + pou.name + "')"); } + f->is_function = pou.kind == PouKind::Function; + f->result_name = pou.name; + if (f->is_function) { + f->regs[pou.name] = 0; // 结果寄存器 r0 + uint8_t idx = 1; + for (const VarBlock& b : pou.blocks) { + if (b.section != VarSection::Input) { + continue; + } + for (const VarDecl& d : b.vars) { + f->regs[d.name] = idx++; // 输入 r1..r7 + } + } + } + uint8_t r = 8; // 变量/临时基址(调用约定区 r0..r7 不占用) for (const VarBlock& b : pou.blocks) { // External / Global 走数据区槽,不占帧寄存器 if (b.section == VarSection::External || b.section == VarSection::Global) { continue; } + // FUNCTION 输入已分配(r1..r7) + if (f->is_function && b.section == VarSection::Input) { + continue; + } for (const VarDecl& d : b.vars) { if (d.type.kind == TypeKind::FbUser || d.type.kind == TypeKind::FbBuiltin) { - return fail("FB instance not supported in slice 1 ('" + d.name + "')"); + return fail("FB instance not supported in slice 6 ('" + d.name + "')"); } - if (f->regs.size() > 255) { + if (r > 255) { return fail("register overflow in function '" + pou.name + "'"); } - f->regs[d.name] = f->nregs++; + f->regs[d.name] = r++; } } - f->nlocals = f->nregs; + f->nlocals = r; + f->nregs = r; for (const Stmt& st : pou.body) { begin_stmt(*f); if (!compile_stmt(*f, st)) { @@ -259,6 +284,10 @@ namespace { // 全局 / 外部左值 → STORE_* 到数据区 return store_target(f, st.target, *st.value); } + // 函数输入只读(调用约定,见 Doc/compiler/寄存器码.md) + if (f.is_function && it->second >= 1 && it->second <= 7) { + return fail("cannot write function input '" + st.target + "'"); + } const uint8_t rd = it->second; return compile_expr(f, *st.value, rd); } @@ -452,7 +481,46 @@ namespace { patch_jump(f, jmp_idx, f.code.size()); return true; } - return fail("expression not supported in slice 3"); + if (e.kind == ExprKind::Call) { + // 调用约定:实参求值到 r8+ 临时 → MOVE 到 r1..r7 → CALL → 结果 r0 + if (e.args.size() > 7) { + return fail("too many arguments (max 7)"); + } + std::vector args; + for (const auto& a : e.args) { + const uint8_t t = alloc_temp(f); + if (!compile_expr(f, *a, t)) { + return false; + } + args.push_back(t); + } + const int fn_id = find_fn_id(e.name); + if (fn_id < 0) { + return fail("no fn_id for '" + e.name + "'"); + } + for (size_t i = 0; i < args.size(); ++i) { + f.code.push_back( + isa::enc_rr(isa::Op::MOVE, static_cast(1 + i), args[i])); + } + f.code.push_back(isa::enc_call(static_cast(fn_id))); + f.code.push_back(isa::enc_rr(isa::Op::MOVE, rd, 0)); + return true; + } + return fail("expression not supported in slice 6"); + } + + /** + * @brief 按名查 fn_id(POU 收集序下标) + * @param name POU 名(小写) + * @return fn_id;未找到返回 -1 + */ + int find_fn_id(const std::string& name) const { + for (size_t i = 0; i < link_.scopes.size(); ++i) { + if (link_.scopes[i].name == name) { + return static_cast(i); + } + } + return -1; } /** diff --git a/tests/src/codegen_test.cpp b/tests/src/codegen_test.cpp index 8dac32a..2418128 100644 --- a/tests/src/codegen_test.cpp +++ b/tests/src/codegen_test.cpp @@ -121,7 +121,7 @@ static bool test_case01() { CHECK(v.header().project_hash != isa::kFnvBasis); const isa::FuncRow r = v.func_row(0); - CHECK(r.nregs == 0); + CHECK(r.nregs == 8); // 帧基址(调用约定区) CHECK(r.code_len == 1); char buf[32]; @@ -143,16 +143,16 @@ static bool test_case02() { CHECK(v.header().n_consts == 2); // TRUE 与 FALSE const isa::FuncRow r = v.func_row(0); - CHECK(r.nregs == 2); // a, b + CHECK(r.nregs == 10); // a, b(r8 起) CHECK(r.code_len == 3); // 从字节码段取指令(按函数行 code_offset 定位) const uint8_t* base = v.code_bytes(); char buf[64]; isa::disasm(reinterpret_cast(base)[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r0, 0") == 0); // a := TRUE(常量 0) + CHECK(std::strcmp(buf, "LOADK r8, 0") == 0); // a := TRUE(常量 0) isa::disasm(reinterpret_cast(base)[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r1, 1") == 0); // b := FALSE(常量 1) + CHECK(std::strcmp(buf, "LOADK r9, 1") == 0); // b := FALSE(常量 1) isa::disasm(reinterpret_cast(base)[2], buf, sizeof buf); CHECK(std::strcmp(buf, "RET") == 0); @@ -179,13 +179,13 @@ static bool test_case09() { CHECK(v.data_bytes()[1] == 0); const isa::FuncRow r = v.func_row(0); - CHECK(r.nregs == 1); // 仅局部 x + CHECK(r.nregs == 9); // r8 起:仅局部 x CHECK(r.code_len == 2); char buf[64]; const uint8_t* base = v.code_bytes(); isa::disasm(reinterpret_cast(base)[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOAD_GLOBAL r0, 0") == 0); // x := G1(槽 0 偏移 0) + CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 0") == 0); // x := G1(槽 0 偏移 0) isa::disasm(reinterpret_cast(base)[1], buf, sizeof buf); CHECK(std::strcmp(buf, "RET") == 0); return true; @@ -213,16 +213,16 @@ static bool test_io_ops() { CHECK(v.header().n_globals == 2); const isa::FuncRow r = v.func_row(0); - CHECK(r.nregs == 2); // q + 1 临时 + CHECK(r.nregs == 10); // r8 起:q + 1 临时 char buf[64]; const uint8_t* base = v.code_bytes(); isa::disasm(reinterpret_cast(base)[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOAD_I r0, 0") == 0); // q := I0_0(io.input → LOAD_I) + CHECK(std::strcmp(buf, "LOAD_I r8, 0") == 0); // q := I0_0(io.input → LOAD_I) isa::disasm(reinterpret_cast(base)[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r1, r0") == 0); // 临时 ← q + CHECK(std::strcmp(buf, "MOVE r9, r8") == 0); // 临时 ← q isa::disasm(reinterpret_cast(base)[2], buf, sizeof buf); - CHECK(std::strcmp(buf, "STORE_Q r1, 1") == 0); // Q0_0 := 临时(io.output → STORE_Q) + CHECK(std::strcmp(buf, "STORE_Q r9, 1") == 0); // Q0_0 := 临时(io.output → STORE_Q) isa::disasm(reinterpret_cast(base)[3], buf, sizeof buf); CHECK(std::strcmp(buf, "RET") == 0); return true; @@ -255,7 +255,7 @@ static bool test_layout() { char buf[64]; isa::disasm(reinterpret_cast(v.code_bytes())[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOAD_GLOBAL r0, 2") == 0); // x := I(INT 偏移 2) + CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 2") == 0); // x := I(INT 偏移 2) return true; } @@ -289,7 +289,7 @@ static bool test_case03() { const isa::ImageView v = isa::ImageView::from(img); CHECK(v.ok()); const isa::FuncRow r = v.func_row(0); - CHECK(r.nregs == 4); // a b x + 1 临时 + CHECK(r.nregs == 12); // r8 起:a b x + 1 临时 CHECK(r.code_len == 9); char buf[64]; @@ -297,22 +297,22 @@ static bool test_case03() { const uint32_t* ins = reinterpret_cast(base); // x := a AND b:MOVE r2,r0 / JF r2,+2 / MOVE r3,r1 / MOVE r2,r3 isa::disasm(ins[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r2, r0") == 0); + CHECK(std::strcmp(buf, "MOVE r10, r8") == 0); isa::disasm(ins[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "JF r2, +2") == 0); + CHECK(std::strcmp(buf, "JF r10, +2") == 0); isa::disasm(ins[2], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r3, r1") == 0); + CHECK(std::strcmp(buf, "MOVE r11, r9") == 0); isa::disasm(ins[3], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r2, r3") == 0); + CHECK(std::strcmp(buf, "MOVE r10, r11") == 0); // x := a OR b:MOVE r2,r0 / JT r2,+2 / MOVE r3,r1 / MOVE r2,r3 isa::disasm(ins[4], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r2, r0") == 0); + CHECK(std::strcmp(buf, "MOVE r10, r8") == 0); isa::disasm(ins[5], buf, sizeof buf); - CHECK(std::strcmp(buf, "JT r2, +2") == 0); + CHECK(std::strcmp(buf, "JT r10, +2") == 0); isa::disasm(ins[6], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r3, r1") == 0); + CHECK(std::strcmp(buf, "MOVE r11, r9") == 0); isa::disasm(ins[7], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r2, r3") == 0); + CHECK(std::strcmp(buf, "MOVE r10, r11") == 0); isa::disasm(ins[8], buf, sizeof buf); CHECK(std::strcmp(buf, "RET") == 0); return true; @@ -332,16 +332,16 @@ static bool test_not() { const isa::ImageView v = isa::ImageView::from(img); CHECK(v.ok()); const isa::FuncRow r = v.func_row(0); - CHECK(r.nregs == 3); // a x + 1 临时 + CHECK(r.nregs == 11); // r8 起:a x + 1 临时 CHECK(r.code_len == 3); char buf[64]; const uint8_t* base = v.code_bytes(); const uint32_t* ins = reinterpret_cast(base); isa::disasm(ins[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r2, r0") == 0); // 临时 ← a + CHECK(std::strcmp(buf, "MOVE r10, r8") == 0); // 临时 ← a isa::disasm(ins[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "NOT r1, r2") == 0); // x := NOT 临时 + CHECK(std::strcmp(buf, "NOT r9, r10") == 0); // x := NOT 临时 isa::disasm(ins[2], buf, sizeof buf); CHECK(std::strcmp(buf, "RET") == 0); return true; @@ -357,7 +357,7 @@ static bool test_case04() { 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.nregs == 13); // r8 起:sel out + 3 临时 CHECK(r.code_len == 14); char buf[64]; @@ -365,33 +365,33 @@ static bool test_case04() { const uint32_t* ins = reinterpret_cast(base); // IF sel = 0 THEN:sel→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); + CHECK(std::strcmp(buf, "MOVE r11, r8") == 0); isa::disasm(ins[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r4, 0") == 0); + CHECK(std::strcmp(buf, "LOADK r12, 0") == 0); isa::disasm(ins[2], buf, sizeof buf); - CHECK(std::strcmp(buf, "CMP_EQ r2, r3, r4") == 0); + CHECK(std::strcmp(buf, "CMP_EQ r10, r11, r12") == 0); isa::disasm(ins[3], buf, sizeof buf); - CHECK(std::strcmp(buf, "JF r2, +2") == 0); // 假 → elsif([6]) + CHECK(std::strcmp(buf, "JF r10, +2") == 0); // 假 → elsif([6]) isa::disasm(ins[4], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r1, 1") == 0); // out := 10(常量 1) + CHECK(std::strcmp(buf, "LOADK r9, 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); + CHECK(std::strcmp(buf, "MOVE r11, r8") == 0); isa::disasm(ins[7], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r4, 2") == 0); + CHECK(std::strcmp(buf, "LOADK r12, 2") == 0); isa::disasm(ins[8], buf, sizeof buf); - CHECK(std::strcmp(buf, "CMP_EQ r2, r3, r4") == 0); + CHECK(std::strcmp(buf, "CMP_EQ r10, r11, r12") == 0); isa::disasm(ins[9], buf, sizeof buf); - CHECK(std::strcmp(buf, "JF r2, +2") == 0); // 假 → else([12]) + CHECK(std::strcmp(buf, "JF r10, +2") == 0); // 假 → else([12]) isa::disasm(ins[10], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r1, 3") == 0); + CHECK(std::strcmp(buf, "LOADK r9, 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); + CHECK(std::strcmp(buf, "LOADK r9, 4") == 0); isa::disasm(ins[13], buf, sizeof buf); CHECK(std::strcmp(buf, "RET") == 0); return true; @@ -407,30 +407,30 @@ static bool test_case05() { const isa::ImageView v = isa::ImageView::from(img); CHECK(v.ok()); const isa::FuncRow r = v.func_row(0); - CHECK(r.nregs == 4); // n + 3 临时(CMP 目的 + 两操作数) + CHECK(r.nregs == 12); // r8 起:n + 3 临时(条件结果 r9 + 两操作数) CHECK(r.code_len == 10); char buf[64]; const uint8_t* base = v.code_bytes(); const uint32_t* ins = reinterpret_cast(base); isa::disasm(ins[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r0, 0") == 0); // n := 0 - // L_loop [1]:n < 10 → r1 + CHECK(std::strcmp(buf, "LOADK r8, 0") == 0); // n := 0 + // L_loop [1]:n < 10 → r9 isa::disasm(ins[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r2, r0") == 0); + CHECK(std::strcmp(buf, "MOVE r10, r8") == 0); isa::disasm(ins[2], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r3, 1") == 0); // 常量 1 = 10 + CHECK(std::strcmp(buf, "LOADK r11, 1") == 0); // 常量 1 = 10 isa::disasm(ins[3], buf, sizeof buf); - CHECK(std::strcmp(buf, "CMP_LT r1, r2, r3") == 0); + CHECK(std::strcmp(buf, "CMP_LT r9, r10, r11") == 0); isa::disasm(ins[4], buf, sizeof buf); - CHECK(std::strcmp(buf, "JF r1, +4") == 0); // 假 → RET([9]) - // n := n + 1(体语句临时复用 r1/r2) + CHECK(std::strcmp(buf, "JF r9, +4") == 0); // 假 → RET([9]) + // n := n + 1(体语句临时复用 r9/r10) isa::disasm(ins[5], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r1, r0") == 0); + CHECK(std::strcmp(buf, "MOVE r9, r8") == 0); isa::disasm(ins[6], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r2, 2") == 0); // 常量 2 = 1 + CHECK(std::strcmp(buf, "LOADK r10, 2") == 0); // 常量 2 = 1 isa::disasm(ins[7], buf, sizeof buf); - CHECK(std::strcmp(buf, "ADD r0, r1, r2") == 0); + CHECK(std::strcmp(buf, "ADD r8, r9, r10") == 0); isa::disasm(ins[8], buf, sizeof buf); CHECK(std::strcmp(buf, "JMP -8") == 0); // 回 L_loop([1]) isa::disasm(ins[9], buf, sizeof buf); @@ -448,7 +448,7 @@ static bool test_case07() { const isa::ImageView v = isa::ImageView::from(img); CHECK(v.ok()); const isa::FuncRow r = v.func_row(0); - CHECK(r.nregs == 6); // a b c eq + 2 临时(语句间复用) + CHECK(r.nregs == 14); // r8 起:a b c eq + 2 临时 char buf[64]; const uint8_t* base = v.code_bytes(); @@ -456,10 +456,10 @@ static bool test_case07() { bool saw_mul = false, saw_div = false, saw_sub = false, saw_gt = false; for (uint32_t i = 0; i < r.code_len; ++i) { isa::disasm(ins[i], buf, sizeof buf); - if (std::strstr(buf, "MUL r0, ")) saw_mul = true; - if (std::strstr(buf, "DIV r1, ")) saw_div = true; - if (std::strstr(buf, "SUB r2, ")) saw_sub = true; - if (std::strstr(buf, "CMP_GT r3, ")) saw_gt = true; + if (std::strstr(buf, "MUL r8, ")) saw_mul = true; + if (std::strstr(buf, "DIV r9, ")) saw_div = true; + if (std::strstr(buf, "SUB r10, ")) saw_sub = true; + if (std::strstr(buf, "CMP_GT r11, ")) saw_gt = true; } CHECK(saw_mul && saw_div && saw_sub && saw_gt); return true; @@ -481,13 +481,63 @@ static bool test_case08() { CHECK(c1.tag == isa::types::Time && c1.value == 1250); // T#1s250ms const isa::FuncRow r = v.func_row(0); - CHECK(r.nregs == 2); + CHECK(r.nregs == 10); // r8 起 char buf[64]; const uint32_t* ins = reinterpret_cast(v.code_bytes()); isa::disasm(ins[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r0, 0") == 0); + CHECK(std::strcmp(buf, "LOADK r8, 0") == 0); isa::disasm(ins[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r1, 1") == 0); + CHECK(std::strcmp(buf, "LOADK r9, 1") == 0); + return true; +} + +// ---- 13. 用例 13:FUNCTION + CALL(调用约定 r0 结果 / r1.. 参数)---- + +static bool test_case13() { + std::vector img; + std::string err; + CHECK(compile_case("13_function_call", &img, &err)); + + const isa::ImageView v = isa::ImageView::from(img); + CHECK(v.ok()); + CHECK(v.header().n_funcs == 2); + CHECK(v.header().entry_fn_id == 1); // MAIN 是第二个 POU + + char buf[64]; + const uint8_t* base = v.code_bytes(); + // Add(fn_id 0):结果 r0、输入 r1(a) r2(b)、临时 r8/r9 + const isa::FuncRow fa = v.func_row(0); + CHECK(fa.nregs == 10); + CHECK(fa.code_len == 4); + const uint32_t* ia = reinterpret_cast(base); + isa::disasm(ia[0], buf, sizeof buf); + CHECK(std::strcmp(buf, "MOVE r8, r1") == 0); // t ← a + isa::disasm(ia[1], buf, sizeof buf); + CHECK(std::strcmp(buf, "MOVE r9, r2") == 0); // t ← b + isa::disasm(ia[2], buf, sizeof buf); + CHECK(std::strcmp(buf, "ADD r0, r8, r9") == 0); // 结果 r0 + isa::disasm(ia[3], buf, sizeof buf); + CHECK(std::strcmp(buf, "RET") == 0); + + // MAIN(fn_id 1):x 在 r8,实参临时 r9/r10 + const isa::FuncRow fm = v.func_row(1); + CHECK(fm.nregs == 11); + CHECK(fm.code_len == 7); + const uint32_t* im = reinterpret_cast(base) + fm.code_offset / 4; + isa::disasm(im[0], buf, sizeof buf); + CHECK(std::strcmp(buf, "LOADK r9, 0") == 0); // 3 + isa::disasm(im[1], buf, sizeof buf); + CHECK(std::strcmp(buf, "LOADK r10, 1") == 0); // 4 + isa::disasm(im[2], buf, sizeof buf); + CHECK(std::strcmp(buf, "MOVE r1, r9") == 0); // 参数 1 ← 3 + isa::disasm(im[3], buf, sizeof buf); + CHECK(std::strcmp(buf, "MOVE r2, r10") == 0); // 参数 2 ← 4 + isa::disasm(im[4], buf, sizeof buf); + CHECK(std::strcmp(buf, "CALL 0") == 0); + isa::disasm(im[5], buf, sizeof buf); + CHECK(std::strcmp(buf, "MOVE r8, r0") == 0); // x ← 结果 + isa::disasm(im[6], buf, sizeof buf); + CHECK(std::strcmp(buf, "RET") == 0); return true; } @@ -504,6 +554,7 @@ int main() { if (!test_case05()) return 1; if (!test_case07()) return 1; if (!test_case08()) return 1; + if (!test_case13()) return 1; std::printf("codegen_test: %d checks passed\n", g_checks); return 0; } From 0263813a8f451046c45440e550d0a1684bc338e3 Mon Sep 17 00:00:00 2001 From: chentianya Date: Fri, 21 Aug 2026 11:52:25 +0800 Subject: [PATCH 8/9] =?UTF-8?q?12.8=20=E5=88=87=E7=89=87=207=EF=BC=9AFB=20?= =?UTF-8?q?=E5=AE=9E=E4=BE=8B=E5=9D=97=E3=80=81=E7=94=A8=E6=88=B7=20FB=20?= =?UTF-8?q?=E5=86=85=E8=81=94=E3=80=81CAL=5FTON/TOF/CTU=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 负例按期望在前层拒绝 --- compiler/src/Codegen.cpp | 237 ++++++++++++++++++++++++++++++++----- tests/src/codegen_test.cpp | 149 +++++++++++++++++++++++ 2 files changed, 359 insertions(+), 27 deletions(-) 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; } From 0a42d5876e9cadb62f2f55164004c8620a8581a2 Mon Sep 17 00:00:00 2001 From: chentianya Date: Fri, 21 Aug 2026 11:54:02 +0800 Subject: [PATCH 9/9] =?UTF-8?q?STCompiler=20=E6=8E=A5=E7=BA=BF=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=E7=BC=96=E8=AF=91=E7=AE=A1=E7=BA=BF=EF=BC=9A-o=20?= =?UTF-8?q?=E4=BA=A7=E5=87=BA=20.stb=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - STCompiler -o .stb:读 .st → 链接 → 类型检查 → 寄存器码 → 写映像 - 无 -o 保留 12.3 行为(打印文件集合与哈希) - 验证:line1 编译出 184 字节 .stb(2 函数 4 全局);负例 10/14/19 按期望报错退出码 1 --- compiler/src/main.cpp | 72 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 11 deletions(-) diff --git a/compiler/src/main.cpp b/compiler/src/main.cpp index a45da05..e7bccc1 100644 --- a/compiler/src/main.cpp +++ b/compiler/src/main.cpp @@ -10,12 +10,18 @@ #include #include +#include "compiler/Codegen.h" +#include "compiler/Linker.h" #include "compiler/Project.h" +#include "compiler/Typecheck.h" +#include "isa/Image.h" namespace { void usage() { - std::printf("usage: STCompiler \n" - " 解析工程并打印文件集合与工程哈希(12.3 阶段)\n" + std::printf("usage: STCompiler [-o .stb]\n" + " 解析工程并编译(词法 → 语法 → 链接 → 类型 → 寄存器码)\n" + " -o .stb 编译并写出映像文件\n" + " 无 -o 只打印文件集合与工程哈希(12.3 阶段)\n" " --help 打印本帮助\n"); } } @@ -31,24 +37,68 @@ int main(int argc, char** argv) { return 0; } + const std::string toml_path = argv[1]; + std::string out_path; + for (int i = 2; i + 1 < argc; ++i) { + if (std::strcmp(argv[i], "-o") == 0) { + out_path = argv[i + 1]; + } + } + compiler::Project proj; std::string err; - if (!compiler::parse_project(argv[1], &proj, &err)) { + if (!compiler::parse_project(toml_path, &proj, &err)) { std::fprintf(stderr, "error: %s\n", err.c_str()); return 1; } const std::vector files = compiler::compile_files(proj); - uint64_t hash = 0; - if (!compiler::compute_project_hash(proj, &hash, &err)) { + + if (out_path.empty()) { + uint64_t hash = 0; + if (!compiler::compute_project_hash(proj, &hash, &err)) { + std::fprintf(stderr, "error: %s\n", err.c_str()); + return 1; + } + std::printf("project: %s\n", proj.name.c_str()); + std::printf("files:\n"); + for (const std::string& f : files) { + std::printf(" %s\n", f.c_str()); + } + std::printf("hash: 0x%016llx\n", static_cast(hash)); + return 0; + } + + // 完整管线:读 .st → 链接 → 类型检查 → 寄存器码 + std::vector units; + for (const std::string& f : files) { + compiler::SourceUnit u; + if (!compiler::load_unit(proj.base_dir + "/" + f, &u, &err)) { + std::fprintf(stderr, "error: %s\n", err.c_str()); + return 1; + } + units.push_back(std::move(u)); + } + compiler::LinkResult link; + if (!compiler::link_project(proj, units, &link, &err)) { + std::fprintf(stderr, "error: %s\n", err.c_str()); + return 1; + } + if (!compiler::check_project(proj, units, link, &err)) { + std::fprintf(stderr, "error: %s\n", err.c_str()); + return 1; + } + std::vector image; + if (!compiler::codegen_project(proj, units, link, &image, &err)) { + std::fprintf(stderr, "error: %s\n", err.c_str()); + return 1; + } + if (!isa::write_stb_file(out_path.c_str(), image, &err)) { std::fprintf(stderr, "error: %s\n", err.c_str()); return 1; } - std::printf("project: %s\n", proj.name.c_str()); - std::printf("files:\n"); - for (const std::string& f : files) { - std::printf(" %s\n", f.c_str()); - } - std::printf("hash: 0x%016llx\n", static_cast(hash)); + const isa::ImageView v = isa::ImageView::from(image); + std::printf("compiled: %s (%zu bytes, %u functions, %u globals)\n", + out_path.c_str(), image.size(), v.header().n_funcs, v.header().n_globals); return 0; }