Files
Interpreter/Doc/compiler/符号表与链接.md
Admin 26256a36b9 扩充内置功能块为 8 个:TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG。
- 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 全绿
- 文档:指令与映像/词法/初步计划/符号表与链接/指令执行/扫描周期/使用说明 同步
2026-08-21 20:38:27 +08:00

5.3 KiB
Raw Permalink Blame History

符号表与链接(12.6

compiler 模块的符号收集与链接。输入:Projecttoml+ 各 .st 文件的 UnitAST,见 语法.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(第一版不拆,见 初步计划.md 5.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 局部暂 012.8 分配)
    std::string source_file;
    uint32_t line, col;
};

kind 与 12.1 冻结表一致:global / external / local / input / output / pou / fb_instance / constconst 本期未用,保留)。

链接流程

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)

完成标准

  1. 用例 9(同一槽)、10(非 GVL 写全局)、11(同名全局)、12(io.var 未声明)、16(未声明实例)、19(循环调用)按表变绿
  2. 符号表能打印 name kind type address file
  3. 全部构建 + ctest 无回归

执行计划(单步确认)

  1. Linker.h(新建 compiler/include/compiler/Linker.hSymbol/SymbolKind + 链接结果结构 + link_project 声明
  2. Linker.cpp 前半:收集导出(gvl 校验 / 全局定槽 / POU 登记)+ 逐 POU 局部符号
  3. Linker.cpp 后半:引用解析(FB 实例 / 字段 / 变量 / 函数)+ 循环检测 + io 校验
  4. linker_testtests/src/linker_test.cpp + CMake:用例 09/10/11/12/16/19 + 符号表打印断言
  5. 验证cmake --build + ctest(新增 linker_links 后 7/7 全绿)
  6. 提交Linker.h/cpp + linker_test + 文档