- isa:CAL_* 操作码连续占 24..31(CALL/RET 后移为 32/33,kOpCount=34);disasm 支持 5 个新操作码 - 词法/语法/链接:新 8 个关键字与冻结布局(TP=in/pt/q/et、CTD=cd/ld/pv/q/cv、 CTUD=cu/cd/r/lu/pv/qu/qd/cv、R_TRIG/F_TRIG=clk/q) - VM:do_cal 全 8 个语义(TP 脉冲、CTD 递减、CTUD 双向+装载、R_TRIG 上升沿、F_TRIG 下降沿); edge_prev_ 每槽 2 字节存边沿上次输入;exec_one 分发补 5 个新操作码 - 验证:TP 30ms 脉冲 2 周期、CTD 装载+递减到 q=1、CTUD cu/cd 双向、R/F_TRIG 交替; ctest 11/11 全绿 - 文档:指令与映像/词法/初步计划/符号表与链接/指令执行/扫描周期/使用说明 同步
5.3 KiB
5.3 KiB
符号表与链接(12.6)
compiler 模块的符号收集与链接。输入:Project(toml)+ 各 .st 文件的 Unit(AST,见 语法.md);输出:链接结果(全局槽表、POU 符号表、函数表、FB 实例布局)。全工程定案见 初步计划.md 12.6。
做 / 不做
做
- 先扫完全部文件再解析引用(禁止边解析边执行)
- 全局收集:仅
gvl.file顶层VAR_GLOBAL,声明顺序 = 全局槽顺序 VAR_EXTERNAL接已有槽,种类/类型一致,不是新变量FUNCTION标无状态;FUNCTION_BLOCK必须先有实例才能调用CALL目标解析成fn_id(立即数),无函数指针- 校验:同名全局、未声明
io.var、重复 POU 名、未声明 FB 实例、函数循环调用 → 失败 - FB 实例布局:按类型算字段清单(输入/输出/内部/内建 8 个 FB 固定字段(12.11 扩充))
不做(第一版)
- 寄存器分配(12.8)、类型检查(12.7)
- 多 GVL 文件、I/Q/M 与全局分槽(v1 单一数据区,见下)
数据区定址(v1 定案)
- 单一数据区:全部全局(含 I/Q/M)按
globals.st声明顺序编槽号 0..n-1 - 映像头
n_globals= 声明总数;n_i/n_q/n_m= 0(第一版不拆,见初步计划.md5.1) LOAD_I/STORE_Q/LOAD_GLOBAL/STORE_GLOBAL操作码不同,槽号指向同一数据区- io 绑定(toml)只负责 channel/bit → 槽号映射(sidecar),不创造变量
符号表一行
enum class SymbolKind { Global, External, Local, Input, Output, Pou, FbInstance, Const };
struct Symbol {
SymbolKind kind;
std::string name; // 小写
std::string type_name; // bool / int / time / fb 类型名(小写)
uint32_t address; // 全局槽号(Global/External);POU 局部暂 0(12.8 分配)
std::string source_file;
uint32_t line, col;
};
kind 与 12.1 冻结表一致:global / external / local / input / output / pou / fb_instance / const(const 本期未用,保留)。
链接流程
1. 收集导出
遍历全部文件:
- 顶层 globals:仅 gvl.file 允许;同名 → duplicate global;声明顺序编槽号
- POU 导出:program / function / fb 名登记;重复 → duplicate POU
2. 逐 POU 建局部符号
VAR / VAR_INPUT / VAR_OUTPUT → local / input / output
VAR_EXTERNAL → 查全局表,必须存在、类型一致 → External(同一槽)
POU 内 VAR_GLOBAL → error(仅 gvl 顶层允许)
3. 语句引用解析
- FB 调用 instance(...) → 局部表查实例,类型必须是 FB → 否则 undeclared FB instance
- 字段读 fb.field → 实例类型有该字段(用户 FB 的 input/output;内建固定字段)
- VarRef / 赋值左值 → 局部表或全局表查得到 → 否则 undeclared identifier
- 函数调用 fn(...) → POU 表查得到且是 FUNCTION → 记依赖边;FUNCTION 不得调用 PROGRAM/FB
4. 循环调用检测
函数依赖图 DFS 找环 → recursive call(用例 19)
5. io 校验
io.var 必须在全局表 → 否则 io.var not in GVL(用例 12)
6. 输出
全局表 / POU 符号表 / 函数表(fn_id 按收集序)/ FB 实例布局
FB 实例布局
- 用户 FB:字段 = 全部
VAR_INPUT+VAR_OUTPUT+VAR(内部),按声明段序排 - 内建(冻结,isa 规格;12.11 扩为 8 个):
| FB | 字段(名 → 类型) |
|---|---|
| TON | in BOOL, pt TIME, q BOOL, et TIME |
| TOF | in BOOL, pt TIME, q BOOL, et TIME |
| TP | in BOOL, pt TIME, q BOOL, et TIME |
| CTU | cu BOOL, r BOOL, pv INT, q BOOL, cv INT |
| CTD | cd BOOL, ld BOOL, pv INT, q BOOL, cv INT |
| CTUD | cu BOOL, cd BOOL, r BOOL, lu BOOL, pv INT, qu BOOL, qd BOOL, cv INT |
| R_TRIG | clk BOOL, q BOOL |
| F_TRIG | clk BOOL, q BOOL |
- 实例布局 = 类型字段表的一份拷贝;字段偏移 = 字段序号 × 8(8 字节定宽槽)
错误
稳定前缀 link error:
link error: duplicate global 'g1' (globals.st, line 4, col 1)
link error: VAR_GLOBAL only allowed in gvl file (other.st, line 2, col 1)
link error: io.var 'Nope' not in GVL (project.toml)
link error: undeclared FB instance 'starter' (main.st, line 4, col 5)
link error: recursive call involving 'a' (main.st, line 2, col 4)
link error: undeclared identifier 'x' (main.st, line 3, col 3)
完成标准
- 用例 9(同一槽)、10(非 GVL 写全局)、11(同名全局)、12(io.var 未声明)、16(未声明实例)、19(循环调用)按表变绿
- 符号表能打印
name kind type address file - 全部构建 +
ctest无回归
执行计划(单步确认)
Linker.h(新建compiler/include/compiler/Linker.h):Symbol/SymbolKind+ 链接结果结构 +link_project声明Linker.cpp前半:收集导出(gvl 校验 / 全局定槽 / POU 登记)+ 逐 POU 局部符号Linker.cpp后半:引用解析(FB 实例 / 字段 / 变量 / 函数)+ 循环检测 + io 校验linker_test(tests/src/linker_test.cpp+ CMake):用例 09/10/11/12/16/19 + 符号表打印断言- 验证:
cmake --build+ctest(新增linker_links后 7/7 全绿) - 提交:
Linker.h/cpp+linker_test+ 文档