diff --git a/Doc/compiler/STCompiler使用说明.md b/Doc/compiler/STCompiler使用说明.md index 5f980c0..4a1c268 100644 --- a/Doc/compiler/STCompiler使用说明.md +++ b/Doc/compiler/STCompiler使用说明.md @@ -15,45 +15,52 @@ ctest --test-dir build/gcc-debug # 测试(当前 9 个用例) ## 用法 ```text -STCompiler [-o .stb] -STCompiler .stb --disasm +STCompiler [-o .stb] --machine +STCompiler .stb --disasm --machine ``` | 参数 | 作用 | |---|---| | `` | 工程文件(必填) | -| `-o .stb` | 编译并写出映像文件;**缺省时只打印文件集合与工程哈希**(12.3 阶段行为) | +| `-o .stb` | 编译并写出映像文件(+ sidecar);**缺省时只打印文件集合与工程哈希**(不需 `--machine`) | +| `--machine ` | 机器定义(指令 opcode / 类型 / FB 布局,见 `Doc/isa/指令配置.md`);**编译与 `--disasm` 必填**,加载/校验失败报错退出 | | `.stb --disasm` | **反汇编一个已编译的映像**(不重新编译,见下) | | `--help` | 打印帮助 | -退出码:`0` 成功;`1` 编译失败(错误信息打到 stderr)。 +退出码:`0` 成功;`1` 失败(错误信息打到 stderr)。 ## 反汇编(--disasm) -对已编译的 `.stb` 逐段查看:段摘要、函数表逐条指令(文件绝对偏移)、常量表、数据段 hex。 +对已编译的 `.stb` 逐段查看:段摘要(含型号与 SHA 校验)、函数表逐条指令(文件绝对偏移)、常量表、数据段 hex。 ```bash -$ STCompiler line1.stb --disasm -image: line1.stb (184 bytes, 2 functions, 4 globals, entry fn 1) - dt_ms=10 cycle_limit=100000 hash=0xc3fe4ae74ad45900 +$ STCompiler line1.stb --disasm --machine machine.toml +image: line1.stb (288 bytes, 2 functions, 4 globals, entry fn 1) + dt_ms=10 cycle_limit=100000 hash=0xc3fe4ae74ad45900 model=STATOR1 sha=ok functions: fn 0: nregs=8, offset=0, len=1 - 0x0060 RET + 0x0080 RET fn 1: nregs=13, offset=4, len=17 - 0x0064 LOAD_I r8, 1 - 0x0068 STORE_GLOBAL r8, 8 - 0x006c LOAD_GLOBAL r9, 2 + 0x0084 LOAD_I r8, 1 + 0x0088 STORE_GLOBAL r8, 4 ... -data (16 bytes): - 0x0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +data (56 bytes): + 0x0000: ... ``` 说明: - 指令偏移是**文件绝对字节偏移**,可直接对照 `xxd line1.stb` - 映像里**没有符号名**(函数表只有 fn_id,数据段无名字映射),因此按 `fn 0` / `fn 1`、槽号显示 +- `model=` 显示型号标识(`machine.toml [meta]` 生成);`sha=ok|BAD` 显示文件尾 SHA-256 校验结果 - 文件损坏或打不开 → `error: ...` 退出码 1 +## 编译产物(.stb 加固) + +- **型号标识[32]**:`[meta] name + version` 拼成(如 `"STATOR1"`),写进头偏移 72 +- **SHA-256 文件尾[32]**:对文件尾之前全部内容计算;compiler 写侧与 vm 读侧各实现一份 +- 执行器读取时型号不匹配 / SHA 不符 → 直接拒绝(详见 `Doc/isa/指令与映像.md` 12.13 修订) + ## 示例(line1) ```bash @@ -65,11 +72,11 @@ files: main.st hash: 0xc3fe4ae74ad45900 -$ STCompiler examples/line1/project.toml -o line1.stb -compiled: line1.stb (184 bytes, 2 functions, 4 globals) +$ STCompiler examples/line1/project.toml -o line1.stb --machine compiler/machine.toml +compiled: line1.stb (288 bytes, 2 functions, 4 globals) ``` -产物 `line1.stb`:魔数 `STSC` + 版本 1 的单一映像(指令见 `Doc/isa/指令与映像.md`)。 +产物:`line1.stb`(288 字节:头 104 含型号标识 + 各段 + SHA-256 文件尾)+ `line1.runtime.toml`(sidecar)。指令见 `Doc/isa/指令与映像.md`,结构拆解见 `Doc/isa/stb文件格式.md`。 ## 工程文件(project.toml) @@ -142,10 +149,10 @@ project.toml + .st - `examples/line1/`:最小闭环(MAIN + MotorStarter FB + I/O + 全局),给人看 - `tests/cases/01~20/`:20 个用例(每份 `project.toml` + `.st` + `EXPECTED.md`),编译结果按表 -- `tests/`:CTest(`isa_roundtrip` / `compiler_toml` / `lexer_tokens` / `parser_syntax` / `linker_links` / `typecheck_types` / `codegen_slice1`) +- `tests/`:CTest(`compiler_toml` / `lexer_tokens` / `parser_syntax` / `linker_links` / `typecheck_types` / `codegen_slice1` / `machine_config` / `vm_cycles` / `cases_all` / `cli_*`) ## 当前状态 -- ✅ 可编译:14 个正例用例全部产出 `.stb`;6 个负例按期望拒绝 -- ⏳ 执行:`BytecodeExecutor`(加载 `.stb` 跑扫描周期)在 12.9/12.10 -- ⏳ sidecar(`.runtime.toml`,I/O 绑定)在 12.10 随执行器一起接线 +- ✅ 可编译:14 个正例用例全部产出 `.stb`(含型号标识 + SHA-256);6 个负例按期望拒绝 +- ✅ 执行:`BytecodeExecutor` 加载 `.stb` 跑扫描周期(型号/SHA 校验、回放、单步) +- ✅ 配置驱动:指令 opcode / 类型元数据 / FB 布局来自 `compiler/machine.toml`(`--machine`) diff --git a/Doc/executor/执行器入口.md b/Doc/executor/执行器入口.md index 76de4af..bad9343 100644 --- a/Doc/executor/执行器入口.md +++ b/Doc/executor/执行器入口.md @@ -18,8 +18,9 @@ BytecodeExecutor .stb [--cycles N] [--replay ] [--step] ## 职责 - 加载 `.stb` 映像与 `.runtime.toml` sidecar(I/O 绑定 var → 槽号 → channel/bit) +- 读取时**校验型号标识与 SHA-256**:型号不匹配 / 文件被篡改 → 直接报错拒绝(执行器只接受与自身内建型号一致的映像) - 按 sidecar 采样 I、写回 Q(不创造变量) -- `dt_ms` / `cycle_limit` 从映像头取,推进 `TON` / `TOF` / `CTU` +- `dt_ms` / `cycle_limit` 从映像头取,推进内置 FB(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG) - 单步、看寄存器和映像、按录制的 `I` 回放 ## 边界 diff --git a/Doc/isa/stb文件格式.md b/Doc/isa/stb文件格式.md index f7ccf98..3f59a55 100644 --- a/Doc/isa/stb文件格式.md +++ b/Doc/isa/stb文件格式.md @@ -1,6 +1,6 @@ # .stb 映像文件结构 -编译产物 `.stb` 的**面向使用者**的结构说明。规范以 [`指令与映像.md`](指令与映像.md) 为准,本文用 `line1.stb`(224 字节)做真实拆解。 +编译产物 `.stb` 的**面向使用者**的结构说明。规范以 [`指令与映像.md`](指令与映像.md) 为准,本文用 `line1.stb`(288 字节,12.13 修订后)做真实拆解。 ## 概览 @@ -10,7 +10,7 @@ ``` ┌──────────────────────────────────────────────────┐ -│ 头(72 字节,偏移 0) │ +│ 头(104 字节,偏移 0,含型号标识[32]) │ ├──────────────────────────────────────────────────┤ │ 常量表段 offset_const .. offset_funcs │ ├──────────────────────────────────────────────────┤ @@ -20,11 +20,13 @@ ├──────────────────────────────────────────────────┤ │ FB 布局段 offset_fb .. offset_data(v1 为空)│ ├──────────────────────────────────────────────────┤ -│ 数据段 offset_data .. 文件末尾 │ +│ 数据段 offset_data .. offset_data+len │ +├──────────────────────────────────────────────────┤ +│ SHA-256 文件尾(32 字节,对文件尾之前全部内容) │ └──────────────────────────────────────────────────┘ ``` -## 头(72 字节) +## 头(104 字节) | 偏移 | 宽 | 字段 | line1 实际值 | |---|---|---|---| @@ -40,11 +42,15 @@ | 40 | 4 | M 槽数 `n_m` | 0 | | 44 | 4 | 常量数 `n_consts` | 0 | | 48 | 4 | 函数数 `n_funcs` | 2 | -| 52 | 4 | `offset_const` | 72 | -| 56 | 4 | `offset_funcs` | 72 | -| 60 | 4 | `offset_code` | 96 | -| 64 | 4 | `offset_fb` | 168 | -| 68 | 4 | `offset_data` | 168 | +| 52 | 4 | `offset_const` | 104(`0x68`) | +| 56 | 4 | `offset_funcs` | 104 | +| 60 | 4 | `offset_code` | 128(`0x80`) | +| 64 | 4 | `offset_fb` | 200(`0xC8`) | +| 68 | 4 | `offset_data` | 200 | +| 72 | 32 | **型号标识**(定长 ASCII 含版本,补 `'\0'`) | `"STATOR1"`(`0x48..0x67`) | + +> 12.13 修订:型号标识由 `machine.toml [meta] name + version` 拼成(如 `STATOR + 1` → `"STATOR1"`); +> 执行器读取时型号不匹配 → 直接报错。 ## 常量表段(`offset_const` 起) @@ -74,15 +80,15 @@ line1(2 行): - `CALL` 的 `fn_id` = 函数表行下标 - 调用约定:结果 r0、参数 r1..r7、变量与临时 r8 起 -line1 `MAIN` 前 5 条(`0x60` 起,slot 为 8 字节定宽槽号): +line1 `MAIN` 前 5 条(`0x84` 起;`0x80` 是 `fn 0` 的占位 `RET`,slot 为 8 字节定宽槽号): | 偏移 | 字节 | 反汇编 | 含义 | |---|---|---|---| -| 0x64 | `12 08 01 00` | `LOAD_I r8, 1` | I0_0(io.input 绑定 → LOAD_I,槽 1) | -| 0x68 | `17 08 04 00` | `STORE_GLOBAL r8, 4` | starter.start(槽 4)← I0_0 | -| 0x6C | `16 09 02 00` | `LOAD_GLOBAL r9, 2` | I0_1(槽 2) | -| 0x70 | `17 09 05 00` | `STORE_GLOBAL r9, 5` | starter.stop(槽 5)← I0_1 | -| 0x74 | `16 08 04 00` | `LOAD_GLOBAL r8, 4` | 内联体:读 starter.start(槽 4) | +| 0x84 | `12 08 01 00` | `LOAD_I r8, 1` | I0_0(io.input 绑定 → LOAD_I,槽 1) | +| 0x88 | `17 08 04 00` | `STORE_GLOBAL r8, 4` | starter.start(槽 4)← I0_0 | +| 0x8C | `16 09 02 00` | `LOAD_GLOBAL r9, 2` | I0_1(槽 2) | +| 0x90 | `17 09 05 00` | `STORE_GLOBAL r9, 5` | starter.stop(槽 5)← I0_1 | +| 0x94 | `16 08 04 00` | `LOAD_GLOBAL r8, 4` | 内联体:读 starter.start(槽 4) | ## FB 布局段(`offset_fb` 起) @@ -94,24 +100,28 @@ v1 为空(用户 FB 内联展开,字段偏移已算进数据段;内建 8 槽序:**全局块**(`n_globals` 个,声明序)→ **FB 实例块**(POU 收集序、实例声明序,每字段一槽)。 -line1 数据段(56 字节 = 7 槽 × 8B,`0xA8` 起): +line1 数据段(56 字节 = 7 槽 × 8B,`0xC8` 起): | 槽号 | 偏移 | 内容 | |---|---|---| -| 0 | 0xA8 | EmergencyStop(BOOL,初值 0) | -| 1 | 0xB0 | I0_0(BOOL) | -| 2 | 0xB8 | I0_1(BOOL) | -| 3 | 0xC0 | Q0_0(BOOL) | -| 4 | 0xC8 | starter.start(BOOL) | -| 5 | 0xD0 | starter.stop(BOOL) | -| 6 | 0xD8 | starter.q(BOOL) | +| 0 | 0xC8 | EmergencyStop(BOOL,初值 0) | +| 1 | 0xD0 | I0_0(BOOL) | +| 2 | 0xD8 | I0_1(BOOL) | +| 3 | 0xE0 | Q0_0(BOOL) | +| 4 | 0xE8 | starter.start(BOOL) | +| 5 | 0xF0 | starter.stop(BOOL) | +| 6 | 0xF8 | starter.q(BOOL) | + +## SHA-256 文件尾(32 字节) + +数据段之后追加 32 字节 SHA-256:**对文件尾之前全部内容计算**(`0x100..0x11F`)。执行器必须校验通过才运行;任一字节被篡改 → 直接拒绝。compiler 写侧与 vm 读侧**各自实现**一份 SHA-256。 ## 用工具验证 -- `ctest` 的 `isa_roundtrip` / `codegen_slice1` 覆盖编解码往返与映像校验 -- `isa::ImageView` 提供只读视图(校验魔数/版本/段边界/入口 fn_id) +- `ctest`:`codegen_slice1`(写侧 model/sha 自检)、`vm_cycles`(读侧型号/SHA 拒绝)、`machine_config` +- `STCompiler x.stb --disasm --machine machine.toml`:显示 `model=` 与 `sha=ok|BAD` - 手工检查:`xxd line1.stb` 对照上表逐段核对 ## 与执行器的关系 -`BytecodeExecutor`(12.9/12.10 实现)只读本格式:头(`cycle_limit`/`dt_ms`)、函数表(帧 `nregs` + 代码定位)、数据段(全局 + FB 实例状态)。I/O 绑定**不在**映像里,在 sidecar `.runtime.toml`(var → 槽号 → channel/bit)。 +`BytecodeExecutor` 只读本格式:头(`cycle_limit`/`dt_ms`/型号标识)、函数表(帧 `nregs` + 代码定位)、数据段(全局 + FB 实例状态)。读取时**型号匹配 + SHA-256 校验**,任一不符 → 报错拒绝。I/O 绑定**不在**映像里,在 sidecar `.runtime.toml`(var → 槽号 → channel/bit)。 diff --git a/Doc/isa/指令与映像.md b/Doc/isa/指令与映像.md index 3cb6867..1805a6a 100644 --- a/Doc/isa/指令与映像.md +++ b/Doc/isa/指令与映像.md @@ -142,9 +142,13 @@ FB 实例固定布局,字段偏移编译期算死。 ## 映像 -魔数 `STSC`,版本 `1`。全部整数小端。文件 = 头 + 五个段,段序:常量表 → 函数表 → 字节码 → FB 布局 → 数据。偏移记在头里,段必须连续、偏移单调不减,末段终点不超过文件长度。 +魔数 `STSC`,版本 `1`。全部整数小端。文件 = 头 + 五个段 + **文件尾校验**,段序:常量表 → 函数表 → 字节码 → FB 布局 → 数据。偏移记在头里,段必须连续、偏移单调不减,末段终点不超过文件长度(文件尾在数据段之后)。 -### 头(72 字节) +> 12.13 修订:头新增 **型号标识[32]**(原头 72 字节 → 104 字节,段偏移基准同步后移); +> 文件尾新增 **SHA-256[32]**(对文件尾之前全部内容计算,compiler 写侧 / vm 读侧各自实现)。 +> 执行器读取时:型号标识与自身支持型号不匹配 → 报错;SHA-256 不匹配 → 报错(损坏/篡改)。 + +### 头(104 字节) | 偏移 | 宽 | 字段 | |---|---|---| @@ -165,6 +169,11 @@ FB 实例固定布局,字段偏移编译期算死。 | 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 字节) diff --git a/Doc/isa/指令配置.md b/Doc/isa/指令配置.md index 66d21e0..88baef7 100644 --- a/Doc/isa/指令配置.md +++ b/Doc/isa/指令配置.md @@ -1,15 +1,42 @@ -# 指令配置文件(ops.txt) +# 机器定义文件(machine.toml) -指令集的**唯一事实来源**:name / opcode / class / format / 参数名 / enabled。代码实现与配置文件在启动时**强校验**一致,防止两处漂移。 +指令集与类型的**唯一事实来源**,**只给编译器**(STCompiler/compiler)使用。执行器(vm)不读本文件——它是下位机程序,内建全部指令。 -## 目标 +## 架构(最终定案) -- 加指令/屏蔽指令的登记集中在一处(配置文件),不再散落在代码各处手改 -- 执行器(VM):**语义实现必须在代码里**,不能纯从文件加载;配置文件用于**屏蔽**(`enabled=false` 的指令执行到 → 故障) -- 编译器(STCompiler):**启动时从文件加载**(校验 + 屏蔽传导) -- `.stb` ABI 不变:opcode 数值仍是冻结契约;屏蔽是编译期/运行期策略,不改数值 +```text +machine.toml(只给编译器:meta + type 别名表 + op 表 + fb 表) + │ 运行时加载(toml++)+ 强校验 + ▼ +STCompiler → compiler(编码 opcode / 类型元数据 / FB 布局 全部来自配置) + │ 写 .stb(compiler 自带写实现:型号标识[32] + SHA-256 文件尾) + ▼ +.stb ──→ BytecodeExecutor → vm(执行 switch 在 vm;读 .stb 自实现) + │ + 型号匹配校验 + SHA-256 校验 + ▼ + isa(归执行器侧,只管指令定义) +``` -## 指令的三层分类(定案) +| 模块 | 职责 | +|---|---| +| `compiler` | 配置驱动编译;自带 `.stb` 写实现;`--disasm` 用配置参数名/format 自格式化 | +| `vm` | 内建全指令集(执行 switch 在 vm);自带 `.stb` 读实现 + 型号/SHA-256 校验;无屏蔽、不读配置 | +| `isa` | **归执行器侧**(vm 依赖),**只管指令定义**:Op 枚举/Instr/Encode/OpFormat/OpClass/类型/饱和/disasm。指令的具体执行实现在 vm | +| `machine.toml` | 编译器唯一知识来源 | + +## 决策清单(定案) + +1. 指令两大分类:**PLAIN**(无实例,26 条)/ **INSTANCE**(有实例,8 条 `CAL_*`) +2. 三层分类:`class`(操作对象)→ `format`(操作数形态)→ `参数名`(语义) +3. isa 归执行器侧、只管指令定义;执行 switch 留在 vm +4. 配置改 TOML、改名 machine.toml,只给编译器;vm 不需要配置 +5. 一致性靠 `.stb`:**型号标识[32]**(定长字符串含版本)+ **SHA-256 文件尾**(compiler/vm 各实现一份)+ 强校验 +6. 类型:**基元内置**(编译器内建 bit/int8..uint64/float32/float64,元数据在代码);配置类型行 = `name + base + range? + tag`(别名);**值域约束进配置**(如 BOOL range=[0,1]) +7. 字符串:**方案 A**(v1 不做;"组合类型"(含定长字符串 STRING(n))为未来扩展,需时再扩 .stb 格式) +8. `.stb` 读写:compiler 写、vm 读,**各自实现**(无共享 image 模块) +9. 冻结哈希测试更新(接受) + +## 指令三层分类 ```text class(操作对象) → format(操作数形态) → 参数名(操作数语义) @@ -17,129 +44,133 @@ class(操作对象) → format(操作数形态) → 参数名(操作 JMP/JC/CALL/CAL/NONE slot/off/fn_id/instance ``` -| 层 | 回答的问题 | 谁需要它 | +| 大类 | 操作对象 | 现有指令 | |---|---|---| -| **class** | 操作对象:**值/搬运** 还是 **实例算法** | 语义归属、校验互锁(INSTANCE ↔ CAL) | -| **format** | 操作数的**形态**:寄存器 / 常量 id / 槽号 / 带符号偏移 | disasm 渲染、编码助手 | -| **参数名** | 每个操作数的**语义命名** | 元数据、文档、校验 | +| **PLAIN**(无实例) | 寄存器 / 立即数 / 槽号 / 分支 / 调用 | MOVE/LOADK/NOT/AND/OR/算术/CMP_* / JMP/JT/JF / LOAD_*/STORE_* / CALL/RET(26 条) | +| **INSTANCE**(有实例) | 数据区实例块(内建 FB) | CAL_TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG(8 条) | -### 两个大类 +format 不可省:同是"第二个操作数是数字",`LOADK`/`LOAD_I`/`CALL` 渲染相同但语义不同(常量 id / 槽号 / fn_id,由参数名表达);"数字 vs 寄存器 vs 偏移"的渲染差异(`r%d` / `%u` / `%+d`)只有 format 能表达。 -| 大类 | 操作对象 | 现有指令 | 操作数形态 | -|---|---|---|---| -| **无实例**(PLAIN) | 寄存器 / 立即数 / 槽号 / 分支 / 调用 | MOVE/LOADK/NOT/AND/OR/算术/CMP_* / JMP/JT/JF / LOAD_*/STORE_* / CALL/RET(26 条) | RR/RRR/IMM/SLOT/JMP/JC/CALL/NONE | -| **有实例**(INSTANCE) | **数据区实例块**(内建 FB) | CAL_TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG(8 条) | CAL(一个实例基槽号) | +## 配置文件格式(TOML) -区别的本质: +**全属性必填,不可省略**: + +```toml +[meta] +name = "STATOR" # 型号名(参与 .stb 型号标识) +version = 1 # 指令集版本(参与 .stb 型号标识) + +# ---- 类型:编译器内建基元(bit/int8/int16/int32/int64/uint8/uint16/uint32/uint64/float32/float64) +# 配置类型 = 基元别名(+ 值域约束 + .stb 契约 tag) +[[type]] +name = "BOOL" +base = "uint8" +range = [0, 1] # 值域约束(语言语义数据化;缺省 = 基元全范围) +tag = 0 # 常量表契约(强校验) + +[[type]] +name = "INT" +base = "int16" +tag = 1 + +[[type]] +name = "TIME" +base = "int64" +tag = 2 + +# ---- 指令(全属性必填) +[[op]] +name = "MOVE" +opcode = 0 +class = "plain" # plain / instance +format = "RR" +params = ["rd", "rs"] +enabled = true + +[[op]] +name = "CAL_TON" +opcode = 24 +class = "instance" +format = "CAL" +params = ["instance"] +enabled = true + +[[op]] +name = "RET" +opcode = 33 +class = "plain" +format = "NONE" +params = [] +enabled = true + +# ---- 内建 FB(布局登记;cal_opcode 与 op 行强校验一致) +[[fb]] +name = "ton" +opcode = 24 +fields = [["in", "BOOL"], ["pt", "TIME"], ["q", "BOOL"], ["et", "TIME"]] + +[[fb]] +name = "ctud" +opcode = 29 +fields = [["cu", "BOOL"], ["cd", "BOOL"], ["r", "BOOL"], ["lu", "BOOL"], + ["pv", "INT"], ["qu", "BOOL"], ["qd", "BOOL"], ["cv", "INT"]] +``` + +## 类型系统 + +- **基元内置**:编译器内建 `bit/int8/int16/int32/int64/uint8/uint16/uint32/uint64/float32/float64`(宽度/有无符号/是否浮点元数据在代码,C 语义) +- **别名**:配置类型行只写 `name + base + range? + tag`——宽度/符号/浮点从 base 继承 +- **值域约束**:`range`(如 BOOL [0,1])供编译器做常量/赋值检查;缺省 = 基元全范围 +- **tag 契约**:`tag`(0/1/2)与 `.stb` 常量表格式强校验(防 type 表改序破坏旧 .stb) +- **字符串**:v1 不做(方案 A);组合类型(含 `STRING(n)`)为未来扩展,文档预留,需时再扩 `.stb` 格式 +- 半数据化边界:**类型定义数据化**(配置),**运算/类型检查语义代码化**(如 AND 只吃 BOOL 是语言规则,在 compiler 代码;饱和算术在 vm/isa) + +## .stb 加固(格式冻结更新) + +见 [`指令与映像.md`](指令与映像.md):头新增 **型号标识[32]**(定长 ASCII 字符串,含版本,不足补 `'\0'`);文件尾新增 **SHA-256[32]**(对前面全部内容计算,compiler 写侧 / vm 读侧各自实现)。执行器读取时: ```text -LOAD_GLOBAL(无实例):从槽读一个"值" → 地址是编译期槽号,纯搬运 -CAL_TON(有实例) :对实例块执行"算法" → 基槽号 + 完整定时器语义(读字段/推进/写字段) +型号标识与自身支持型号不匹配 → 直接报错 +SHA-256 不匹配 → 报错(文件损坏/篡改) ``` -format 不可省:同是"第二个操作数是数字",`LOADK`/`LOAD_I`/`CALL` 渲染相同但语义不同(常量 id / 槽号 / fn_id,由参数名表达);"数字 vs 寄存器 vs 偏移"的**渲染差异**只有 format 能表达(`r%d` / `%u` / `%+d`)。 +## 强校验清单(加载/读取时,任一不符 → 报错退出) -## 配置文件格式(`isa/ops.txt`) +1. TOML 语法与字段完整性(全属性必填) +2. `type.base` 命中内建基元;`range` 合法(min ≤ max、类型相符) +3. `tag` 与 `.stb` 常量表契约一致(0/1/2) +4. `op.opcode` 唯一、0..255;`class` 合法;`format` 合法;**类别-格式互锁**(INSTANCE ↔ CAL) +5. `fb.opcode` 与 op 行的 CAL_* 一致;fb 字段类型命中配置类型表 +6. 型号标识(.stb 侧) -**全属性必填,不可省略**;`#` 注释、空行忽略: +## 加东西的流程 ```text -# op <参数名...> -# class: PLAIN(无实例)/ INSTANCE(有实例) -op MOVE 0 PLAIN RR rd rs enabled -op LOADK 1 PLAIN IMM rd const_id enabled -op NOT 2 PLAIN RR rd rs enabled -op AND 3 PLAIN RRR rd ra rb enabled -op ADD 5 PLAIN RRR rd ra rb enabled -op CMP_EQ 9 PLAIN RRR rd ra rb enabled -op JMP 15 PLAIN JMP off enabled -op JT 16 PLAIN JC r off enabled -op LOAD_I 18 PLAIN SLOT rd slot enabled -op CAL_TON 24 INSTANCE CAL instance enabled -op CALL 32 PLAIN CALL fn_id enabled -op RET 33 PLAIN NONE enabled +加普通指令:machine.toml 加 [[op]] 一行 + vm 一个语义 case → 重建 vm +加内建 FB :machine.toml 加 [[fb]] 一行 + vm 一个语义 case → 重建 vm(compiler 免重建) +加语言类型:machine.toml 加 [[type]] 一行(别名+tag)+ 类型检查/VM 语义代码 → 重建 +改型号版本:machine.toml [meta] version +1 → 旧 .stb 被旧/新执行器按型号校验决定是否接受 ``` -- 参数个数由 format 决定,解析时校验字段数: +## 执行计划(分支 dev_ops_config) -| format | 参数 | 现有指令 | -|---|---|---| -| `RR` | 2(rd rs) | MOVE / NOT | -| `RRR` | 3(rd ra rb) | AND/OR/ADD/SUB/MUL/DIV/CMP_xx | -| `IMM` | 2(rd const_id) | LOADK | -| `SLOT` | 2(rd slot) | LOAD_I/STORE_Q/LOAD_M/STORE_M/LOAD_GLOBAL/STORE_GLOBAL | -| `JMP` | 1(off) | JMP | -| `JC` | 2(r off) | JT / JF | -| `CALL` | 1(fn_id) | CALL | -| `CAL` | 1(instance) | CAL_*(8 个) | -| `NONE` | 0 | RET | - -- **类别与格式互锁**:`INSTANCE` 类的 format 必须是 `CAL`,反之亦然——配置错误启动期暴露 - -### 内建 FB 注册(定案:并入 ops.txt) - -`CAL_*` 已是 op 行(与 ADD 同机制:编码/译码/disasm/屏蔽);fb 行登记其**数据结构(布局)**——ST 语言层的登记处(Lexer/Parser/Linker/Codegen 查表): - -```text -# fb <字段:类型 ...> -fb ton 24 in:BOOL pt:TIME q:BOOL et:TIME -fb tof 25 in:BOOL pt:TIME q:BOOL et:TIME -fb tp 26 in:BOOL pt:TIME q:BOOL et:TIME -fb ctu 27 cu:BOOL r:BOOL pv:INT q:BOOL cv:INT -fb ctd 28 cd:BOOL ld:BOOL pv:INT q:BOOL cv:INT -fb ctud 29 cu:BOOL cd:BOOL r:BOOL lu:BOOL pv:INT qu:BOOL qd:BOOL cv:INT -fb r_trig 30 clk:BOOL q:BOOL -fb f_trig 31 clk:BOOL q:BOOL +``` +0. 文档定案(本文 + 指令与映像.md 格式冻结更新 + stb文件格式.md 修订注记) ← 当前 +1. ✅ OpFormat/OpClass 显式化 + disasm 表驱动(已完成,35dabf4) +2. machine.toml 落地:meta + type 表 + op 34 条 + fb 8 条 +3. compiler 配置驱动改造:toml++ 加载 + 强校验;编码 opcode / 类型元数据 / + FB 布局来自配置(compiler 不再依赖 isa) +4. .stb 写侧:compiler 实现 型号标识[32] + SHA-256 文件尾 +5. .stb 读侧:vm 自实现读 + 型号匹配 + SHA-256 校验(isa 的 Image 拆出) +6. CLI:STCompiler --machine (编译路径必填) +7. 测试:machine 解析/校验负例、型号不匹配报错、SHA-256 篡改检测、 + 冻结哈希更新、全量回归 +8. 文档 + 提交 ``` -- fb 行的 `cal_opcode` 与 op 行的 CAL_* 操作码**强校验一致**(防两处漂移) -- 字段类型名小写(`bool/int/time`),与语言关键字对齐 -- **加 FB = fb 行 + VM 一个语义 case,编译器免重建**(关键字/布局/操作码映射全部从表读) +## 实现前确认项(4 项,未定案) -## isa 层:`OpTable` - -```cpp -class OpTable { - bool load(const char* path, std::string* err); // 解析 + 强校验 - bool enabled(uint8_t op) const; - // name / format / 参数名查询(后续 FB 注册也走这里) -}; -``` - -**强校验(load 时)**: - -- 语法:字段数符合 format、enabled 必须出现 -- 操作码唯一、0..255 -- **与编译期枚举一致**:文件里每条指令的 name/opcode/format 必须与代码(`Op` 枚举 + 助记符 + `OpFormat`)逐条一致,任一不符 → 报错列出差异 -- 任一失败 → `load` 返回 false,调用方报错退出 - -前置改造:**OpFormat 显式化**——现有隐式"形态"(disasm 的 case)变成枚举 `RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE`;`disasm` 改表驱动(输出文本不变,现有断言兼容)。 - -## 各端接线 - -| 端 | 参数 | 行为 | -|---|---|---| -| STCompiler | `STCompiler [-o out.stb] --ops ` | **仅 `-o` 编译路径要求**;加载/校验失败 → 报错退出;codegen 生成被屏蔽指令 → 编译错误(屏蔽传导) | -| BytecodeExecutor | `--ops `(**可选**) | 不传 = 全部启用(兼容现有用法);传入 → 加载 + 校验,执行到 disabled 指令 → `Fault::Disabled`,周期中止退出码 1 | -| 语义实现 | — | 仍在 VM 代码里(每指令一个 case,不可省) | - -## 加东西的流程(改造后) - -```text -加普通指令:ops.txt 加一行 op(name opcode class format 参数 enabled) - + Op.h 枚举加一项 + VM 一个语义 case → 重建 isa/VM -加内建 FB:ops.txt 加一行 fb(name cal_opcode 字段:类型) - + VM 一个语义 case → 重建 VM(编译器免重建) -屏蔽指令:ops.txt 把 enabled 改 disabled - → 编译器拒绝生成、执行器运行时报 Disabled 故障(零代码改动) -``` - -## 执行计划(单步确认) - -1. **OpFormat / OpClass 显式化**:新增 `OpFormat`(RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE)与 `OpClass`(PLAIN/INSTANCE);`disasm` 改表驱动(输出不变,11 个 ctest 全绿) -2. **`ops.txt` 落地**:34 条 op 行 + 8 条 fb 行,全属性必填 -3. **`OpTable`**:`isa/include/isa/OpTable.h` + `isa/src/OpTable.cpp`(解析 + 强校验:语法/重号/越界/类别-格式互锁/fb-opcode 一致/与编译期枚举一致) -4. **STCompiler `--ops`**:加载/校验/屏蔽传导(codegen 生成被屏蔽指令 → 编译错误) -5. **BytecodeExecutor `--ops`**:可选加载 / `Fault::Disabled` -6. **测试**:`ops_test`(正例 + 缺属性/字段数错/重号/互锁破坏/与枚举不一致负例)、屏蔽传导用例、11 个 ctest 回归 -7. **文档 + 提交** +1. 配置文件名与位置:建议 `compiler/machine.toml`(只归编译器用) +2. 型号标识生成规则:建议 `[meta] name + version` 拼成 32 字节(如 `"STATOR" + "1"` → 补 `'\0'`);vm 内建"支持型号列表"比对 +3. compiler 内部 `TypeKind`(Bool/Int/Time):保留为语言语义枚举(类型检查规则),元数据从配置 base 查 +4. 依赖方向确认:`compiler` 不再依赖 `isa`(`compiler → toml++` 自有;`STCompiler → compiler`;`vm → isa`) diff --git a/compiler/CMakeLists.txt b/compiler/CMakeLists.txt index eecfc8e..08045cf 100644 --- a/compiler/CMakeLists.txt +++ b/compiler/CMakeLists.txt @@ -7,17 +7,21 @@ project(Compiler DESCRIPTION "ST / toml 编译器") # 词法、语法、符号表、类型检查、codegen、链接都留在 compiler 一个库里 +# 指令 opcode / 类型 / FB 布局来自 machine.toml(MachineConfig),不依赖 isa add_library(compiler STATIC ./src/Lexer.cpp ./src/Project.cpp ./src/Parser.cpp ./src/Linker.cpp ./src/Typecheck.cpp - ./src/Codegen.cpp) + ./src/Codegen.cpp + ./src/MachineConfig.cpp + ./src/Stb.cpp + ./src/Codec.cpp + ./src/TypeInfo.cpp) target_include_directories(compiler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) target_include_directories(compiler PRIVATE ${CMAKE_SOURCE_DIR}/third_party/tomlplusplus/include) -target_link_libraries(compiler PUBLIC isa) # 可执行入口:STCompiler -o .stb add_executable(STCompiler diff --git a/compiler/include/compiler/Codec.h b/compiler/include/compiler/Codec.h new file mode 100644 index 0000000..f62abc7 --- /dev/null +++ b/compiler/include/compiler/Codec.h @@ -0,0 +1,36 @@ +/** + * @file Codec.h + * @brief 指令字编解码 + 配置驱动反汇编(编译器侧) + * @author + * @date 2026-08-21 + * + * @details 编译器不依赖 isa:指令字布局([op:8|rd:8|a:8|b:8] 小端)与 + * 反汇编输出(按 machine.toml 的 format/参数名)全部由本模块 + MachineConfig 提供, + * 保证与执行器(isa)产生的字节一致。 + */ + +#pragma once + +#include +#include + +#include "compiler/MachineConfig.h" + +namespace compiler { + + // 指令字:[op:8 | rd:8 | a:8 | b:8],小端 u32 + typedef uint32_t Instr; + + Instr pack(uint8_t opcode, uint8_t rd, uint8_t a, uint8_t b); + + uint8_t op_of(Instr w); + uint8_t rd_of(Instr w); + uint8_t a_of(Instr w); + uint8_t b_of(Instr w); + uint16_t imm16_of(Instr w); // a|b 拼 u16(const_id / slot / fn_id) + int16_t off16_of(Instr w); // a|b 有符号偏移(相对下一条) + + // 配置驱动反汇编:按 machine.toml 的 format 输出一行文本。 + // 未知操作码 → "??? 0x"。输出格式与 Doc/isa/指令与映像.md 一致。 + void disasm(const MachineConfig& cfg, Instr w, char* out, size_t cap); +} diff --git a/compiler/include/compiler/Codegen.h b/compiler/include/compiler/Codegen.h index ee72f53..1312f20 100644 --- a/compiler/include/compiler/Codegen.h +++ b/compiler/include/compiler/Codegen.h @@ -12,14 +12,16 @@ #include #include "compiler/Linker.h" +#include "compiler/MachineConfig.h" #include "compiler/Parser.h" #include "compiler/Project.h" namespace compiler { // 编译工程为 .stb 映像字节(在链接 + 类型检查成功后调用)。 + // 指令 opcode / 类型 tag / FB 布局全部来自 machine.toml(cfg)。 // 失败返回 false,err 前缀 "codegen error"。 bool codegen_project(const Project& proj, const std::vector& units, - const LinkResult& link, std::vector* image, - std::string* err); + const LinkResult& link, const MachineConfig& cfg, + std::vector* image, std::string* err); } diff --git a/compiler/include/compiler/MachineConfig.h b/compiler/include/compiler/MachineConfig.h new file mode 100644 index 0000000..52f7600 --- /dev/null +++ b/compiler/include/compiler/MachineConfig.h @@ -0,0 +1,92 @@ +/** + * @file MachineConfig.h + * @brief 机器定义(machine.toml)加载与强校验 + * @author + * @date 2026-08-21 + * + * @details 见 Doc/isa/指令配置.md: + * - 配置只给编译器(toml++ 运行时加载);vm 不读配置(内建全指令集) + * - 强校验 5 项:字段必填 / type.base 命中内建基元·range 合法·tag 契约 / + * op opcode 唯一·类别-格式互锁 / fb.opcode 与 op 一致·字段类型命中 type 表 + * - 基元内置(bit/int8..uint64/float32/float64,元数据在代码) + */ + +#pragma once + +#include +#include +#include + +namespace compiler { + + // 内建基元(编译器内建,C 语义) + struct PrimType { + const char* name; + uint32_t width; // 字节 + bool is_signed; + bool is_float; + }; + + // 配置类型 = 基元别名(+ 值域约束 + .stb 契约 tag) + struct ConfigType { + std::string name; + std::string base; + bool has_range = false; + int64_t range_min = 0; + int64_t range_max = 0; + uint32_t tag = 0; + }; + + // 配置指令 + struct ConfigOp { + std::string name; + uint32_t opcode = 0; + bool is_instance = false; // class: instance / plain + std::string format; // RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE + std::vector params; + bool enabled = false; + }; + + struct ConfigFbField { + std::string name; + std::string type; // 配置类型名(BOOL/INT/TIME) + }; + + struct ConfigFb { + std::string name; + uint32_t opcode = 0; + std::vector fields; + }; + + class MachineConfig { + public: + // 加载 + 强校验;失败返回 false 并写 err(前缀 "machine error") + bool load(const std::string& path, std::string* err); + + bool ok() const { return ok_; } + const std::string& model_name() const { return model_name_; } + uint32_t version() const { return version_; } + + const std::vector& types() const { return types_; } + const std::vector& ops() const { return ops_; } + const std::vector& fbs() const { return fbs_; } + + const ConfigType* find_type(const std::string& name) const; + const ConfigOp* find_op(const std::string& name) const; + const ConfigOp* find_op_by_code(uint32_t opcode) const; + bool op_enabled(uint32_t opcode) const; + const ConfigFb* find_fb(const std::string& name) const; + + // 内建基元表 + static const std::vector& prims(); + static const PrimType* find_prim(const std::string& name); + + private: + bool ok_ = false; + std::string model_name_; + uint32_t version_ = 0; + std::vector types_; + std::vector ops_; + std::vector fbs_; + }; +} diff --git a/compiler/include/compiler/Project.h b/compiler/include/compiler/Project.h index eb9e24f..81bbe4e 100644 --- a/compiler/include/compiler/Project.h +++ b/compiler/include/compiler/Project.h @@ -11,10 +11,17 @@ #include #include -#include "isa/Image.h" - namespace compiler { + // I/O 绑定(project.toml [[io.*]];不创造变量,slot 由链接结果解析) + struct IoBinding { + std::string var; + uint32_t slot = 0; + uint32_t channel = 0; + uint32_t bit = 0; + bool is_input = false; // true = [[io.input]],false = [[io.output]] + }; + // 与 Doc/初步计划.md 12.1 的 toml 字段表一一对应。 // 字段白名单 / 必填 / io 完整性在 parse_project 内校验。 struct Project { @@ -24,7 +31,7 @@ namespace compiler { uint32_t dt_ms = 0; // 必填 > 0 std::vector files_st; // [files] 必填非空 std::string gvl_file; // [gvl] 可选;无则空串 - std::vector io; // [[io.*]] 可选;slot 12.6 才解析,此处填 0 + std::vector io; // [[io.*]] 可选;slot 12.6 才解析,此处填 0 std::string base_dir; // toml 所在目录(解析相对路径用) }; @@ -37,6 +44,6 @@ namespace compiler { std::vector compile_files(const Project& p); // 校验全部文件存在并计算工程哈希:集合按路径排序,对内容做 FNV-1a 64 增量; - // 空集合 = basis(isa::kFnvBasis)。缺文件报错前缀 "file missing"。 + // 空集合 = basis(Stb::kFnvBasis)。缺文件报错前缀 "file missing"。 bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err); } diff --git a/compiler/include/compiler/Stb.h b/compiler/include/compiler/Stb.h new file mode 100644 index 0000000..6ee618f --- /dev/null +++ b/compiler/include/compiler/Stb.h @@ -0,0 +1,116 @@ +/** + * @file Stb.h + * @brief 编译器自带的 .stb 映像规范(写侧)+ 只读视图 + FNV-1a + * @author + * @date 2026-08-21 + * + * @details 执行器(vm)各有自己的读写实现;格式契约见 Doc/isa/指令与映像.md。 + * 12.13 修订(型号标识[32] 头 104 字节 + SHA-256 文件尾)在后续步骤落地。 + */ + +#pragma once + +#include +#include +#include +#include + +#include "compiler/Project.h" + +namespace compiler { + + // ---- .stb 格式常量(契约;12.13 修订:头 104 = 原 72 + 型号标识[32],文件尾 SHA-256[32])---- + static const uint32_t kMagic = 0x43545353u; // "STSC" 小端 + static const uint32_t kVersion = 1; + static const size_t kHeaderSize = 104; + static const size_t kConstEntrySize = 12; + static const size_t kFuncRowSize = 12; + static const size_t kSha256Size = 32; + static const size_t kModelIdSize = 32; + static const uint64_t kFnvBasis = 0xcbf29ce484222325ull; + static const uint64_t kFnvPrime = 0x100000001b3ull; + + // 常量表一行(tag:0=BOOL、1=INT、2=TIME,契约) + struct ConstEntry { + uint32_t tag = 0; + uint64_t value = 0; + }; + + // FNV-1a 64(工程哈希,非密码学) + uint64_t fnv1a64_update(uint64_t h, const uint8_t* data, size_t len); + uint64_t fnv1a64(const uint8_t* data, size_t len); + + // SHA-256(文件完整性校验,非密码学用途之外的完整性) + void sha256(const uint8_t* data, size_t len, uint8_t out[kSha256Size]); + + // 型号标识:name + version 拼 32 字节 ASCII(如 "STATOR1"),不足补 '\0',超长截断 + void fill_model_id(const std::string& name, uint32_t version, char out[kModelIdSize]); + + // 只读视图(--disasm 用;校验魔数/版本/段边界) + class StbView { + public: + struct FuncRow { + uint32_t nregs = 0; + uint32_t code_offset = 0; + uint32_t code_len = 0; + }; + + static StbView from(const uint8_t* buf, size_t len); + static StbView from(const std::vector& buf); + + bool ok() const { return ok_; } + const std::string& error() const { return err_; } + + uint32_t cycle_limit() const { return cycle_limit_; } + uint32_t dt_ms() const { return dt_ms_; } + uint64_t project_hash() const { return project_hash_; } + uint32_t entry_fn_id() const { return entry_fn_id_; } + uint32_t n_globals() const { return n_globals_; } + uint32_t n_consts() const { return n_consts_; } + uint32_t n_funcs() const { return n_funcs_; } + uint32_t offset_code() const { return offset_code_; } + uint32_t offset_fb() const { return offset_fb_; } + uint32_t offset_data() const { return offset_data_; } + + ConstEntry const_entry(size_t i) const; + FuncRow func_row(size_t i) const; + const uint8_t* code_bytes() const; + size_t code_len() const; + const uint8_t* data_bytes() const; + size_t data_len() const; + + // 12.13:型号标识与 SHA-256 校验 + std::string model_id() const; // 头 72..103(补 '\0' 后字符串) + bool model_matches(const std::string& name, uint32_t version) const; + bool sha_ok() const; // 文件尾 32 字节 SHA-256 校验 + + private: + StbView() : buf_(0), len_(0) {} + + uint32_t offs_of_const() const; // offset_const(from 已校验段起点) + + const uint8_t* buf_; + size_t len_; + bool ok_ = false; + std::string err_; + uint32_t cycle_limit_ = 0; + uint32_t dt_ms_ = 0; + uint64_t project_hash_ = 0; + uint32_t entry_fn_id_ = 0; + uint32_t n_globals_ = 0; + uint32_t n_consts_ = 0; + uint32_t n_funcs_ = 0; + uint32_t offset_code_ = 0; + uint32_t offset_fb_ = 0; + uint32_t offset_data_ = 0; + }; + + // 文件读写(纯字节;校验交给 StbView) + bool read_stb_file(const char* path, std::vector* out, std::string* err); + bool write_stb_file(const char* path, const std::vector& img, std::string* err); + + // sidecar 生成与写文件(I/O 绑定 var → 槽号 → channel/bit) + std::string make_sidecar(const std::vector& bindings); + bool write_sidecar_file(const char* path, const std::vector& bindings, + std::string* err); +} diff --git a/compiler/include/compiler/TypeInfo.h b/compiler/include/compiler/TypeInfo.h new file mode 100644 index 0000000..45157a1 --- /dev/null +++ b/compiler/include/compiler/TypeInfo.h @@ -0,0 +1,41 @@ +/** + * @file TypeInfo.h + * @brief 语言类型元数据(machine.toml 别名 + 内建基元解析) + * @author + * @date 2026-08-21 + * + * @details 类型定义数据化(配置),运算/类型检查语义代码化: + * - 元数据(宽度/符号/浮点/base/range/tag)全部来自 machine.toml + 内建基元表 + * - 查询大小写不敏感(配置名大写,Linker type_name 小写) + */ + +#pragma once + +#include +#include + +#include "compiler/MachineConfig.h" + +namespace compiler { + + // 语言类型元数据 + struct TypeMeta { + const ConfigType* config = nullptr; // 配置类型行(别名 + range + tag) + const PrimType* prim = nullptr; // 内建基元(宽度/符号/浮点) + + uint32_t width() const { return prim ? prim->width : 0; } + bool is_signed() const { return prim ? prim->is_signed : false; } + bool is_float() const { return prim ? prim->is_float : false; } + uint32_t tag() const { return config ? config->tag : 0; } + bool has_range() const { return config ? config->has_range : false; } + bool value_in_range(int64_t v) const { + return !config || !config->has_range || + (v >= config->range_min && v <= config->range_max); + } + + explicit operator bool() const { return config != nullptr && prim != nullptr; } + }; + + // 按语言类型名查元数据(大小写不敏感;未找到返回空 TypeMeta) + TypeMeta type_meta(const MachineConfig& cfg, const std::string& name); +} diff --git a/compiler/machine.toml b/compiler/machine.toml new file mode 100644 index 0000000..1145146 --- /dev/null +++ b/compiler/machine.toml @@ -0,0 +1,342 @@ +# 机器定义:指令集与类型的唯一事实来源(只给编译器) +# 格式见 Doc/isa/指令配置.md;全属性必填,与代码强校验一致。 + +[meta] +name = "STATOR" # 型号名(参与 .stb 型号标识[32]) +version = 1 # 指令集版本(参与 .stb 型号标识[32]) + +# ---- 类型:编译器内建基元(bit/int8/int16/int32/int64/uint8/uint16/uint32/uint64/float32/float64) +# 配置类型 = 基元别名(+ 值域约束 + .stb 契约 tag) + +[[type]] +name = "BOOL" +base = "uint8" +range = [0, 1] # 值域约束:只允许 0/1 +tag = 0 + +[[type]] +name = "INT" +base = "int16" +tag = 1 + +[[type]] +name = "TIME" +base = "int64" +tag = 2 + +# ---- 指令(全属性必填;class: plain/instance;format: RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE) + +[[op]] +name = "MOVE" +opcode = 0 +class = "plain" +format = "RR" +params = ["rd", "rs"] +enabled = true + +[[op]] +name = "LOADK" +opcode = 1 +class = "plain" +format = "IMM" +params = ["rd", "const_id"] +enabled = true + +[[op]] +name = "NOT" +opcode = 2 +class = "plain" +format = "RR" +params = ["rd", "rs"] +enabled = true + +[[op]] +name = "AND" +opcode = 3 +class = "plain" +format = "RRR" +params = ["rd", "ra", "rb"] +enabled = true + +[[op]] +name = "OR" +opcode = 4 +class = "plain" +format = "RRR" +params = ["rd", "ra", "rb"] +enabled = true + +[[op]] +name = "ADD" +opcode = 5 +class = "plain" +format = "RRR" +params = ["rd", "ra", "rb"] +enabled = true + +[[op]] +name = "SUB" +opcode = 6 +class = "plain" +format = "RRR" +params = ["rd", "ra", "rb"] +enabled = true + +[[op]] +name = "MUL" +opcode = 7 +class = "plain" +format = "RRR" +params = ["rd", "ra", "rb"] +enabled = true + +[[op]] +name = "DIV" +opcode = 8 +class = "plain" +format = "RRR" +params = ["rd", "ra", "rb"] +enabled = true + +[[op]] +name = "CMP_EQ" +opcode = 9 +class = "plain" +format = "RRR" +params = ["rd", "ra", "rb"] +enabled = true + +[[op]] +name = "CMP_NE" +opcode = 10 +class = "plain" +format = "RRR" +params = ["rd", "ra", "rb"] +enabled = true + +[[op]] +name = "CMP_LT" +opcode = 11 +class = "plain" +format = "RRR" +params = ["rd", "ra", "rb"] +enabled = true + +[[op]] +name = "CMP_LE" +opcode = 12 +class = "plain" +format = "RRR" +params = ["rd", "ra", "rb"] +enabled = true + +[[op]] +name = "CMP_GT" +opcode = 13 +class = "plain" +format = "RRR" +params = ["rd", "ra", "rb"] +enabled = true + +[[op]] +name = "CMP_GE" +opcode = 14 +class = "plain" +format = "RRR" +params = ["rd", "ra", "rb"] +enabled = true + +[[op]] +name = "JMP" +opcode = 15 +class = "plain" +format = "JMP" +params = ["off"] +enabled = true + +[[op]] +name = "JT" +opcode = 16 +class = "plain" +format = "JC" +params = ["r", "off"] +enabled = true + +[[op]] +name = "JF" +opcode = 17 +class = "plain" +format = "JC" +params = ["r", "off"] +enabled = true + +[[op]] +name = "LOAD_I" +opcode = 18 +class = "plain" +format = "SLOT" +params = ["rd", "slot"] +enabled = true + +[[op]] +name = "STORE_Q" +opcode = 19 +class = "plain" +format = "SLOT" +params = ["rs", "slot"] +enabled = true + +[[op]] +name = "LOAD_M" +opcode = 20 +class = "plain" +format = "SLOT" +params = ["rd", "slot"] +enabled = true + +[[op]] +name = "STORE_M" +opcode = 21 +class = "plain" +format = "SLOT" +params = ["rs", "slot"] +enabled = true + +[[op]] +name = "LOAD_GLOBAL" +opcode = 22 +class = "plain" +format = "SLOT" +params = ["rd", "slot"] +enabled = true + +[[op]] +name = "STORE_GLOBAL" +opcode = 23 +class = "plain" +format = "SLOT" +params = ["rs", "slot"] +enabled = true + +[[op]] +name = "CAL_TON" +opcode = 24 +class = "instance" +format = "CAL" +params = ["instance"] +enabled = true + +[[op]] +name = "CAL_TOF" +opcode = 25 +class = "instance" +format = "CAL" +params = ["instance"] +enabled = true + +[[op]] +name = "CAL_TP" +opcode = 26 +class = "instance" +format = "CAL" +params = ["instance"] +enabled = true + +[[op]] +name = "CAL_CTU" +opcode = 27 +class = "instance" +format = "CAL" +params = ["instance"] +enabled = true + +[[op]] +name = "CAL_CTD" +opcode = 28 +class = "instance" +format = "CAL" +params = ["instance"] +enabled = true + +[[op]] +name = "CAL_CTUD" +opcode = 29 +class = "instance" +format = "CAL" +params = ["instance"] +enabled = true + +[[op]] +name = "CAL_R_TRIG" +opcode = 30 +class = "instance" +format = "CAL" +params = ["instance"] +enabled = true + +[[op]] +name = "CAL_F_TRIG" +opcode = 31 +class = "instance" +format = "CAL" +params = ["instance"] +enabled = true + +[[op]] +name = "CALL" +opcode = 32 +class = "plain" +format = "CALL" +params = ["fn_id"] +enabled = true + +[[op]] +name = "RET" +opcode = 33 +class = "plain" +format = "NONE" +params = [] +enabled = true + +# ---- 内建 FB(布局登记;opcode 与 op 行强校验一致;字段类型命中 type 表) + +[[fb]] +name = "ton" +opcode = 24 +fields = [["in", "BOOL"], ["pt", "TIME"], ["q", "BOOL"], ["et", "TIME"]] + +[[fb]] +name = "tof" +opcode = 25 +fields = [["in", "BOOL"], ["pt", "TIME"], ["q", "BOOL"], ["et", "TIME"]] + +[[fb]] +name = "tp" +opcode = 26 +fields = [["in", "BOOL"], ["pt", "TIME"], ["q", "BOOL"], ["et", "TIME"]] + +[[fb]] +name = "ctu" +opcode = 27 +fields = [["cu", "BOOL"], ["r", "BOOL"], ["pv", "INT"], ["q", "BOOL"], ["cv", "INT"]] + +[[fb]] +name = "ctd" +opcode = 28 +fields = [["cd", "BOOL"], ["ld", "BOOL"], ["pv", "INT"], ["q", "BOOL"], ["cv", "INT"]] + +[[fb]] +name = "ctud" +opcode = 29 +fields = [["cu", "BOOL"], ["cd", "BOOL"], ["r", "BOOL"], ["lu", "BOOL"], + ["pv", "INT"], ["qu", "BOOL"], ["qd", "BOOL"], ["cv", "INT"]] + +[[fb]] +name = "r_trig" +opcode = 30 +fields = [["clk", "BOOL"], ["q", "BOOL"]] + +[[fb]] +name = "f_trig" +opcode = 31 +fields = [["clk", "BOOL"], ["q", "BOOL"]] diff --git a/compiler/src/Codec.cpp b/compiler/src/Codec.cpp new file mode 100644 index 0000000..0947b4c --- /dev/null +++ b/compiler/src/Codec.cpp @@ -0,0 +1,65 @@ +/** + * @file Codec.cpp + * @brief 指令字编解码 + 配置驱动反汇编(编译器侧) + * @author + * @date 2026-08-21 + */ + +#include "compiler/Codec.h" + +#include + +namespace compiler { + +Instr pack(uint8_t opcode, uint8_t rd, uint8_t a, uint8_t b) { + return static_cast(opcode) + | (static_cast(rd) << 8) + | (static_cast(a) << 16) + | (static_cast(b) << 24); +} + +uint8_t op_of(Instr w) { return static_cast(w & 0xFFu); } +uint8_t rd_of(Instr w) { return static_cast((w >> 8) & 0xFFu); } +uint8_t a_of(Instr w) { return static_cast((w >> 16) & 0xFFu); } +uint8_t b_of(Instr w) { return static_cast((w >> 24) & 0xFFu); } + +uint16_t imm16_of(Instr w) { + return static_cast(a_of(w) | (static_cast(b_of(w)) << 8)); +} + +int16_t off16_of(Instr w) { return static_cast(imm16_of(w)); } + +void disasm(const MachineConfig& cfg, Instr w, char* out, size_t cap) { + if (cap == 0) { + return; + } + out[0] = '\0'; + const ConfigOp* op = cfg.find_op_by_code(op_of(w)); + if (op == nullptr) { + snprintf(out, cap, "??? 0x%08x", static_cast(w)); + return; + } + const uint8_t rd = rd_of(w); + const std::string& f = op->format; + if (f == "RR") { + snprintf(out, cap, "%s r%u, r%u", op->name.c_str(), static_cast(rd), + static_cast(a_of(w))); + } else if (f == "RRR") { + snprintf(out, cap, "%s r%u, r%u, r%u", op->name.c_str(), static_cast(rd), + static_cast(a_of(w)), static_cast(b_of(w))); + } else if (f == "IMM" || f == "SLOT") { + snprintf(out, cap, "%s r%u, %u", op->name.c_str(), static_cast(rd), + static_cast(imm16_of(w))); + } else if (f == "JMP") { + snprintf(out, cap, "%s %+d", op->name.c_str(), static_cast(off16_of(w))); + } else if (f == "JC") { + snprintf(out, cap, "%s r%u, %+d", op->name.c_str(), static_cast(rd), + static_cast(off16_of(w))); + } else if (f == "CALL" || f == "CAL") { + snprintf(out, cap, "%s %u", op->name.c_str(), static_cast(imm16_of(w))); + } else { // NONE + snprintf(out, cap, "%s", op->name.c_str()); + } +} + +} // namespace compiler diff --git a/compiler/src/Codegen.cpp b/compiler/src/Codegen.cpp index eb18252..a3b6584 100644 --- a/compiler/src/Codegen.cpp +++ b/compiler/src/Codegen.cpp @@ -1,38 +1,32 @@ /** * @file Codegen.cpp - * @brief 寄存器码生成(12.8,切片 7:+ FB 实例、内联展开、CAL_*) + * @brief 寄存器码生成(配置驱动;compiler 不依赖 isa) * @author * @date 2026-08-21 * - * @details 设计说明(详见 Doc/compiler/寄存器码.md): - * - 切片 1~6:帧/字面量/MOVE/RET;全局数据区;短路 AND/OR;CMP 与 IF;WHILE 与四则; - * FUNCTION 与 CALL(调用约定 r0/r1..r7/r8+)。 - * - 切片 7:FB 实例 → 数据区实例块(跨周期持久);字段槽号 = 实例基槽 + 字段序号 - * (编译期算死);字段读写走 LOAD_GLOBAL / STORE_GLOBAL; - * 内建 TON/TOF/CTU → 实参写字段后 CAL_* <实例基槽>; - * 用户 FB → 调用点内联展开(实参写字段 → FB 体以实例字段为变量编译) - * - 方案 a(12.9 前置):数据段**每槽 8 字节定宽**,指令 slot = 槽号(无映射) + * @details 设计说明(详见 Doc/compiler/寄存器码.md 与 Doc/isa/指令配置.md): + * - 指令 opcode / 类型 tag / FB 布局全部来自 machine.toml(MachineConfig) + * - 帧/字面量/MOVE/RET;全局数据区;短路 AND/OR;CMP 与 IF;WHILE 与四则; + * FUNCTION 与 CALL(调用约定 r0/r1..r7/r8+) + * - FB 实例 → 数据区实例块(跨周期持久);字段槽号 = 实例基槽 + 字段序号 + * - 用户 FB 调用点内联展开;内建 FB → CAL_* <实例基槽> + * - 数据段每槽 8 字节定宽,指令 slot = 槽号 * * 函数清单: - * - put_le32 / put_le64 小端写入映像缓冲 - * - Builder::Builder (构造)存工程/源文件/链接结果/输出,收集 io 绑定分类 - * - Builder::run 布局数据区(全局 + 实例)→ 逐 POU 建函数 → 拼映像 - * - Builder::fail 组装 "codegen error: " 返回 false + * - Builder::Builder (构造)存工程/源文件/链接结果/机器配置/输出,收集 io 绑定 + * - Builder::run 布局数据区 → 逐 POU 建函数 → 拼映像 + * - Builder::fail / opc 错误 / 按名查配置 opcode + * - E_rr / E_rrr / E_imm / E_slot / E_jmp / E_jc / E_call / E_ret 指令发射(配置 opcode) * - find_pou / find_fn_id 按名查 POU AST / fn_id - * - build_function 编译一个 POU(PROGRAM/FUNCTION/FB 骨架;帧约定分配) - * - begin_stmt / alloc_temp 临时寄存器:语句内递增、语句结束复用基址 + * - build_function 编译一个 POU(帧约定分配;FB 占位) + * - begin_stmt / alloc_temp 临时寄存器管理 * - compile_stmt 语句编译(赋值 / IF / WHILE / FB 调用) - * - compile_if / compile_while IF 链 / WHILE 回环 - * - compile_fb_call FB 调用:内建 CAL_* 或用户内联展开 - * - compile_fb_inline 内联编译用户 FB 体(实例字段为变量) - * - store_target 赋值左值:STORE_* 到全局槽(或内联模式实例字段) - * - compile_expr 表达式编译到寄存器(含 Field 字段读) - * - cmp_op / arith_op / load_op / store_op 操作码选择 - * - global_offset 槽号恒等(方案 a 无映射) - * - instance_of 实例 → 字段槽号表 - * - patch_jump 回填跳转偏移(相对下一条指令) - * - layout_data / layout_fb_instances / init_of 数据区布局(8 字节定宽槽) - * - const_id 取常量表 id(无则追加) + * - compile_if / compile_while / compile_fb_call / compile_fb_inline + * - store_target / compile_expr 左值存储 / 表达式编译 + * - arith_name / cmp_name / load_name / store_name 操作码名选择 + * - patch_jump 回填跳转偏移(相对下一条) + * - layout_data / layout_fb_instances / instance_of / init_of + * - const_id 取常量表 id(tag 来自配置类型表) * - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + 数据段 * - codegen_project 对外入口 */ @@ -45,31 +39,16 @@ #include #include -#include "isa/Encode.h" -#include "isa/Image.h" -#include "isa/Instr.h" -#include "isa/Types.h" +#include "compiler/Codec.h" +#include "compiler/MachineConfig.h" +#include "compiler/Stb.h" +#include "compiler/TypeInfo.h" namespace compiler { namespace { - // ---- 小端写入(Image.cpp 内部实现不可见,这里自带最小版)---- - - void put_le32(std::vector& b, size_t off, uint32_t v) { - b[off + 0] = static_cast(v & 0xFFu); - b[off + 1] = static_cast((v >> 8) & 0xFFu); - b[off + 2] = static_cast((v >> 16) & 0xFFu); - b[off + 3] = static_cast((v >> 24) & 0xFFu); - } - - void put_le64(std::vector& b, size_t off, uint64_t v) { - for (int i = 0; i < 8; ++i) { - b[off + i] = static_cast((v >> (8 * i)) & 0xFFu); - } - } - /** - * @brief 代码生成器(切片 1) + * @brief 代码生成器 */ class Builder { public: @@ -78,14 +57,16 @@ namespace { * @param proj 工程定义(cycle_limit / dt_ms / 哈希用) * @param units 全部源文件的 AST * @param link 链接结果(POU 顺序 / 符号) + * @param cfg 机器定义(machine.toml 强校验后;指令 opcode / 类型 tag / FB 布局) * @param image 输出映像字节 * @param err 错误输出;可为 nullptr(静默) */ Builder(const Project& proj, const std::vector& units, - const LinkResult& link, std::vector* image, std::string* err) - : proj_(proj), units_(units), link_(link), image_(image), err_(err) { + const LinkResult& link, const MachineConfig& cfg, + std::vector* image, std::string* err) + : proj_(proj), units_(units), link_(link), cfg_(cfg), image_(image), err_(err) { // io 绑定分类(名已折小写;不创造变量,只影响操作码选择) - for (const isa::IoBinding& b : proj_.io) { + for (const IoBinding& b : proj_.io) { std::string key = b.var; for (char& ch : key) { ch = static_cast(std::tolower(static_cast(ch))); @@ -100,7 +81,6 @@ namespace { /** * @brief 布局数据区 → 布局 FB 实例 → 逐 POU 建函数 → 拼映像 - * @details 偏移先定(函数编译引用 global_offset / instance_of) * @return true 成功;false(err 已写,前缀 "codegen error") */ bool run() { @@ -127,11 +107,6 @@ namespace { } private: - /** - * @brief 组装错误消息并返回 false - * @param msg 错误描述(不含前缀) - * @return 恒 false - */ bool fail(const std::string& msg) { if (err_) { *err_ = "codegen error: " + msg; @@ -140,10 +115,42 @@ namespace { } /** - * @brief 按名查 POU AST - * @param name POU 名(小写) - * @return POU 指针;未找到返回 nullptr + * @brief 按名查配置 opcode(MachineConfig 强校验保证存在) + * @param name 指令名(大写,如 "ADD") + * @return opcode(找不到返回 0,不应发生) */ + uint8_t opc(const char* name) const { + const ConfigOp* op = cfg_.find_op(name); + return op ? static_cast(op->opcode) : 0; + } + + // ---- 指令发射(配置 opcode)---- + Instr E_rr(const char* name, uint8_t rd, uint8_t rs) { + return pack(opc(name), rd, rs, 0); + } + Instr E_rrr(const char* name, uint8_t rd, uint8_t ra, uint8_t rb) { + return pack(opc(name), rd, ra, rb); + } + Instr E_imm(const char* name, uint8_t rd, uint16_t imm) { + return pack(opc(name), rd, static_cast(imm & 0xFFu), + static_cast((imm >> 8) & 0xFFu)); + } + Instr E_slot(const char* name, uint8_t rd, uint16_t slot) { + return E_imm(name, rd, slot); + } + Instr E_jmp(int16_t off) { + const uint16_t u = static_cast(off); + return pack(opc("JMP"), 0, static_cast(u & 0xFFu), + static_cast((u >> 8) & 0xFFu)); + } + Instr E_jc(const char* name, uint8_t r, int16_t off) { + const uint16_t u = static_cast(off); + return pack(opc(name), r, static_cast(u & 0xFFu), + static_cast((u >> 8) & 0xFFu)); + } + Instr E_call(uint16_t fn_id) { return E_imm("CALL", 0, fn_id); } + Instr E_ret() { return pack(opc("RET"), 0, 0, 0); } + const POU* find_pou(const std::string& name) const { for (const SourceUnit& u : units_) { for (const POU& p : u.ast.pous) { @@ -159,33 +166,24 @@ namespace { struct FuncCtx { std::string name; std::string pou_name; // 所属 POU 名(实例查找键) - std::vector code; // 字节码(函数表 code_offset 相对此段) + std::vector code; // 字节码 std::map regs; // 变量名 → 帧寄存器 - uint8_t nlocals = 0; // 变量区终点 = 临时寄存器起始 - uint8_t nregs = 0; // 峰值(变量 + 临时) - uint8_t temp_used = 0; // 本语句已用临时数(语句结束清零) - bool is_function = false; // FUNCTION(结果 r0 / 输入只读) - std::string result_name; // FUNCTION 名(结果寄存器映射) + uint8_t nlocals = 0; // 变量区终点 = 临时寄存器起始 + uint8_t nregs = 0; // 峰值(变量 + 临时) + uint8_t temp_used = 0; // 本语句已用临时数(语句结束清零) + bool is_function = false; // FUNCTION(结果 r0 / 输入只读) + std::string result_name; // FUNCTION 名(结果寄存器映射) }; - // FB 实例:字段名 → 数据区字节地址 + // FB 实例:字段名 → 数据区槽号 struct InstFields { std::map field_addr; uint32_t base = 0; std::string type_name; // 实例的 FB 类型名(内建 / 用户) }; - /** - * @brief 语句开始:临时寄存器基址复用 - * @param f 当前函数 - */ void begin_stmt(FuncCtx& f) { f.temp_used = 0; } - /** - * @brief 分配一个临时寄存器(语句内递增) - * @param f 当前函数 - * @return 临时寄存器号(可能更新 nregs 峰值) - */ uint8_t alloc_temp(FuncCtx& f) { const uint8_t r = f.nlocals + f.temp_used; ++f.temp_used; @@ -201,16 +199,12 @@ namespace { * FB 不生成业务字节码(调用点内联),只出空函数占位保持 fn_id 一致; * 帧约定(见 Doc/compiler/寄存器码.md):结果 r0、输入 r1..r7(只读)、 * 变量与临时全部从 r8 起 - * @param pou POU AST - * @param f 输出函数编译态 - * @return true 成功;false(err 已写) */ bool build_function(const POU& pou, FuncCtx* f) { if (pou.kind == PouKind::FunctionBlock) { - // FB 内联展开(见 compile_fb_call),此处只占 fn_id f->nlocals = 8; f->nregs = 8; - f->code.push_back(isa::enc_ret()); + f->code.push_back(E_ret()); return true; } f->is_function = pou.kind == PouKind::Function; @@ -229,16 +223,13 @@ namespace { } uint8_t r = 8; // 变量/临时基址(调用约定区 r0..r7 不占用) for (const VarBlock& b : pou.blocks) { - // External / Global 走数据区槽,不占帧寄存器 if (b.section == VarSection::External || b.section == VarSection::Global) { continue; } - // FUNCTION 输入已分配(r1..r7) if (f->is_function && b.section == VarSection::Input) { continue; } for (const VarDecl& d : b.vars) { - // FB 实例走数据区(layout_fb_instances),不占帧寄存器 if (d.type.kind == TypeKind::FbUser || d.type.kind == TypeKind::FbBuiltin) { continue; } @@ -253,16 +244,10 @@ namespace { return false; } } - f->code.push_back(isa::enc_ret()); + f->code.push_back(E_ret()); return true; } - /** - * @brief 编译一条语句(切片 4:赋值 / IF) - * @param f 当前函数 - * @param st 语句 AST - * @return true 成功;false(err 已写) - */ bool compile_stmt(FuncCtx& f, const Stmt& st) { if (st.kind == StmtKind::If) { return compile_if(f, st); @@ -274,7 +259,7 @@ namespace { return compile_fb_call(f, st); } if (st.kind != StmtKind::Assign) { - return fail("statement not supported in slice 7"); + return fail("statement not supported"); } // 内联 FB 体内:左值可能是实例字段(STORE_GLOBAL) if (inline_fields_) { @@ -284,32 +269,20 @@ namespace { if (!compile_expr(f, *st.value, t)) { return false; } - f.code.push_back(isa::enc_slot(isa::Op::STORE_GLOBAL, t, fit->second)); + f.code.push_back(E_slot("STORE_GLOBAL", t, fit->second)); return true; } } const auto it = f.regs.find(st.target); if (it == f.regs.end()) { - // 全局 / 外部左值 → STORE_* 到数据区 return store_target(f, st.target, *st.value); } - // 函数输入只读(调用约定,见 Doc/compiler/寄存器码.md) if (f.is_function && it->second >= 1 && it->second <= 7) { return fail("cannot write function input '" + st.target + "'"); } - const uint8_t rd = it->second; - return compile_expr(f, *st.value, rd); + return compile_expr(f, *st.value, it->second); } - /** - * @brief 编译 FB 调用 - * @details 实参求值到临时 → STORE_GLOBAL 到实例字段; - * 内建 TON/TOF/CTU → CAL_* <实例偏移>; - * 用户 FB → compile_fb_inline 内联展开 - * @param f 当前函数 - * @param st FbCall 语句 AST - * @return true 成功;false(err 已写) - */ bool compile_fb_call(FuncCtx& f, const Stmt& st) { const InstFields* inst = instance_of(f.pou_name, st.instance); if (inst == nullptr) { @@ -324,22 +297,15 @@ namespace { if (!compile_expr(f, *a.value, t)) { return false; } - f.code.push_back(isa::enc_slot(isa::Op::STORE_GLOBAL, t, it->second)); + f.code.push_back(E_slot("STORE_GLOBAL", t, it->second)); } - if (inst->type_name == "ton" || inst->type_name == "tof" || - inst->type_name == "tp" || inst->type_name == "ctu" || - inst->type_name == "ctd" || inst->type_name == "ctud" || - inst->type_name == "r_trig" || inst->type_name == "f_trig") { - const isa::Op op = - inst->type_name == "ton" ? isa::Op::CAL_TON - : inst->type_name == "tof" ? isa::Op::CAL_TOF - : inst->type_name == "tp" ? isa::Op::CAL_TP - : inst->type_name == "ctu" ? isa::Op::CAL_CTU - : inst->type_name == "ctd" ? isa::Op::CAL_CTD - : inst->type_name == "ctud" ? isa::Op::CAL_CTUD - : inst->type_name == "r_trig" ? isa::Op::CAL_R_TRIG - : isa::Op::CAL_F_TRIG; - f.code.push_back(isa::enc_slot(op, 0, inst->base)); + // 内建 FB:配置里必须有对应 op 行(强校验保证)→ 直接查配置 opcode + const std::string& tn = inst->type_name; + if (cfg_.find_op("CAL_" + uppercase_of(tn)) != nullptr && + (tn == "ton" || tn == "tof" || tn == "tp" || tn == "ctu" || + tn == "ctd" || tn == "ctud" || tn == "r_trig" || tn == "f_trig")) { + const std::string opname = "CAL_" + uppercase_of(tn); + f.code.push_back(E_slot(opname.c_str(), 0, inst->base)); return true; } const POU* fb = find_pou(inst->type_name); @@ -349,15 +315,14 @@ namespace { return compile_fb_inline(f, *fb, *inst); } - /** - * @brief 内联编译用户 FB 体 - * @details 切到实例字段上下文(VarRef/赋值左值映射到字段地址); - * 表达式临时与调用方共用 r8+;字段读写全走 LOAD/STORE_GLOBAL - * @param f 当前函数 - * @param fb FB 类型 POU - * @param inst 本实例字段表 - * @return true 成功;false(err 已写) - */ + static std::string uppercase_of(const std::string& s) { + std::string out = s; + for (char& ch : out) { + ch = static_cast(std::toupper(static_cast(ch))); + } + return out; + } + bool compile_fb_inline(FuncCtx& f, const POU& fb, const InstFields& inst) { const InstFields* saved = inline_fields_; inline_fields_ = &inst; @@ -371,14 +336,6 @@ namespace { return true; } - /** - * @brief 编译 WHILE - * @details L_loop: JF t, L_end JMP L_loop L_end: - * 条件 JF 与回环 JMP 双回填 - * @param f 当前函数 - * @param st WHILE 语句 AST - * @return true 成功;false(err 已写) - */ bool compile_while(FuncCtx& f, const Stmt& st) { const size_t loop = f.code.size(); const uint8_t t = alloc_temp(f); @@ -386,7 +343,7 @@ namespace { return false; } const size_t jf_idx = f.code.size(); - f.code.push_back(isa::enc_jc(isa::Op::JF, t, 0)); // 假 → L_end + f.code.push_back(E_jc("JF", t, 0)); for (const Stmt& s : st.body) { begin_stmt(f); if (!compile_stmt(f, s)) { @@ -394,29 +351,20 @@ namespace { } } const size_t jmp_idx = f.code.size(); - f.code.push_back(isa::enc_jmp(0)); // 回环 + f.code.push_back(E_jmp(0)); patch_jump(f, jmp_idx, loop); patch_jump(f, jf_idx, f.code.size()); return true; } - /** - * @brief 编译 IF / ELSIF / ELSE - * @details 每分支: JF t, L_next → → JMP L_end; - * 最后一个分支无 else 时 JF 直接落 L_end(不补 JMP) - * @param f 当前函数 - * @param st IF 语句 AST - * @return true 成功;false(err 已写) - */ bool compile_if(FuncCtx& f, const Stmt& st) { - // 条件链:IF 体 + ELSIF 各体;ELSE 体可选 std::vector*>> branches; branches.push_back({st.cond.get(), &st.body}); for (const IfBranch& b : st.elsifs) { branches.push_back({b.cond.get(), &b.body}); } const bool has_else = !st.else_body.empty(); - std::vector end_jmps; // 各分支尾部 JMP(回填到 L_end) + std::vector end_jmps; for (size_t i = 0; i < branches.size(); ++i) { const uint8_t t = alloc_temp(f); @@ -424,7 +372,7 @@ namespace { return false; } const size_t jf_idx = f.code.size(); - f.code.push_back(isa::enc_jc(isa::Op::JF, t, 0)); // 假 → 下一分支 + f.code.push_back(E_jc("JF", t, 0)); for (const Stmt& s : *branches[i].second) { begin_stmt(f); if (!compile_stmt(f, s)) { @@ -433,10 +381,10 @@ namespace { } if (i + 1 < branches.size() || has_else) { const size_t jmp_idx = f.code.size(); - f.code.push_back(isa::enc_jmp(0)); // 分支尾部 → L_end + f.code.push_back(E_jmp(0)); end_jmps.push_back(jmp_idx); } - patch_jump(f, jf_idx, f.code.size()); // 回填 JF 到下一分支起点 + patch_jump(f, jf_idx, f.code.size()); } if (has_else) { for (const Stmt& s : st.else_body) { @@ -453,13 +401,6 @@ namespace { return true; } - /** - * @brief 赋值左值:帧寄存器直写或 STORE_* 到全局槽 - * @param f 当前函数 - * @param target 左值名(全局 / 外部) - * @param value 右值表达式 - * @return true 成功;false(err 已写) - */ bool store_target(FuncCtx& f, const std::string& target, const Expr& value) { const auto git = link_.global_index.find(target); if (git == link_.global_index.end()) { @@ -472,49 +413,36 @@ namespace { if (!compile_expr(f, value, tmp)) { return false; } - const uint16_t slot = global_offset(git->second); - f.code.push_back(isa::enc_slot(store_op(target), tmp, slot)); + f.code.push_back(E_slot(store_name(target), tmp, static_cast(git->second))); return true; } - /** - * @brief 表达式编译到目标寄存器 - * @details 字面量 → LOADK;帧变量 → MOVE;全局/外部 → LOAD_*(io.input 用 LOAD_I); - * NOT → 求值后 NOT rd, t;AND/OR → 短路跳转(JF/JT 跳过右侧) - * @param f 当前函数 - * @param e 表达式 AST - * @param rd 目标寄存器 - * @return true 成功;false(err 已写) - */ bool compile_expr(FuncCtx& f, const Expr& e, uint8_t rd) { if (e.kind == ExprKind::LitBool || e.kind == ExprKind::LitInt || e.kind == ExprKind::LitTime) { - const isa::types::TypeTag tag = - e.kind == ExprKind::LitBool ? isa::types::Bool - : e.kind == ExprKind::LitInt ? isa::types::Int - : isa::types::Time; - f.code.push_back( - isa::enc_imm(isa::Op::LOADK, rd, const_id(tag, e.int_value))); + const char* type_name = e.kind == ExprKind::LitBool ? "BOOL" + : e.kind == ExprKind::LitInt ? "INT" + : "TIME"; + f.code.push_back(E_imm("LOADK", rd, const_id(type_name, e.int_value))); return true; } if (e.kind == ExprKind::VarRef) { - // 内联 FB 体内:字段优先(实例字段为变量) if (inline_fields_) { const auto fit = inline_fields_->field_addr.find(e.name); if (fit != inline_fields_->field_addr.end()) { - f.code.push_back(isa::enc_slot(isa::Op::LOAD_GLOBAL, rd, fit->second)); + f.code.push_back(E_slot("LOAD_GLOBAL", rd, fit->second)); return true; } } const auto sit = f.regs.find(e.name); if (sit != f.regs.end()) { - f.code.push_back(isa::enc_rr(isa::Op::MOVE, rd, sit->second)); + f.code.push_back(E_rr("MOVE", rd, sit->second)); return true; } const auto git = link_.global_index.find(e.name); if (git != link_.global_index.end()) { - const uint16_t slot = global_offset(git->second); - f.code.push_back(isa::enc_slot(load_op(e.name), rd, slot)); + f.code.push_back( + E_slot(load_name(e.name), rd, static_cast(git->second))); return true; } return fail("no register or slot for '" + e.name + "'"); @@ -528,7 +456,7 @@ namespace { if (fit == inst->field_addr.end()) { return fail("unknown field '" + e.field + "' for '" + e.name + "'"); } - f.code.push_back(isa::enc_slot(isa::Op::LOAD_GLOBAL, rd, fit->second)); + f.code.push_back(E_slot("LOAD_GLOBAL", rd, fit->second)); return true; } if (e.kind == ExprKind::Add || e.kind == ExprKind::Sub || @@ -541,7 +469,7 @@ namespace { if (!compile_expr(f, *e.rhs, r)) { return false; } - f.code.push_back(isa::enc_rrr(arith_op(e.kind), rd, l, r)); + f.code.push_back(E_rrr(arith_name(e.kind), rd, l, r)); return true; } if (e.kind == ExprKind::Cmp) { @@ -553,7 +481,7 @@ namespace { if (!compile_expr(f, *e.rhs, r)) { return false; } - f.code.push_back(isa::enc_rrr(cmp_op(e.op), rd, l, r)); + f.code.push_back(E_rrr(cmp_name(e.op), rd, l, r)); return true; } if (e.kind == ExprKind::Not) { @@ -561,27 +489,25 @@ namespace { if (!compile_expr(f, *e.operand, t)) { return false; } - f.code.push_back(isa::enc_rr(isa::Op::NOT, rd, t)); + f.code.push_back(E_rr("NOT", rd, t)); return true; } if (e.kind == ExprKind::And || e.kind == ExprKind::Or) { - // 短路:左侧结果在 rd;AND 为假 / OR 为真时跳过右侧 if (!compile_expr(f, *e.lhs, rd)) { return false; } - const isa::Op jop = (e.kind == ExprKind::And) ? isa::Op::JF : isa::Op::JT; + const char* jname = (e.kind == ExprKind::And) ? "JF" : "JT"; const size_t jmp_idx = f.code.size(); - f.code.push_back(isa::enc_jc(jop, rd, 0)); // 偏移留洞,稍后回填 + f.code.push_back(E_jc(jname, rd, 0)); const uint8_t t = alloc_temp(f); if (!compile_expr(f, *e.rhs, t)) { return false; } - f.code.push_back(isa::enc_rr(isa::Op::MOVE, rd, t)); + f.code.push_back(E_rr("MOVE", rd, t)); patch_jump(f, jmp_idx, f.code.size()); return true; } if (e.kind == ExprKind::Call) { - // 调用约定:实参求值到 r8+ 临时 → MOVE 到 r1..r7 → CALL → 结果 r0 if (e.args.size() > 7) { return fail("too many arguments (max 7)"); } @@ -598,21 +524,15 @@ namespace { return fail("no fn_id for '" + e.name + "'"); } for (size_t i = 0; i < args.size(); ++i) { - f.code.push_back( - isa::enc_rr(isa::Op::MOVE, static_cast(1 + i), args[i])); + f.code.push_back(E_rr("MOVE", static_cast(1 + i), args[i])); } - f.code.push_back(isa::enc_call(static_cast(fn_id))); - f.code.push_back(isa::enc_rr(isa::Op::MOVE, rd, 0)); + f.code.push_back(E_call(static_cast(fn_id))); + f.code.push_back(E_rr("MOVE", rd, 0)); return true; } - return fail("expression not supported in slice 6"); + return fail("expression not supported"); } - /** - * @brief 按名查 fn_id(POU 收集序下标) - * @param name POU 名(小写) - * @return fn_id;未找到返回 -1 - */ int find_fn_id(const std::string& name) const { for (size_t i = 0; i < link_.scopes.size(); ++i) { if (link_.scopes[i].name == name) { @@ -622,87 +542,43 @@ namespace { return -1; } - /** - * @brief 回填跳转偏移(相对下一条指令) - * @param f 当前函数 - * @param idx 跳转指令下标(占位) - * @param target_idx 目标指令下标 - */ void patch_jump(FuncCtx& f, size_t idx, size_t target_idx) { const int16_t off = static_cast( static_cast(target_idx) - (static_cast(idx) + 1)); - const isa::Instr w = f.code[idx]; - f.code[idx] = isa::pack(isa::op(w), isa::rd(w), - static_cast(static_cast(off) & 0xFFu), - static_cast((static_cast(off) >> 8) & 0xFFu)); + const Instr w = f.code[idx]; + const uint16_t u = static_cast(off); + f.code[idx] = pack(op_of(w), rd_of(w), static_cast(u & 0xFFu), + static_cast((u >> 8) & 0xFFu)); } - /** - * @brief 算术运算 → 操作码 - * @param k 表达式种类(Add/Sub/Mul/Div) - * @return 对应操作码 - */ - isa::Op arith_op(ExprKind k) const { + // ---- 操作码名(MachineConfig 已强校验存在)---- + static const char* arith_name(ExprKind k) { switch (k) { - case ExprKind::Add: return isa::Op::ADD; - case ExprKind::Sub: return isa::Op::SUB; - case ExprKind::Mul: return isa::Op::MUL; - case ExprKind::Div: return isa::Op::DIV; - default: return isa::Op::ADD; + case ExprKind::Add: return "ADD"; + case ExprKind::Sub: return "SUB"; + case ExprKind::Mul: return "MUL"; + case ExprKind::Div: return "DIV"; + default: return "ADD"; } } - - /** - * @brief 比较符 → CMP_xx 操作码 - * @param op 比较运算 - * @return 对应操作码(CMP_EQ / CMP_NE / CMP_LT / CMP_LE / CMP_GT / CMP_GE) - */ - isa::Op cmp_op(BinOp op) const { + static const char* cmp_name(BinOp op) { switch (op) { - case BinOp::Eq: return isa::Op::CMP_EQ; - case BinOp::Ne: return isa::Op::CMP_NE; - case BinOp::Lt: return isa::Op::CMP_LT; - case BinOp::Le: return isa::Op::CMP_LE; - case BinOp::Gt: return isa::Op::CMP_GT; - case BinOp::Ge: return isa::Op::CMP_GE; - default: return isa::Op::CMP_EQ; // Add/Sub/Mul/Div 不经此函数 + case BinOp::Eq: return "CMP_EQ"; + case BinOp::Ne: return "CMP_NE"; + case BinOp::Lt: return "CMP_LT"; + case BinOp::Le: return "CMP_LE"; + case BinOp::Gt: return "CMP_GT"; + case BinOp::Ge: return "CMP_GE"; + default: return "CMP_EQ"; } } - - /** - * @brief 按 io 绑定选读取操作码 - * @param name 变量名(小写) - * @return io.input 绑定 → LOAD_I;否则 LOAD_GLOBAL - */ - isa::Op load_op(const std::string& name) const { - return io_input_.count(name) ? isa::Op::LOAD_I : isa::Op::LOAD_GLOBAL; + const char* load_name(const std::string& name) const { + return io_input_.count(name) ? "LOAD_I" : "LOAD_GLOBAL"; + } + const char* store_name(const std::string& name) const { + return io_output_.count(name) ? "STORE_Q" : "STORE_GLOBAL"; } - /** - * @brief 按 io 绑定选写入操作码 - * @param name 变量名(小写) - * @return io.output 绑定 → STORE_Q;否则 STORE_GLOBAL - */ - isa::Op store_op(const std::string& name) const { - return io_output_.count(name) ? isa::Op::STORE_Q : isa::Op::STORE_GLOBAL; - } - - /** - * @brief 逻辑槽号(符号表 address)即指令 slot(方案 a:8 字节定宽,无映射) - * @param slot 全局逻辑槽号 - * @return 同值(u16 可容,layout_data 已校验) - */ - uint16_t global_offset(uint32_t slot) const { - return static_cast(slot); - } - - /** - * @brief 布局全局数据区:每槽 8 字节定宽(方案 a) - * @details 槽号 = 符号表 address(声明序,12.6); - * BOOL 用低 1 字节、INT 用低 2 字节(小端)、TIME 全 8 字节; - * 初值取自 GVL 声明的 has_init(无则 0) - * @return true 成功;false(槽号越界,err 已写) - */ bool layout_data() { for (const Symbol& s : link_.globals) { if (s.address > 0xFFFF) { @@ -714,30 +590,32 @@ namespace { const int64_t v = has_init ? init : 0; const size_t off = static_cast(s.address) * 8; data_.resize(off + 8, 0); - if (s.type_name == "int") { + // 初值按类型元数据写(配置别名 → 基元宽度/浮点) + const TypeMeta tm = type_meta(cfg_, s.type_name); + if (!tm) { + return fail("no type in machine.toml for '" + s.type_name + "'"); + } + if (tm.is_float()) { + return fail("float initializer not supported yet"); + } + if (tm.width() == 2) { const uint16_t iv = static_cast(v); data_[off] = static_cast(iv & 0xFFu); data_[off + 1] = static_cast((iv >> 8) & 0xFFu); - } else if (s.type_name == "time") { + } else if (tm.width() == 8) { const uint64_t tv = static_cast(v); for (int i = 0; i < 8; ++i) { data_[off + i] = static_cast((tv >> (8 * i)) & 0xFFu); } - } else { // bool + } else { data_[off] = v ? 1 : 0; } } return true; } - /** - * @brief 布局 FB 实例块:数据区全局之后,按 POU 收集序 / 实例声明序 - * @details 每实例:字段按布局段序各占一槽(8 字节定宽), - * 字段地址 = 实例基槽 + 字段序号;实例基槽 = 累计槽数 - * @return true 成功;false(err 已写) - */ bool layout_fb_instances() { - uint32_t cur = static_cast(data_.size() / 8); // 槽号起点 + uint32_t cur = static_cast(data_.size() / 8); bool any = false; for (const LinkResult::PouScope& sc : link_.scopes) { for (const Symbol& s : sc.syms) { @@ -749,7 +627,7 @@ namespace { return fail("no layout for instance '" + s.name + "'"); } InstFields inst; - inst.base = cur; // 实例基槽(CAL_* 操作数) + inst.base = cur; inst.type_name = s.type_name; for (size_t i = 0; i < lay->second.fields.size(); ++i) { inst.field_addr[lay->second.fields[i].name] = cur + i; @@ -759,31 +637,18 @@ namespace { any = true; } } - // 仅在有实例时把数据区补到实例区终点(无实例则保持全局区原样) if (any) { data_.resize(static_cast(cur) * 8, 0); } return true; } - /** - * @brief 按 POU + 实例名查实例字段表 - * @param pou POU 名 - * @param name 实例名 - * @return 实例字段表指针;未找到返回 nullptr - */ const InstFields* instance_of(const std::string& pou, const std::string& name) const { const auto it = instances_.find(pou + "/" + name); return it == instances_.end() ? nullptr : &it->second; } - /** - * @brief 查全局声明的初值(AST;gvl 文件顶层段,声明序 = 槽号序) - * @param name 全局名(小写) - * @param has_init 输出是否有初值 - * @param init 输出初值 - */ void init_of(const std::string& name, bool* has_init, int64_t* init) const { for (const SourceUnit& u : units_) { for (const VarBlock& b : u.ast.globals) { @@ -802,11 +667,13 @@ namespace { /** * @brief 取常量表 id(无则追加) - * @param tag 类型标记(BOOL/INT/TIME) + * @param type_name 语言类型名("BOOL"/"INT"/"TIME")→ 配置 tag(契约) * @param value 常量值 * @return const_id(u16) */ - uint16_t const_id(isa::types::TypeTag tag, int64_t value) { + uint16_t const_id(const char* type_name, int64_t value) { + const TypeMeta tm = type_meta(cfg_, type_name); + const uint32_t tag = tm ? tm.tag() : 0; for (size_t i = 0; i < consts_.size(); ++i) { if (consts_[i].tag == tag && consts_[i].value == static_cast(value)) { return static_cast(i); @@ -816,33 +683,28 @@ namespace { return static_cast(consts_.size() - 1); } - /** - * @brief 拼映像:头 + 常量表 + 函数表 + 字节码 + 数据段 - * @details 段序:const → funcs → code → fb(空)→ data; - * 数据段放全局初值(layout_data 已生成字节) - * @return true 成功;false(err 已写) - */ bool assemble_image() { - const uint32_t off_const = isa::kHeaderSize; + const uint32_t off_const = static_cast(kHeaderSize); const uint32_t off_funcs = off_const + - static_cast(consts_.size()) * isa::kConstEntrySize; + static_cast(consts_.size()) * static_cast(kConstEntrySize); uint32_t off_code = off_funcs + - static_cast(funcs_.size()) * isa::kFuncRowSize; + static_cast(funcs_.size()) * static_cast(kFuncRowSize); uint32_t code_total = 0; for (const FuncCtx& f : funcs_) { code_total += static_cast(f.code.size()) * 4; } const uint32_t off_data = off_code + code_total; - const uint32_t off_end = off_data + static_cast(data_.size()); + const uint32_t off_end = off_data + static_cast(data_.size()) + + static_cast(kSha256Size); std::vector& b = *image_; b.assign(off_end, 0); - put_le32(b, 0, isa::kMagic); - put_le32(b, 4, isa::kVersion); + put_le32(b, 0, kMagic); + put_le32(b, 4, kVersion); put_le32(b, 8, proj_.cycle_limit); put_le32(b, 12, proj_.dt_ms); - uint64_t hash = isa::kFnvBasis; + uint64_t hash = kFnvBasis; if (!compute_project_hash(proj_, &hash, err_)) { return false; } @@ -855,80 +717,97 @@ namespace { } } put_le32(b, 24, entry); - put_le32(b, 28, static_cast(link_.globals.size())); // n_globals - put_le32(b, 32, 0); // n_i - put_le32(b, 36, 0); // n_q - put_le32(b, 40, 0); // n_m + put_le32(b, 28, static_cast(link_.globals.size())); + put_le32(b, 32, 0); + put_le32(b, 36, 0); + put_le32(b, 40, 0); put_le32(b, 44, static_cast(consts_.size())); put_le32(b, 48, static_cast(funcs_.size())); put_le32(b, 52, off_const); put_le32(b, 56, off_funcs); put_le32(b, 60, off_code); - put_le32(b, 64, off_data); // offset_fb(空,与数据段起点相同) - put_le32(b, 68, off_data); // offset_data + put_le32(b, 64, off_data); + put_le32(b, 68, off_data); + + // 型号标识[32](偏移 72;12.13 修订) + char mid[kModelIdSize]; + fill_model_id(cfg_.model_name(), cfg_.version(), mid); + for (size_t i = 0; i < kModelIdSize; ++i) { + b[72 + i] = static_cast(mid[i]); + } for (size_t i = 0; i < consts_.size(); ++i) { - const size_t o = off_const + i * isa::kConstEntrySize; - put_le32(b, o, static_cast(consts_[i].tag)); + const size_t o = off_const + i * kConstEntrySize; + put_le32(b, o, consts_[i].tag); put_le64(b, o + 4, consts_[i].value); } uint32_t c = off_code; for (size_t i = 0; i < funcs_.size(); ++i) { const FuncCtx& f = funcs_[i]; - const size_t o = off_funcs + i * isa::kFuncRowSize; + const size_t o = off_funcs + i * kFuncRowSize; put_le32(b, o, f.nregs); put_le32(b, o + 8, static_cast(f.code.size())); - for (const isa::Instr in : f.code) { + for (const Instr in : f.code) { put_le32(b, c, in); c += 4; } } - // code_offset 回填(相对字节码段起点) uint32_t acc = 0; for (size_t i = 0; i < funcs_.size(); ++i) { - const size_t o = off_funcs + i * isa::kFuncRowSize; + const size_t o = off_funcs + i * kFuncRowSize; put_le32(b, o + 4, acc); acc += static_cast(funcs_[i].code.size()) * 4; } - // 数据段:全局初值字节 for (size_t i = 0; i < data_.size(); ++i) { b[off_data + i] = data_[i]; } + + // 文件尾 SHA-256(对文件尾之前全部内容计算) + const size_t content_len = off_data + data_.size(); + uint8_t digest[kSha256Size]; + sha256(&b[0], content_len, digest); + for (size_t i = 0; i < kSha256Size; ++i) { + b[content_len + i] = digest[i]; + } return true; } + static void put_le32(std::vector& b, size_t off, uint32_t v) { + b[off + 0] = static_cast(v & 0xFFu); + b[off + 1] = static_cast((v >> 8) & 0xFFu); + b[off + 2] = static_cast((v >> 16) & 0xFFu); + b[off + 3] = static_cast((v >> 24) & 0xFFu); + } + static void put_le64(std::vector& b, size_t off, uint64_t v) { + for (int i = 0; i < 8; ++i) { + b[off + i] = static_cast((v >> (8 * i)) & 0xFFu); + } + } + // ---- 成员 ---- const Project& proj_; const std::vector& units_; const LinkResult& link_; + const MachineConfig& cfg_; std::vector* image_; std::string* err_; std::vector funcs_; - std::vector consts_; - std::map io_input_; // io.input 绑定名(小写) - std::map io_output_; // io.output 绑定名(小写) - std::vector data_; // 数据区(8 字节定宽槽:全局 + FB 实例块) - std::map instances_; // "POU/实例名" → 字段槽号 - const InstFields* inline_fields_ = nullptr; // 内联 FB 体字段上下文(可空) + std::vector consts_; + std::map io_input_; + std::map io_output_; + std::vector data_; + std::map instances_; + const InstFields* inline_fields_ = nullptr; }; } // namespace -/** - * @brief 编译工程为 .stb 映像字节(对外入口) - * @param proj 工程定义 - * @param units 全部源文件的 AST - * @param link 链接结果(在类型检查成功后调用) - * @param image 输出映像字节 - * @param err 错误输出;可为 nullptr(静默) - * @return true 成功;false 失败(err 前缀 "codegen error") - */ bool codegen_project(const Project& proj, const std::vector& units, - const LinkResult& link, std::vector* image, - std::string* err) { - Builder b(proj, units, link, image, err); + const LinkResult& link, const MachineConfig& cfg, + std::vector* image, std::string* err) { + Builder b(proj, units, link, cfg, image, err); return b.run(); } diff --git a/compiler/src/Linker.cpp b/compiler/src/Linker.cpp index da25153..4bc9969 100644 --- a/compiler/src/Linker.cpp +++ b/compiler/src/Linker.cpp @@ -662,7 +662,7 @@ namespace { * @return true 全部绑定合法;false(err 已写) */ bool check_io() { - for (const isa::IoBinding& b : proj_.io) { + for (const IoBinding& b : proj_.io) { std::string key = b.var; for (char& ch : key) { ch = static_cast(std::tolower(static_cast(ch))); diff --git a/compiler/src/MachineConfig.cpp b/compiler/src/MachineConfig.cpp new file mode 100644 index 0000000..f3c000a --- /dev/null +++ b/compiler/src/MachineConfig.cpp @@ -0,0 +1,396 @@ +/** + * @file MachineConfig.cpp + * @brief 机器定义(machine.toml)加载与强校验 + * @author + * @date 2026-08-21 + * + * @details 校验项(见 Doc/isa/指令配置.md 强校验清单 1~5): + * 1. TOML 语法与字段完整性(全属性必填) + * 2. type.base 命中内建基元;range 合法(min ≤ max) + * 3. tag 与 .stb 常量表契约一致(BOOL=0、INT=1、TIME=2,唯一) + * 4. op.opcode 唯一、0..255;class/format 合法;类别-格式互锁(INSTANCE ↔ CAL); + * params 数量与 format 一致 + * 5. fb.opcode 与 op 行(instance 类)一致;字段类型命中配置类型表;字段名唯一 + */ + +#include "compiler/MachineConfig.h" + +#include + +#include +#include + +namespace compiler { + +const std::vector& MachineConfig::prims() { + static const std::vector kPrims = { + {"bit", 1, false, false}, + {"int8", 1, true, false}, + {"int16", 2, true, false}, + {"int32", 4, true, false}, + {"int64", 8, true, false}, + {"uint8", 1, false, false}, + {"uint16", 2, false, false}, + {"uint32", 4, false, false}, + {"uint64", 8, false, false}, + {"float32", 4, true, true}, + {"float64", 8, true, true}, + }; + return kPrims; +} + +const PrimType* MachineConfig::find_prim(const std::string& name) { + for (const PrimType& p : prims()) { + if (p.name == name) { + return &p; + } + } + return nullptr; +} + +namespace { + + // 稳定前缀 + std::string fail(std::string* err, const std::string& msg) { + if (err) { + *err = "machine error: " + msg; + } + return msg; + } + + // format → 参数数量(解析时校验) + int params_of(const std::string& format) { + if (format == "RR") return 2; + if (format == "RRR") return 3; + if (format == "IMM") return 2; + if (format == "SLOT") return 2; + if (format == "JMP") return 1; + if (format == "JC") return 2; + if (format == "CALL") return 1; + if (format == "CAL") return 1; + if (format == "NONE") return 0; + return -1; + } + + bool is_known_format(const std::string& f) { + return params_of(f) >= 0; + } + + bool is_known_tag(uint32_t tag) { + return tag <= 2; // .stb 常量表契约:0=BOOL 1=INT 2=TIME + } + + // 必填字符串 + bool req_string(const toml::table& t, const char* what, const std::string& key, + std::string* out, std::string* err) { + const auto nv = t[key]; + if (!nv || !nv.is_string()) { + fail(err, std::string("missing field '") + key + "' in " + what); + return false; + } + *out = nv.value_or(std::string()); + return true; + } + + // 必填整数 + bool req_int(const toml::table& t, const char* what, const std::string& key, + int64_t* out, std::string* err) { + const auto nv = t[key]; + if (!nv || !nv.is_integer()) { + fail(err, std::string("missing field '") + key + "' in " + what); + return false; + } + *out = nv.value_or(0); + return true; + } + + // 必填布尔 + bool req_bool(const toml::table& t, const char* what, const std::string& key, + bool* out, std::string* err) { + const auto nv = t[key]; + if (!nv || !nv.is_boolean()) { + fail(err, std::string("missing field '") + key + "' in " + what); + return false; + } + *out = nv.value_or(false); + return true; + } + +} // namespace + +bool MachineConfig::load(const std::string& path, std::string* err) { + ok_ = false; + model_name_.clear(); + version_ = 0; + types_.clear(); + ops_.clear(); + fbs_.clear(); + + toml::table root; + try { + root = toml::parse_file(path); + } catch (const toml::parse_error& e) { + fail(err, std::string("parse error: ") + std::string(e.description())); + return false; + } + + // ---- meta ---- + const toml::table* meta = root["meta"].as_table(); + if (!meta) { + fail(err, "missing table 'meta'"); + return false; + } + if (!req_string(*meta, "[meta]", "name", &model_name_, err)) return false; + int64_t ver = 0; + if (!req_int(*meta, "[meta]", "version", &ver, err)) return false; + if (ver < 0 || ver > 0xFFFFFFFFLL) { + fail(err, "invalid version in [meta]"); + return false; + } + version_ = static_cast(ver); + + // ---- type ---- + { + const toml::array* arr = root["type"].as_array(); + if (!arr || arr->empty()) { + fail(err, "missing 'type' table"); + return false; + } + std::map tag_owner; // tag → 类型名 + for (const auto& el : *arr) { + const toml::table* t = el.as_table(); + if (!t) { + fail(err, "bad entry in type"); + return false; + } + ConfigType ct; + if (!req_string(*t, "type", "name", &ct.name, err)) return false; + if (!req_string(*t, "type", "base", &ct.base, err)) return false; + if (find_prim(ct.base) == nullptr) { + fail(err, "type '" + ct.name + "': unknown base '" + ct.base + "'"); + return false; + } + int64_t tag = 0; + if (!req_int(*t, "type", "tag", &tag, err)) return false; + if (tag < 0 || !is_known_tag(static_cast(tag))) { + fail(err, "type '" + ct.name + "': tag out of contract (0..2)"); + return false; + } + ct.tag = static_cast(tag); + if (tag_owner.count(ct.tag)) { + fail(err, "duplicate tag " + std::to_string(ct.tag)); + return false; + } + tag_owner[ct.tag] = ct.name; + if (t->contains("range")) { + const toml::array* r = (*t)["range"].as_array(); + if (!r || r->size() != 2) { + fail(err, "type '" + ct.name + "': range must be [min, max]"); + return false; + } + ct.has_range = true; + ct.range_min = (*r)[0].value_or(0); + ct.range_max = (*r)[1].value_or(0); + if (ct.range_min > ct.range_max) { + fail(err, "type '" + ct.name + "': range min > max"); + return false; + } + } + types_.push_back(ct); + } + // .stb 契约:BOOL/INT/TIME 必须存在且 tag 为 0/1/2 + const std::string need[3] = {"BOOL", "INT", "TIME"}; + for (uint32_t i = 0; i < 3; ++i) { + const auto it = tag_owner.find(i); + if (it == tag_owner.end() || it->second != need[i]) { + fail(err, std::string("type contract broken: tag ") + std::to_string(i) + + " must be " + need[i]); + return false; + } + } + } + + // ---- op ---- + { + const toml::array* arr = root["op"].as_array(); + if (!arr || arr->empty()) { + fail(err, "missing 'op' table"); + return false; + } + std::map code_owner; + std::map name_owner; + for (const auto& el : *arr) { + const toml::table* t = el.as_table(); + if (!t) { + fail(err, "bad entry in op"); + return false; + } + ConfigOp op; + if (!req_string(*t, "op", "name", &op.name, err)) return false; + int64_t code = 0; + if (!req_int(*t, "op", "opcode", &code, err)) return false; + if (code < 0 || code > 255) { + fail(err, "op '" + op.name + "': opcode out of range"); + return false; + } + op.opcode = static_cast(code); + if (code_owner.count(op.opcode)) { + fail(err, "duplicate opcode " + std::to_string(op.opcode)); + return false; + } + code_owner[op.opcode] = op.name; + if (name_owner.count(op.name)) { + fail(err, "duplicate op name '" + op.name + "'"); + return false; + } + name_owner[op.name] = op.opcode; + std::string cls; + if (!req_string(*t, "op", "class", &cls, err)) return false; + if (cls == "instance") { + op.is_instance = true; + } else if (cls == "plain") { + op.is_instance = false; + } else { + fail(err, "op '" + op.name + "': bad class '" + cls + "'"); + return false; + } + if (!req_string(*t, "op", "format", &op.format, err)) return false; + if (!is_known_format(op.format)) { + fail(err, "op '" + op.name + "': bad format '" + op.format + "'"); + return false; + } + // 类别-格式互锁:INSTANCE ↔ CAL + const bool is_cal = op.format == "CAL"; + if (op.is_instance != is_cal) { + fail(err, "op '" + op.name + "': class-format mismatch (instance <-> CAL)"); + return false; + } + if (!req_bool(*t, "op", "enabled", &op.enabled, err)) return false; + const auto pv = (*t)["params"]; + if (!pv || !pv.is_array()) { + fail(err, "op '" + op.name + "': missing 'params'"); + return false; + } + const int want = params_of(op.format); + for (const auto& p : *pv.as_array()) { + if (!p.is_string()) { + fail(err, "op '" + op.name + "': params must be strings"); + return false; + } + op.params.push_back(p.value_or(std::string())); + } + if (static_cast(op.params.size()) != want) { + fail(err, "op '" + op.name + "': params count " + + std::to_string(op.params.size()) + " != format expects " + + std::to_string(want)); + return false; + } + ops_.push_back(op); + } + } + + // ---- fb ---- + { + const toml::array* arr = root["fb"].as_array(); + if (!arr || arr->empty()) { + fail(err, "missing 'fb' table"); + return false; + } + for (const auto& el : *arr) { + const toml::table* t = el.as_table(); + if (!t) { + fail(err, "bad entry in fb"); + return false; + } + ConfigFb fb; + if (!req_string(*t, "fb", "name", &fb.name, err)) return false; + int64_t code = 0; + if (!req_int(*t, "fb", "opcode", &code, err)) return false; + fb.opcode = static_cast(code); + // opcode 必须命中 instance 类 op 行 + const ConfigOp* op = find_op_by_code(fb.opcode); + if (op == nullptr || !op->is_instance) { + fail(err, "fb '" + fb.name + "': opcode " + std::to_string(code) + + " must match an instance op"); + return false; + } + const auto fv = (*t)["fields"]; + if (!fv || !fv.is_array()) { + fail(err, "fb '" + fb.name + "': missing 'fields'"); + return false; + } + std::map fnames; + for (const auto& f : *fv.as_array()) { + const toml::array* pair = f.as_array(); + if (!pair || pair->size() != 2) { + fail(err, "fb '" + fb.name + "': field must be [name, type]"); + return false; + } + ConfigFbField field; + field.name = (*pair)[0].value_or(std::string()); + field.type = (*pair)[1].value_or(std::string()); + if (field.name.empty() || field.type.empty()) { + fail(err, "fb '" + fb.name + "': empty field name/type"); + return false; + } + if (fnames.count(field.name)) { + fail(err, "fb '" + fb.name + "': duplicate field '" + field.name + "'"); + return false; + } + fnames[field.name] = true; + if (find_type(field.type) == nullptr) { + fail(err, "fb '" + fb.name + "': unknown field type '" + field.type + "'"); + return false; + } + fb.fields.push_back(field); + } + fbs_.push_back(fb); + } + } + + ok_ = true; + return true; +} + +const ConfigType* MachineConfig::find_type(const std::string& name) const { + for (const ConfigType& t : types_) { + if (t.name == name) { + return &t; + } + } + return nullptr; +} + +const ConfigOp* MachineConfig::find_op(const std::string& name) const { + for (const ConfigOp& o : ops_) { + if (o.name == name) { + return &o; + } + } + return nullptr; +} + +const ConfigOp* MachineConfig::find_op_by_code(uint32_t opcode) const { + for (const ConfigOp& o : ops_) { + if (o.opcode == opcode) { + return &o; + } + } + return nullptr; +} + +bool MachineConfig::op_enabled(uint32_t opcode) const { + const ConfigOp* op = find_op_by_code(opcode); + return op != nullptr && op->enabled; +} + +const ConfigFb* MachineConfig::find_fb(const std::string& name) const { + for (const ConfigFb& f : fbs_) { + if (f.name == name) { + return &f; + } + } + return nullptr; +} + +} // namespace compiler diff --git a/compiler/src/Project.cpp b/compiler/src/Project.cpp index a7f748a..af2b20c 100644 --- a/compiler/src/Project.cpp +++ b/compiler/src/Project.cpp @@ -16,7 +16,7 @@ #include #include -#include "isa/Image.h" +#include "compiler/Stb.h" namespace compiler { namespace { @@ -241,7 +241,7 @@ namespace { if (!check_keys(sec, is_input ? "io.input" : "io.output", allowed, 3, err)) { return false; } - isa::IoBinding b; + IoBinding b; b.is_input = is_input; b.slot = 0; // 12.6 链接阶段再解析 if (!req_string(sec, is_input ? "io.input" : "io.output", "var", &b.var, err)) { @@ -353,7 +353,7 @@ bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err) { std::sort(sorted.begin(), sorted.end()); std::filesystem::path base(p.base_dir); - uint64_t h = isa::kFnvBasis; + uint64_t h = kFnvBasis; for (const std::string& f : sorted) { std::ifstream in(base / f, std::ios::binary); if (!in) { @@ -365,7 +365,7 @@ bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err) { in.read(buf, sizeof buf); const std::streamsize n = in.gcount(); if (n > 0) { - h = isa::fnv1a64_update(h, reinterpret_cast(buf), + h = fnv1a64_update(h, reinterpret_cast(buf), static_cast(n)); } } diff --git a/compiler/src/Stb.cpp b/compiler/src/Stb.cpp new file mode 100644 index 0000000..156fc2f --- /dev/null +++ b/compiler/src/Stb.cpp @@ -0,0 +1,378 @@ +/** + * @file Stb.cpp + * @brief 编译器自带的 .stb 映像规范(写侧)+ 只读视图 + FNV-1a + sidecar + * @author + * @date 2026-08-21 + */ + +#include "compiler/Stb.h" + +#include +#include +#include + +#include "compiler/Project.h" + +namespace compiler { + +uint64_t fnv1a64_update(uint64_t h, const uint8_t* data, size_t len) { + for (size_t i = 0; i < len; ++i) { + h ^= data[i]; + h *= kFnvPrime; + } + return h; +} + +uint64_t fnv1a64(const uint8_t* data, size_t len) { + return fnv1a64_update(kFnvBasis, data, len); +} + +namespace { + + uint32_t get_le32(const uint8_t* p) { + return static_cast(p[0]) + | (static_cast(p[1]) << 8) + | (static_cast(p[2]) << 16) + | (static_cast(p[3]) << 24); + } + + uint64_t get_le64(const uint8_t* p) { + uint64_t v = 0; + for (int i = 0; i < 8; ++i) { + v |= static_cast(p[i]) << (8 * i); + } + return v; + } + + // ---- SHA-256(FIPS 180-4)---- + + const uint32_t kShaK[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + }; + + inline uint32_t rotr(uint32_t x, uint32_t n) { return (x >> n) | (x << (32 - n)); } + + struct Sha256 { + uint32_t h[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; + uint64_t total = 0; + uint8_t block[64]; + size_t block_len = 0; + + void update(const uint8_t* data, size_t len) { + total += len; + while (len > 0) { + const size_t take = (block_len < 64) ? (64 - block_len) : 0; + const size_t n = len < take ? len : take; + if (n > 0) { + for (size_t i = 0; i < n; ++i) { + block[block_len + i] = data[i]; + } + block_len += n; + data += n; + len -= n; + if (block_len == 64) { + process(); + block_len = 0; + } + } else { + break; + } + } + } + + void process() { + uint32_t w[64]; + for (int i = 0; i < 16; ++i) { + w[i] = get_be32(block + i * 4); + } + for (int i = 16; i < 64; ++i) { + const uint32_t s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ (w[i - 15] >> 3); + const uint32_t s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ (w[i - 2] >> 10); + w[i] = w[i - 16] + s0 + w[i - 7] + s1; + } + uint32_t a = h[0], b = h[1], c = h[2], d = h[3]; + uint32_t e = h[4], f = h[5], g = h[6], hh = h[7]; + for (int i = 0; i < 64; ++i) { + const uint32_t s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25); + const uint32_t ch = (e & f) ^ (~e & g); + const uint32_t t1 = hh + s1 + ch + kShaK[i] + w[i]; + const uint32_t s0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22); + const uint32_t maj = (a & b) ^ (a & c) ^ (b & c); + const uint32_t t2 = s0 + maj; + hh = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + h[0] += a; h[1] += b; h[2] += c; h[3] += d; + h[4] += e; h[5] += f; h[6] += g; h[7] += hh; + } + + void final(uint8_t out[32]) { + const uint64_t bitlen = total * 8; + const uint8_t pad = 0x80; + update(&pad, 1); + const uint8_t zeros[64] = {0}; + // 补零到 block_len == 56(跨块时先补满当前块再补) + while (block_len != 56) { + const size_t n = (block_len < 56) ? (56 - block_len) : (64 - block_len); + update(zeros, n); + } + // 64 位大端比特长度 + for (int i = 0; i < 8; ++i) { + const uint8_t b2[1] = {static_cast((bitlen >> (56 - 8 * i)) & 0xFF)}; + update(b2, 1); + } + for (int i = 0; i < 8; ++i) { + out[i * 4 + 0] = static_cast((h[i] >> 24) & 0xFF); + out[i * 4 + 1] = static_cast((h[i] >> 16) & 0xFF); + out[i * 4 + 2] = static_cast((h[i] >> 8) & 0xFF); + out[i * 4 + 3] = static_cast(h[i] & 0xFF); + } + } + + static uint32_t get_be32(const uint8_t* p) { + return (static_cast(p[0]) << 24) | (static_cast(p[1]) << 16) | + (static_cast(p[2]) << 8) | static_cast(p[3]); + } + }; + +} // namespace + +void sha256(const uint8_t* data, size_t len, uint8_t out[kSha256Size]) { + Sha256 s; + s.update(data, len); + s.final(out); +} + +void fill_model_id(const std::string& name, uint32_t version, char out[kModelIdSize]) { + const std::string id = name + std::to_string(version); + for (size_t i = 0; i < kModelIdSize; ++i) { + out[i] = i < id.size() ? id[i] : '\0'; + } +} + +StbView StbView::from(const uint8_t* buf, size_t len) { + StbView v; + v.buf_ = buf; + v.len_ = len; + if (buf == nullptr) { + v.err_ = "null buffer"; + return v; + } + if (len < kHeaderSize) { + v.err_ = "image too short"; + return v; + } + if (get_le32(buf + 0) != kMagic) { + v.err_ = "bad magic"; + return v; + } + if (get_le32(buf + 4) != kVersion) { + v.err_ = "bad version"; + return v; + } + v.cycle_limit_ = get_le32(buf + 8); + v.dt_ms_ = get_le32(buf + 12); + v.project_hash_ = get_le64(buf + 16); + v.entry_fn_id_ = get_le32(buf + 24); + v.n_globals_ = get_le32(buf + 28); + v.n_consts_ = get_le32(buf + 44); + v.n_funcs_ = get_le32(buf + 48); + v.offset_code_ = get_le32(buf + 60); + v.offset_fb_ = get_le32(buf + 64); + v.offset_data_ = get_le32(buf + 68); + + // 段校验(12.13:文件尾 SHA-256[32] 在数据段之后) + if (len < static_cast(v.offset_data_) + kSha256Size) { + v.err_ = "missing sha256 tail"; + return v; + } + const uint64_t offs[5] = {get_le32(buf + 52), get_le32(buf + 56), v.offset_code_, + v.offset_fb_, v.offset_data_}; + for (int i = 0; i < 5; ++i) { + if (offs[i] < kHeaderSize || offs[i] > len - kSha256Size) { + v.err_ = "segment offset out of range"; + return v; + } + if (i > 0 && offs[i] < offs[i - 1]) { + v.err_ = "segment offsets not monotonic"; + return v; + } + } + if (offs[1] - offs[0] != static_cast(v.n_consts_) * kConstEntrySize) { + v.err_ = "const table size mismatch"; + return v; + } + if (offs[2] - offs[1] != static_cast(v.n_funcs_) * kFuncRowSize) { + v.err_ = "function table size mismatch"; + return v; + } + if ((v.offset_fb_ - v.offset_code_) % 4 != 0) { + v.err_ = "code segment not 4-byte aligned"; + return v; + } + if (v.entry_fn_id_ >= v.n_funcs_ && v.n_funcs_ != 0) { + v.err_ = "entry fn_id out of range"; + return v; + } + v.ok_ = true; + v.err_.clear(); + return v; +} + +StbView StbView::from(const std::vector& buf) { + return from(buf.data(), buf.size()); +} + +ConstEntry StbView::const_entry(size_t i) const { + ConstEntry e; + if (ok_ && i < n_consts_) { + const uint8_t* p = buf_ + offs_of_const() + i * kConstEntrySize; + e.tag = get_le32(p); + e.value = get_le64(p + 4); + } + return e; +} + +uint32_t StbView::offs_of_const() const { + // offset_const = offs[0],由 from() 已校验的段起点 + return static_cast(get_le32(buf_ + 52)); +} + +StbView::FuncRow StbView::func_row(size_t i) const { + FuncRow r; + if (ok_ && i < n_funcs_) { + const uint8_t* p = buf_ + get_le32(buf_ + 56) + i * kFuncRowSize; + r.nregs = get_le32(p + 0); + r.code_offset = get_le32(p + 4); + r.code_len = get_le32(p + 8); + } + return r; +} + +const uint8_t* StbView::code_bytes() const { + return ok_ ? buf_ + offset_code_ : nullptr; +} + +size_t StbView::code_len() const { + return ok_ ? offset_fb_ - offset_code_ : 0; +} + +const uint8_t* StbView::data_bytes() const { + return ok_ ? buf_ + offset_data_ : nullptr; +} + +size_t StbView::data_len() const { + return ok_ ? (len_ - kSha256Size) - offset_data_ : 0; +} + +std::string StbView::model_id() const { + if (!ok_) { + return ""; + } + std::string s(reinterpret_cast(buf_ + 72), kModelIdSize); + const size_t z = s.find('\0'); + if (z != std::string::npos) { + s.resize(z); + } + return s; +} + +bool StbView::model_matches(const std::string& name, uint32_t version) const { + char want[kModelIdSize]; + fill_model_id(name, version, want); + return std::memcmp(buf_ + 72, want, kModelIdSize) == 0; +} + +bool StbView::sha_ok() const { + if (!ok_) { + return false; + } + const size_t content_len = len_ - kSha256Size; + uint8_t digest[kSha256Size]; + sha256(buf_, content_len, digest); + return std::memcmp(buf_ + content_len, digest, kSha256Size) == 0; +} + +bool read_stb_file(const char* path, std::vector* out, std::string* err) { + std::ifstream in(path, std::ios::binary); + if (!in) { + if (err) { + *err = "cannot open for read: " + std::string(path); + } + return false; + } + out->assign(std::istreambuf_iterator(in), std::istreambuf_iterator()); + return true; +} + +bool write_stb_file(const char* path, const std::vector& img, std::string* err) { + std::FILE* f = std::fopen(path, "wb"); + if (f == nullptr) { + if (err) { + *err = "cannot open for write: " + std::string(path); + } + return false; + } + const bool ok = img.empty() || std::fwrite(&img[0], 1, img.size(), f) == img.size(); + std::fclose(f); + if (!ok && err) { + *err = "write failed: " + std::string(path); + } + return ok; +} + +std::string make_sidecar(const std::vector& bindings) { + std::string out; + for (const IoBinding& b : bindings) { + char buf[64]; + out += b.is_input ? "[[io.input]]\n" : "[[io.output]]\n"; + out += "var = \""; + out += b.var; + out += "\"\n"; + std::snprintf(buf, sizeof buf, "slot = %u\n", static_cast(b.slot)); + out += buf; + std::snprintf(buf, sizeof buf, "channel = %u\n", static_cast(b.channel)); + out += buf; + std::snprintf(buf, sizeof buf, "bit = %u\n", static_cast(b.bit)); + out += buf; + out += "\n"; + } + return out; +} + +bool write_sidecar_file(const char* path, const std::vector& bindings, + std::string* err) { + std::FILE* f = std::fopen(path, "wb"); + if (f == nullptr) { + if (err) { + *err = "cannot open for write: " + std::string(path); + } + return false; + } + const std::string s = make_sidecar(bindings); + const bool ok = s.empty() || std::fwrite(s.data(), 1, s.size(), f) == s.size(); + std::fclose(f); + if (!ok && err) { + *err = "write failed: " + std::string(path); + } + return ok; +} + +} // namespace compiler diff --git a/compiler/src/TypeInfo.cpp b/compiler/src/TypeInfo.cpp new file mode 100644 index 0000000..3c43e5c --- /dev/null +++ b/compiler/src/TypeInfo.cpp @@ -0,0 +1,41 @@ +/** + * @file TypeInfo.cpp + * @brief 语言类型元数据(machine.toml 别名 + 内建基元解析) + * @author + * @date 2026-08-21 + */ + +#include "compiler/TypeInfo.h" + +#include + +namespace compiler { + +namespace { + + std::string lower(const std::string& s) { + std::string out = s; + for (char& ch : out) { + ch = static_cast(std::tolower(static_cast(ch))); + } + return out; + } + +} // namespace + +TypeMeta type_meta(const MachineConfig& cfg, const std::string& name) { + TypeMeta m; + const std::string key = lower(name); + for (const ConfigType& t : cfg.types()) { + if (lower(t.name) == key) { + m.config = &t; + break; + } + } + if (m.config != nullptr) { + m.prim = MachineConfig::find_prim(m.config->base); + } + return m; +} + +} // namespace compiler diff --git a/compiler/src/main.cpp b/compiler/src/main.cpp index 90f36ce..05ed636 100644 --- a/compiler/src/main.cpp +++ b/compiler/src/main.cpp @@ -12,67 +12,68 @@ #include #include +#include "compiler/Codec.h" #include "compiler/Codegen.h" #include "compiler/Linker.h" +#include "compiler/MachineConfig.h" #include "compiler/Project.h" +#include "compiler/Stb.h" #include "compiler/Typecheck.h" -#include "isa/Encode.h" -#include "isa/Image.h" namespace { void usage() { - std::printf("usage: STCompiler [-o .stb]\n" - " STCompiler .stb --disasm\n" + std::printf("usage: STCompiler [-o .stb] --machine \n" + " STCompiler .stb --disasm --machine \n" " 解析工程并编译(词法 → 语法 → 链接 → 类型 → 寄存器码)\n" " -o .stb 编译并写出映像文件\n" - " 无 -o 只打印文件集合与工程哈希(12.3 阶段)\n" - " --disasm 反汇编一个已编译的 .stb 映像\n" + " --machine 机器定义(machine.toml,编译路径必填)\n" + " --disasm 反汇编一个已编译的 .stb 映像(需要 --machine)\n" " --help 打印本帮助\n"); } // 反汇编一个已编译映像(函数表逐条 + 常量表 + 数据段摘要)。 - // 映像无符号表,按 fn_id / 槽号显示;指令偏移为文件绝对字节偏移。 - int dump_image(const char* path) { + // 指令偏移为文件绝对字节偏移;反汇编按 machine.toml 的 format 输出。 + int dump_image(const char* path, const compiler::MachineConfig& cfg) { std::vector bytes; std::string err; - if (!isa::read_stb_file(path, &bytes, &err)) { + if (!compiler::read_stb_file(path, &bytes, &err)) { std::fprintf(stderr, "error: %s\n", err.c_str()); return 1; } - const isa::ImageView v = isa::ImageView::from(bytes); + const compiler::StbView v = compiler::StbView::from(bytes); if (!v.ok()) { std::fprintf(stderr, "error: %s\n", v.error().c_str()); return 1; } - const isa::ImageHeader& h = v.header(); std::printf("image: %s (%zu bytes, %u functions, %u globals, entry fn %u)\n", - path, bytes.size(), h.n_funcs, h.n_globals, h.entry_fn_id); - std::printf(" dt_ms=%u cycle_limit=%u hash=0x%016llx\n", h.dt_ms, - h.cycle_limit, static_cast(h.project_hash)); + path, bytes.size(), v.n_funcs(), v.n_globals(), v.entry_fn_id()); + std::printf(" dt_ms=%u cycle_limit=%u hash=0x%016llx model=%s sha=%s\n", + v.dt_ms(), v.cycle_limit(), + static_cast(v.project_hash()), + v.model_id().c_str(), v.sha_ok() ? "ok" : "BAD"); - if (h.n_consts) { + if (v.n_consts()) { std::printf("constants:\n"); - for (uint32_t i = 0; i < h.n_consts; ++i) { - const isa::ConstEntry c = v.const_entry(i); - const char* tag = c.tag == isa::types::Bool ? "BOOL" - : c.tag == isa::types::Int ? "INT" - : "TIME"; + for (uint32_t i = 0; i < v.n_consts(); ++i) { + const compiler::ConstEntry c = v.const_entry(i); + const char* tag = c.tag == 0 ? "BOOL" : c.tag == 1 ? "INT" : "TIME"; std::printf(" [%u] %s %llu\n", i, tag, static_cast(c.value)); } } std::printf("functions:\n"); - for (uint32_t i = 0; i < h.n_funcs; ++i) { - const isa::FuncRow r = v.func_row(i); + for (uint32_t i = 0; i < v.n_funcs(); ++i) { + const compiler::StbView::FuncRow r = v.func_row(i); std::printf(" fn %u: nregs=%u, offset=%u, len=%u\n", i, r.nregs, r.code_offset, r.code_len); const uint8_t* base = v.code_bytes() + r.code_offset; for (uint32_t j = 0; j < r.code_len; ++j) { char buf[64]; - isa::disasm(reinterpret_cast(base)[j], buf, sizeof buf); + compiler::disasm(cfg, reinterpret_cast(base)[j], buf, + sizeof buf); std::printf(" 0x%04x %s\n", - h.offset_code + r.code_offset + j * 4, buf); + v.offset_code() + r.code_offset + j * 4, buf); } } @@ -102,21 +103,40 @@ int main(int argc, char** argv) { return 0; } - // --disasm:第一参数是 .stb 路径,不重新编译 - for (int i = 1; i < argc; ++i) { - if (std::strcmp(argv[i], "--disasm") == 0) { - return dump_image(argv[1]); - } - } - - const std::string toml_path = argv[1]; + // 参数收集 + std::string machine_path; std::string out_path; + bool disasm_mode = false; for (int i = 2; i + 1 < argc; ++i) { - if (std::strcmp(argv[i], "-o") == 0) { + if (std::strcmp(argv[i], "--machine") == 0) { + machine_path = argv[i + 1]; + } else if (std::strcmp(argv[i], "-o") == 0) { out_path = argv[i + 1]; } } + for (int i = 1; i < argc; ++i) { + if (std::strcmp(argv[i], "--disasm") == 0) { + disasm_mode = true; + } + } + // --disasm:需要机器定义(反汇编按 machine.toml 的 format 输出) + if (disasm_mode) { + if (machine_path.empty()) { + std::fprintf(stderr, "error: missing --machine \n"); + usage(); + return 1; + } + compiler::MachineConfig cfg; + std::string err; + if (!cfg.load(machine_path, &err)) { + std::fprintf(stderr, "error: %s\n", err.c_str()); + return 1; + } + return dump_image(argv[1], cfg); + } + + const std::string toml_path = argv[1]; compiler::Project proj; std::string err; if (!compiler::parse_project(toml_path, &proj, &err)) { @@ -125,6 +145,7 @@ int main(int argc, char** argv) { } const std::vector files = compiler::compile_files(proj); + // 打印模式(无 -o):不需要机器定义 if (out_path.empty()) { uint64_t hash = 0; if (!compiler::compute_project_hash(proj, &hash, &err)) { @@ -140,6 +161,18 @@ int main(int argc, char** argv) { return 0; } + // 编译路径:机器定义必填(编码/类型/FB 布局来自 machine.toml) + if (machine_path.empty()) { + std::fprintf(stderr, "error: missing --machine \n"); + usage(); + return 1; + } + compiler::MachineConfig cfg; + if (!cfg.load(machine_path, &err)) { + std::fprintf(stderr, "error: %s\n", err.c_str()); + return 1; + } + // 完整管线:读 .st → 链接 → 类型检查 → 寄存器码 std::vector units; for (const std::string& f : files) { @@ -160,19 +193,30 @@ int main(int argc, char** argv) { return 1; } std::vector image; - if (!compiler::codegen_project(proj, units, link, &image, &err)) { + if (!compiler::codegen_project(proj, units, link, cfg, &image, &err)) { std::fprintf(stderr, "error: %s\n", err.c_str()); return 1; } - if (!isa::write_stb_file(out_path.c_str(), image, &err)) { + if (!compiler::write_stb_file(out_path.c_str(), image, &err)) { std::fprintf(stderr, "error: %s\n", err.c_str()); return 1; } + // 写后自检:型号标识与 SHA-256 + const compiler::StbView self = compiler::StbView::from(image); + if (!self.ok() || !self.sha_ok()) { + std::fprintf(stderr, "error: self-check failed: %s\n", self.error().c_str()); + return 1; + } + if (!self.model_matches(cfg.model_name(), cfg.version())) { + std::fprintf(stderr, "error: self-check model mismatch\n"); + return 1; + } + // sidecar:I/O 绑定 var → 槽号 → channel/bit(不进映像,执行器采样用) - std::vector bindings; - for (const isa::IoBinding& b : proj.io) { - isa::IoBinding out_b = b; + std::vector bindings; + for (const compiler::IoBinding& b : proj.io) { + compiler::IoBinding out_b = b; std::string key = b.var; for (char& ch : key) { ch = static_cast(std::tolower(static_cast(ch))); @@ -185,13 +229,13 @@ int main(int argc, char** argv) { } std::filesystem::path sidecar = std::filesystem::path(out_path); sidecar.replace_extension(".runtime.toml"); - if (!isa::write_sidecar_file(sidecar.string().c_str(), bindings, &err)) { + if (!compiler::write_sidecar_file(sidecar.string().c_str(), bindings, &err)) { std::fprintf(stderr, "error: %s\n", err.c_str()); return 1; } - const isa::ImageView v = isa::ImageView::from(image); + const compiler::StbView v = compiler::StbView::from(image); std::printf("compiled: %s (%zu bytes, %u functions, %u globals)\n", - out_path.c_str(), image.size(), v.header().n_funcs, v.header().n_globals); + out_path.c_str(), image.size(), v.n_funcs(), v.n_globals()); return 0; } diff --git a/executor/src/main.cpp b/executor/src/main.cpp index a11e14d..a1ca3a5 100644 --- a/executor/src/main.cpp +++ b/executor/src/main.cpp @@ -21,7 +21,7 @@ #include #include "isa/Encode.h" -#include "isa/Image.h" +#include "vm/Image.h" #include "vm/Machine.h" namespace { @@ -199,8 +199,17 @@ int main(int argc, char** argv) { std::string err; std::vector bytes; - if (!isa::read_stb_file(stb_path.c_str(), &bytes, &err)) { - std::fprintf(stderr, "error: %s\n", err.c_str()); + { + std::ifstream in(stb_path, std::ios::binary); + if (!in) { + std::fprintf(stderr, "error: cannot open: %s\n", stb_path.c_str()); + return 1; + } + bytes.assign(std::istreambuf_iterator(in), std::istreambuf_iterator()); + } + const vm::Image img = vm::Image::from(bytes); + if (!img.ok()) { + std::fprintf(stderr, "error: %s\n", img.error().c_str()); return 1; } vm::Machine machine; @@ -233,7 +242,7 @@ int main(int argc, char** argv) { } } - const isa::ImageHeader& h = machine.header(); + const vm::ImageHeader& h = machine.header(); std::printf("image: %s (%zu bytes, dt_ms=%u, cycle_limit=%u)\n", stb_path.c_str(), bytes.size(), h.dt_ms, h.cycle_limit); diff --git a/isa/CMakeLists.txt b/isa/CMakeLists.txt index 508b752..9df22ef 100644 --- a/isa/CMakeLists.txt +++ b/isa/CMakeLists.txt @@ -7,7 +7,6 @@ project(ISA DESCRIPTION "指令集架构") add_library(isa STATIC - ./src/Encode.cpp - ./src/Image.cpp) + ./src/Encode.cpp) target_include_directories(isa PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) \ No newline at end of file diff --git a/isa/include/isa/Image.h b/isa/include/isa/Image.h deleted file mode 100644 index fd1cc47..0000000 --- a/isa/include/isa/Image.h +++ /dev/null @@ -1,125 +0,0 @@ -/** - * @file Image.h - * @brief 映像头、段、FNV-1a 哈希、.stb 文件与 sidecar - * @author - * @date 2026-08-19 - */ - -#pragma once - -#include -#include -#include -#include - -#include "isa/Types.h" - -namespace isa { - - // 魔数 "STSC"(小端 u32)、版本 - static const uint32_t kMagic = 0x43545353u; - static const uint32_t kVersion = 1; - - // 头与表行宽(见 Doc/isa/指令与映像.md) - static const size_t kHeaderSize = 72; - static const size_t kConstEntrySize = 12; - static const size_t kFuncRowSize = 12; - - // FNV-1a 64:basis / prime(见 Doc/isa/指令与映像.md) - static const uint64_t kFnvBasis = 0xcbf29ce484222325ull; - static const uint64_t kFnvPrime = 0x100000001b3ull; - - // FNV-1a 64 增量与一次性 - uint64_t fnv1a64_update(uint64_t h, const uint8_t* data, size_t len); - uint64_t fnv1a64(const uint8_t* data, size_t len); - - // 映像头(字节布局见 Doc/isa/指令与映像.md) - struct ImageHeader { - uint32_t cycle_limit; - uint32_t dt_ms; - uint64_t project_hash; - uint32_t entry_fn_id; - uint32_t n_globals; - uint32_t n_i; - uint32_t n_q; - uint32_t n_m; - uint32_t n_consts; - uint32_t n_funcs; - uint32_t offset_const; - uint32_t offset_funcs; - uint32_t offset_code; - uint32_t offset_fb; - uint32_t offset_data; - }; - - // 常量表一行 - struct ConstEntry { - types::TypeTag tag; - uint64_t value; - }; - - // 函数表一行 - struct FuncRow { - uint32_t nregs; - uint32_t code_offset; // 相对字节码段起点 - uint32_t code_len; // 指令条数 - }; - - // I/O 绑定(sidecar 一行) - struct IoBinding { - std::string var; - uint32_t slot; - uint32_t channel; - uint32_t bit; - bool is_input; // true = [[io.input]],false = [[io.output]] - }; - - // 只读视图:校验过的映像 - class ImageView { - public: - // 默认构造为无效态(ok() == false,error() == "uninitialized") - ImageView(); - static ImageView from(const uint8_t* buf, size_t len); - static ImageView from(const std::vector& buf); - - bool ok() const; - const std::string& error() const; - const ImageHeader& header() const; - - const uint8_t* raw() const; - size_t raw_len() const; - - ConstEntry const_entry(size_t i) const; - FuncRow func_row(size_t i) const; - - const uint8_t* code_bytes() const; // 字节码段起点 - size_t code_len() const; // 字节数 - const uint8_t* data_bytes() const; // 数据段起点 - size_t data_len() const; // 字节数 - - private: - // 私有成员与内部构造辅助(from 使用) - - const uint8_t* buf_; - size_t len_; - bool ok_; - std::string err_; - ImageHeader hdr_; - }; - - // 写最小映像:空槽 + MAIN(fn_id=0)+ 一条 RET - std::vector write_ret_main(uint32_t cycle_limit, uint32_t dt_ms, - uint64_t project_hash); - - // .stb 文件读写(纯字节,校验交给 read_image / ImageView) - bool write_stb_file(const char* path, const std::vector& image, - std::string* err); - bool read_stb_file(const char* path, std::vector* image, - std::string* err); - - // sidecar 生成与写文件(格式见 Doc/isa/指令与映像.md) - std::string make_sidecar(const std::vector& bindings); - bool write_sidecar_file(const char* path, - const std::vector& bindings, - std::string* err); -} diff --git a/isa/include/isa/Op.h b/isa/include/isa/Op.h index cfa3d83..a28440d 100644 --- a/isa/include/isa/Op.h +++ b/isa/include/isa/Op.h @@ -53,20 +53,82 @@ namespace isa { // 操作码总数 static const int kOpCount = 34; - // 助记符:下标与 Op 数值一一对应 + // 操作数形态(三层分类第二层,见 Doc/isa/指令配置.md) + enum class OpFormat : uint8_t { + RR, // 两寄存器:rd rs(MOVE / NOT) + RRR, // 三寄存器:rd ra rb(逻辑 / 算术 / 比较) + IMM, // 常量:rd const_id(LOADK) + SLOT, // 槽:rd slot(LOAD_* / STORE_*) + JMP, // 偏移:off + JC, // 条件跳转:r off(JT / JF) + CALL, // 调用:fn_id + CAL, // 实例:instance(CAL_*) + NONE, // 无操作数(RET) + }; + + // 操作对象大类(三层分类第一层) + enum class OpClass : uint8_t { + Plain, // 无实例:寄存器 / 立即数 / 槽 / 分支 / 调用 + Instance, // 有实例:操作数据区实例块(内建 FB) + }; + + // 编译期指令表:下标与 Op 数值一一对应(ops.txt 强校验的基准) + struct OpDef { + const char* name; + OpFormat format; + OpClass cls; + }; + + static const OpDef kOpDefs[kOpCount] = { + {"MOVE", OpFormat::RR, OpClass::Plain}, // 0 + {"LOADK", OpFormat::IMM, OpClass::Plain}, // 1 + {"NOT", OpFormat::RR, OpClass::Plain}, // 2 + {"AND", OpFormat::RRR, OpClass::Plain}, // 3 + {"OR", OpFormat::RRR, OpClass::Plain}, // 4 + {"ADD", OpFormat::RRR, OpClass::Plain}, // 5 + {"SUB", OpFormat::RRR, OpClass::Plain}, // 6 + {"MUL", OpFormat::RRR, OpClass::Plain}, // 7 + {"DIV", OpFormat::RRR, OpClass::Plain}, // 8 + {"CMP_EQ", OpFormat::RRR, OpClass::Plain}, // 9 + {"CMP_NE", OpFormat::RRR, OpClass::Plain}, // 10 + {"CMP_LT", OpFormat::RRR, OpClass::Plain}, // 11 + {"CMP_LE", OpFormat::RRR, OpClass::Plain}, // 12 + {"CMP_GT", OpFormat::RRR, OpClass::Plain}, // 13 + {"CMP_GE", OpFormat::RRR, OpClass::Plain}, // 14 + {"JMP", OpFormat::JMP, OpClass::Plain}, // 15 + {"JT", OpFormat::JC, OpClass::Plain}, // 16 + {"JF", OpFormat::JC, OpClass::Plain}, // 17 + {"LOAD_I", OpFormat::SLOT, OpClass::Plain}, // 18 + {"STORE_Q", OpFormat::SLOT, OpClass::Plain}, // 19 + {"LOAD_M", OpFormat::SLOT, OpClass::Plain}, // 20 + {"STORE_M", OpFormat::SLOT, OpClass::Plain}, // 21 + {"LOAD_GLOBAL", OpFormat::SLOT, OpClass::Plain}, // 22 + {"STORE_GLOBAL", OpFormat::SLOT, OpClass::Plain},// 23 + {"CAL_TON", OpFormat::CAL, OpClass::Instance}, // 24 + {"CAL_TOF", OpFormat::CAL, OpClass::Instance}, // 25 + {"CAL_TP", OpFormat::CAL, OpClass::Instance}, // 26 + {"CAL_CTU", OpFormat::CAL, OpClass::Instance}, // 27 + {"CAL_CTD", OpFormat::CAL, OpClass::Instance}, // 28 + {"CAL_CTUD", OpFormat::CAL, OpClass::Instance}, // 29 + {"CAL_R_TRIG", OpFormat::CAL, OpClass::Instance},// 30 + {"CAL_F_TRIG", OpFormat::CAL, OpClass::Instance},// 31 + {"CALL", OpFormat::CALL, OpClass::Plain}, // 32 + {"RET", OpFormat::NONE, OpClass::Plain}, // 33 + }; + + // 助记符 / 格式 / 大类:查表(越界返回哨兵) inline const char* mnemonic(Op op) { - static const char* const kMnemonic[kOpCount] = { - "MOVE", "LOADK", "NOT", "AND", "OR", - "ADD", "SUB", "MUL", "DIV", - "CMP_EQ", "CMP_NE", "CMP_LT", "CMP_LE", "CMP_GT", "CMP_GE", - "JMP", "JT", "JF", - "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", "RET", - }; const int idx = static_cast(op); - return (idx >= 0 && idx < kOpCount) ? kMnemonic[idx] : "???"; + return (idx >= 0 && idx < kOpCount) ? kOpDefs[idx].name : "???"; + } + + inline OpFormat format(Op op) { + const int idx = static_cast(op); + return (idx >= 0 && idx < kOpCount) ? kOpDefs[idx].format : OpFormat::NONE; + } + + inline OpClass op_class(Op op) { + const int idx = static_cast(op); + return (idx >= 0 && idx < kOpCount) ? kOpDefs[idx].cls : OpClass::Plain; } } diff --git a/isa/src/Encode.cpp b/isa/src/Encode.cpp index 9990d66..fdd74a9 100644 --- a/isa/src/Encode.cpp +++ b/isa/src/Encode.cpp @@ -38,61 +38,34 @@ void disasm(Instr w, char* out, size_t cap) { } const char* m = mnemonic(o); - switch (o) { - case Op::MOVE: - case Op::NOT: + // 表驱动:按操作数形态输出(文本与 Doc/isa/指令与映像.md 一致) + switch (format(o)) { + case OpFormat::RR: snprintf(out, cap, "%s r%u, r%u", m, static_cast(rd(w)), static_cast(a(w))); break; - case Op::AND: - case Op::OR: - case Op::ADD: - case Op::SUB: - case Op::MUL: - case Op::DIV: - case Op::CMP_EQ: - case Op::CMP_NE: - case Op::CMP_LT: - case Op::CMP_LE: - case Op::CMP_GT: - case Op::CMP_GE: + case OpFormat::RRR: snprintf(out, cap, "%s r%u, r%u, r%u", m, static_cast(rd(w)), static_cast(a(w)), static_cast(b(w))); break; - case Op::LOADK: + case OpFormat::IMM: + case OpFormat::SLOT: snprintf(out, cap, "%s r%u, %u", m, static_cast(rd(w)), static_cast(imm16(w))); break; - case Op::JMP: + case OpFormat::JMP: snprintf(out, cap, "%s %+d", m, static_cast(off16(w))); break; - case Op::JT: - case Op::JF: + case OpFormat::JC: snprintf(out, cap, "%s r%u, %+d", m, static_cast(rd(w)), static_cast(off16(w))); break; - case Op::LOAD_I: - case Op::STORE_Q: - case Op::LOAD_M: - case Op::STORE_M: - case Op::LOAD_GLOBAL: - case Op::STORE_GLOBAL: - snprintf(out, cap, "%s r%u, %u", m, - static_cast(rd(w)), static_cast(imm16(w))); - break; - case Op::CAL_TON: - case Op::CAL_TOF: - case Op::CAL_TP: - case Op::CAL_CTU: - case Op::CAL_CTD: - case Op::CAL_CTUD: - case Op::CAL_R_TRIG: - case Op::CAL_F_TRIG: - case Op::CALL: + case OpFormat::CALL: + case OpFormat::CAL: snprintf(out, cap, "%s %u", m, static_cast(imm16(w))); break; - case Op::RET: + case OpFormat::NONE: snprintf(out, cap, "%s", m); break; } diff --git a/isa/src/Image.cpp b/isa/src/Image.cpp deleted file mode 100644 index 1d2fd7b..0000000 --- a/isa/src/Image.cpp +++ /dev/null @@ -1,321 +0,0 @@ -/** - * @file Image.cpp - * @brief 映像头、段、FNV-1a 哈希、.stb 文件与 sidecar - * @author - * @date 2026-08-19 - */ - -#include "isa/Image.h" - -#include -#include - -#include "isa/Instr.h" - -namespace isa { - -// ---- 小端读写(只放 .cpp,不把 packed struct 当 ABI)---- - -static void put_le32(std::vector& b, size_t off, uint32_t v) { - b[off + 0] = static_cast(v & 0xFFu); - b[off + 1] = static_cast((v >> 8) & 0xFFu); - b[off + 2] = static_cast((v >> 16) & 0xFFu); - b[off + 3] = static_cast((v >> 24) & 0xFFu); -} - -static uint32_t get_le32(const uint8_t* p) { - return static_cast(p[0]) - | (static_cast(p[1]) << 8) - | (static_cast(p[2]) << 16) - | (static_cast(p[3]) << 24); -} - -static void put_le64(std::vector& b, size_t off, uint64_t v) { - for (int i = 0; i < 8; ++i) { - b[off + i] = static_cast((v >> (8 * i)) & 0xFFu); - } -} - -static uint64_t get_le64(const uint8_t* p) { - uint64_t v = 0; - for (int i = 0; i < 8; ++i) { - v |= static_cast(p[i]) << (8 * i); - } - return v; -} - -// ---- FNV-1a 64 ---- - -uint64_t fnv1a64_update(uint64_t h, const uint8_t* data, size_t len) { - for (size_t i = 0; i < len; ++i) { - h ^= data[i]; - h *= kFnvPrime; - } - return h; -} - -uint64_t fnv1a64(const uint8_t* data, size_t len) { - return fnv1a64_update(kFnvBasis, data, len); -} - -// ---- 最小映像:空槽 + MAIN + 一条 RET ---- - -std::vector write_ret_main(uint32_t cycle_limit, uint32_t dt_ms, - uint64_t project_hash) { - std::vector b(kHeaderSize + kFuncRowSize + 4, 0); - - put_le32(b, 0, kMagic); - put_le32(b, 4, kVersion); - put_le32(b, 8, cycle_limit); - put_le32(b, 12, dt_ms); - put_le64(b, 16, project_hash); - put_le32(b, 24, 0); // entry_fn_id = MAIN = 0 - // n_globals / n_i / n_q / n_m = 0 - // n_consts = 0 - put_le32(b, 48, 1); // n_funcs = 1 - - const size_t off_funcs = kHeaderSize; // 72 - const size_t off_code = off_funcs + kFuncRowSize; // 84 - const size_t off_end = off_code + 4; // 88 - - put_le32(b, 52, static_cast(off_funcs)); // offset_const - put_le32(b, 56, static_cast(off_funcs)); // offset_funcs - put_le32(b, 60, static_cast(off_code)); // offset_code - put_le32(b, 64, static_cast(off_end)); // offset_fb - put_le32(b, 68, static_cast(off_end)); // offset_data - - // 函数表一行:MAIN,无寄存器,代码 0 起 1 条 - put_le32(b, off_funcs + 0, 0); // nregs - put_le32(b, off_funcs + 4, 0); // code_offset - put_le32(b, off_funcs + 8, 1); // code_len - - // 字节码:RET - put_le32(b, off_code, pack(Op::RET, 0, 0, 0)); - - return b; -} - -// ---- 只读视图 ---- - -ImageView::ImageView() - : buf_(0), len_(0), ok_(false), err_("uninitialized"), hdr_() {} - -ImageView ImageView::from(const uint8_t* buf, size_t len) { - ImageView v; - v.buf_ = buf; - v.len_ = len; - - if (buf == 0) { - v.err_ = "null buffer"; - return v; - } - if (len < kHeaderSize) { - v.err_ = "image too short"; - return v; - } - if (get_le32(buf + 0) != kMagic) { - v.err_ = "bad magic"; - return v; - } - if (get_le32(buf + 4) != kVersion) { - v.err_ = "bad version"; - return v; - } - - ImageHeader& h = v.hdr_; - h.cycle_limit = get_le32(buf + 8); - h.dt_ms = get_le32(buf + 12); - h.project_hash = get_le64(buf + 16); - h.entry_fn_id = get_le32(buf + 24); - h.n_globals = get_le32(buf + 28); - h.n_i = get_le32(buf + 32); - h.n_q = get_le32(buf + 36); - h.n_m = get_le32(buf + 40); - h.n_consts = get_le32(buf + 44); - h.n_funcs = get_le32(buf + 48); - h.offset_const = get_le32(buf + 52); - h.offset_funcs = get_le32(buf + 56); - h.offset_code = get_le32(buf + 60); - h.offset_fb = get_le32(buf + 64); - h.offset_data = get_le32(buf + 68); - - // 段校验:连续、单调不减、末段终点不越界 - const uint64_t offs[5] = { - h.offset_const, h.offset_funcs, h.offset_code, h.offset_fb, h.offset_data, - }; - for (int i = 0; i < 5; ++i) { - if (offs[i] < kHeaderSize || offs[i] > len) { - v.err_ = "segment offset out of range"; - return v; - } - if (i > 0 && offs[i] < offs[i - 1]) { - v.err_ = "segment offsets not monotonic"; - return v; - } - } - - const uint64_t const_len = h.offset_funcs - h.offset_const; - const uint64_t funcs_len = h.offset_code - h.offset_funcs; - if (const_len != static_cast(h.n_consts) * kConstEntrySize) { - v.err_ = "const table size mismatch"; - return v; - } - if (funcs_len != static_cast(h.n_funcs) * kFuncRowSize) { - v.err_ = "function table size mismatch"; - return v; - } - if ((h.offset_fb - h.offset_code) % 4 != 0) { - v.err_ = "code segment not 4-byte aligned"; - return v; - } - if (h.entry_fn_id >= h.n_funcs && h.n_funcs != 0) { - v.err_ = "entry fn_id out of range"; - return v; - } - - v.ok_ = true; - v.err_.clear(); - return v; -} - -ImageView ImageView::from(const std::vector& buf) { - return from(buf.data(), buf.size()); -} - -bool ImageView::ok() const { return ok_; } -const std::string& ImageView::error() const { return err_; } -const ImageHeader& ImageView::header() const { return hdr_; } -const uint8_t* ImageView::raw() const { return buf_; } -size_t ImageView::raw_len() const { return len_; } - -ConstEntry ImageView::const_entry(size_t i) const { - ConstEntry e; - e.tag = types::Bool; - e.value = 0; - if (ok_ && i < hdr_.n_consts) { - const uint8_t* p = buf_ + hdr_.offset_const + i * kConstEntrySize; - const uint32_t tag = get_le32(p); - if (tag <= static_cast(types::Time)) { - e.tag = static_cast(tag); - } - e.value = get_le64(p + 4); - } - return e; -} - -FuncRow ImageView::func_row(size_t i) const { - FuncRow r = {0, 0, 0}; - if (ok_ && i < hdr_.n_funcs) { - const uint8_t* p = buf_ + hdr_.offset_funcs + i * kFuncRowSize; - r.nregs = get_le32(p + 0); - r.code_offset = get_le32(p + 4); - r.code_len = get_le32(p + 8); - } - return r; -} - -const uint8_t* ImageView::code_bytes() const { - return ok_ ? buf_ + hdr_.offset_code : 0; -} - -size_t ImageView::code_len() const { - return ok_ ? hdr_.offset_fb - hdr_.offset_code : 0; -} - -const uint8_t* ImageView::data_bytes() const { - return ok_ ? buf_ + hdr_.offset_data : 0; -} - -size_t ImageView::data_len() const { - return ok_ ? len_ - hdr_.offset_data : 0; -} - -// ---- .stb 文件读写 ---- - -bool write_stb_file(const char* path, const std::vector& image, - std::string* err) { - FILE* f = std::fopen(path, "wb"); - if (f == 0) { - if (err) { - *err = std::string("cannot open for write: ") + path; - } - return false; - } - const bool ok = image.empty() || std::fwrite(&image[0], 1, image.size(), f) == image.size(); - std::fclose(f); - if (!ok && err) { - *err = std::string("write failed: ") + path; - } - return ok; -} - -bool read_stb_file(const char* path, std::vector* image, - std::string* err) { - FILE* f = std::fopen(path, "rb"); - if (f == 0) { - if (err) { - *err = std::string("cannot open for read: ") + path; - } - return false; - } - std::fseek(f, 0, SEEK_END); - const long size = std::ftell(f); - std::fseek(f, 0, SEEK_SET); - if (size < 0) { - std::fclose(f); - if (err) { - *err = "tell failed"; - } - return false; - } - image->resize(static_cast(size)); - const bool ok = size == 0 || std::fread(&(*image)[0], 1, static_cast(size), f) == static_cast(size); - std::fclose(f); - if (!ok && err) { - *err = std::string("read failed: ") + path; - } - return ok; -} - -// ---- sidecar ---- - -std::string make_sidecar(const std::vector& bindings) { - std::string out; - for (size_t i = 0; i < bindings.size(); ++i) { - const IoBinding& b = bindings[i]; - char buf[64]; - out += b.is_input ? "[[io.input]]\n" : "[[io.output]]\n"; - out += "var = \""; - out += b.var; - out += "\"\n"; - std::snprintf(buf, sizeof buf, "slot = %u\n", static_cast(b.slot)); - out += buf; - std::snprintf(buf, sizeof buf, "channel = %u\n", static_cast(b.channel)); - out += buf; - std::snprintf(buf, sizeof buf, "bit = %u\n", static_cast(b.bit)); - out += buf; - out += "\n"; - } - return out; -} - -bool write_sidecar_file(const char* path, - const std::vector& bindings, - std::string* err) { - FILE* f = std::fopen(path, "wb"); - if (f == 0) { - if (err) { - *err = std::string("cannot open for write: ") + path; - } - return false; - } - const std::string s = make_sidecar(bindings); - const bool ok = s.empty() || std::fwrite(s.data(), 1, s.size(), f) == s.size(); - std::fclose(f); - if (!ok && err) { - *err = std::string("write failed: ") + path; - } - return ok; -} - -} // namespace isa diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7f96223..e05b6e8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,6 +4,16 @@ add_test(NAME stcompiler_version add_test(NAME bytecode_executor_version COMMAND BytecodeExecutor) +# CLI 级:编译需 --machine;缺参必须失败(WILL_FAIL) +add_test(NAME cli_compile + COMMAND STCompiler ${CMAKE_SOURCE_DIR}/tests/cases/01_empty_main/project.toml + -o ${CMAKE_CURRENT_BINARY_DIR}/cli_case01.stb + --machine ${CMAKE_SOURCE_DIR}/compiler/machine.toml) +add_test(NAME cli_missing_machine + COMMAND STCompiler ${CMAKE_SOURCE_DIR}/tests/cases/01_empty_main/project.toml + -o ${CMAKE_CURRENT_BINARY_DIR}/cli_nope.stb) +set_tests_properties(cli_missing_machine PROPERTIES WILL_FAIL TRUE) + # isa 合同测试:链接 isa 库,断言覆盖饱和/编解码/disasm/哈希/映像/sidecar add_executable(isa_test ./src/isa_test.cpp) @@ -72,7 +82,7 @@ add_test(NAME typecheck_types add_executable(codegen_test ./src/codegen_test.cpp) -target_link_libraries(codegen_test PRIVATE compiler) +target_link_libraries(codegen_test PRIVATE compiler isa) target_compile_definitions(codegen_test PRIVATE REPO_ROOT="${CMAKE_SOURCE_DIR}") @@ -100,3 +110,14 @@ target_compile_definitions(cases_test PRIVATE add_test(NAME cases_all COMMAND cases_test) + +# machine.toml 加载与强校验(阶段 A 步骤 3;REPO_ROOT 注入源目录绝对路径) +add_executable(machine_test + ./src/machine_test.cpp) + +target_link_libraries(machine_test PRIVATE compiler) +target_compile_definitions(machine_test PRIVATE + REPO_ROOT="${CMAKE_SOURCE_DIR}") + +add_test(NAME machine_config + COMMAND machine_test) diff --git a/tests/src/cases_test.cpp b/tests/src/cases_test.cpp index 0374248..2500ba6 100644 --- a/tests/src/cases_test.cpp +++ b/tests/src/cases_test.cpp @@ -119,8 +119,13 @@ namespace { std::printf("FAIL %s type: %s\n", dir, err.c_str()); return false; } + MachineConfig cfg; + if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)) { + std::printf("FAIL %s machine load\n", dir); + return false; + } std::vector img; - if (!codegen_project(p, units, link, &img, &err)) { + if (!codegen_project(p, units, link, cfg, &img, &err)) { if (want == Outcome::CompileError) { ++g_checks; return true; diff --git a/tests/src/codegen_test.cpp b/tests/src/codegen_test.cpp index 5090cab..2146d57 100644 --- a/tests/src/codegen_test.cpp +++ b/tests/src/codegen_test.cpp @@ -16,7 +16,7 @@ #include "compiler/Project.h" #include "compiler/Typecheck.h" #include "isa/Encode.h" -#include "isa/Image.h" +#include "compiler/Stb.h" #ifndef REPO_ROOT #define REPO_ROOT "." @@ -34,8 +34,19 @@ static int g_checks = 0; } while (0) // 从用例目录走完整编译管线(解析 → 链接 → 类型检查 → 代码生成) +static bool load_cfg(compiler::MachineConfig* cfg) { + std::string err; + if (!cfg->load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)) { + std::printf("FAIL load machine.toml: %s\n", err.c_str()); + return false; + } + return true; +} + static bool compile_case(const char* dir, std::vector* image, std::string* err) { using namespace compiler; + compiler::MachineConfig cfg; + if (!load_cfg(&cfg)) return false; const std::string toml = std::string(REPO_ROOT) + "/tests/cases/" + dir + "/project.toml"; Project p; if (!parse_project(toml, &p, err)) { @@ -56,7 +67,7 @@ static bool compile_case(const char* dir, std::vector* image, std::stri if (!check_project(p, units, link, err)) { return false; } - return codegen_project(p, units, link, image, err); + return codegen_project(p, units, link, cfg, image, err); } // 从临时工程(globals.st + main.st + 可含 io 绑定)走完整管线 @@ -64,6 +75,8 @@ static bool compile_src(const char* name, const char* io_extra, const char* glob const char* main_st, std::vector* image, std::string* err) { using namespace compiler; + compiler::MachineConfig cfg; + if (!load_cfg(&cfg)) return false; const std::string dir = std::string(REPO_ROOT) + "/build/cg_tmp_" + name; std::filesystem::remove_all(dir); std::filesystem::create_directories(dir); @@ -100,7 +113,7 @@ static bool compile_src(const char* name, const char* io_extra, const char* glob if (!check_project(p, units, link, err)) { return false; } - return codegen_project(p, units, link, image, err); + return codegen_project(p, units, link, cfg, image, err); } // ---- 1. 用例 01:空 MAIN + RET ---- @@ -110,17 +123,17 @@ static bool test_case01() { std::string err; CHECK(compile_case("01_empty_main", &img, &err)); - const isa::ImageView v = isa::ImageView::from(img); + const compiler::StbView v = compiler::StbView::from(img); CHECK(v.ok()); - CHECK(v.header().cycle_limit == 1000); - CHECK(v.header().dt_ms == 10); - CHECK(v.header().entry_fn_id == 0); - CHECK(v.header().n_funcs == 1); - CHECK(v.header().n_consts == 0); - CHECK(v.header().n_globals == 0); - CHECK(v.header().project_hash != isa::kFnvBasis); + CHECK(v.cycle_limit() == 1000); + CHECK(v.dt_ms() == 10); + CHECK(v.entry_fn_id() == 0); + CHECK(v.n_funcs() == 1); + CHECK(v.n_consts() == 0); + CHECK(v.n_globals() == 0); + CHECK(v.project_hash() != compiler::kFnvBasis); - const isa::FuncRow r = v.func_row(0); + const compiler::StbView::FuncRow r = v.func_row(0); CHECK(r.nregs == 8); // 帧基址(调用约定区) CHECK(r.code_len == 1); @@ -137,12 +150,12 @@ static bool test_case02() { std::string err; CHECK(compile_case("02_bool_assign", &img, &err)); - const isa::ImageView v = isa::ImageView::from(img); + const compiler::StbView v = compiler::StbView::from(img); CHECK(v.ok()); - CHECK(v.header().n_funcs == 1); - CHECK(v.header().n_consts == 2); // TRUE 与 FALSE + CHECK(v.n_funcs() == 1); + CHECK(v.n_consts() == 2); // TRUE 与 FALSE - const isa::FuncRow r = v.func_row(0); + const compiler::StbView::FuncRow r = v.func_row(0); CHECK(r.nregs == 10); // a, b(r8 起) CHECK(r.code_len == 3); @@ -157,10 +170,10 @@ static bool test_case02() { CHECK(std::strcmp(buf, "RET") == 0); // 常量表:0 = BOOL 1,1 = BOOL 0 - const isa::ConstEntry c0 = v.const_entry(0); - CHECK(c0.tag == isa::types::Bool && c0.value == 1); - const isa::ConstEntry c1 = v.const_entry(1); - CHECK(c1.tag == isa::types::Bool && c1.value == 0); + const compiler::ConstEntry c0 = v.const_entry(0); + CHECK(c0.tag == 0 && c0.value == 1); + const compiler::ConstEntry c1 = v.const_entry(1); + CHECK(c1.tag == 0 && c1.value == 0); return true; } @@ -171,14 +184,14 @@ static bool test_case09() { std::string err; CHECK(compile_case("09_gvl_external", &img, &err)); - const isa::ImageView v = isa::ImageView::from(img); + const compiler::StbView v = compiler::StbView::from(img); CHECK(v.ok()); - CHECK(v.header().n_globals == 1); - CHECK(v.data_len() == 8); // 8 字节定宽槽 + CHECK(v.n_globals() == 1); + CHECK(v.data_len() == 8); // 8 字节定宽槽(不含 SHA 尾) CHECK(v.data_bytes()[0] == 5); // G1 初值 5(小端低位) CHECK(v.data_bytes()[1] == 0); - const isa::FuncRow r = v.func_row(0); + const compiler::StbView::FuncRow r = v.func_row(0); CHECK(r.nregs == 9); // r8 起:仅局部 x CHECK(r.code_len == 2); @@ -208,11 +221,11 @@ static bool test_io_ops() { "END_PROGRAM\n"; CHECK(compile_src("io", io_extra, globals_st, main_st, &img, &err)); - const isa::ImageView v = isa::ImageView::from(img); + const compiler::StbView v = compiler::StbView::from(img); CHECK(v.ok()); - CHECK(v.header().n_globals == 2); + CHECK(v.n_globals() == 2); - const isa::FuncRow r = v.func_row(0); + const compiler::StbView::FuncRow r = v.func_row(0); CHECK(r.nregs == 10); // r8 起:q + 1 临时 char buf[64]; @@ -245,9 +258,9 @@ static bool test_layout() { "END_PROGRAM\n"; CHECK(compile_src("layout", "", globals_st, main_st, &img, &err)); - const isa::ImageView v = isa::ImageView::from(img); + const compiler::StbView v = compiler::StbView::from(img); CHECK(v.ok()); - CHECK(v.header().n_globals == 3); + CHECK(v.n_globals() == 3); CHECK(v.data_len() == 24); // 3 槽 × 8 字节定宽 CHECK(v.data_bytes()[0] == 0); // B 无初值 CHECK(v.data_bytes()[8] == 0x2C && v.data_bytes()[9] == 0x01); // I = 300(槽 1) @@ -286,9 +299,9 @@ static bool test_case03() { std::string err; CHECK(compile_case("03_short_circuit", &img, &err)); - const isa::ImageView v = isa::ImageView::from(img); + const compiler::StbView v = compiler::StbView::from(img); CHECK(v.ok()); - const isa::FuncRow r = v.func_row(0); + const compiler::StbView::FuncRow r = v.func_row(0); CHECK(r.nregs == 12); // r8 起:a b x + 1 临时 CHECK(r.code_len == 9); @@ -329,9 +342,9 @@ static bool test_not() { "END_PROGRAM\n"; CHECK(compile_src("not", "", "", main_st, &img, &err)); - const isa::ImageView v = isa::ImageView::from(img); + const compiler::StbView v = compiler::StbView::from(img); CHECK(v.ok()); - const isa::FuncRow r = v.func_row(0); + const compiler::StbView::FuncRow r = v.func_row(0); CHECK(r.nregs == 11); // r8 起:a x + 1 临时 CHECK(r.code_len == 3); @@ -354,9 +367,9 @@ static bool test_case04() { std::string err; CHECK(compile_case("04_if_elsif_else", &img, &err)); - const isa::ImageView v = isa::ImageView::from(img); + const compiler::StbView v = compiler::StbView::from(img); CHECK(v.ok()); - const isa::FuncRow r = v.func_row(0); + const compiler::StbView::FuncRow r = v.func_row(0); CHECK(r.nregs == 13); // r8 起:sel out + 3 临时 CHECK(r.code_len == 14); @@ -404,9 +417,9 @@ static bool test_case05() { std::string err; CHECK(compile_case("05_while_normal", &img, &err)); - const isa::ImageView v = isa::ImageView::from(img); + const compiler::StbView v = compiler::StbView::from(img); CHECK(v.ok()); - const isa::FuncRow r = v.func_row(0); + const compiler::StbView::FuncRow r = v.func_row(0); CHECK(r.nregs == 12); // r8 起:n + 3 临时(条件结果 r9 + 两操作数) CHECK(r.code_len == 10); @@ -445,9 +458,9 @@ static bool test_case07() { std::string err; CHECK(compile_case("07_int_arith", &img, &err)); - const isa::ImageView v = isa::ImageView::from(img); + const compiler::StbView v = compiler::StbView::from(img); CHECK(v.ok()); - const isa::FuncRow r = v.func_row(0); + const compiler::StbView::FuncRow r = v.func_row(0); CHECK(r.nregs == 14); // r8 起:a b c eq + 2 临时 char buf[64]; @@ -472,15 +485,15 @@ static bool test_case08() { std::string err; CHECK(compile_case("08_time_literal", &img, &err)); - const isa::ImageView v = isa::ImageView::from(img); + const compiler::StbView v = compiler::StbView::from(img); CHECK(v.ok()); - CHECK(v.header().n_consts == 2); - const isa::ConstEntry c0 = v.const_entry(0); - CHECK(c0.tag == isa::types::Time && c0.value == 10); // T#10ms - const isa::ConstEntry c1 = v.const_entry(1); - CHECK(c1.tag == isa::types::Time && c1.value == 1250); // T#1s250ms + CHECK(v.n_consts() == 2); + const compiler::ConstEntry c0 = v.const_entry(0); + CHECK(c0.tag == 2 && c0.value == 10); // T#10ms + const compiler::ConstEntry c1 = v.const_entry(1); + CHECK(c1.tag == 2 && c1.value == 1250); // T#1s250ms - const isa::FuncRow r = v.func_row(0); + const compiler::StbView::FuncRow r = v.func_row(0); CHECK(r.nregs == 10); // r8 起 char buf[64]; const uint32_t* ins = reinterpret_cast(v.code_bytes()); @@ -498,15 +511,15 @@ static bool test_case13() { std::string err; CHECK(compile_case("13_function_call", &img, &err)); - const isa::ImageView v = isa::ImageView::from(img); + const compiler::StbView v = compiler::StbView::from(img); CHECK(v.ok()); - CHECK(v.header().n_funcs == 2); - CHECK(v.header().entry_fn_id == 1); // MAIN 是第二个 POU + CHECK(v.n_funcs() == 2); + CHECK(v.entry_fn_id() == 1); // MAIN 是第二个 POU char buf[64]; const uint8_t* base = v.code_bytes(); // Add(fn_id 0):结果 r0、输入 r1(a) r2(b)、临时 r8/r9 - const isa::FuncRow fa = v.func_row(0); + const compiler::StbView::FuncRow fa = v.func_row(0); CHECK(fa.nregs == 10); CHECK(fa.code_len == 4); const uint32_t* ia = reinterpret_cast(base); @@ -520,7 +533,7 @@ static bool test_case13() { CHECK(std::strcmp(buf, "RET") == 0); // MAIN(fn_id 1):x 在 r8,实参临时 r9/r10 - const isa::FuncRow fm = v.func_row(1); + const compiler::StbView::FuncRow fm = v.func_row(1); CHECK(fm.nregs == 11); CHECK(fm.code_len == 7); const uint32_t* im = reinterpret_cast(base) + fm.code_offset / 4; @@ -548,13 +561,13 @@ static bool test_case15() { std::string err; CHECK(compile_case("15_fb_instance", &img, &err)); - const isa::ImageView v = isa::ImageView::from(img); + const compiler::StbView v = compiler::StbView::from(img); CHECK(v.ok()); - CHECK(v.header().n_funcs == 2); // FB 占位 + MAIN - CHECK(v.header().entry_fn_id == 1); + CHECK(v.n_funcs() == 2); // FB 占位 + MAIN + CHECK(v.entry_fn_id() == 1); CHECK(v.data_len() == 24); // 实例 3 槽 × 8 字节定宽 - const isa::FuncRow fm = v.func_row(1); + const compiler::StbView::FuncRow fm = v.func_row(1); CHECK(fm.nregs == 12); CHECK(fm.code_len == 12); const uint32_t* im = reinterpret_cast(v.code_bytes()) + fm.code_offset / 4; @@ -588,10 +601,10 @@ static bool test_case17() { std::string err; CHECK(compile_case("17_ton", &img, &err)); - const isa::ImageView v = isa::ImageView::from(img); + const compiler::StbView v = compiler::StbView::from(img); CHECK(v.ok()); CHECK(v.data_len() == 32); // in@0 pt@8 q@16 et@24 - const isa::FuncRow r = v.func_row(0); + const compiler::StbView::FuncRow r = v.func_row(0); CHECK(r.nregs == 11); const uint32_t* ins = reinterpret_cast(v.code_bytes()); char buf[64]; @@ -617,10 +630,10 @@ static bool test_case18() { std::string err; CHECK(compile_case("18_tof_ctu", &img, &err)); - const isa::ImageView v = isa::ImageView::from(img); + const compiler::StbView v = compiler::StbView::from(img); CHECK(v.ok()); CHECK(v.data_len() == 72); // tf 4 槽 + c 5 槽 = 9 槽 × 8 - const isa::FuncRow r = v.func_row(0); + const compiler::StbView::FuncRow r = v.func_row(0); CHECK(r.nregs == 14); const uint32_t* ins = reinterpret_cast(v.code_bytes()); char buf[64]; @@ -653,16 +666,18 @@ static bool test_line1() { LinkResult link; CHECK(link_project(p, units, &link, &err)); CHECK(check_project(p, units, link, &err)); - CHECK(codegen_project(p, units, link, &img, &err)); + compiler::MachineConfig cfg; + CHECK(load_cfg(&cfg)); + CHECK(codegen_project(p, units, link, cfg, &img, &err)); - const isa::ImageView v = isa::ImageView::from(img); + const compiler::StbView v = compiler::StbView::from(img); CHECK(v.ok()); - CHECK(v.header().n_globals == 4); - CHECK(v.header().n_funcs == 2); - CHECK(v.header().entry_fn_id == 1); + CHECK(v.n_globals() == 4); + CHECK(v.n_funcs() == 2); + CHECK(v.entry_fn_id() == 1); CHECK(v.data_len() == 56); // 7 槽 × 8 字节定宽 - const isa::FuncRow fm = v.func_row(1); + const compiler::StbView::FuncRow fm = v.func_row(1); CHECK(fm.nregs == 13); CHECK(fm.code_len == 17); const uint32_t* im = reinterpret_cast(v.code_bytes()) + fm.code_offset / 4; @@ -683,6 +698,16 @@ static bool test_line1() { CHECK(std::strcmp(buf, "STORE_Q r8, 3") == 0); // Q0_0(io.output) isa::disasm(im[16], buf, sizeof buf); CHECK(std::strcmp(buf, "RET") == 0); + + // 12.13 写侧自检:型号标识 + SHA-256(compiler::StbView) + const compiler::StbView self = compiler::StbView::from(img); + CHECK(self.ok()); + CHECK(self.model_matches("STATOR", 1)); + CHECK(self.sha_ok()); + std::vector tampered = img; + tampered[100] ^= 0x01; // 篡改代码段一字节 + const compiler::StbView bad = compiler::StbView::from(tampered); + CHECK(bad.ok() && !bad.sha_ok()); // 结构可解析但 SHA 校验失败 return true; } diff --git a/tests/src/isa_test.cpp b/tests/src/isa_test.cpp index 6588332..5583c14 100644 --- a/tests/src/isa_test.cpp +++ b/tests/src/isa_test.cpp @@ -12,7 +12,6 @@ #include #include "isa/Encode.h" -#include "isa/Image.h" #include "isa/Instr.h" #include "isa/Op.h" #include "isa/Types.h" @@ -127,114 +126,11 @@ static bool test_encode() { return true; } -// ---- 5. FNV-1a 64 ---- - -static bool test_fnv() { - using namespace isa; - CHECK(fnv1a64(0, 0) == kFnvBasis); - const uint8_t a1[] = {'a'}; - CHECK(fnv1a64(a1, 1) == 0xaf63dc4c8601ec8cull); - return true; -} - -// ---- 6. 映像:write_ret_main → ImageView ---- - -static bool test_image() { - using namespace isa; - const uint64_t hash = 0x123456789abcdef0ull; - std::vector img = write_ret_main(100000, 10, hash); - - ImageView v = ImageView::from(img); - CHECK(v.ok()); - CHECK(v.error().empty()); - CHECK(v.header().cycle_limit == 100000); - CHECK(v.header().dt_ms == 10); - CHECK(v.header().project_hash == hash); - CHECK(v.header().entry_fn_id == 0); - CHECK(v.header().n_funcs == 1); - CHECK(v.header().n_globals == 0); - CHECK(v.header().n_i == 0); - CHECK(v.header().n_q == 0); - CHECK(v.header().n_m == 0); - CHECK(v.header().offset_const == kHeaderSize); - CHECK(v.header().offset_data == kHeaderSize + kFuncRowSize + 4); - CHECK(v.code_len() == 4); - CHECK(v.data_len() == 0); - - const FuncRow r = v.func_row(0); - CHECK(r.nregs == 0); - CHECK(r.code_offset == 0); - CHECK(r.code_len == 1); - - char buf[32]; - disasm(v.code_bytes()[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "RET") == 0); - - // 损坏映像必须拒绝 - std::vector bad = img; - bad[0] = 'X'; - CHECK(!ImageView::from(bad).ok()); - std::vector bad2 = img; - bad2[60] = 255; // offset_code 越界 - CHECK(!ImageView::from(bad2).ok()); - std::vector bad3 = img; - bad3[48] = 2; // n_funcs=2 但表只有 1 行 - CHECK(!ImageView::from(bad3).ok()); - std::vector bad4 = img; - bad4[24] = 1; // entry_fn_id 越界 - CHECK(!ImageView::from(bad4).ok()); - std::vector short_img = img; - short_img.resize(kHeaderSize - 1); - CHECK(!ImageView::from(short_img).ok()); - return true; -} - -// ---- 7. .stb 文件与 sidecar ---- - -static bool test_files() { - using namespace isa; - std::vector img = write_ret_main(100000, 10, 0); - - const char* stb_path = "isa_test_tmp.stb"; - std::string err; - CHECK(write_stb_file(stb_path, img, &err)); - std::vector back; - CHECK(read_stb_file(stb_path, &back, &err)); - CHECK(back == img); - CHECK(ImageView::from(back).ok()); - std::remove(stb_path); - - std::vector bs; - IoBinding b1; - b1.var = "I0_0"; b1.slot = 1; b1.channel = 0; b1.bit = 0; b1.is_input = true; - IoBinding b2; - b2.var = "Q0_0"; b2.slot = 3; b2.channel = 0; b2.bit = 0; b2.is_input = false; - bs.push_back(b1); - bs.push_back(b2); - - const std::string sc = make_sidecar(bs); - CHECK(sc.find("[[io.input]]") == 0); - CHECK(sc.find("var = \"I0_0\"") != std::string::npos); - CHECK(sc.find("slot = 1") != std::string::npos); - CHECK(sc.find("channel = 0") != std::string::npos); - CHECK(sc.find("[[io.output]]") != std::string::npos); - CHECK(sc.find("var = \"Q0_0\"") != std::string::npos); - CHECK(sc.find("slot = 3") != std::string::npos); - - const char* sc_path = "isa_test_tmp.runtime.toml"; - CHECK(write_sidecar_file(sc_path, bs, &err)); - std::remove(sc_path); - return true; -} - int main() { if (!test_sat()) return 1; if (!test_op()) return 1; if (!test_instr()) return 1; if (!test_encode()) return 1; - if (!test_fnv()) return 1; - if (!test_image()) return 1; - if (!test_files()) return 1; std::printf("isa_test: %d checks passed\n", g_checks); return 0; } diff --git a/tests/src/machine_test.cpp b/tests/src/machine_test.cpp new file mode 100644 index 0000000..a17bfb9 --- /dev/null +++ b/tests/src/machine_test.cpp @@ -0,0 +1,329 @@ +/** + * @file machine_test.cpp + * @brief machine.toml 加载与强校验测试(阶段 A 步骤 3) + * @author + * @date 2026-08-21 + */ + +#include +#include +#include +#include + +#include "compiler/MachineConfig.h" +#include "compiler/Stb.h" +#include "compiler/TypeInfo.h" + +#ifndef REPO_ROOT +#define REPO_ROOT "." +#endif + +static int g_checks = 0; + +#define CHECK(cond) \ + do { \ + if (!(cond)) { \ + std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + return false; \ + } \ + ++g_checks; \ + } while (0) + +// ---- 写临时 toml ---- + +static std::string write_tmp(const char* name, const char* content) { + const std::filesystem::path dir = std::filesystem::temp_directory_path(); + const std::filesystem::path path = dir / name; + std::FILE* f = std::fopen(path.string().c_str(), "w"); + std::fputs(content, f); + std::fclose(f); + return path.string(); +} + +static bool expect_load_err(const char* name, const char* content, const char* keyword) { + const std::string path = write_tmp(name, content); + compiler::MachineConfig cfg; + std::string err; + if (cfg.load(path, &err)) { + std::printf("FAIL %s: loaded ok\n", name); + std::remove(path.c_str()); + return false; + } + if (err.find("machine error") != 0) { + std::printf("FAIL %s: want 'machine error', got '%s'\n", name, err.c_str()); + std::remove(path.c_str()); + return false; + } + if (err.find(keyword) == std::string::npos) { + std::printf("FAIL %s: want '%s', got '%s'\n", name, keyword, err.c_str()); + std::remove(path.c_str()); + return false; + } + ++g_checks; + std::remove(path.c_str()); + return true; +} + +// 合法内容模板(可注入损坏) +static const char* kGood = + "[meta]\nname = \"STATOR\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nrange = [0, 1]\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n" + "[[op]]\nname = \"CAL_TON\"\nopcode = 24\nclass = \"instance\"\nformat = \"CAL\"\nparams = [\"instance\"]\nenabled = true\n" + "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n" + "[[fb]]\nname = \"ton\"\nopcode = 24\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n"; + +// ---- 1. 正例:仓库 machine.toml ---- + +static bool test_positive() { + compiler::MachineConfig cfg; + std::string err; + CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)); + CHECK(cfg.ok()); + CHECK(cfg.model_name() == "STATOR"); + CHECK(cfg.version() == 1); + + CHECK(cfg.types().size() == 3); + const compiler::ConfigType* bt = cfg.find_type("BOOL"); + CHECK(bt != nullptr && bt->base == "uint8" && bt->has_range && bt->range_min == 0 && + bt->range_max == 1 && bt->tag == 0); + CHECK(cfg.find_type("INT") != nullptr && cfg.find_type("INT")->tag == 1); + CHECK(cfg.find_type("TIME") != nullptr && cfg.find_type("TIME")->tag == 2); + CHECK(cfg.find_type("REAL") == nullptr); + + CHECK(cfg.ops().size() == 34); + const compiler::ConfigOp* add = cfg.find_op("ADD"); + CHECK(add != nullptr && add->opcode == 5 && add->format == "RRR" && !add->is_instance && + add->params.size() == 3); + const compiler::ConfigOp* ton = cfg.find_op("CAL_TON"); + CHECK(ton != nullptr && ton->opcode == 24 && ton->is_instance && ton->format == "CAL"); + CHECK(cfg.find_op("CAL_F_TRIG") != nullptr && cfg.find_op("CAL_F_TRIG")->opcode == 31); + CHECK(cfg.find_op_by_code(33) != nullptr && cfg.find_op_by_code(33)->name == "RET"); + CHECK(cfg.op_enabled(0) && cfg.op_enabled(24)); + CHECK(cfg.find_op("NOPE") == nullptr); + + CHECK(cfg.fbs().size() == 8); + const compiler::ConfigFb* ctud = cfg.find_fb("ctud"); + CHECK(ctud != nullptr && ctud->opcode == 29 && ctud->fields.size() == 8); + CHECK(cfg.find_fb("r_trig") != nullptr && cfg.find_fb("r_trig")->fields.size() == 2); + + // 基元表 + CHECK(compiler::MachineConfig::find_prim("int16") != nullptr); + const compiler::PrimType* p = compiler::MachineConfig::find_prim("uint8"); + CHECK(p != nullptr && p->width == 1 && !p->is_signed && !p->is_float); + CHECK(compiler::MachineConfig::find_prim("float64") != nullptr); + CHECK(compiler::MachineConfig::find_prim("bfloat16") == nullptr); + return true; +} + +// ---- 2. 负例:9 类 ---- + +static bool test_negative() { + // 缺属性(enabled 缺失) + if (!expect_load_err( + "mc_bad1.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\", \"rs\"]\n", + "missing field 'enabled'")) { + return false; + } + // base 未命中基元 + if (!expect_load_err( + "mc_bad2.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"bigint\"\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n", + "unknown base")) { + return false; + } + // range min > max + if (!expect_load_err( + "mc_bad3.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nrange = [1, 0]\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n", + "range min > max")) { + return false; + } + // tag 契约破坏(BOOL tag=5) + if (!expect_load_err( + "mc_bad4.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 5\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n", + "tag out of contract")) { + return false; + } + // tag 契约破坏(INT 占 tag 0) + if (!expect_load_err( + "mc_bad5.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 1\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 0\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n", + "type contract broken")) { + return false; + } + // opcode 重号 + if (!expect_load_err( + "mc_bad6.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n" + "[[op]]\nname = \"NOPE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"a\", \"b\"]\nenabled = true\n", + "duplicate opcode")) { + return false; + } + // 类别-格式互锁破坏(instance + RRR) + if (!expect_load_err( + "mc_bad7.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"BAD\"\nopcode = 24\nclass = \"instance\"\nformat = \"RRR\"\nparams = [\"a\", \"b\", \"c\"]\nenabled = true\n", + "class-format mismatch")) { + return false; + } + // params 数量与 format 不符 + if (!expect_load_err( + "mc_bad8.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\"]\nenabled = true\n", + "params count")) { + return false; + } + // fb.opcode 不匹配 instance op + if (!expect_load_err( + "mc_bad9.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n" + "[[fb]]\nname = \"ton\"\nopcode = 33\nfields = [[\"in\", \"BOOL\"]]\n", + "must match an instance op")) { + return false; + } + // fb 字段类型未命中 type 表 + if (!expect_load_err( + "mc_bad10.toml", + "[meta]\nname = \"S\"\nversion = 1\n" + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" + "[[op]]\nname = \"CAL_TON\"\nopcode = 24\nclass = \"instance\"\nformat = \"CAL\"\nparams = [\"instance\"]\nenabled = true\n" + "[[fb]]\nname = \"ton\"\nopcode = 24\nfields = [[\"in\", \"REAL\"]]\n", + "unknown field type")) { + return false; + } + // 语法错误 + if (!expect_load_err("mc_bad11.toml", "[meta\nname = \"S\"\n", "parse error")) { + return false; + } + return true; +} + +// ---- 3. 合法最小配置正例 ---- + +static bool test_positive_min() { + const std::string path = write_tmp("mc_good.toml", kGood); + compiler::MachineConfig cfg; + std::string err; + CHECK(cfg.load(path, &err)); + CHECK(cfg.ops().size() == 3); + CHECK(cfg.fbs().size() == 1); + CHECK(cfg.find_fb("ton")->fields.size() == 4); + std::remove(path.c_str()); + return true; +} + +// ---- 4. 类型元数据(TypeInfo)---- + +static bool test_type_meta() { + compiler::MachineConfig cfg; + std::string err; + CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)); + + // BOOL:base=uint8 → 1 字节、无符号、非浮点、tag 0、range [0,1] + const compiler::TypeMeta b = compiler::type_meta(cfg, "BOOL"); + CHECK(b && b.width() == 1 && !b.is_signed() && !b.is_float() && b.tag() == 0); + CHECK(b.has_range() && b.value_in_range(0) && b.value_in_range(1)); + CHECK(!b.value_in_range(2) && !b.value_in_range(-1)); + // 大小写不敏感(Linker type_name 是小写) + const compiler::TypeMeta b2 = compiler::type_meta(cfg, "bool"); + CHECK(b2 && b2.tag() == 0 && b2.width() == 1); + + // INT:int16 → 2 字节、有符号、tag 1、无 range + const compiler::TypeMeta i = compiler::type_meta(cfg, "int"); + CHECK(i && i.width() == 2 && i.is_signed() && !i.is_float() && i.tag() == 1); + CHECK(!i.has_range() || i.value_in_range(30000)); + + // TIME:int64 → 8 字节、有符号、tag 2 + const compiler::TypeMeta t = compiler::type_meta(cfg, "TIME"); + CHECK(t && t.width() == 8 && t.is_signed() && !t.is_float() && t.tag() == 2); + + // 未定义类型 → 空 + CHECK(!compiler::type_meta(cfg, "REAL")); + CHECK(!compiler::type_meta(cfg, "")); + return true; +} + +// ---- 5. SHA-256 已知向量 + 型号标识 ---- + +static bool test_sha256() { + // 标准测试向量 + { + uint8_t out[compiler::kSha256Size]; + compiler::sha256(nullptr, 0, out); + const uint8_t want[32] = {0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, + 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, + 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, + 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}; + CHECK(std::memcmp(out, want, 32) == 0); + } + { + const uint8_t abc[] = {'a', 'b', 'c'}; + uint8_t out[compiler::kSha256Size]; + compiler::sha256(abc, 3, out); + const uint8_t want[32] = {0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, + 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, + 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, + 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad}; + CHECK(std::memcmp(out, want, 32) == 0); + } + // 型号标识:STATOR + 1 → "STATOR1" 补 '\0' + char mid[compiler::kModelIdSize]; + compiler::fill_model_id("STATOR", 1, mid); + CHECK(std::strncmp(mid, "STATOR1", 7) == 0); + CHECK(mid[7] == '\0'); + CHECK(mid[31] == '\0'); + return true; +} + +int main() { + if (!test_positive()) return 1; + if (!test_negative()) return 1; + if (!test_positive_min()) return 1; + if (!test_type_meta()) return 1; + if (!test_sha256()) return 1; + std::printf("machine_test: %d checks passed\n", g_checks); + return 0; +} diff --git a/tests/src/vm_test.cpp b/tests/src/vm_test.cpp index 01cec55..d5df419 100644 --- a/tests/src/vm_test.cpp +++ b/tests/src/vm_test.cpp @@ -13,9 +13,9 @@ #include "compiler/Codegen.h" #include "compiler/Linker.h" #include "compiler/Project.h" +#include "compiler/Stb.h" #include "compiler/Typecheck.h" #include "isa/Encode.h" -#include "isa/Image.h" #include "isa/Instr.h" #include "isa/Op.h" #include "vm/Machine.h" @@ -55,6 +55,10 @@ static void wslot(vm::Machine& m, int s, int64_t v) { static bool make_machine(const char* dir, vm::Machine* m, std::string* err) { using namespace compiler; + MachineConfig cfg; + if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", err)) { + return false; + } const std::string toml = std::string(REPO_ROOT) + "/" + dir + "/project.toml"; Project p; if (!parse_project(toml, &p, err)) { @@ -76,7 +80,7 @@ static bool make_machine(const char* dir, vm::Machine* m, std::string* err) { return false; } std::vector img; - if (!codegen_project(p, units, link, &img, err)) { + if (!codegen_project(p, units, link, cfg, &img, err)) { return false; } return vm::Machine::create(img, m, err); @@ -89,7 +93,7 @@ struct HFunc { uint32_t nregs = 8; }; -static std::vector hand_image(const std::vector& consts, +static std::vector hand_image(const std::vector& consts, const std::vector& funcs, uint32_t entry = 0, uint32_t cycle_limit = 100000, @@ -98,11 +102,11 @@ static std::vector hand_image(const std::vector& const for (const HFunc& f : funcs) { code_total += f.code.size() * 4; } - const uint32_t off_const = 72; + const uint32_t off_const = 104; const uint32_t off_funcs = off_const + static_cast(consts.size()) * 12; const uint32_t off_code = off_funcs + static_cast(funcs.size()) * 12; const uint32_t off_data = off_code + static_cast(code_total); - const uint32_t off_end = off_data + static_cast(data.size()); + const uint32_t off_end = off_data + static_cast(data.size()) + 32; // SHA 尾占位 std::vector b(off_end, 0); auto put32 = [&](size_t o, uint32_t v) { @@ -116,8 +120,8 @@ static std::vector hand_image(const std::vector& const b[o + i] = static_cast((v >> (8 * i)) & 0xFFu); } }; - put32(0, isa::kMagic); - put32(4, isa::kVersion); + put32(0, 0x43545353u); + put32(4, 1u); put32(8, cycle_limit); put32(12, 10); // dt_ms put32(24, entry); @@ -152,6 +156,16 @@ static std::vector hand_image(const std::vector& const for (size_t i = 0; i < data.size(); ++i) { b[off_data + i] = data[i]; } + // 型号标识(STATOR1)+ 真实 SHA-256 尾(用 compiler 实现填) + const char* mid = "STATOR1"; + for (size_t i = 0; i < 32; ++i) { + b[72 + i] = i < 7 ? static_cast(mid[i]) : 0; + } + uint8_t digest[32]; + compiler::sha256(b.data(), b.size() - 32, digest); + for (int i = 0; i < 32; ++i) { + b[b.size() - 32 + i] = digest[i]; + } return b; } @@ -159,10 +173,10 @@ static std::vector hand_image(const std::vector& const static bool test_hand_scalar() { // r8 := 5;r9 := r8 + 3;槽 0 ← r9;槽 1 → r10;JT 跳过一条 - const std::vector consts = { - {isa::types::Int, 5}, - {isa::types::Int, 3}, - {isa::types::Int, 1}, + const std::vector consts = { + {1, 5}, + {1, 3}, + {1, 1}, }; const std::vector funcs = {{ { @@ -197,9 +211,9 @@ static bool test_hand_scalar() { static bool test_hand_call() { // fn0(入口):r8=5、r2=7、实参 r1←r8;CALL 1;结果 r0 → r8 // fn1:r0 = r1 + r2(r1/r2 来自调用约定区复制) - const std::vector consts = { - {isa::types::Int, 5}, - {isa::types::Int, 7}, + const std::vector consts = { + {1, 5}, + {1, 7}, }; const std::vector funcs = { {{ @@ -425,6 +439,64 @@ static bool test_bad_const() { return true; } +// ---- 10. 12.13 校验:SHA 篡改拒绝 + 型号不匹配拒绝 ---- + +static bool test_verify() { + std::string err; + vm::Machine m; + + // 正常映像可跑 + CHECK(make_machine("tests/cases/01_empty_main", &m, &err)); + CHECK(m.run_cycle() == vm::Fault::None); + + // SHA 篡改:改一字节 → create 拒绝 + { + const std::string toml = std::string(REPO_ROOT) + "/tests/cases/01_empty_main/project.toml"; + compiler::Project p; + CHECK(compiler::parse_project(toml, &p, &err)); + std::vector units; + for (const std::string& f : compiler::compile_files(p)) { + compiler::SourceUnit u; + CHECK(compiler::load_unit(p.base_dir + "/" + f, &u, &err)); + units.push_back(std::move(u)); + } + compiler::LinkResult link; + CHECK(compiler::link_project(p, units, &link, &err)); + compiler::MachineConfig cfg; + CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)); + std::vector img; + CHECK(compiler::codegen_project(p, units, link, cfg, &img, &err)); + img[100] ^= 0x01; // 篡改代码段一字节 + vm::Machine bad; + CHECK(!vm::Machine::create(img, &bad, &err)); + CHECK(err.find("sha256 mismatch") != std::string::npos); + } + + // 型号不匹配:手拼映像改型号(重算 SHA,仅型号不一致)→ create 拒绝 + { + const std::vector consts; + const std::vector funcs = {{ + { + isa::enc_ret(), + }, + 8, + }}; + std::vector img = hand_image(consts, funcs); + for (int i = 0; i < 5; ++i) { + img[72 + i] = "OTHER"[i]; + } + uint8_t digest[32]; + compiler::sha256(img.data(), img.size() - 32, digest); + for (int i = 0; i < 32; ++i) { + img[img.size() - 32 + i] = digest[i]; + } + vm::Machine bad; + CHECK(!vm::Machine::create(img, &bad, &err)); + CHECK(err.find("model mismatch") != std::string::npos); + } + return true; +} + int main() { if (!test_hand_scalar()) return 1; if (!test_hand_call()) return 1; @@ -435,6 +507,7 @@ int main() { if (!test_determinism()) return 1; if (!test_step()) return 1; if (!test_bad_const()) return 1; + if (!test_verify()) return 1; std::printf("vm_test: %d checks passed\n", g_checks); return 0; } diff --git a/vm/CMakeLists.txt b/vm/CMakeLists.txt index 6f39576..c7a06ae 100644 --- a/vm/CMakeLists.txt +++ b/vm/CMakeLists.txt @@ -7,7 +7,8 @@ project(VM DESCRIPTION "寄存器虚拟机") add_library(vm STATIC - ./src/Machine.cpp) + ./src/Machine.cpp + ./src/Image.cpp) target_include_directories(vm PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) target_link_libraries(vm PUBLIC isa) diff --git a/vm/include/vm/Image.h b/vm/include/vm/Image.h new file mode 100644 index 0000000..e3dac36 --- /dev/null +++ b/vm/include/vm/Image.h @@ -0,0 +1,87 @@ +/** + * @file Image.h + * @brief vm 自带的 .stb 只读视图(执行器侧;compiler 各有实现) + * @author + * @date 2026-08-21 + * + * @details 格式契约见 Doc/isa/指令与映像.md(12.13 修订:头 104 = 72 + 型号标识[32], + * 文件尾 SHA-256[32])。步骤 7:解析头与段;步骤 8:补型号匹配与 SHA-256 校验。 + */ + +#pragma once + +#include +#include +#include +#include + +namespace vm { + + // 执行器内建支持型号(与 machine.toml [meta] 对齐;.stb 型号不匹配直接拒绝) + static const char* const kModelName = "STATOR"; + static const uint32_t kModelVersion = 1; + + // 映像头(字段与 Doc/isa/指令与映像.md 一致) + struct ImageHeader { + uint32_t cycle_limit = 0; + uint32_t dt_ms = 0; + uint64_t project_hash = 0; + uint32_t entry_fn_id = 0; + uint32_t n_globals = 0; + uint32_t n_i = 0; + uint32_t n_q = 0; + uint32_t n_m = 0; + uint32_t n_consts = 0; + uint32_t n_funcs = 0; + uint32_t offset_const = 0; + uint32_t offset_funcs = 0; + uint32_t offset_code = 0; + uint32_t offset_fb = 0; + uint32_t offset_data = 0; + }; + + struct FuncRow { + uint32_t nregs = 0; + uint32_t code_offset = 0; // 相对字节码段起点(字节) + uint32_t code_len = 0; // 指令条数 + }; + + struct ConstEntry { + uint32_t tag = 0; + uint64_t value = 0; + }; + + // 只读视图:校验过的映像(指向外部缓冲) + class Image { + public: + static Image from(const uint8_t* buf, size_t len); + static Image from(const std::vector& buf); + + bool ok() const { return ok_; } + const std::string& error() const { return err_; } + const ImageHeader& header() const { return hdr_; } + + FuncRow func_row(size_t i) const; + ConstEntry const_entry(size_t i) const; + + const uint8_t* code_bytes() const; // 字节码段起点 + const uint8_t* data_bytes() const; // 数据段起点 + size_t data_len() const; // 字节数(不含 SHA 尾) + + // 12.13:型号标识与 SHA-256 校验 + std::string model_id() const; + bool model_matches(const std::string& name, uint32_t version) const; + bool sha_ok() const; // 文件尾 32 字节 SHA-256 校验 + + public: + // 默认构造为无效态(ok() == false) + Image() : buf_(nullptr), len_(0) {} + + private: + const uint8_t* buf_; + size_t len_; + bool ok_ = false; + std::string err_; + ImageHeader hdr_; + }; +} diff --git a/vm/include/vm/Machine.h b/vm/include/vm/Machine.h index 9a63669..a3f4ba5 100644 --- a/vm/include/vm/Machine.h +++ b/vm/include/vm/Machine.h @@ -19,7 +19,7 @@ #include #include -#include "isa/Image.h" +#include "vm/Image.h" #include "isa/Instr.h" namespace vm { @@ -67,7 +67,7 @@ namespace vm { bool ended() const; /** @brief 映像头(dt_ms / cycle_limit 等) */ - const isa::ImageHeader& header() const; + const vm::ImageHeader& header() const; /** @brief 当前指令下标(相对当前函数字节码段) */ uint32_t pc() const; @@ -105,7 +105,7 @@ namespace vm { uint32_t ret_fn_id = 0; // 返回目标函数 }; - isa::ImageView image_; // 映像只读视图 + vm::Image image_; // 映像只读视图(vm 自实现) std::vector data_; // 数据区工作副本(8 字节定宽槽) std::vector frames_; // 调用栈([0] = MAIN,跨周期保留) // 边沿检测上次输入(每槽 2 字节:[slot*2] 第一边沿、[slot*2+1] 第二边沿; diff --git a/vm/src/Image.cpp b/vm/src/Image.cpp new file mode 100644 index 0000000..d826c00 --- /dev/null +++ b/vm/src/Image.cpp @@ -0,0 +1,300 @@ +/** + * @file Image.cpp + * @brief vm 自带的 .stb 只读视图(执行器侧) + * @author + * @date 2026-08-21 + */ + +#include "vm/Image.h" + +#include +#include + +namespace vm { + +namespace { + + const uint32_t kMagic = 0x43545353u; // "STSC" 小端 + const uint32_t kVersion = 1; + const size_t kHeaderSize = 104; + const size_t kConstEntrySize = 12; + const size_t kFuncRowSize = 12; + const size_t kSha256Size = 32; + const size_t kModelIdSize = 32; + + uint32_t get_le32(const uint8_t* p) { + return static_cast(p[0]) + | (static_cast(p[1]) << 8) + | (static_cast(p[2]) << 16) + | (static_cast(p[3]) << 24); + } + + uint64_t get_le64(const uint8_t* p) { + uint64_t v = 0; + for (int i = 0; i < 8; ++i) { + v |= static_cast(p[i]) << (8 * i); + } + return v; + } + + // ---- SHA-256(执行器侧实现,与 compiler 各一份)---- + + const uint32_t kShaK[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + }; + + inline uint32_t rotr(uint32_t x, uint32_t n) { return (x >> n) | (x << (32 - n)); } + + struct Sha256 { + uint32_t h[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; + uint64_t total = 0; + uint8_t block[64]; + size_t block_len = 0; + + void update(const uint8_t* data, size_t len) { + total += len; + while (len > 0) { + const size_t n = (block_len < 64) ? (64 - block_len) : 0; + const size_t take = len < n ? len : n; + if (take == 0) { + break; + } + for (size_t i = 0; i < take; ++i) { + block[block_len + i] = data[i]; + } + block_len += take; + data += take; + len -= take; + if (block_len == 64) { + process(); + block_len = 0; + } + } + } + + void process() { + uint32_t w[64]; + for (int i = 0; i < 16; ++i) { + w[i] = get_be32(block + i * 4); + } + for (int i = 16; i < 64; ++i) { + const uint32_t s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ (w[i - 15] >> 3); + const uint32_t s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ (w[i - 2] >> 10); + w[i] = w[i - 16] + s0 + w[i - 7] + s1; + } + uint32_t a = h[0], b = h[1], c = h[2], d = h[3]; + uint32_t e = h[4], f = h[5], g = h[6], hh = h[7]; + for (int i = 0; i < 64; ++i) { + const uint32_t s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25); + const uint32_t ch = (e & f) ^ (~e & g); + const uint32_t t1 = hh + s1 + ch + kShaK[i] + w[i]; + const uint32_t s0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22); + const uint32_t maj = (a & b) ^ (a & c) ^ (b & c); + const uint32_t t2 = s0 + maj; + hh = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + h[0] += a; h[1] += b; h[2] += c; h[3] += d; + h[4] += e; h[5] += f; h[6] += g; h[7] += hh; + } + + void final(uint8_t out[32]) { + const uint64_t bitlen = total * 8; + const uint8_t pad = 0x80; + update(&pad, 1); + const uint8_t zeros[64] = {0}; + while (block_len != 56) { + const size_t n = (block_len < 56) ? (56 - block_len) : (64 - block_len); + update(zeros, n); + } + for (int i = 0; i < 8; ++i) { + const uint8_t b2[1] = {static_cast((bitlen >> (56 - 8 * i)) & 0xFF)}; + update(b2, 1); + } + for (int i = 0; i < 8; ++i) { + out[i * 4 + 0] = static_cast((h[i] >> 24) & 0xFF); + out[i * 4 + 1] = static_cast((h[i] >> 16) & 0xFF); + out[i * 4 + 2] = static_cast((h[i] >> 8) & 0xFF); + out[i * 4 + 3] = static_cast(h[i] & 0xFF); + } + } + + static uint32_t get_be32(const uint8_t* p) { + return (static_cast(p[0]) << 24) | (static_cast(p[1]) << 16) | + (static_cast(p[2]) << 8) | static_cast(p[3]); + } + }; + + void sha256(const uint8_t* data, size_t len, uint8_t out[32]) { + Sha256 s; + s.update(data, len); + s.final(out); + } + + void fill_model_id(const std::string& name, uint32_t version, char out[32]) { + const std::string id = name + std::to_string(version); + for (size_t i = 0; i < 32; ++i) { + out[i] = i < id.size() ? id[i] : '\0'; + } + } + +} // namespace + +Image Image::from(const uint8_t* buf, size_t len) { + Image v; + v.buf_ = buf; + v.len_ = len; + if (buf == nullptr) { + v.err_ = "null buffer"; + return v; + } + if (len < kHeaderSize) { + v.err_ = "image too short"; + return v; + } + if (get_le32(buf + 0) != kMagic) { + v.err_ = "bad magic"; + return v; + } + if (get_le32(buf + 4) != kVersion) { + v.err_ = "bad version"; + return v; + } + ImageHeader& h = v.hdr_; + h.cycle_limit = get_le32(buf + 8); + h.dt_ms = get_le32(buf + 12); + h.project_hash = get_le64(buf + 16); + h.entry_fn_id = get_le32(buf + 24); + h.n_globals = get_le32(buf + 28); + h.n_i = get_le32(buf + 32); + h.n_q = get_le32(buf + 36); + h.n_m = get_le32(buf + 40); + h.n_consts = get_le32(buf + 44); + h.n_funcs = get_le32(buf + 48); + h.offset_const = get_le32(buf + 52); + h.offset_funcs = get_le32(buf + 56); + h.offset_code = get_le32(buf + 60); + h.offset_fb = get_le32(buf + 64); + h.offset_data = get_le32(buf + 68); + + // 段校验(数据段之后是 SHA-256 文件尾;步骤 8 做校验,这里仅保证有尾) + if (len < static_cast(h.offset_data) + kSha256Size) { + v.err_ = "missing sha256 tail"; + return v; + } + const uint64_t offs[5] = {h.offset_const, h.offset_funcs, h.offset_code, h.offset_fb, + h.offset_data}; + for (int i = 0; i < 5; ++i) { + if (offs[i] < kHeaderSize || offs[i] > len - kSha256Size) { + v.err_ = "segment offset out of range"; + return v; + } + if (i > 0 && offs[i] < offs[i - 1]) { + v.err_ = "segment offsets not monotonic"; + return v; + } + } + if (offs[1] - offs[0] != static_cast(h.n_consts) * kConstEntrySize) { + v.err_ = "const table size mismatch"; + return v; + } + if (offs[2] - offs[1] != static_cast(h.n_funcs) * kFuncRowSize) { + v.err_ = "function table size mismatch"; + return v; + } + if ((h.offset_fb - h.offset_code) % 4 != 0) { + v.err_ = "code segment not 4-byte aligned"; + return v; + } + if (h.entry_fn_id >= h.n_funcs && h.n_funcs != 0) { + v.err_ = "entry fn_id out of range"; + return v; + } + v.ok_ = true; + v.err_.clear(); + return v; +} + +Image Image::from(const std::vector& buf) { + return from(buf.data(), buf.size()); +} + +FuncRow Image::func_row(size_t i) const { + FuncRow r; + if (ok_ && i < hdr_.n_funcs) { + const uint8_t* p = buf_ + hdr_.offset_funcs + i * kFuncRowSize; + r.nregs = get_le32(p + 0); + r.code_offset = get_le32(p + 4); + r.code_len = get_le32(p + 8); + } + return r; +} + +ConstEntry Image::const_entry(size_t i) const { + ConstEntry e; + if (ok_ && i < hdr_.n_consts) { + const uint8_t* p = buf_ + hdr_.offset_const + i * kConstEntrySize; + e.tag = get_le32(p); + e.value = get_le64(p + 4); + } + return e; +} + +const uint8_t* Image::code_bytes() const { + return ok_ ? buf_ + hdr_.offset_code : nullptr; +} + +const uint8_t* Image::data_bytes() const { + return ok_ ? buf_ + hdr_.offset_data : nullptr; +} + +size_t Image::data_len() const { + return ok_ ? (len_ - kSha256Size) - hdr_.offset_data : 0; +} + +std::string Image::model_id() const { + if (!ok_) { + return ""; + } + std::string s(reinterpret_cast(buf_ + 72), kModelIdSize); + const size_t z = s.find('\0'); + if (z != std::string::npos) { + s.resize(z); + } + return s; +} + +bool Image::model_matches(const std::string& name, uint32_t version) const { + char want[kModelIdSize]; + fill_model_id(name, version, want); + return std::memcmp(buf_ + 72, want, kModelIdSize) == 0; +} + +bool Image::sha_ok() const { + if (!ok_) { + return false; + } + const size_t content_len = len_ - kSha256Size; + uint8_t digest[32]; + sha256(buf_, content_len, digest); + return std::memcmp(buf_ + content_len, digest, 32) == 0; +} + +} // namespace vm diff --git a/vm/src/Machine.cpp b/vm/src/Machine.cpp index 8493be0..ab869cf 100644 --- a/vm/src/Machine.cpp +++ b/vm/src/Machine.cpp @@ -25,13 +25,27 @@ namespace vm { bool Machine::create(const std::vector& image, Machine* out, std::string* err) { - out->image_ = isa::ImageView::from(image); + out->image_ = vm::Image::from(image); if (!out->image_.ok()) { if (err) { *err = out->image_.error(); } return false; } + // 12.13:型号匹配与 SHA-256 校验(不匹配/篡改直接拒绝) + if (!out->image_.sha_ok()) { + if (err) { + *err = "image sha256 mismatch"; + } + return false; + } + if (!out->image_.model_matches(kModelName, kModelVersion)) { + if (err) { + *err = "image model mismatch: '" + out->image_.model_id() + + "' (expect " + kModelName + std::to_string(kModelVersion) + ")"; + } + return false; + } // 数据区工作副本(8 字节定宽槽) out->data_.assign(out->image_.data_bytes(), out->image_.data_bytes() + out->image_.data_len()); @@ -39,7 +53,7 @@ bool Machine::create(const std::vector& image, Machine* out, // MAIN 帧(跨周期保留) const uint32_t entry = out->image_.header().entry_fn_id; - const isa::FuncRow main = out->image_.func_row(entry); + const vm::FuncRow main = out->image_.func_row(entry); out->frames_.clear(); Frame f; f.fn_id = entry; @@ -79,7 +93,7 @@ bool Machine::step() { Fault Machine::fault() const { return fault_; } bool Machine::ended() const { return ended_; } -const isa::ImageHeader& Machine::header() const { return image_.header(); } +const vm::ImageHeader& Machine::header() const { return image_.header(); } uint32_t Machine::pc() const { return pc_; } uint32_t Machine::fn_id() const { return cur_frame().fn_id; } uint32_t Machine::cycle_count() const { return cycle_count_; } @@ -94,7 +108,7 @@ uint32_t Machine::nregs() const { } isa::Instr Machine::cur_instr() const { - const isa::FuncRow row = image_.func_row(cur_frame().fn_id); + const vm::FuncRow row = image_.func_row(cur_frame().fn_id); const uint8_t* base = image_.code_bytes() + row.code_offset; if (pc_ >= row.code_len) { return isa::pack(isa::Op::RET, 0, 0, 0); @@ -107,7 +121,7 @@ const Machine::Frame& Machine::cur_frame() const { return frames_.back(); } bool Machine::exec_one() { // ---- 取指 ---- - const isa::FuncRow row = image_.func_row(cur_frame().fn_id); + const vm::FuncRow row = image_.func_row(cur_frame().fn_id); if (pc_ >= row.code_len) { fault_ = Fault::BadOp; return false; @@ -165,7 +179,7 @@ bool Machine::exec_one() { fault_ = Fault::BadConst; return false; } - const isa::ConstEntry c = image_.const_entry(cid); + const vm::ConstEntry c = image_.const_entry(cid); if (c.tag == isa::types::Bool) { regs[rd] = c.value ? 1 : 0; } else if (c.tag == isa::types::Int) { @@ -310,7 +324,7 @@ Fault Machine::do_call(uint32_t fn_id) { if (frames_.size() >= 64) { return Fault::StackOverflow; } - const isa::FuncRow row = image_.func_row(fn_id); + const vm::FuncRow row = image_.func_row(fn_id); Frame f; f.fn_id = fn_id; f.regs.assign(row.nregs, 0);