10 KiB
10 KiB
指令执行流程(V2)
vm 模块的指令执行语义:取指、译码、执行、故障。规范以 指令与映像.md 为准,本文描述执行器怎么跑一条指令。执行模型见 初步计划.md 第 2 节。
执行模型
加载(Machine::create):解析 .stb(vm 自读实现)→ 型号匹配(内建 STATOR2)→ SHA-256 校验
任一不符 → 直接拒绝(不创建机器)
值区(data_)取映像值段的写副本(周期内 FB 字段/全局可变)
边沿缓冲 edge_prev_(每槽 2 字节,跨周期保留)
栈区预分配 max_stack 字节(栈方案 C,编译期算出)
每个扫描周期(run_cycle):
1. cycle_count = 0;pc = 0,从入口函数(MAIN)逐条执行
2. 执行到 MAIN 的 RET → 周期结束
3. 周期末清调用栈(保留栈底 MAIN 帧,sp 回退到 MAIN 帧顶)
4. (I 采样 / Q 写回由 executor 按 sidecar 做,VM 只管值区)
5. 定时器在 CAL 指令执行时用映像 dt_ms 推进;RTC 用累计周期数 × dt_ms
- pc:当前指令在函数字节码段内的字节下标(从 0 起)
- 取指:函数表
func_row(fn_id)→code_offset+code_len(字节)→ pc 越界 →BadOp;instr_len按前缀定长(4/8/16/32B)搬字节 - 译码:前缀分区 +
[func:4|空:4]字节1;op按前缀分区查名;寄存器越界按uses_*直推(RRR/CC/RR 等)→BadOp - 周期计数:每条指令执行后
cycle_count++;超过映像头cycle_limit→CycleLimit(周期中止) - pc 先指向下一条(字节);跳转指令再
pc += off(off 相对下一条,与编译端 patch_jump 一致)
帧与调用栈(栈方案 C)
// 栈区(stack_,预分配 max_stack 字节):
// [MAIN 帧][帧2][帧3]... 从低地址起连续压栈
// 每帧 = 12B 帧头 + nregs×8 寄存器文件
// 帧头:hdr[0]=fn_id hdr[1]=ret_pc(字节) hdr[2]=ret_fn_id
- 调用栈深度上限 64(
frames_.size() >= 64时 CALL →StackOverflow);同时sp_ + 帧大小 > max_stack→StackOverflow(双检查) - MAIN 帧跨周期保留(PROGRAM 变量状态持久);周期结束只清调用栈(保留栈底 MAIN 帧)
- 帧寄存器初值 0
CALL / RET(调用约定)
CALL fn_id:
1. 新帧:regs 大小 = callee 的 nregs,全部 0
2. 复制当前帧 r0..r7 → 新帧 r0..r7(实参已在调用点 MOVE 进 r1..r7)——全宽复制
3. 新帧 ret_pc = 当前 pc(已指向 CALL 下一条,字节)、ret_fn_id = 当前函数
4. 压栈,pc = 0,fn_id = callee
RET:
1. 若当前是 MAIN(栈深 1)→ 周期结束(ended_ = true)
2. 否则:复制当前帧 r0..r7 → 调用方帧 r0..r7(r0 = 结果,r1..r7 原样返回)
3. 弹栈,pc = ret_pc,fn_id = ret_fn_id
值表示(func 位段驱动)
| func | 语义 | 宽度 | 读值扩展 | 类型 |
|---|---|---|---|---|
| 0 | 无符号 | 1B | 高位清 0 | BOOL/BYTE 等 |
| 1 | 有符号 | 1B | 符号扩展 | SINT |
| 2 | 无符号 | 2B | 高位清 0 | WORD/UINT |
| 3 | 有符号 | 2B | 符号扩展 | INT |
| 4 | 无符号 | 4B | 高位清 0 | DWORD/UDINT |
| 5 | 有符号 | 4B | 符号扩展 | DINT |
| 6 | 无符号 | 8B | 原样 | LWORD/ULINT |
| 7 | 有符号 | 8B | 原样 | LINT |
| 8 | F32 | 4B | 位模式(int64 低 32 位) | REAL |
| 9 | F64 | 8B | 位模式原样 | LREAL |
- 寄存器(int64)统一承载:窄类型符号扩展/清 0 后,全宽 int64 比较等价于各类型自身比较;类型语义由编译期类型检查保证,VM 只按 func 解释
- TIME/DATE/TOD/DT 走 func 6/7(64 位整数值)
数据区访问(槽表两级)
槽号 slot(指令操作数,u32)→ 槽表条目(8B:addr:u32 + 预留:u32)→ 值区字节偏移 addr
LOAD rd, slot → rd = 读 value[addr .. addr+宽)(按 func 宽度 + 扩展)
STORE slot, rs → 写 value[addr .. addr+宽) = rs(按 func 宽度截断)
LOAD_OFF rd, slot, off → 读 value[addr+off .. ](FB 字段访问,绝对槽)
STORE_OFF slot, off, rs → 写 value[addr+off .. ](FB 字段访问,绝对槽)
LOAD_PTR rd, rs, off → 读 value[regs[rs] + off .. ](基址寻址:rs = 实例指针,FB 共享体函数体内)
STORE_PTR rd, rs, off → 写 value[regs[rs] + off .. ](基址寻址)
槽表条目数 = n_slots;槽号越界(slot >= n_slots)或地址 + 宽度越出值段 → BadSlot。
指令执行表
| 指令 | 流程 |
|---|---|
MOVE rd, rs |
reg[rd] = reg[rs](func 0..3 限宽 1/2/4/8B,高位清 0) |
LOADK rd, cid |
查常量表 const_entry(cid)(8B 原始值):按 func 读低 w 字节 + 扩展(同 read_value;F32/F64 位模式) |
NOT rd, rs |
逻辑非:reg[rd] = (reg[rs] == 0) ? 1 : 0(恒产 0/1) |
AND/OR/XOR/NAND/NOR/XNOR rd, ra, rb |
位串逐位(64 位整域);BOOL 输入 0/1 下与逻辑等价 |
ADD/SUB/MUL/DIV rd, ra, rb |
按 func:整数饱和(__int128 中间量)、无符号、F32/F64 IEEE |
CMP_EQ/NE/LT/LE/GT/GE rd, ra, rb |
按 func 符号性比较(浮点 IEEE)→ 1 / 0 |
NEG rd, rs |
func 8/9 浮点取负(位模式),整数取负 |
JMP off |
pc += off |
JT rd, off / JF rd, off |
reg[rd] != 0(JT)/ == 0(JF)时 pc += off |
LOAD / STORE |
见数据区访问(槽表两级) |
LOAD_OFF / STORE_OFF |
见数据区访问(FB 字段,绝对实例槽) |
LOAD_PTR / STORE_PTR |
见数据区访问(基址寻址:rs 寄存器 = 值区地址 + 偏移;FB 共享体) |
CAL fb_id, slot |
见内置 FB(15 条) |
CALL fn_id / RET |
见帧与调用栈 |
NOP |
空转 |
整数算术(除法 A:饱和 + 截断)
整数(func 0..7):sat_add/sat_sub/sat_mul/sat_div(__int128 中间量,结果夹到类型范围)
sat_div:除零 → 返回被除数原值(不抛错);MIN / -1 → 夹到 MAX;其余截断向零
浮点(func 8/9):IEEE 754(除零 → inf;编译期类型检查保证使用域)
内置 FB(CAL,15 条)
CAL fb_id, slot:slot 是实例槽号(槽表条目指向实例基址);字段偏移编译期对齐计算(1/2/4/8),字段类型按 machine.toml [[fb]] 表。每次执行 = 推进一次,Δt = 映像头 dt_ms(不读系统时钟)。边沿检测的"上次输入"存 VM 侧 edge_prev_(每槽 2 字节),跨周期保留:
| fb_id | FB | 字段布局(偏移:类型) | 语义 |
|---|---|---|---|
| 0 | TON | in@0:B,pt@8:TIME,q@16:B,et@24:TIME | in 真:et += dt(et ≥ pt 停在 pt);q = (et ≥ pt) |
| 1 | TOF | 同 TON | in 真:q=1、et=0;in 掉电:et += dt,et ≥ pt → q=0 |
| 2 | TP | 同 TON | in 上升沿启动 PT 时长脉冲;计时 et += dt,期满 q=0、et=0 |
| 3 | CTU | cu@0:B,r@1:B,pv@2:INT,q@4:B,cv@6:INT | cu 上升沿 cv += 1;r → cv=0;q = (cv ≥ pv) |
| 4 | DCTU | cu@0:B,r@1:B,pv@4:DINT,q@8:B,cv@12:DINT | 同 CTU(DINT) |
| 5 | CTD | cd@0:B,ld@1:B,pv@2:INT,q@4:B,cv@6:INT | cd 上升沿 cv -= 1;ld → cv=pv;q = (cv ≤ 0) |
| 6 | DCTD | 同 DCTU 布局 | 同 CTD(DINT) |
| 7 | CTUD | cu@0:B,cd@1:B,r@2:B,lu@3:B,pv@4:INT,qu@6:B,qd@7:B,cv@8:INT | cu 上升沿 +1、cd 上升沿 -1;r → 0;lu → pv;qu=(cv≥pv)、qd=(cv≤0) |
| 8 | DCTUD | cu@0,cd@1,r@2,lu@3:B,pv@4:DINT,qu@8,qd@9:B,cv@12:DINT | 同 CTUD(DINT) |
| 9 | R_TRIG | clk@0:B,q@1:B | q = clk 且上次 clk 假(上升沿) |
| 10 | F_TRIG | clk@0:B,q@1:B | q = !clk 且上次 clk 真(下降沿) |
| 11 | SR | S1@0:B,R@1:B,Q1@2:B | 置位优先:S1 → Q1=1;否则 R → Q1=0 |
| 12 | RS | SET@0:B,RESET1@1:B,Q1@2:B | 复位优先:RESET1 → Q1=0;否则 SET → Q1=1 |
| 13 | PWM | in@0:B,pt@8:TIME,duty@16:REAL,q@20:B,et@24:TIME | in 真:et += dt(≥ pt 回绕);q = (et < pt × duty);in 假:et=0、q=0 |
| 14 | RTC | enable@0:B,date@4:DATE,tod@8:TOD | 虚拟时钟:累计周期数 × dt_ms(1970 纪元);enable 真时写 date/tod |
故障
| 故障 | 触发 | 周期行为 |
|---|---|---|
None |
— | 正常完成 |
CycleLimit |
周期指令数 > cycle_limit(用例 6) |
中止,返回故障 |
StackOverflow |
调用栈深度 > 64 或 sp_ + 帧大小 > max_stack |
中止 |
BadOp |
pc 越界 / 指令截断 / 前缀 RESERVED / 寄存器越界 / CAL fb_id 未知 | 中止 |
BadSlot |
槽号 ≥ n_slots,或地址 + 宽度越出值段 | 中止 |
BadConst |
LOADK const_id ≥ n_consts | 中止 |
示例:line1 MAIN 逐指令推演(V2)
映像(--disasm 实取):dt_ms=10,cycle_limit=100000,值区 7 字节;槽 0..4 → 值区 0..4(紧凑 U8:0=EmergencyStop、1=I0_0、2=I0_1、3=Q0_0、4=starter 实例)。
初始:MAIN 帧 regs[13] 全 0;槽 0..4 全 0(初值 0)
假定 executor 已采样:槽 0 = EmergencyStop = 0、槽 1 = I0_0 = 1、槽 2 = I0_1 = 0
pc 指令 执行后
── ───────────────────── ──────────────────────────────
0x9c LOAD.U8 r8, s1 r8 = value[1] = 1(I0_0)
0xa4 STORE_OFF.U8 r8, s4, 0 starter.S1(实例基址 4 + 0)= 1
0xb4 LOAD.U8 r9, s2 r9 = value[2] = 0(I0_1)
0xbc STORE_OFF.U8 r9, s4, 1 starter.R(4 + 1)= 0
0xcc LOAD_OFF.U8 r8, s4, 0 r8 = 1(S1)
0xdc JF r8, +24 r8≠0 不跳;pc = 0xe4 + 0
0xe4 LOAD_OFF.U8 r10, s4, 1 r10 = 0(R)
0xf4 NOT r9, r10 r9 = 1
0xf8 MOVE r8, r9 r8 = 1
0xfc JF r8, +16 r8≠0 不跳;pc = 0x104
0x104 LOAD.U8 r12, s0 r12 = value[0] = 0(EmergencyStop)
0x10c NOT r11, r12 r11 = 1
0x110 MOVE r8, r11 r8 = 1
0x114 STORE_OFF.U8 r8, s4, 2 starter.Q1(4 + 2)= 1
0x124 LOAD_OFF.U8 r8, s4, 2 r8 = 1(Q1)
0x134 STORE.U8 r8, s3 Q0_0(槽 3)= 1
0x13c RET 周期结束;executor 读槽 3 → 硬件
结果:Q0_0 = 1(start=1、stop=0、急停=0 → 电机启动)——与 EXPECTED.md 真值表一致(vm_test 94 checks + executor 端到端验证)。