Files
Interpreter/Doc/compiler/寄存器码.md
T
Admin 0a36e36b16 12.8 切片 6:FUNCTION 与 CALL,冻结调用约定。
- 调用约定(冻结进 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
2026-08-21 11:48:33 +08:00

6.4 KiB
Raw Blame History

寄存器码(12.8

compiler 模块的代码生成。输入:Project + UnitAST+ LinkResult;输出:.stb 映像字节(std::vector<uint8_t>)。全工程定案见 初步计划.md 12.8,指令/映像布局见 指令与映像.md

做 / 不做

  • 寄存器分配:局部/变量固定寄存器,表达式临时值往后编号,nregs 写进函数头
  • 跳转先留洞、后回填;AND / OR 必须编成跳转(短路),禁止两边都算
  • 常量表(LOADK)、全局/实例槽(LOAD_GLOBAL / STORE_GLOBAL 等)
  • CALL fn_id 有界帧;用户 FB 调用内联展开;内建 CAL_TON / CAL_TOF / CAL_CTU
  • 拼装映像:函数表 / 常量表 / 数据段 / 头(cycle_limitdt_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.QLOAD_GLOBAL rd, <实例基址+字段偏移>(编译期算死);I/Q/M 用 LOAD_I / STORE_Q / LOAD_M / STORE_M 同槽号
  • 函数帧寄存器:FUNCTION 结果固定 r0VAR_INPUT 从 r1 起按声明序,VAR 续后;PROGRAM / FB 变量从 r0 起按声明序

调用约定(v1 冻结,VM 12.9 按此实现)

约定
结果寄存器 r0FUNCTION 结果写入处)
参数寄存器 r1..r7(最多 7 个输入,按声明序)
变量 / 临时 全部从 r8 起(调用约定区不受覆盖)
CALL VM 复制当前帧 r0..r7 → 新帧 r0..r7
RET VM 复制当前帧 r0..r7 → 调用方帧 r0..r7r0=结果,r1..r7 原样返回)
函数输入 函数体内只读(写输入 → codegen error
  • 调用点:实参求值到 r8+ 临时 → MOVE r(1+i), tCALL fn_idMOVE rd, r0
  • 所有 POU 的变量/临时统一 r8 起,故 r1..r7 在调用点可安全覆盖

寄存器分配

  • 局部变量固定占用前段(0..k-1
  • 表达式临时从 k 起,语句内递增、语句结束复用基址(不分析生命周期)
  • nregs = 全函数临时峰值 + 1

表达式与语句的指令模式

字面量     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 恒 0enc_jmp 用),SUB rd, r0, t 实现取负

用户 FB 调用内联(v1 定案)

FB 体共享会与「绝对字段地址」冲突(不同实例基址不同),v1 选择调用点内联展开

  • 调用点:实参写入该实例字段 → 内联该 FB 的函数体(字段地址按本实例基址算死)→ 字段读取自然可用
  • 代价:字节码随调用点数膨胀;v1 规模可接受;以后可升级为「基址寄存器寻址」指令
  • FB 无递归、无动态实例,内联安全

映像拼装

函数表:收集序 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
  • isaenc_* 编码指令、ImageView 校验回读
  • 工程哈希用 12.3 的 compute_project_hash

错误

稳定前缀 codegen error

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/MLOAD_I / STORE_Q / LOAD_M / STORE_M / LOAD_GLOBAL / STORE_GLOBAL(用例 9
  3. NOT + 短路 AND / OR(用例 3,断言产物含跳转)
  4. CMP_* + JMP / JT / JFIF(用例 4
  5. WHILE(用例 5cycle_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。