Merge branch 'dev_RegisterEncoding' into dev

This commit is contained in:
2026-08-21 11:54:19 +08:00
9 changed files with 1926 additions and 13 deletions
+135
View File
@@ -0,0 +1,135 @@
# 寄存器码(12.8
compiler 模块的代码生成。输入:`Project` + `Unit`AST+ `LinkResult`;输出:`.stb` 映像字节(`std::vector<uint8_t>`)。全工程定案见 [`初步计划.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 起按声明序
### 调用约定(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)
- 表达式临时从 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 <operand → t> NOT rd, t
AND/OR 短路(必须跳转):
<lhs → rd> JF(AND)/JT(OR) rd, L_end
<rhs → t> MOVE rd, t
L_end:
比较 <l→t1> <r→t2> CMP_xx rd, t1, t2
算术 <l→t1> <r→t2> ADD/SUB/MUL/DIV rd, t1, t2
负号 <operand → t> SUB rd, r0, t 0 - tr0 恒 0
IF <cond → t> JF t, L_else
<then body> JMP L_end
L_else: (ELSIF 逐级) <elsif cond → t> JF t, L_next ...
L_end:
WHILE L_loop: <cond → t> JF t, L_end <body> 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. 用例 19、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。
+9 -1
View File
@@ -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..r7r0 = 结果;r1..r7 原样返回)
- 函数输入在函数体内只读(compiler 拒绝写入)
| 映像魔数 | `STSC`,版本 `1` |
| 工程哈希 | 源文件路径排序后对内容做 FNV-1a 64(非密码学) |
| I/O 绑定 | **不进映像**;编译期把 `var` 收成槽号,channel/bit 由 STCompiler 写进 sidecar`.runtime.toml`),BytecodeExecutor 读 sidecar 采样 |
@@ -82,7 +90,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` 冻下面六种):
+2
View File
@@ -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,跑扫描周期 |
+2 -1
View File
@@ -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)
+25
View File
@@ -0,0 +1,25 @@
/**
* @file Codegen.h
* @brief 寄存器码生成(12.8,切片 1MOVE / LOADK / RET
* @author
* @date 2026-08-21
*/
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include "compiler/Linker.h"
#include "compiler/Parser.h"
#include "compiler/Project.h"
namespace compiler {
// 编译工程为 .stb 映像字节(在链接 + 类型检查成功后调用)。
// 失败返回 falseerr 前缀 "codegen error"。
bool codegen_project(const Project& proj, const std::vector<SourceUnit>& units,
const LinkResult& link, std::vector<uint8_t>* image,
std::string* err);
}
+972
View File
@@ -0,0 +1,972 @@
/**
* @file Codegen.cpp
* @brief 寄存器码生成(12.8,切片 7:+ FB 实例、内联展开、CAL_*)
* @author
* @date 2026-08-21
*
* @details 设计说明(详见 Doc/compiler/寄存器码.md):
* - 切片 1~6:帧/字面量/MOVE/RET;全局数据区;短路 AND/ORCMP 与 IFWHILE 与四则;
* FUNCTION 与 CALL(调用约定 r0/r1..r7/r8+)。
* - 切片 7:FB 实例 → 数据区实例块(跨周期持久);字段地址 = 实例基址 + 字段偏移
* (编译期算死);字段读写走 LOAD_GLOBAL / STORE_GLOBAL
* 内建 TON/TOF/CTU → 实参写字段后 CAL_* <实例偏移>
* 用户 FB → 调用点内联展开(实参写字段 → FB 体以实例字段为变量编译)
* - 其余构造报 codegen error
*
* 函数清单:
* - put_le32 / put_le64 小端写入映像缓冲
* - align_up / width_of 对齐与槽宽
* - Builder::Builder (构造)存工程/源文件/链接结果/输出,收集 io 绑定分类
* - Builder::run 布局数据区(全局 + 实例)→ 逐 POU 建函数 → 拼映像
* - Builder::fail 组装 "codegen error: <msg>" 返回 false
* - find_pou / find_fn_id 按名查 POU AST / fn_id
* - build_function 编译一个 POUPROGRAM/FUNCTION/FB 骨架;帧约定分配)
* - begin_stmt / alloc_temp 临时寄存器:语句内递增、语句结束复用基址
* - 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 / layout_fb_instances / init_of 数据区布局
* - const_id 取常量表 id(无则追加)
* - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + 数据段
* - codegen_project 对外入口
*/
#include "compiler/Codegen.h"
#include <cctype>
#include <cstdio>
#include <map>
#include <string>
#include <vector>
#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<uint8_t>& b, size_t off, uint32_t v) {
b[off + 0] = static_cast<uint8_t>(v & 0xFFu);
b[off + 1] = static_cast<uint8_t>((v >> 8) & 0xFFu);
b[off + 2] = static_cast<uint8_t>((v >> 16) & 0xFFu);
b[off + 3] = static_cast<uint8_t>((v >> 24) & 0xFFu);
}
void put_le64(std::vector<uint8_t>& b, size_t off, uint64_t v) {
for (int i = 0; i < 8; ++i) {
b[off + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFFu);
}
}
/**
* @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 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)
*/
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<SourceUnit>& units,
const LinkResult& link, std::vector<uint8_t>* image, std::string* 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<char>(std::tolower(static_cast<unsigned char>(ch)));
}
if (b.is_input) {
io_input_[key] = true;
} else {
io_output_[key] = true;
}
}
}
/**
* @brief 布局数据区 → 布局 FB 实例 → 逐 POU 建函数 → 拼映像
* @details 偏移先定(函数编译引用 global_offset / instance_of
* @return true 成功;falseerr 已写,前缀 "codegen error"
*/
bool run() {
if (!layout_data()) {
return false;
}
if (!layout_fb_instances()) {
return false;
}
for (const LinkResult::PouScope& sc : link_.scopes) {
const POU* pou = find_pou(sc.name);
if (pou == nullptr) {
continue;
}
FuncCtx f;
f.name = sc.name;
f.pou_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::string pou_name; // 所属 POU 名(实例查找键)
std::vector<isa::Instr> code; // 字节码(函数表 code_offset 相对此段)
std::map<std::string, uint8_t> regs; // 变量名 → 帧寄存器
uint8_t nlocals = 0; // 变量区终点 = 临时寄存器起始
uint8_t nregs = 0; // 峰值(变量 + 临时)
uint8_t temp_used = 0; // 本语句已用临时数(语句结束清零)
bool is_function = false; // FUNCTION(结果 r0 / 输入只读)
std::string result_name; // FUNCTION 名(结果寄存器映射)
};
// FB 实例:字段名 → 数据区字节地址
struct InstFields {
std::map<std::string, uint32_t> field_addr;
uint32_t base = 0;
std::string type_name; // 实例的 FB 类型名(内建 / 用户)
};
/**
* @brief 语句开始:临时寄存器基址复用
* @param f 当前函数
*/
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<uint16_t>(f.nlocals) + f.temp_used > f.nregs) {
f.nregs = f.nlocals + f.temp_used;
}
return r;
}
/**
* @brief 编译一个 POU
* @details 支持 PROGRAM / FUNCTION / FUNCTION_BLOCK
* FB 不生成业务字节码(调用点内联),只出空函数占位保持 fn_id 一致;
* 帧约定(见 Doc/compiler/寄存器码.md):结果 r0、输入 r1..r7(只读)、
* 变量与临时全部从 r8 起
* @param pou POU AST
* @param f 输出函数编译态
* @return true 成功;falseerr 已写)
*/
bool build_function(const POU& pou, FuncCtx* f) {
if (pou.kind == PouKind::FunctionBlock) {
// 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;
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) {
// FB 实例走数据区(layout_fb_instances),不占帧寄存器
if (d.type.kind == TypeKind::FbUser || d.type.kind == TypeKind::FbBuiltin) {
continue;
}
if (r > 255) {
return fail("register overflow in function '" + pou.name + "'");
}
f->regs[d.name] = r++;
}
}
f->nlocals = r;
f->nregs = r;
for (const Stmt& st : pou.body) {
begin_stmt(*f);
if (!compile_stmt(*f, st)) {
return false;
}
}
f->code.push_back(isa::enc_ret());
return true;
}
/**
* @brief 编译一条语句(切片 4:赋值 / IF)
* @param f 当前函数
* @param st 语句 AST
* @return true 成功;falseerr 已写)
*/
bool compile_stmt(FuncCtx& f, const Stmt& st) {
if (st.kind == StmtKind::If) {
return compile_if(f, st);
}
if (st.kind == StmtKind::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 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()) {
// 全局 / 外部左值 → 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);
}
/**
* @brief 编译 FB 调用
* @details 实参求值到临时 → STORE_GLOBAL 到实例字段;
* 内建 TON/TOF/CTU → CAL_* <实例偏移>
* 用户 FB → compile_fb_inline 内联展开
* @param f 当前函数
* @param st FbCall 语句 AST
* @return true 成功;falseerr 已写)
*/
bool compile_fb_call(FuncCtx& f, const Stmt& st) {
const InstFields* inst = instance_of(f.pou_name, st.instance);
if (inst == nullptr) {
return fail("no instance '" + st.instance + "' in '" + f.pou_name + "'");
}
for (const FbArg& a : st.args) {
const auto it = inst->field_addr.find(a.name);
if (it == inst->field_addr.end()) {
return fail("unknown input '" + a.name + "' for '" + st.instance + "'");
}
const uint8_t t = alloc_temp(f);
if (!compile_expr(f, *a.value, t)) {
return false;
}
f.code.push_back(isa::enc_slot(isa::Op::STORE_GLOBAL, t, it->second));
}
if (inst->type_name == "ton" || inst->type_name == "tof" ||
inst->type_name == "ctu") {
const isa::Op op = inst->type_name == "ton" ? isa::Op::CAL_TON
: inst->type_name == "tof" ? isa::Op::CAL_TOF
: isa::Op::CAL_CTU;
f.code.push_back(isa::enc_slot(op, 0, inst->base));
return true;
}
const POU* fb = find_pou(inst->type_name);
if (fb == nullptr) {
return fail("no FB type '" + inst->type_name + "'");
}
return compile_fb_inline(f, *fb, *inst);
}
/**
* @brief 内联编译用户 FB 体
* @details 切到实例字段上下文(VarRef/赋值左值映射到字段地址);
* 表达式临时与调用方共用 r8+;字段读写全走 LOAD/STORE_GLOBAL
* @param f 当前函数
* @param fb FB 类型 POU
* @param inst 本实例字段表
* @return true 成功;falseerr 已写)
*/
bool compile_fb_inline(FuncCtx& f, const POU& fb, const InstFields& inst) {
const InstFields* saved = inline_fields_;
inline_fields_ = &inst;
for (const Stmt& s : fb.body) {
begin_stmt(f);
if (!compile_stmt(f, s)) {
return false;
}
}
inline_fields_ = saved;
return true;
}
/**
* @brief 编译 WHILE
* @details L_loop: <cond→t> JF t, L_end <body> JMP L_loop L_end:
* 条件 JF 与回环 JMP 双回填
* @param f 当前函数
* @param st WHILE 语句 AST
* @return true 成功;falseerr 已写)
*/
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 每分支:<cond→t> JF t, L_next → <body> → JMP L_end
* 最后一个分支无 else 时 JF 直接落 L_end(不补 JMP
* @param f 当前函数
* @param st IF 语句 AST
* @return true 成功;falseerr 已写)
*/
bool compile_if(FuncCtx& f, const Stmt& st) {
// 条件链:IF 体 + ELSIF 各体;ELSE 体可选
std::vector<std::pair<const Expr*, const std::vector<Stmt>*>> branches;
branches.push_back({st.cond.get(), &st.body});
for (const IfBranch& b : st.elsifs) {
branches.push_back({b.cond.get(), &b.body});
}
const bool has_else = !st.else_body.empty();
std::vector<size_t> end_jmps; // 各分支尾部 JMP(回填到 L_end)
for (size_t i = 0; i < branches.size(); ++i) {
const uint8_t t = alloc_temp(f);
if (!compile_expr(f, *branches[i].first, t)) {
return false;
}
const size_t jf_idx = f.code.size();
f.code.push_back(isa::enc_jc(isa::Op::JF, t, 0)); // 假 → 下一分支
for (const Stmt& s : *branches[i].second) {
begin_stmt(f);
if (!compile_stmt(f, s)) {
return false;
}
}
if (i + 1 < branches.size() || has_else) {
const size_t jmp_idx = f.code.size();
f.code.push_back(isa::enc_jmp(0)); // 分支尾部 → L_end
end_jmps.push_back(jmp_idx);
}
patch_jump(f, jf_idx, f.code.size()); // 回填 JF 到下一分支起点
}
if (has_else) {
for (const Stmt& s : st.else_body) {
begin_stmt(f);
if (!compile_stmt(f, s)) {
return false;
}
}
}
const size_t end = f.code.size();
for (const size_t j : end_jmps) {
patch_jump(f, j, end);
}
return true;
}
/**
* @brief 赋值左值:帧寄存器直写或 STORE_* 到全局槽
* @param f 当前函数
* @param target 左值名(全局 / 外部)
* @param value 右值表达式
* @return true 成功;falseerr 已写)
*/
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_*io.input 用 LOAD_I);
* NOT → 求值后 NOT rd, tAND/OR → 短路跳转(JF/JT 跳过右侧)
* @param f 当前函数
* @param e 表达式 AST
* @param rd 目标寄存器
* @return true 成功;falseerr 已写)
*/
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 =
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, e.int_value)));
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));
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 + "'");
}
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);
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)) {
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)) {
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;
}
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<uint8_t> 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<uint8_t>(1 + i), args[i]));
}
f.code.push_back(isa::enc_call(static_cast<uint16_t>(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_idPOU 收集序下标)
* @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<int>(i);
}
}
return -1;
}
/**
* @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<int16_t>(
static_cast<int64_t>(target_idx) - (static_cast<int64_t>(idx) + 1));
const isa::Instr w = f.code[idx];
f.code[idx] = isa::pack(isa::op(w), isa::rd(w),
static_cast<uint8_t>(static_cast<uint16_t>(off) & 0xFFu),
static_cast<uint8_t>((static_cast<uint16_t>(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 比较运算
* @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 变量名(小写)
* @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<uint16_t>(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<uint8_t>(has_init ? init : 0);
} else if (s.type_name == "int") {
const uint16_t v = static_cast<uint16_t>(has_init ? init : 0);
data_[cur] = static_cast<uint8_t>(v & 0xFFu);
data_[cur + 1] = static_cast<uint8_t>((v >> 8) & 0xFFu);
} else if (s.type_name == "time") {
const uint64_t v = static_cast<uint64_t>(has_init ? init : 0);
for (int i = 0; i < 8; ++i) {
data_[cur + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFFu);
}
}
cur += w;
}
return true;
}
/**
* @brief 布局 FB 实例块:数据区全局之后,按 POU 收集序 / 实例声明序
* @details 每实例:字段按布局段序对齐排布(BOOL 1 / INT 2 / TIME 8),
* 字段地址 = 实例基址 + 字段偏移;实例块间 8 字节对齐,块内容初值 0
* @return true 成功;falseerr 已写)
*/
bool layout_fb_instances() {
uint32_t cur = align_up(static_cast<uint32_t>(data_.size()), 8);
bool any = false;
for (const LinkResult::PouScope& sc : link_.scopes) {
for (const Symbol& s : sc.syms) {
if (s.kind != SymbolKind::FbInstance) {
continue;
}
const auto lay = sc.fb_instances.find(s.name);
if (lay == sc.fb_instances.end()) {
return fail("no layout for instance '" + s.name + "'");
}
cur = align_up(cur, 8);
InstFields inst;
inst.base = cur;
inst.type_name = s.type_name;
uint32_t off = 0;
for (const FbField& fd : lay->second.fields) {
const uint32_t w = width_of(fd.type);
off = align_up(off, w);
inst.field_addr[fd.name] = cur + off;
off += w;
}
cur += align_up(off, 8);
instances_[sc.name + "/" + s.name] = inst;
any = true;
}
}
// 仅在有实例时把数据区补到实例区终点(无实例则保持全局区原样)
if (any) {
while (data_.size() < cur) {
data_.push_back(0);
}
}
return true;
}
/**
* @brief 按 POU + 实例名查实例字段表
* @param pou POU 名
* @param name 实例名
* @return 实例字段表指针;未找到返回 nullptr
*/
const InstFields* instance_of(const std::string& pou,
const std::string& name) const {
const auto it = instances_.find(pou + "/" + name);
return it == instances_.end() ? nullptr : &it->second;
}
/**
* @brief 查全局声明的初值(AST;gvl 文件顶层段,声明序 = 槽号序)
* @param name 全局名(小写)
* @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;
}
/**
* @brief 取常量表 id(无则追加)
* @param tag 类型标记(BOOL/INT/TIME
* @param value 常量值
* @return const_idu16
*/
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<uint64_t>(value)) {
return static_cast<uint16_t>(i);
}
}
consts_.push_back({tag, static_cast<uint64_t>(value)});
return static_cast<uint16_t>(consts_.size() - 1);
}
/**
* @brief 拼映像:头 + 常量表 + 函数表 + 字节码 + 数据段
* @details 段序:const → funcs → code → fb(空)→ data
* 数据段放全局初值(layout_data 已生成字节)
* @return true 成功;falseerr 已写)
*/
bool assemble_image() {
const uint32_t off_const = isa::kHeaderSize;
const uint32_t off_funcs = off_const +
static_cast<uint32_t>(consts_.size()) * isa::kConstEntrySize;
uint32_t off_code = off_funcs +
static_cast<uint32_t>(funcs_.size()) * isa::kFuncRowSize;
uint32_t code_total = 0;
for (const FuncCtx& f : funcs_) {
code_total += static_cast<uint32_t>(f.code.size()) * 4;
}
const uint32_t off_data = off_code + code_total;
const uint32_t off_end = off_data + static_cast<uint32_t>(data_.size());
std::vector<uint8_t>& 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<uint32_t>(i);
}
}
put_le32(b, 24, entry);
put_le32(b, 28, static_cast<uint32_t>(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
put_le32(b, 44, static_cast<uint32_t>(consts_.size()));
put_le32(b, 48, static_cast<uint32_t>(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_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;
put_le32(b, o, static_cast<uint32_t>(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 + 8, static_cast<uint32_t>(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<uint32_t>(funcs_[i].code.size()) * 4;
}
// 数据段:全局初值字节
for (size_t i = 0; i < data_.size(); ++i) {
b[off_data + i] = data_[i];
}
return true;
}
// ---- 成员 ----
const Project& proj_;
const std::vector<SourceUnit>& units_;
const LinkResult& link_;
std::vector<uint8_t>* image_;
std::string* err_;
std::vector<FuncCtx> funcs_;
std::vector<isa::ConstEntry> consts_;
std::map<std::string, bool> io_input_; // io.input 绑定名(小写)
std::map<std::string, bool> io_output_; // io.output 绑定名(小写)
std::vector<uint32_t> global_offsets_; // 逻辑槽号 → 数据区字节偏移
std::vector<uint8_t> data_; // 数据区(全局初值 + FB 实例块)
std::map<std::string, InstFields> instances_; // "POU/实例名" → 字段地址
const InstFields* inline_fields_ = nullptr; // 内联 FB 体字段上下文(可空)
};
} // 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<SourceUnit>& units,
const LinkResult& link, std::vector<uint8_t>* image,
std::string* err) {
Builder b(proj, units, link, image, err);
return b.run();
}
} // namespace compiler
+54 -4
View File
@@ -10,12 +10,18 @@
#include <string>
#include <vector>
#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 <project.toml>\n"
" 解析工程并打印文件集合与工程哈希(12.3 阶段\n"
std::printf("usage: STCompiler <project.toml> [-o <name>.stb]\n"
" 解析工程并编译(词法 → 语法 → 链接 → 类型 → 寄存器码\n"
" -o <name>.stb 编译并写出映像文件\n"
" 无 -o 只打印文件集合与工程哈希(12.3 阶段)\n"
" --help 打印本帮助\n");
}
}
@@ -31,19 +37,28 @@ 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<std::string> files = compiler::compile_files(proj);
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) {
@@ -52,3 +67,38 @@ int main(int argc, char** argv) {
std::printf("hash: 0x%016llx\n", static_cast<unsigned long long>(hash));
return 0;
}
// 完整管线:读 .st → 链接 → 类型检查 → 寄存器码
std::vector<compiler::SourceUnit> 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<uint8_t> 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;
}
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;
}
+11
View File
@@ -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)
+709
View File
@@ -0,0 +1,709 @@
/**
* @file codegen_test.cpp
* @brief 寄存器码测试(12.8 切片 1):用例 01/02 出映像
* @author
* @date 2026-08-21
*/
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <string>
#include <vector>
#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<uint8_t>* 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<SourceUnit> 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);
}
// 从临时工程(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<uint8_t>* 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<SourceUnit> 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() {
std::vector<uint8_t> 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 == 8); // 帧基址(调用约定区)
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. 用例 02BOOL 赋值(TRUE / FALSE → LOADK----
static bool test_case02() {
std::vector<uint8_t> 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 == 10); // a, br8 起)
CHECK(r.code_len == 3);
// 从字节码段取指令(按函数行 code_offset 定位)
const uint8_t* base = v.code_bytes();
char buf[64];
isa::disasm(reinterpret_cast<const uint32_t*>(base)[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r8, 0") == 0); // a := TRUE(常量 0
isa::disasm(reinterpret_cast<const uint32_t*>(base)[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r9, 1") == 0); // b := FALSE(常量 1
isa::disasm(reinterpret_cast<const uint32_t*>(base)[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "RET") == 0);
// 常量表:0 = BOOL 11 = 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;
}
// ---- 3. 用例 09GVL + VAR_EXTERNAL(全局读取 LOAD_GLOBAL + 初值数据段)----
static bool test_case09() {
std::vector<uint8_t> 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 == 9); // r8 起:仅局部 x
CHECK(r.code_len == 2);
char buf[64];
const uint8_t* base = v.code_bytes();
isa::disasm(reinterpret_cast<const uint32_t*>(base)[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 0") == 0); // x := G1(槽 0 偏移 0
isa::disasm(reinterpret_cast<const uint32_t*>(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<uint8_t> 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 == 10); // r8 起:q + 1 临时
char buf[64];
const uint8_t* base = v.code_bytes();
isa::disasm(reinterpret_cast<const uint32_t*>(base)[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_I r8, 0") == 0); // q := I0_0io.input → LOAD_I
isa::disasm(reinterpret_cast<const uint32_t*>(base)[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r9, r8") == 0); // 临时 ← q
isa::disasm(reinterpret_cast<const uint32_t*>(base)[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_Q r9, 1") == 0); // Q0_0 := 临时(io.output → STORE_Q
isa::disasm(reinterpret_cast<const uint32_t*>(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<uint8_t> 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<const uint32_t*>(v.code_bytes())[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 2") == 0); // x := IINT 偏移 2
return true;
}
// ---- 6. 写 io.input → codegen error ----
static bool test_write_input() {
std::vector<uint8_t> 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;
}
// ---- 7. 用例 03:短路 AND / OR 必须编跳转 ----
static bool test_case03() {
std::vector<uint8_t> 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 == 12); // r8 起: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<const uint32_t*>(base);
// x := a AND bMOVE r2,r0 / JF r2,+2 / MOVE r3,r1 / MOVE r2,r3
isa::disasm(ins[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r10, r8") == 0);
isa::disasm(ins[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "JF r10, +2") == 0);
isa::disasm(ins[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r11, r9") == 0);
isa::disasm(ins[3], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r10, r11") == 0);
// x := a OR bMOVE r2,r0 / JT r2,+2 / MOVE r3,r1 / MOVE r2,r3
isa::disasm(ins[4], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r10, r8") == 0);
isa::disasm(ins[5], buf, sizeof buf);
CHECK(std::strcmp(buf, "JT r10, +2") == 0);
isa::disasm(ins[6], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r11, r9") == 0);
isa::disasm(ins[7], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r10, r11") == 0);
isa::disasm(ins[8], buf, sizeof buf);
CHECK(std::strcmp(buf, "RET") == 0);
return true;
}
// ---- 8. NOT ----
static bool test_not() {
std::vector<uint8_t> 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 == 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<const uint32_t*>(base);
isa::disasm(ins[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r10, r8") == 0); // 临时 ← a
isa::disasm(ins[1], buf, sizeof buf);
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;
}
// ---- 9. 用例 04IF / ELSIF / ELSECMP + JF + JMP 回填)----
static bool test_case04() {
std::vector<uint8_t> img;
std::string err;
CHECK(compile_case("04_if_elsif_else", &img, &err));
const isa::ImageView v = isa::ImageView::from(img);
CHECK(v.ok());
const isa::FuncRow r = v.func_row(0);
CHECK(r.nregs == 13); // r8 起:sel out + 3 临时
CHECK(r.code_len == 14);
char buf[64];
const uint8_t* base = v.code_bytes();
const uint32_t* ins = reinterpret_cast<const uint32_t*>(base);
// IF sel = 0 THENsel→r3、0→r4、CMP_EQ r2(常量序:0、10、1、20、30
isa::disasm(ins[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r11, r8") == 0);
isa::disasm(ins[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r12, 0") == 0);
isa::disasm(ins[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "CMP_EQ r10, r11, r12") == 0);
isa::disasm(ins[3], buf, sizeof buf);
CHECK(std::strcmp(buf, "JF r10, +2") == 0); // 假 → elsif[6]
isa::disasm(ins[4], buf, sizeof buf);
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 r11, r8") == 0);
isa::disasm(ins[7], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r12, 2") == 0);
isa::disasm(ins[8], buf, sizeof buf);
CHECK(std::strcmp(buf, "CMP_EQ r10, r11, r12") == 0);
isa::disasm(ins[9], buf, sizeof buf);
CHECK(std::strcmp(buf, "JF r10, +2") == 0); // 假 → else[12]
isa::disasm(ins[10], buf, sizeof buf);
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 r9, 4") == 0);
isa::disasm(ins[13], buf, sizeof buf);
CHECK(std::strcmp(buf, "RET") == 0);
return true;
}
// ---- 10. 用例 05WHILE 回环(JF 出口 + JMP 回填)----
static bool test_case05() {
std::vector<uint8_t> 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 == 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<const uint32_t*>(base);
isa::disasm(ins[0], buf, sizeof buf);
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 r10, r8") == 0);
isa::disasm(ins[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r11, 1") == 0); // 常量 1 = 10
isa::disasm(ins[3], buf, sizeof buf);
CHECK(std::strcmp(buf, "CMP_LT r9, r10, r11") == 0);
isa::disasm(ins[4], buf, sizeof buf);
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 r9, r8") == 0);
isa::disasm(ins[6], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r10, 2") == 0); // 常量 2 = 1
isa::disasm(ins[7], buf, sizeof buf);
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);
CHECK(std::strcmp(buf, "RET") == 0);
return true;
}
// ---- 11. 用例 07:四则 + 比较 ----
static bool test_case07() {
std::vector<uint8_t> 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 == 14); // r8 起:a b c eq + 2 临时
char buf[64];
const uint8_t* base = v.code_bytes();
const uint32_t* ins = reinterpret_cast<const uint32_t*>(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 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;
}
// ---- 12. 用例 08TIME 字面量进常量表 ----
static bool test_case08() {
std::vector<uint8_t> 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 == 10); // r8 起
char buf[64];
const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes());
isa::disasm(ins[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r8, 0") == 0);
isa::disasm(ins[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r9, 1") == 0);
return true;
}
// ---- 13. 用例 13FUNCTION + CALL(调用约定 r0 结果 / r1.. 参数)----
static bool test_case13() {
std::vector<uint8_t> 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();
// Addfn_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<const uint32_t*>(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);
// MAINfn_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<const uint32_t*>(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;
}
// ---- 14. 用例 15:用户 FB 内联展开 ----
static bool test_case15() {
std::vector<uint8_t> img;
std::string err;
CHECK(compile_case("15_fb_instance", &img, &err));
const isa::ImageView v = isa::ImageView::from(img);
CHECK(v.ok());
CHECK(v.header().n_funcs == 2); // FB 占位 + MAIN
CHECK(v.header().entry_fn_id == 1);
CHECK(v.data_len() == 8); // 实例 3Bstart@0 stop@1 q@2),8 对齐
const isa::FuncRow fm = v.func_row(1);
CHECK(fm.nregs == 12);
CHECK(fm.code_len == 12);
const uint32_t* im = reinterpret_cast<const uint32_t*>(v.code_bytes()) + fm.code_offset / 4;
char buf[64];
// 实参写字段:start := TRUE / stop := FALSE
isa::disasm(im[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r9, 0") == 0);
isa::disasm(im[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 0") == 0);
isa::disasm(im[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r10, 1") == 0);
isa::disasm(im[3], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_GLOBAL r10, 1") == 0);
// 内联体:Q := start AND NOT stop(短路跳转)
isa::disasm(im[4], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 0") == 0);
isa::disasm(im[5], buf, sizeof buf);
CHECK(std::strcmp(buf, "JF r9, +3") == 0);
isa::disasm(im[9], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 2") == 0); // Q 字段
// q := starter.Q
isa::disasm(im[10], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 2") == 0);
return true;
}
// ---- 15. 用例 17TONCAL_TON + 字段偏移)----
static bool test_case17() {
std::vector<uint8_t> img;
std::string err;
CHECK(compile_case("17_ton", &img, &err));
const isa::ImageView v = isa::ImageView::from(img);
CHECK(v.ok());
CHECK(v.data_len() == 32); // in@0 pt@8 q@16 et@24
const isa::FuncRow r = v.func_row(0);
CHECK(r.nregs == 11);
const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes());
char buf[64];
isa::disasm(ins[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r9, 0") == 0);
isa::disasm(ins[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 0") == 0); // in
isa::disasm(ins[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r10, 1") == 0);
isa::disasm(ins[3], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_GLOBAL r10, 8") == 0); // ptTIME 8 对齐)
isa::disasm(ins[4], buf, sizeof buf);
CHECK(std::strcmp(buf, "CAL_TON 0") == 0);
isa::disasm(ins[5], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 16") == 0); // t.Q
return true;
}
// ---- 16. 用例 18TOF / CTU ----
static bool test_case18() {
std::vector<uint8_t> img;
std::string err;
CHECK(compile_case("18_tof_ctu", &img, &err));
const isa::ImageView v = isa::ImageView::from(img);
CHECK(v.ok());
CHECK(v.data_len() == 40); // tf 块 0..31in@0 pt@8 q@16 et@24)、c 块 32..
const isa::FuncRow r = v.func_row(0);
CHECK(r.nregs == 14);
const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes());
char buf[64];
isa::disasm(ins[4], buf, sizeof buf);
CHECK(std::strcmp(buf, "CAL_TOF 0") == 0);
isa::disasm(ins[12], buf, sizeof buf);
CHECK(std::strcmp(buf, "CAL_CTU 32") == 0);
isa::disasm(ins[13], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 36") == 0); // c.Q
isa::disasm(ins[14], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r10, 38") == 0); // c.CVINT 2 对齐)
return true;
}
// ---- 17. line1 全链路(MAIN + Motor FB + I/O + 全局)----
static bool test_line1() {
std::vector<uint8_t> img;
std::string err;
using namespace compiler;
const std::string toml = std::string(REPO_ROOT) + "/examples/line1/project.toml";
Project p;
CHECK(parse_project(toml, &p, &err));
std::vector<SourceUnit> units;
for (const std::string& f : compile_files(p)) {
SourceUnit u;
CHECK(load_unit(p.base_dir + "/" + f, &u, &err));
units.push_back(std::move(u));
}
LinkResult link;
CHECK(link_project(p, units, &link, &err));
CHECK(check_project(p, units, link, &err));
CHECK(codegen_project(p, units, link, &img, &err));
const isa::ImageView v = isa::ImageView::from(img);
CHECK(v.ok());
CHECK(v.header().n_globals == 4);
CHECK(v.header().n_funcs == 2);
CHECK(v.header().entry_fn_id == 1);
CHECK(v.data_len() == 16); // 全局 4B + 实例块 8..108 对齐)
const isa::FuncRow fm = v.func_row(1);
CHECK(fm.nregs == 13);
CHECK(fm.code_len == 17);
const uint32_t* im = reinterpret_cast<const uint32_t*>(v.code_bytes()) + fm.code_offset / 4;
char buf[64];
isa::disasm(im[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_I r8, 1") == 0); // I0_0io.input
isa::disasm(im[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_GLOBAL r8, 8") == 0); // starter.start
isa::disasm(im[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 2") == 0); // I0_1
isa::disasm(im[3], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 9") == 0); // starter.stop
isa::disasm(im[13], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_GLOBAL r8, 10") == 0); // starter.Q
isa::disasm(im[14], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 10") == 0); // starter.Q 读回
isa::disasm(im[15], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_Q r8, 3") == 0); // Q0_0io.output
isa::disasm(im[16], buf, sizeof buf);
CHECK(std::strcmp(buf, "RET") == 0);
return true;
}
int main() {
if (!test_case01()) return 1;
if (!test_case02()) return 1;
if (!test_case09()) return 1;
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;
if (!test_case04()) return 1;
if (!test_case05()) return 1;
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;
}