Files
Interpreter/Doc/isa/指令与映像.md
T
Admin 6873b5ee0b 文档同步:machine.toml 配置驱动架构收尾。
- README.md:依赖方向(STCompiler→compiler 不链 isa)、模块表、isa 归执行器侧、
  数据流图(machine.toml + 型号/SHA)
- isa/README.md:重写——只管指令定义(Types/Op/Instr/Encode),删 Image 部分
- 指令与映像.md:依赖图与 CMake(compiler 不链 isa)
- 编译管线.md:管线首步加载 machine.toml、产物含型号/SHA
- 初步计划.md:12.10/12.13 修订(--machine、依赖方向)
- 扫描周期.md / 指令执行.md:12.9 步骤标注已落地、vm 读校验说明
- compiler/vm/executor README:依赖与边界同步
2026-08-21 22:39:27 +08:00

10 KiB
Raw Blame History

指令与映像

isa 模块规范:指令集 + 映像 + 定宽类型。不是编译器,不把 .st 编成映像。

compiler.stbvm.stb,两边各自实现读写;isa 归执行器侧只管指令定义。指令 opcode / 类型 / FB 布局的登记见 指令配置.mdcompiler/machine.toml,与代码强校验)。

全工程定案见 初步计划.md类型、指令编码、映像布局以本文为准,不要另写一份互相打架的表。头文件实现本文,不另当规范。

STCompiler      → compiler        (编码查 machine.toml;自带 .stb 写)
BytecodeExecutor → vm → isa        isa 只管指令;vm 自带 .stb 读)

CMake 目标:isaSTATIC),无依赖;vm PUBLIC 链 isa。公开头:isa/include/isa/


做 / 不做

  • 操作码与 32 位指令布局
  • 映像头与段(函数字节码、全局 / FB 槽、工程哈希)
  • BOOL / INT / TIME 宽度与溢出规则
  • 编解码、工程哈希、一条指令的文本转储(反汇编,给 STCompiler --disasm 与测试打印)

不做

  • toml 字段、关键字表、符号表、AST
  • 扫描周期循环、I/O 采样
  • 短路语义(AND/OR 指令只组合已算好的值;短路由 compiler 编成跳转)

指令表可作为内部 IR 的文本形态,不必当主语言。


特性

第一版
BOOL uint8_t,只允许 0/1
INT int16_tIEC INT
TIME int64_t 毫秒(有符号);与 INT 不混用
整数溢出 饱和(夹到 INT 最小/最大)
字节序 小端
指令宽度 32 bit,一种主布局
寄存器 下标 0..255;每函数实际个数写在函数头 nregs
跳转 相对 offset,单位是指令条数,不是字节
CALL 立即数 fn_idu16),无函数指针

调用约定(冻结,compiler 12.8 与 VM 12.9 共同遵守)

  • 结果寄存器 r0;参数寄存器 r1..r7(最多 7 个输入,按声明序)
  • 变量与表达式临时全部从 r8 起(调用约定区 r0..r7 不受用户数据占用)
  • CALL:VM 复制当前帧 r0..r7 到新帧 r0..r7
  • RET:VM 复制当前帧 r0..r7 回调用方帧 r0..r7r0 = 结果;r1..r7 原样返回)
  • 函数输入在函数体内只读(compiler 拒绝写入) | 映像魔数 | STSC,版本 1 | | 工程哈希 | 源文件路径排序后对内容做 FNV-1a 64(非密码学) | | I/O 绑定 | 不进映像;编译期把 var 收成槽号,channel/bit 由 STCompiler 写进 sidecar.runtime.toml),BytecodeExecutor 读 sidecar 采样 | | 不做 | REAL、隐式宽化 |

类型

C++20 定宽:

  • BOOLuint8_t0/1
  • INTint16_t;加减乘除溢出饱和
  • TIMEint64_t 毫秒,有符号

compilervm 都按同一规则,不再另选溢出策略。

TIMEint64_t 的理由(冻结,不再反复问):

  • 有符号:TOF 剩余时间、两个时间点相减会出负值,免去无符号边界处理
  • 64 位:为将来 DATE_AND_TIME(毫秒时间戳)与 TON / TOF / TP 的 64 位定时器预留;毫秒量程约 2.9 亿年,扫描周期场景永不溢出
  • 代价:槽位 8 字节对齐。槽位规则(12.9 方案 a):每槽 8 字节定宽,BOOL 用低 1 字节、INT 用低 2 字节(小端)、TIME 全 8 字节;槽号 × 8 = 数据偏移,无 padding。

指令

定宽 32 bit

[ op:8 | rd:8 | a:8 | b:8 ]
  • 三寄存器:a/bra/rbMOVE/NOT 只用 rd+a
  • LOADKa|bconst_idu16
  • JMP / JT / JFa|b 为有符号相对 offset(指令条数),相对下一条指令:目标下标 = 当前下标 + 1 + off
  • CALLa|bfn_idu16

操作码一次列全(CMP_xx 冻下面六种):

MOVE rd, rs
LOADK rd, const_id
NOT rd, rs
AND/OR rd, ra, rb          (* 已算好的值;短路用跳转 *)
ADD/SUB/MUL/DIV rd, ra, rb
CMP_EQ/NE/LT/LE/GT/GE rd, ra, rb
JMP  offset
JT/JF r, offset
LOAD_I / STORE_Q / LOAD_M / STORE_M
LOAD_GLOBAL / STORE_GLOBAL
CAL_TON / CAL_TOF / CAL_TP / CAL_CTU / CAL_CTD / CAL_CTUD / CAL_R_TRIG / CAL_F_TRIG
CALL fn_id
RET

12.11 修订:内置功能块扩为 8 个(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG), CAL_* 操作码连续占 24..31(原 CALL=27、RET=28 后移为 32、33)。 操作码数值即编码,本表为准。

局部 / VAR = 固定寄存器;表达式临时值往后编号;nregs 写进函数头。
I/Q/M 走映像指令,不是通用寄存器。
FB 实例固定布局,字段偏移编译期算死。

反汇编格式(disasm

disasm 输出一条指令的一行文本,格式冻结:

形态 格式 示例
二寄存器 MOVE rd, rs / NOT rd, rs MOVE r1, r2
三寄存器 OP rd, ra, rb(逻辑 / 算术 / 比较) ADD r1, r2, r3
常量 LOADK rd, imm LOADK r0, 3
无条件跳转 JMP off JMP +4
条件跳转 JT rd, off / JF rd, off JT r0, -1
I/Q/M/全局 LOAD_I rd, slot 等;STORE 同形 STORE_Q rs, slot LOAD_I r0, 2
FB 内置 CAL_TON instance CAL_TON 0
调用 CALL fn_id CALL 1
返回 RET RET
未知操作码 ??? 0x<hex>

跳转偏移带符号、恒显正负号(%+d);寄存器一律 r<n>;常量/槽号/fn_id 无符号十进制。


映像

魔数 STSC,版本 1。全部整数小端。文件 = 头 + 五个段 + 文件尾校验,段序:常量表 → 函数表 → 字节码 → FB 布局 → 数据。偏移记在头里,段必须连续、偏移单调不减,末段终点不超过文件长度(文件尾在数据段之后)。

12.13 修订:头新增 型号标识[32](原头 72 字节 → 104 字节,段偏移基准同步后移); 文件尾新增 SHA-256[32](对文件尾之前全部内容计算,compiler 写侧 / vm 读侧各自实现)。 执行器读取时:型号标识与自身支持型号不匹配 → 报错;SHA-256 不匹配 → 报错(损坏/篡改)。

头(104 字节)

偏移 字段
0 4 魔数 STSC
4 4 版本 1
8 4 cycle_limit
12 4 dt_ms
16 8 工程哈希(FNV-1a 64
24 4 入口 entry_fn_id
28 4 全局槽数 n_globals
32 4 I 槽数 n_i
36 4 Q 槽数 n_q
40 4 M 槽数 n_m
44 4 常量数 n_consts
48 4 函数数 n_funcs
52 4 offset_const
56 4 offset_funcs
60 4 offset_code
64 4 offset_fb
68 4 offset_data
72 32 型号标识(定长 ASCII,含版本,如 "STATOR" + "1" 拼 32 字节补 '\0'

文件尾(SHA-256

数据段之后追加 32 字节 SHA-256:对文件尾之前全部内容计算。执行器必须校验通过才运行;compiler 写入。

常量表一行(12 字节)

[tag:u32][value:u64]tag0=BOOL、1=INT、2=TIME。value 按 tag 解释,未用高位为 0。

函数表一行(12 字节)

[nregs:u32][code_offset:u32][code_len:u32];行下标即 fn_idcode_offset 相对字节码段起点,code_len 为指令条数。

字节码段

指令按 u32 连续排;CALLfn_id 是函数表行下标。

FB 布局段

第一版为空;每类型一行 [field_count:u32][field_count × (tag:u32, offset:u32)]offset 相对实例基址,12.8 落地 codegen 时冻精确表。

数据段

槽初值按序排:全局(n_globalsv1 不拆 I/Q/M)→ FB 实例块(每字段一槽)。每槽 8 字节定宽12.9 方案 a):BOOL 用低 1 字节、INT 用低 2 字节(小端)、TIME 全 8 字节。指令 slot 操作数即槽号,数据偏移 = 槽号 × 8;符号表 address 与指令 slot 一致,无映射。

工程哈希:收录源文件路径排序后对内容做 FNV-1a 64,只保证回放槽位一致,不是密码学哈希。FNV-1a 64basis 0xcbf29ce484222325prime 0x100000001b3

[[io.input]] / [[io.output]] 不进映像。


映像文件(.stb)与 sidecar

编译产物是单文件 <name>.stb:映像头 + 各段按本文布局连续排,魔数 STSC、版本 1、小端。STCompiler 写,BytecodeExecutor 读。

  • dt_ms / cycle_limit 在映像头,执行器只认映像,不重复配置。
  • I/O 绑定不进映像:STCompiler 另写 sidecar <name>.runtime.tomlBytecodeExecutor 读 sidecar 做采样与写回,不创造变量。

sidecar 格式(TOML 子集,冻结):

[[io.input]]        # 或 [[io.output]]
var = "I0_0"        # 已在 GVL 声明的名字
slot = 1            # 全局槽号(编译期已解析)
channel = 0
bit = 0

每行一个绑定;var 顺序不要求与映像槽序一致(slot 为准)。


目录与 CMake

isa/
  CMakeLists.txt
  README.md                 # 代码目录入口,指向本文
  include/isa/
    Types.h                 # 宽度、饱和、槽值表示
    Op.h                    # 操作码枚举 + 助记符
    Instr.h                 # 32-bit 打包/拆字段(inline
    Image.h                 # 头、段、函数表/常量表/FB 布局
    Encode.h                # 编解码、哈希、反汇编一行
  src/
    Encode.cpp
    Image.cpp
add_library(isa STATIC src/Encode.cpp)
target_include_directories(isa PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)

vmtarget_link_libraries(vm PUBLIC isa)compiler 不链 isa


实现顺序(对齐初步计划 12.0~12.1)

  1. Types.h:三类型 + sat_add 等饱和函数(inline 即可)
  2. Op.h + Instr.h:枚举与 32-bit 编解码
  3. Encode.cppencode / decode / disasm 一条指令
  4. Image.h + Image.cpp:头布局、FNV-1a、读写缓冲(先能写空映像 + 一条 RET
  5. 测试随后在 tests/ 加编解码往返;本阶段以头文件和库能链为准

不在本模块做:VM switch、toml、关键字、符号表。