- 调用约定(冻结进 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
6.4 KiB
6.4 KiB
寄存器码(12.8)
compiler 模块的代码生成。输入:Project + Unit(AST)+ 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_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
表达式与语句的指令模式
字面量 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 - t,r0 恒 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 无递归、无动态实例,内联安全
映像拼装
函数表:收集序 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:
codegen error: register overflow (>256) in function 'f'
codegen error: constant table overflow
codegen error: slot out of range
完成标准
line1能写出映像(函数字节码 + 全局/FB 槽 + 工程哈希),ImageView校验通过- 跳转回填无悬空(disasm 抽查)
- 用例 1~9、13、15、17、18、20 编译出映像;负例(10/11/12/14/16/19)仍在前层被拒
- 全部构建 +
ctest无回归
执行计划(8 片,每片验证编译产物)
Codegen.h(新建)+Codegen.cpp骨架:帧/槽/常量表/指令缓冲/回填基础设施;MOVELOADKRET→ 用例 1、2 出映像- 全局与 I/Q/M:
LOAD_I/STORE_Q/LOAD_M/STORE_M/LOAD_GLOBAL/STORE_GLOBAL(用例 9) NOT+ 短路AND/OR(用例 3,断言产物含跳转)CMP_*+JMP/JT/JF→IF(用例 4)WHILE(用例 5;cycle_limit进映像头)ADD/SUB/MUL/DIV+ TIME 常量(用例 7、8)CALL fn_id+ 有界帧(用例 13)- FB 实例块 + 字段偏移;内联用户 FB;
CAL_TON/CAL_TOF/CAL_CTU(用例 15、17、18、20 line1 全链路)
每片配 codegen_test 断言(nregs / 指令序列 disasm / 回填 / 常量表 / 数据区);最终 ctest 9/9。