Files
Interpreter/Doc/compiler/符号表与链接.md
T

6.2 KiB
Raw 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 实例布局:按类型算字段清单(输入/输出/内部;内建 FB 15 条字段来自 machine.toml [[fb]] 表,编译期按 1/2/4/8 对齐计算偏移)

不做(第一版)

  • 寄存器分配(12.8)、类型检查(12.7)
  • 多 GVL 文件、I/Q/M 与全局分槽(v1 单一数据区,见下)

数据区定址(V2 定案)

  • 槽表两级:全部全局(含 I/Q/M)与 FB 实例统一编槽号(0..n-1);槽表条目(8B:addr:u32 + 预留)指向值区字节偏移;值区按变量类型紧凑变宽(1/2/4/8 对齐)
  • 全局按 globals.st 声明顺序编槽;FB 实例 = 1 个槽表条目,字段走 LOAD_OFF / STORE_OFF 实例槽 + 编译期算死的字段偏移
  • 映像头 n_globals = 全局声明总数;n_i / n_q / n_m = 0(第一版不拆,见 初步计划.md 5.1
  • 访问统一 LOAD / STORE+ LOAD_OFF / STORE_OFF),无 I/Q/M 专用操作码
  • 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(内部),按声明段序排
  • 内建 FB 15 条(compiler/machine.toml [[fb]] 表驱动,fb_id 0..14):
fb_id FB 字段(名 → 类型)
0 TON in BOOL, pt TIME, q BOOL, et TIME
1 TOF 同 TON
2 TP 同 TON
3 CTU cu BOOL, r BOOL, pv INT, q BOOL, cv INT
4 DCTU cu BOOL, r BOOL, pv DINT, q BOOL, cv DINT
5 CTD cd BOOL, ld BOOL, pv INT, q BOOL, cv INT
6 DCTD cd BOOL, ld BOOL, pv DINT, q BOOL, cv DINT
7 CTUD cu BOOL, cd BOOL, r BOOL, lu BOOL, pv INT, qu BOOL, qd BOOL, cv INT
8 DCTUD cu BOOL, cd BOOL, r BOOL, lu BOOL, pv DINT, qu BOOL, qd BOOL, cv DINT
9 R_TRIG clk BOOL, q BOOL
10 F_TRIG clk BOOL, q BOOL
11 SR S1 BOOL, R BOOL, Q1 BOOL
12 RS SET BOOL, RESET1 BOOL, Q1 BOOL
13 PWM in BOOL, pt TIME, duty REAL, q BOOL, et TIME
14 RTC enable BOOL, date DATE, tod TOD
  • 实例布局 = 类型字段表的一份拷贝(VAR_TEMP 不是字段,不持久,编译期映射为帧寄存器 + 每次调用初值装载);字段偏移编译期按 1/2/4/8 对齐计算(字段类型决定宽度),实例 = 槽表 1 条

错误

稳定前缀 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 + 文档