Merge branch 'dev_ops_config':指令配置(machine.toml)全流程。

- A:OpFormat/OpClass 显式化、machine.toml 落地、MachineConfig 强校验
- B:compiler 配置驱动化去 isa、类型元数据配置化、.stb 写侧(型号标识+SHA-256)
- C:isa 拆出 Image、vm 自读 + 型号/SHA 校验
- D:CLI --machine 接线、测试固化(14/14)、文档同步
This commit is contained in:
2026-08-21 22:36:24 +08:00
38 changed files with 3105 additions and 1256 deletions
+28 -21
View File
@@ -15,45 +15,52 @@ ctest --test-dir build/gcc-debug # 测试(当前 9 个用例)
## 用法 ## 用法
```text ```text
STCompiler <project.toml> [-o <name>.stb] STCompiler <project.toml> [-o <name>.stb] --machine <machine.toml>
STCompiler <name>.stb --disasm STCompiler <name>.stb --disasm --machine <machine.toml>
``` ```
| 参数 | 作用 | | 参数 | 作用 |
|---|---| |---|---|
| `<project.toml>` | 工程文件(必填) | | `<project.toml>` | 工程文件(必填) |
| `-o <name>.stb` | 编译并写出映像文件;**缺省时只打印文件集合与工程哈希**(12.3 阶段行为 | | `-o <name>.stb` | 编译并写出映像文件+ sidecar;**缺省时只打印文件集合与工程哈希**(不需 `--machine` |
| `--machine <machine.toml>` | 机器定义(指令 opcode / 类型 / FB 布局,见 `Doc/isa/指令配置.md`);**编译与 `--disasm` 必填**,加载/校验失败报错退出 |
| `<name>.stb --disasm` | **反汇编一个已编译的映像**(不重新编译,见下) | | `<name>.stb --disasm` | **反汇编一个已编译的映像**(不重新编译,见下) |
| `--help` | 打印帮助 | | `--help` | 打印帮助 |
退出码:`0` 成功;`1` 编译失败(错误信息打到 stderr)。 退出码:`0` 成功;`1` 失败(错误信息打到 stderr)。
## 反汇编(--disasm ## 反汇编(--disasm
对已编译的 `.stb` 逐段查看:段摘要、函数表逐条指令(文件绝对偏移)、常量表、数据段 hex。 对已编译的 `.stb` 逐段查看:段摘要(含型号与 SHA 校验)、函数表逐条指令(文件绝对偏移)、常量表、数据段 hex。
```bash ```bash
$ STCompiler line1.stb --disasm $ STCompiler line1.stb --disasm --machine machine.toml
image: line1.stb (184 bytes, 2 functions, 4 globals, entry fn 1) image: line1.stb (288 bytes, 2 functions, 4 globals, entry fn 1)
dt_ms=10 cycle_limit=100000 hash=0xc3fe4ae74ad45900 dt_ms=10 cycle_limit=100000 hash=0xc3fe4ae74ad45900 model=STATOR1 sha=ok
functions: functions:
fn 0: nregs=8, offset=0, len=1 fn 0: nregs=8, offset=0, len=1
0x0060 RET 0x0080 RET
fn 1: nregs=13, offset=4, len=17 fn 1: nregs=13, offset=4, len=17
0x0064 LOAD_I r8, 1 0x0084 LOAD_I r8, 1
0x0068 STORE_GLOBAL r8, 8 0x0088 STORE_GLOBAL r8, 4
0x006c LOAD_GLOBAL r9, 2
... ...
data (16 bytes): data (56 bytes):
0x0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0000: ...
``` ```
说明: 说明:
- 指令偏移是**文件绝对字节偏移**,可直接对照 `xxd line1.stb` - 指令偏移是**文件绝对字节偏移**,可直接对照 `xxd line1.stb`
- 映像里**没有符号名**(函数表只有 fn_id,数据段无名字映射),因此按 `fn 0` / `fn 1`、槽号显示 - 映像里**没有符号名**(函数表只有 fn_id,数据段无名字映射),因此按 `fn 0` / `fn 1`、槽号显示
- `model=` 显示型号标识(`machine.toml [meta]` 生成);`sha=ok|BAD` 显示文件尾 SHA-256 校验结果
- 文件损坏或打不开 → `error: ...` 退出码 1 - 文件损坏或打不开 → `error: ...` 退出码 1
## 编译产物(.stb 加固)
- **型号标识[32]**`[meta] name + version` 拼成(如 `"STATOR1"`),写进头偏移 72
- **SHA-256 文件尾[32]**:对文件尾之前全部内容计算;compiler 写侧与 vm 读侧各实现一份
- 执行器读取时型号不匹配 / SHA 不符 → 直接拒绝(详见 `Doc/isa/指令与映像.md` 12.13 修订)
## 示例(line1 ## 示例(line1
```bash ```bash
@@ -65,11 +72,11 @@ files:
main.st main.st
hash: 0xc3fe4ae74ad45900 hash: 0xc3fe4ae74ad45900
$ STCompiler examples/line1/project.toml -o line1.stb $ STCompiler examples/line1/project.toml -o line1.stb --machine compiler/machine.toml
compiled: line1.stb (184 bytes, 2 functions, 4 globals) 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 ## 工程文件(project.toml
@@ -142,10 +149,10 @@ project.toml + .st
- `examples/line1/`:最小闭环(MAIN + MotorStarter FB + I/O + 全局),给人看 - `examples/line1/`:最小闭环(MAIN + MotorStarter FB + I/O + 全局),给人看
- `tests/cases/01~20/`20 个用例(每份 `project.toml` + `.st` + `EXPECTED.md`),编译结果按表 - `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 个负例按期望拒绝 - ✅ 可编译:14 个正例用例全部产出 `.stb`(含型号标识 + SHA-2566 个负例按期望拒绝
- 执行:`BytecodeExecutor`加载 `.stb` 跑扫描周期)在 12.9/12.10 - 执行:`BytecodeExecutor` 加载 `.stb` 跑扫描周期(型号/SHA 校验、回放、单步)
- ⏳ sidecar`<name>.runtime.toml`,I/O 绑定)在 12.10 随执行器一起接线 - ✅ 配置驱动:指令 opcode / 类型元数据 / FB 布局来自 `compiler/machine.toml``--machine`
+2 -1
View File
@@ -18,8 +18,9 @@ BytecodeExecutor <name>.stb [--cycles N] [--replay <file>] [--step]
## 职责 ## 职责
- 加载 `.stb` 映像与 `<name>.runtime.toml` sidecarI/O 绑定 var → 槽号 → channel/bit - 加载 `.stb` 映像与 `<name>.runtime.toml` sidecarI/O 绑定 var → 槽号 → channel/bit
- 读取时**校验型号标识与 SHA-256**:型号不匹配 / 文件被篡改 → 直接报错拒绝(执行器只接受与自身内建型号一致的映像)
- 按 sidecar 采样 I、写回 Q(不创造变量) - 按 sidecar 采样 I、写回 Q(不创造变量)
- `dt_ms` / `cycle_limit` 从映像头取,推进 `TON` / `TOF` / `CTU` - `dt_ms` / `cycle_limit` 从映像头取,推进内置 FBTON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG
- 单步、看寄存器和映像、按录制的 `I` 回放 - 单步、看寄存器和映像、按录制的 `I` 回放
## 边界 ## 边界
+36 -26
View File
@@ -1,6 +1,6 @@
# .stb 映像文件结构 # .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 │ │ 常量表段 offset_const .. offset_funcs │
├──────────────────────────────────────────────────┤ ├──────────────────────────────────────────────────┤
@@ -20,11 +20,13 @@
├──────────────────────────────────────────────────┤ ├──────────────────────────────────────────────────┤
│ FB 布局段 offset_fb .. offset_datav1 为空)│ │ FB 布局段 offset_fb .. offset_datav1 为空)│
├──────────────────────────────────────────────────┤ ├──────────────────────────────────────────────────┤
│ 数据段 offset_data .. 文件末尾 │ 数据段 offset_data .. offset_data+len
├──────────────────────────────────────────────────┤
│ SHA-256 文件尾(32 字节,对文件尾之前全部内容) │
└──────────────────────────────────────────────────┘ └──────────────────────────────────────────────────┘
``` ```
## 头(72 字节) ## 头(104 字节)
| 偏移 | 宽 | 字段 | line1 实际值 | | 偏移 | 宽 | 字段 | line1 实际值 |
|---|---|---|---| |---|---|---|---|
@@ -40,11 +42,15 @@
| 40 | 4 | M 槽数 `n_m` | 0 | | 40 | 4 | M 槽数 `n_m` | 0 |
| 44 | 4 | 常量数 `n_consts` | 0 | | 44 | 4 | 常量数 `n_consts` | 0 |
| 48 | 4 | 函数数 `n_funcs` | 2 | | 48 | 4 | 函数数 `n_funcs` | 2 |
| 52 | 4 | `offset_const` | 72 | | 52 | 4 | `offset_const` | 104`0x68` |
| 56 | 4 | `offset_funcs` | 72 | | 56 | 4 | `offset_funcs` | 104 |
| 60 | 4 | `offset_code` | 96 | | 60 | 4 | `offset_code` | 128`0x80` |
| 64 | 4 | `offset_fb` | 168 | | 64 | 4 | `offset_fb` | 200`0xC8` |
| 68 | 4 | `offset_data` | 168 | | 68 | 4 | `offset_data` | 200 |
| 72 | 32 | **型号标识**(定长 ASCII 含版本,补 `'\0'` | `"STATOR1"``0x48..0x67` |
> 12.13 修订:型号标识由 `machine.toml [meta] name + version` 拼成(如 `STATOR + 1` → `"STATOR1"`);
> 执行器读取时型号不匹配 → 直接报错。
## 常量表段(`offset_const` 起) ## 常量表段(`offset_const` 起)
@@ -74,15 +80,15 @@ line12 行):
- `CALL``fn_id` = 函数表行下标 - `CALL``fn_id` = 函数表行下标
- 调用约定:结果 r0、参数 r1..r7、变量与临时 r8 起 - 调用约定:结果 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_0io.input 绑定 → LOAD_I,槽 1 | | 0x84 | `12 08 01 00` | `LOAD_I r8, 1` | I0_0io.input 绑定 → LOAD_I,槽 1 |
| 0x68 | `17 08 04 00` | `STORE_GLOBAL r8, 4` | starter.start(槽 4)← I0_0 | | 0x88 | `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 | | 0x8C | `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 | | 0x90 | `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 | | 0x94 | `16 08 04 00` | `LOAD_GLOBAL r8, 4` | 内联体:读 starter.start(槽 4 |
## FB 布局段(`offset_fb` 起) ## FB 布局段(`offset_fb` 起)
@@ -94,24 +100,28 @@ v1 为空(用户 FB 内联展开,字段偏移已算进数据段;内建 8
槽序:**全局块**`n_globals` 个,声明序)→ **FB 实例块**(POU 收集序、实例声明序,每字段一槽)。 槽序:**全局块**`n_globals` 个,声明序)→ **FB 实例块**(POU 收集序、实例声明序,每字段一槽)。
line1 数据段(56 字节 = 7 槽 × 8B,`0xA8` 起): line1 数据段(56 字节 = 7 槽 × 8B,`0xC8` 起):
| 槽号 | 偏移 | 内容 | | 槽号 | 偏移 | 内容 |
|---|---|---| |---|---|---|
| 0 | 0xA8 | EmergencyStopBOOL,初值 0 | | 0 | 0xC8 | EmergencyStopBOOL,初值 0 |
| 1 | 0xB0 | I0_0BOOL | | 1 | 0xD0 | I0_0BOOL |
| 2 | 0xB8 | I0_1BOOL | | 2 | 0xD8 | I0_1BOOL |
| 3 | 0xC0 | Q0_0BOOL | | 3 | 0xE0 | Q0_0BOOL |
| 4 | 0xC8 | starter.startBOOL | | 4 | 0xE8 | starter.startBOOL |
| 5 | 0xD0 | starter.stopBOOL | | 5 | 0xF0 | starter.stopBOOL |
| 6 | 0xD8 | starter.qBOOL | | 6 | 0xF8 | starter.qBOOL |
## SHA-256 文件尾(32 字节)
数据段之后追加 32 字节 SHA-256:**对文件尾之前全部内容计算**(`0x100..0x11F`)。执行器必须校验通过才运行;任一字节被篡改 → 直接拒绝。compiler 写侧与 vm 读侧**各自实现**一份 SHA-256。
## 用工具验证 ## 用工具验证
- `ctest``isa_roundtrip` / `codegen_slice1` 覆盖编解码往返与映像校验 - `ctest``codegen_slice1`(写侧 model/sha 自检)、`vm_cycles`(读侧型号/SHA 拒绝)、`machine_config`
- `isa::ImageView` 提供只读视图(校验魔数/版本/段边界/入口 fn_id) - `STCompiler x.stb --disasm --machine machine.toml`:显示 `model=``sha=ok|BAD`
- 手工检查:`xxd line1.stb` 对照上表逐段核对 - 手工检查:`xxd line1.stb` 对照上表逐段核对
## 与执行器的关系 ## 与执行器的关系
`BytecodeExecutor`12.9/12.10 实现)只读本格式:头(`cycle_limit`/`dt_ms`)、函数表(帧 `nregs` + 代码定位)、数据段(全局 + FB 实例状态)。I/O 绑定**不在**映像里,在 sidecar `<name>.runtime.toml`var → 槽号 → channel/bit)。 `BytecodeExecutor` 只读本格式:头(`cycle_limit`/`dt_ms`/型号标识)、函数表(帧 `nregs` + 代码定位)、数据段(全局 + FB 实例状态)。读取时**型号匹配 + SHA-256 校验**,任一不符 → 报错拒绝。I/O 绑定**不在**映像里,在 sidecar `<name>.runtime.toml`var → 槽号 → channel/bit)。
+11 -2
View File
@@ -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` | | 60 | 4 | `offset_code` |
| 64 | 4 | `offset_fb` | | 64 | 4 | `offset_fb` |
| 68 | 4 | `offset_data` | | 68 | 4 | `offset_data` |
| 72 | 32 | **型号标识**(定长 ASCII,含版本,如 `"STATOR" + "1"` 拼 32 字节补 `'\0'` |
### 文件尾(SHA-256
数据段之后追加 **32 字节 SHA-256**:对文件尾之前全部内容计算。执行器必须校验通过才运行;compiler 写入。
### 常量表一行(12 字节) ### 常量表一行(12 字节)
+147 -116
View File
@@ -1,15 +1,42 @@
# 指令配置文件(ops.txt # 机器定义文件(machine.toml
指令集的**唯一事实来源**name / opcode / class / format / 参数名 / enabled。代码实现与配置文件在启动时**强校验**一致,防止两处漂移 指令集与类型的**唯一事实来源****只给编译器**STCompiler/compiler)使用。执行器(vm)不读本文件——它是下位机程序,内建全部指令
## 目标 ## 架构(最终定案)
- 加指令/屏蔽指令的登记集中在一处(配置文件),不再散落在代码各处手改 ```text
- 执行器(VM):**语义实现必须在代码里**,不能纯从文件加载;配置文件用于**屏蔽**(`enabled=false` 的指令执行到 → 故障 machine.toml(只给编译器:meta + type 别名表 + op 表 + fb 表
- 编译器(STCompiler):**启动时从文件加载**(校验 + 屏蔽传导) │ 运行时加载(toml++)+ 强校验
- `.stb` ABI 不变:opcode 数值仍是冻结契约;屏蔽是编译期/运行期策略,不改数值
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 ```text
class(操作对象) → format(操作数形态) → 参数名(操作数语义) class(操作对象) → format(操作数形态) → 参数名(操作数语义)
@@ -17,129 +44,133 @@ class(操作对象) → format(操作数形态) → 参数名(操作
JMP/JC/CALL/CAL/NONE slot/off/fn_id/instance JMP/JC/CALL/CAL/NONE slot/off/fn_id/instance
``` ```
| | 回答的问题 | 谁需要它 | | 大类 | 操作对象 | 现有指令 |
|---|---|---| |---|---|---|
| **class** | 操作对象:**值/搬运** 还是 **实例算法** | 语义归属、校验互锁(INSTANCE ↔ CAL | | **PLAIN**(无实例) | 寄存器 / 立即数 / 槽号 / 分支 / 调用 | MOVE/LOADK/NOT/AND/OR/算术/CMP_* / JMP/JT/JF / LOAD_*/STORE_* / CALL/RET26 条 |
| **format** | 操作数的**形态**:寄存器 / 常量 id / 槽号 / 带符号偏移 | disasm 渲染、编码助手 | | **INSTANCE**(有实例) | 数据区实例块(内建 FB | CAL_TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG8 条) |
| **参数名** | 每个操作数的**语义命名** | 元数据、文档、校验 |
### 两个大类 format 不可省:同是"第二个操作数是数字",`LOADK`/`LOAD_I`/`CALL` 渲染相同但语义不同(常量 id / 槽号 / fn_id,由参数名表达);"数字 vs 寄存器 vs 偏移"的渲染差异(`r%d` / `%u` / `%+d`)只有 format 能表达。
| 大类 | 操作对象 | 现有指令 | 操作数形态 | ## 配置文件格式(TOML
|---|---|---|---|
| **无实例**(PLAIN) | 寄存器 / 立即数 / 槽号 / 分支 / 调用 | MOVE/LOADK/NOT/AND/OR/算术/CMP_* / JMP/JT/JF / LOAD_*/STORE_* / CALL/RET26 条) | RR/RRR/IMM/SLOT/JMP/JC/CALL/NONE |
| **有实例**INSTANCE | **数据区实例块**(内建 FB | CAL_TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG8 条) | CAL(一个实例基槽号) |
区别的本质 **全属性必填,不可省略**
```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 ```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 ```text
# op <name> <opcode> <class> <format> <参数名...> <enabled> 加普通指令:machine.toml 加 [[op]] 一行 + vm 一个语义 case → 重建 vm
# class: PLAIN(无实例)/ INSTANCE(有实例 加内建 FB machine.toml 加 [[fb]] 一行 + vm 一个语义 case → 重建 vmcompiler 免重建
op MOVE 0 PLAIN RR rd rs enabled 加语言类型:machine.toml 加 [[type]] 一行(别名+tag)+ 类型检查/VM 语义代码 → 重建
op LOADK 1 PLAIN IMM rd const_id enabled 改型号版本:machine.toml [meta] version +1 → 旧 .stb 被旧/新执行器按型号校验决定是否接受
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
``` ```
- 参数个数由 format 决定,解析时校验字段数: ## 执行计划(分支 dev_ops_config
| format | 参数 | 现有指令 | ```
|---|---|---| 0. 文档定案(本文 + 指令与映像.md 格式冻结更新 + stb文件格式.md 修订注记) ← 当前
| `RR` | 2rd rs | MOVE / NOT | 1. ✅ OpFormat/OpClass 显式化 + disasm 表驱动(已完成,35dabf4
| `RRR` | 3rd ra rb | AND/OR/ADD/SUB/MUL/DIV/CMP_xx | 2. machine.toml 落地:meta + type 表 + op 34 条 + fb 8 条
| `IMM` | 2rd const_id | LOADK | 3. compiler 配置驱动改造:toml++ 加载 + 强校验;编码 opcode / 类型元数据 /
| `SLOT` | 2rd slot | LOAD_I/STORE_Q/LOAD_M/STORE_M/LOAD_GLOBAL/STORE_GLOBAL | FB 布局来自配置(compiler 不再依赖 isa
| `JMP` | 1off | JMP | 4. .stb 写侧:compiler 实现 型号标识[32] + SHA-256 文件尾
| `JC` | 2r off | JT / JF | 5. .stb 读侧:vm 自实现读 + 型号匹配 + SHA-256 校验(isa 的 Image 拆出)
| `CALL` | 1fn_id | CALL | 6. CLISTCompiler --machine <path>(编译路径必填)
| `CAL` | 1instance | CAL_*8 个) | 7. 测试:machine 解析/校验负例、型号不匹配报错、SHA-256 篡改检测、
| `NONE` | 0 | RET | 冻结哈希更新、全量回归
8. 文档 + 提交
- **类别与格式互锁**`INSTANCE` 类的 format 必须是 `CAL`,反之亦然——配置错误启动期暴露
### 内建 FB 注册(定案:并入 ops.txt
`CAL_*` 已是 op 行(与 ADD 同机制:编码/译码/disasm/屏蔽);fb 行登记其**数据结构(布局)**——ST 语言层的登记处(Lexer/Parser/Linker/Codegen 查表):
```text
# fb <name> <cal_opcode> <字段:类型 ...>
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
``` ```
- fb 行的 `cal_opcode` 与 op 行的 CAL_* 操作码**强校验一致**(防两处漂移 ## 实现前确认项(4 项,未定案
- 字段类型名小写(`bool/int/time`),与语言关键字对齐
- **加 FB = fb 行 + VM 一个语义 case,编译器免重建**(关键字/布局/操作码映射全部从表读)
## isa 层:`OpTable` 1. 配置文件名与位置:建议 `compiler/machine.toml`(只归编译器用)
2. 型号标识生成规则:建议 `[meta] name + version` 拼成 32 字节(如 `"STATOR" + "1"` → 补 `'\0'`);vm 内建"支持型号列表"比对
```cpp 3. compiler 内部 `TypeKind`Bool/Int/Time):保留为语言语义枚举(类型检查规则),元数据从配置 base 查
class OpTable { 4. 依赖方向确认:`compiler` 不再依赖 `isa``compiler → toml++` 自有;`STCompiler → compiler``vm → isa`
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 <project.toml> [-o out.stb] --ops <ops.txt>` | **仅 `-o` 编译路径要求**;加载/校验失败 → 报错退出;codegen 生成被屏蔽指令 → 编译错误(屏蔽传导) |
| BytecodeExecutor | `--ops <ops.txt>`(**可选**) | 不传 = 全部启用(兼容现有用法);传入 → 加载 + 校验,执行到 disabled 指令 → `Fault::Disabled`,周期中止退出码 1 |
| 语义实现 | — | 仍在 VM 代码里(每指令一个 case,不可省) |
## 加东西的流程(改造后)
```text
加普通指令:ops.txt 加一行 opname opcode class format 参数 enabled
+ Op.h 枚举加一项 + VM 一个语义 case → 重建 isa/VM
加内建 FBops.txt 加一行 fbname 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. **文档 + 提交**
+6 -2
View File
@@ -7,17 +7,21 @@ project(Compiler
DESCRIPTION "ST / toml 编译器") DESCRIPTION "ST / toml 编译器")
# 词法、语法、符号表、类型检查、codegen、链接都留在 compiler 一个库里 # 词法、语法、符号表、类型检查、codegen、链接都留在 compiler 一个库里
# 指令 opcode / 类型 / FB 布局来自 machine.tomlMachineConfig),不依赖 isa
add_library(compiler STATIC add_library(compiler STATIC
./src/Lexer.cpp ./src/Lexer.cpp
./src/Project.cpp ./src/Project.cpp
./src/Parser.cpp ./src/Parser.cpp
./src/Linker.cpp ./src/Linker.cpp
./src/Typecheck.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 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(compiler PRIVATE ${CMAKE_SOURCE_DIR}/third_party/tomlplusplus/include) target_include_directories(compiler PRIVATE ${CMAKE_SOURCE_DIR}/third_party/tomlplusplus/include)
target_link_libraries(compiler PUBLIC isa)
# 可执行入口:STCompiler <project.toml> -o <name>.stb # 可执行入口:STCompiler <project.toml> -o <name>.stb
add_executable(STCompiler add_executable(STCompiler
+36
View File
@@ -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 <cstddef>
#include <cstdint>
#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 拼 u16const_id / slot / fn_id
int16_t off16_of(Instr w); // a|b 有符号偏移(相对下一条)
// 配置驱动反汇编:按 machine.toml 的 format 输出一行文本。
// 未知操作码 → "??? 0x<hex>"。输出格式与 Doc/isa/指令与映像.md 一致。
void disasm(const MachineConfig& cfg, Instr w, char* out, size_t cap);
}
+4 -2
View File
@@ -12,14 +12,16 @@
#include <vector> #include <vector>
#include "compiler/Linker.h" #include "compiler/Linker.h"
#include "compiler/MachineConfig.h"
#include "compiler/Parser.h" #include "compiler/Parser.h"
#include "compiler/Project.h" #include "compiler/Project.h"
namespace compiler { namespace compiler {
// 编译工程为 .stb 映像字节(在链接 + 类型检查成功后调用)。 // 编译工程为 .stb 映像字节(在链接 + 类型检查成功后调用)。
// 指令 opcode / 类型 tag / FB 布局全部来自 machine.tomlcfg)。
// 失败返回 falseerr 前缀 "codegen error"。 // 失败返回 falseerr 前缀 "codegen error"。
bool codegen_project(const Project& proj, const std::vector<SourceUnit>& units, bool codegen_project(const Project& proj, const std::vector<SourceUnit>& units,
const LinkResult& link, std::vector<uint8_t>* image, const LinkResult& link, const MachineConfig& cfg,
std::string* err); std::vector<uint8_t>* image, std::string* err);
} }
+92
View File
@@ -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 <cstdint>
#include <string>
#include <vector>
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<std::string> 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<ConfigFbField> 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<ConfigType>& types() const { return types_; }
const std::vector<ConfigOp>& ops() const { return ops_; }
const std::vector<ConfigFb>& 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<PrimType>& prims();
static const PrimType* find_prim(const std::string& name);
private:
bool ok_ = false;
std::string model_name_;
uint32_t version_ = 0;
std::vector<ConfigType> types_;
std::vector<ConfigOp> ops_;
std::vector<ConfigFb> fbs_;
};
}
+11 -4
View File
@@ -11,10 +11,17 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "isa/Image.h"
namespace compiler { 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 字段表一一对应。 // 与 Doc/初步计划.md 12.1 的 toml 字段表一一对应。
// 字段白名单 / 必填 / io 完整性在 parse_project 内校验。 // 字段白名单 / 必填 / io 完整性在 parse_project 内校验。
struct Project { struct Project {
@@ -24,7 +31,7 @@ namespace compiler {
uint32_t dt_ms = 0; // 必填 > 0 uint32_t dt_ms = 0; // 必填 > 0
std::vector<std::string> files_st; // [files] 必填非空 std::vector<std::string> files_st; // [files] 必填非空
std::string gvl_file; // [gvl] 可选;无则空串 std::string gvl_file; // [gvl] 可选;无则空串
std::vector<isa::IoBinding> io; // [[io.*]] 可选;slot 12.6 才解析,此处填 0 std::vector<IoBinding> io; // [[io.*]] 可选;slot 12.6 才解析,此处填 0
std::string base_dir; // toml 所在目录(解析相对路径用) std::string base_dir; // toml 所在目录(解析相对路径用)
}; };
@@ -37,6 +44,6 @@ namespace compiler {
std::vector<std::string> compile_files(const Project& p); std::vector<std::string> compile_files(const Project& p);
// 校验全部文件存在并计算工程哈希:集合按路径排序,对内容做 FNV-1a 64 增量; // 校验全部文件存在并计算工程哈希:集合按路径排序,对内容做 FNV-1a 64 增量;
// 空集合 = basisisa::kFnvBasis)。缺文件报错前缀 "file missing"。 // 空集合 = basisStb::kFnvBasis)。缺文件报错前缀 "file missing"。
bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err); bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err);
} }
+116
View File
@@ -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 <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#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;
// 常量表一行(tag0=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<uint8_t>& 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_constfrom 已校验段起点)
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<uint8_t>* out, std::string* err);
bool write_stb_file(const char* path, const std::vector<uint8_t>& img, std::string* err);
// sidecar 生成与写文件(I/O 绑定 var → 槽号 → channel/bit
std::string make_sidecar(const std::vector<IoBinding>& bindings);
bool write_sidecar_file(const char* path, const std::vector<IoBinding>& bindings,
std::string* err);
}
+41
View File
@@ -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 <cstdint>
#include <string>
#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);
}
+342
View File
@@ -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/instanceformat: 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"]]
+65
View File
@@ -0,0 +1,65 @@
/**
* @file Codec.cpp
* @brief 指令字编解码 + 配置驱动反汇编(编译器侧)
* @author
* @date 2026-08-21
*/
#include "compiler/Codec.h"
#include <cstdio>
namespace compiler {
Instr pack(uint8_t opcode, uint8_t rd, uint8_t a, uint8_t b) {
return static_cast<uint32_t>(opcode)
| (static_cast<uint32_t>(rd) << 8)
| (static_cast<uint32_t>(a) << 16)
| (static_cast<uint32_t>(b) << 24);
}
uint8_t op_of(Instr w) { return static_cast<uint8_t>(w & 0xFFu); }
uint8_t rd_of(Instr w) { return static_cast<uint8_t>((w >> 8) & 0xFFu); }
uint8_t a_of(Instr w) { return static_cast<uint8_t>((w >> 16) & 0xFFu); }
uint8_t b_of(Instr w) { return static_cast<uint8_t>((w >> 24) & 0xFFu); }
uint16_t imm16_of(Instr w) {
return static_cast<uint16_t>(a_of(w) | (static_cast<uint16_t>(b_of(w)) << 8));
}
int16_t off16_of(Instr w) { return static_cast<int16_t>(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<unsigned>(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<unsigned>(rd),
static_cast<unsigned>(a_of(w)));
} else if (f == "RRR") {
snprintf(out, cap, "%s r%u, r%u, r%u", op->name.c_str(), static_cast<unsigned>(rd),
static_cast<unsigned>(a_of(w)), static_cast<unsigned>(b_of(w)));
} else if (f == "IMM" || f == "SLOT") {
snprintf(out, cap, "%s r%u, %u", op->name.c_str(), static_cast<unsigned>(rd),
static_cast<unsigned>(imm16_of(w)));
} else if (f == "JMP") {
snprintf(out, cap, "%s %+d", op->name.c_str(), static_cast<int>(off16_of(w)));
} else if (f == "JC") {
snprintf(out, cap, "%s r%u, %+d", op->name.c_str(), static_cast<unsigned>(rd),
static_cast<int>(off16_of(w)));
} else if (f == "CALL" || f == "CAL") {
snprintf(out, cap, "%s %u", op->name.c_str(), static_cast<unsigned>(imm16_of(w)));
} else { // NONE
snprintf(out, cap, "%s", op->name.c_str());
}
}
} // namespace compiler
+216 -337
View File
@@ -1,38 +1,32 @@
/** /**
* @file Codegen.cpp * @file Codegen.cpp
* @brief 寄存器码生成(12.8,切片 7+ FB 实例、内联展开、CAL_* * @brief 寄存器码生成(配置驱动;compiler 不依赖 isa
* @author * @author
* @date 2026-08-21 * @date 2026-08-21
* *
* @details 设计说明(详见 Doc/compiler/寄存器码.md): * @details 设计说明(详见 Doc/compiler/寄存器码.md 与 Doc/isa/指令配置.md):
* - 切片 1~6:帧/字面量/MOVE/RET;全局数据区;短路 AND/ORCMP 与 IFWHILE 与四则; * - 指令 opcode / 类型 tag / FB 布局全部来自 machine.tomlMachineConfig
* FUNCTION 与 CALL(调用约定 r0/r1..r7/r8+)。 * - 帧/字面量/MOVE/RET;全局数据区;短路 AND/ORCMP 与 IFWHILE 与四则;
* - 切片 7:FB 实例 → 数据区实例块(跨周期持久);字段槽号 = 实例基槽 + 字段序号 * FUNCTION 与 CALL(调用约定 r0/r1..r7/r8+
* (编译期算死);字段读写走 LOAD_GLOBAL / STORE_GLOBAL * - FB 实例 → 数据区实例块(跨周期持久);字段槽号 = 实例基槽 + 字段序号
* 内建 TON/TOF/CTU → 实参写字段后 CAL_* <实例基槽> * - 用户 FB 调用点内联展开;内建 FB → CAL_* <实例基槽>
* 用户 FB → 调用点内联展开(实参写字段 → FB 体以实例字段为变量编译) * - 数据段每槽 8 字节定宽,指令 slot = 槽号
* - 方案 a(12.9 前置):数据段**每槽 8 字节定宽**,指令 slot = 槽号(无映射)
* *
* 函数清单: * 函数清单:
* - put_le32 / put_le64 小端写入映像缓冲 * - Builder::Builder (构造)存工程/源文件/链接结果/机器配置/输出,收集 io 绑定
* - Builder::Builder (构造)存工程/源文件/链接结果/输出,收集 io 绑定分类 * - Builder::run 布局数据区 → 逐 POU 建函数 → 拼映像
* - Builder::run 布局数据区(全局 + 实例)→ 逐 POU 建函数 → 拼映像 * - Builder::fail / opc 错误 / 按名查配置 opcode
* - Builder::fail 组装 "codegen error: <msg>" 返回 false * - 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 * - find_pou / find_fn_id 按名查 POU AST / fn_id
* - build_function 编译一个 POU(PROGRAM/FUNCTION/FB 骨架;帧约定分配) * - build_function 编译一个 POU(帧约定分配FB 占位
* - begin_stmt / alloc_temp 临时寄存器:语句内递增、语句结束复用基址 * - begin_stmt / alloc_temp 临时寄存器管理
* - compile_stmt 语句编译(赋值 / IF / WHILE / FB 调用) * - compile_stmt 语句编译(赋值 / IF / WHILE / FB 调用)
* - compile_if / compile_while IF 链 / WHILE 回环 * - compile_if / compile_while / compile_fb_call / compile_fb_inline
* - compile_fb_call FB 调用:内建 CAL_* 或用户内联展开 * - store_target / compile_expr 左值存储 / 表达式编译
* - compile_fb_inline 内联编译用户 FB 体(实例字段为变量) * - arith_name / cmp_name / load_name / store_name 操作码名选择
* - store_target 赋值左值:STORE_* 到全局槽(或内联模式实例字段 * - patch_jump 回填跳转偏移(相对下一条
* - compile_expr 表达式编译到寄存器(含 Field 字段读) * - layout_data / layout_fb_instances / instance_of / init_of
* - cmp_op / arith_op / load_op / store_op 操作码选择 * - const_id 取常量表 id(tag 来自配置类型表)
* - global_offset 槽号恒等(方案 a 无映射)
* - instance_of 实例 → 字段槽号表
* - patch_jump 回填跳转偏移(相对下一条指令)
* - layout_data / layout_fb_instances / init_of 数据区布局(8 字节定宽槽)
* - const_id 取常量表 id(无则追加)
* - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + 数据段 * - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + 数据段
* - codegen_project 对外入口 * - codegen_project 对外入口
*/ */
@@ -45,31 +39,16 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "isa/Encode.h" #include "compiler/Codec.h"
#include "isa/Image.h" #include "compiler/MachineConfig.h"
#include "isa/Instr.h" #include "compiler/Stb.h"
#include "isa/Types.h" #include "compiler/TypeInfo.h"
namespace compiler { namespace compiler {
namespace { namespace {
// ---- 小端写入(Image.cpp 内部实现不可见,这里自带最小版)----
void put_le32(std::vector<uint8_t>& b, size_t off, uint32_t v) {
b[off + 0] = static_cast<uint8_t>(v & 0xFFu);
b[off + 1] = static_cast<uint8_t>((v >> 8) & 0xFFu);
b[off + 2] = static_cast<uint8_t>((v >> 16) & 0xFFu);
b[off + 3] = static_cast<uint8_t>((v >> 24) & 0xFFu);
}
void put_le64(std::vector<uint8_t>& b, size_t off, uint64_t v) {
for (int i = 0; i < 8; ++i) {
b[off + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFFu);
}
}
/** /**
* @brief 代码生成器(切片 1 * @brief 代码生成器
*/ */
class Builder { class Builder {
public: public:
@@ -78,14 +57,16 @@ namespace {
* @param proj 工程定义(cycle_limit / dt_ms / 哈希用) * @param proj 工程定义(cycle_limit / dt_ms / 哈希用)
* @param units 全部源文件的 AST * @param units 全部源文件的 AST
* @param link 链接结果(POU 顺序 / 符号) * @param link 链接结果(POU 顺序 / 符号)
* @param cfg 机器定义(machine.toml 强校验后;指令 opcode / 类型 tag / FB 布局)
* @param image 输出映像字节 * @param image 输出映像字节
* @param err 错误输出;可为 nullptr(静默) * @param err 错误输出;可为 nullptr(静默)
*/ */
Builder(const Project& proj, const std::vector<SourceUnit>& units, Builder(const Project& proj, const std::vector<SourceUnit>& units,
const LinkResult& link, std::vector<uint8_t>* image, std::string* err) const LinkResult& link, const MachineConfig& cfg,
: proj_(proj), units_(units), link_(link), image_(image), err_(err) { std::vector<uint8_t>* image, std::string* err)
: proj_(proj), units_(units), link_(link), cfg_(cfg), image_(image), err_(err) {
// io 绑定分类(名已折小写;不创造变量,只影响操作码选择) // io 绑定分类(名已折小写;不创造变量,只影响操作码选择)
for (const isa::IoBinding& b : proj_.io) { for (const IoBinding& b : proj_.io) {
std::string key = b.var; std::string key = b.var;
for (char& ch : key) { for (char& ch : key) {
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch))); ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
@@ -100,7 +81,6 @@ namespace {
/** /**
* @brief 布局数据区 → 布局 FB 实例 → 逐 POU 建函数 → 拼映像 * @brief 布局数据区 → 布局 FB 实例 → 逐 POU 建函数 → 拼映像
* @details 偏移先定(函数编译引用 global_offset / instance_of
* @return true 成功;falseerr 已写,前缀 "codegen error" * @return true 成功;falseerr 已写,前缀 "codegen error"
*/ */
bool run() { bool run() {
@@ -127,11 +107,6 @@ namespace {
} }
private: private:
/**
* @brief 组装错误消息并返回 false
* @param msg 错误描述(不含前缀)
* @return 恒 false
*/
bool fail(const std::string& msg) { bool fail(const std::string& msg) {
if (err_) { if (err_) {
*err_ = "codegen error: " + msg; *err_ = "codegen error: " + msg;
@@ -140,10 +115,42 @@ namespace {
} }
/** /**
* @brief 按名查 POU AST * @brief 按名查配置 opcodeMachineConfig 强校验保证存在)
* @param name POU 名(小写 * @param name 指令名(大写,如 "ADD"
* @return POU 指针;未找到返回 nullptr * @return opcode(找不到返回 0,不应发生)
*/ */
uint8_t opc(const char* name) const {
const ConfigOp* op = cfg_.find_op(name);
return op ? static_cast<uint8_t>(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<uint8_t>(imm & 0xFFu),
static_cast<uint8_t>((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<uint16_t>(off);
return pack(opc("JMP"), 0, static_cast<uint8_t>(u & 0xFFu),
static_cast<uint8_t>((u >> 8) & 0xFFu));
}
Instr E_jc(const char* name, uint8_t r, int16_t off) {
const uint16_t u = static_cast<uint16_t>(off);
return pack(opc(name), r, static_cast<uint8_t>(u & 0xFFu),
static_cast<uint8_t>((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 { const POU* find_pou(const std::string& name) const {
for (const SourceUnit& u : units_) { for (const SourceUnit& u : units_) {
for (const POU& p : u.ast.pous) { for (const POU& p : u.ast.pous) {
@@ -159,33 +166,24 @@ namespace {
struct FuncCtx { struct FuncCtx {
std::string name; std::string name;
std::string pou_name; // 所属 POU 名(实例查找键) std::string pou_name; // 所属 POU 名(实例查找键)
std::vector<isa::Instr> code; // 字节码(函数表 code_offset 相对此段) std::vector<Instr> code; // 字节码
std::map<std::string, uint8_t> regs; // 变量名 → 帧寄存器 std::map<std::string, uint8_t> regs; // 变量名 → 帧寄存器
uint8_t nlocals = 0; // 变量区终点 = 临时寄存器起始 uint8_t nlocals = 0; // 变量区终点 = 临时寄存器起始
uint8_t nregs = 0; // 峰值(变量 + 临时) uint8_t nregs = 0; // 峰值(变量 + 临时)
uint8_t temp_used = 0; // 本语句已用临时数(语句结束清零) uint8_t temp_used = 0; // 本语句已用临时数(语句结束清零)
bool is_function = false; // FUNCTION(结果 r0 / 输入只读) bool is_function = false; // FUNCTION(结果 r0 / 输入只读)
std::string result_name; // FUNCTION 名(结果寄存器映射) std::string result_name; // FUNCTION 名(结果寄存器映射)
}; };
// FB 实例:字段名 → 数据区字节地址 // FB 实例:字段名 → 数据区槽号
struct InstFields { struct InstFields {
std::map<std::string, uint32_t> field_addr; std::map<std::string, uint32_t> field_addr;
uint32_t base = 0; uint32_t base = 0;
std::string type_name; // 实例的 FB 类型名(内建 / 用户) std::string type_name; // 实例的 FB 类型名(内建 / 用户)
}; };
/**
* @brief 语句开始:临时寄存器基址复用
* @param f 当前函数
*/
void begin_stmt(FuncCtx& f) { f.temp_used = 0; } void begin_stmt(FuncCtx& f) { f.temp_used = 0; }
/**
* @brief 分配一个临时寄存器(语句内递增)
* @param f 当前函数
* @return 临时寄存器号(可能更新 nregs 峰值)
*/
uint8_t alloc_temp(FuncCtx& f) { uint8_t alloc_temp(FuncCtx& f) {
const uint8_t r = f.nlocals + f.temp_used; const uint8_t r = f.nlocals + f.temp_used;
++f.temp_used; ++f.temp_used;
@@ -201,16 +199,12 @@ namespace {
* FB 不生成业务字节码(调用点内联),只出空函数占位保持 fn_id 一致; * FB 不生成业务字节码(调用点内联),只出空函数占位保持 fn_id 一致;
* 帧约定(见 Doc/compiler/寄存器码.md):结果 r0、输入 r1..r7(只读)、 * 帧约定(见 Doc/compiler/寄存器码.md):结果 r0、输入 r1..r7(只读)、
* 变量与临时全部从 r8 起 * 变量与临时全部从 r8 起
* @param pou POU AST
* @param f 输出函数编译态
* @return true 成功;falseerr 已写)
*/ */
bool build_function(const POU& pou, FuncCtx* f) { bool build_function(const POU& pou, FuncCtx* f) {
if (pou.kind == PouKind::FunctionBlock) { if (pou.kind == PouKind::FunctionBlock) {
// FB 内联展开(见 compile_fb_call),此处只占 fn_id
f->nlocals = 8; f->nlocals = 8;
f->nregs = 8; f->nregs = 8;
f->code.push_back(isa::enc_ret()); f->code.push_back(E_ret());
return true; return true;
} }
f->is_function = pou.kind == PouKind::Function; f->is_function = pou.kind == PouKind::Function;
@@ -229,16 +223,13 @@ namespace {
} }
uint8_t r = 8; // 变量/临时基址(调用约定区 r0..r7 不占用) uint8_t r = 8; // 变量/临时基址(调用约定区 r0..r7 不占用)
for (const VarBlock& b : pou.blocks) { for (const VarBlock& b : pou.blocks) {
// External / Global 走数据区槽,不占帧寄存器
if (b.section == VarSection::External || b.section == VarSection::Global) { if (b.section == VarSection::External || b.section == VarSection::Global) {
continue; continue;
} }
// FUNCTION 输入已分配(r1..r7
if (f->is_function && b.section == VarSection::Input) { if (f->is_function && b.section == VarSection::Input) {
continue; continue;
} }
for (const VarDecl& d : b.vars) { for (const VarDecl& d : b.vars) {
// FB 实例走数据区(layout_fb_instances),不占帧寄存器
if (d.type.kind == TypeKind::FbUser || d.type.kind == TypeKind::FbBuiltin) { if (d.type.kind == TypeKind::FbUser || d.type.kind == TypeKind::FbBuiltin) {
continue; continue;
} }
@@ -253,16 +244,10 @@ namespace {
return false; return false;
} }
} }
f->code.push_back(isa::enc_ret()); f->code.push_back(E_ret());
return true; return true;
} }
/**
* @brief 编译一条语句(切片 4:赋值 / IF)
* @param f 当前函数
* @param st 语句 AST
* @return true 成功;falseerr 已写)
*/
bool compile_stmt(FuncCtx& f, const Stmt& st) { bool compile_stmt(FuncCtx& f, const Stmt& st) {
if (st.kind == StmtKind::If) { if (st.kind == StmtKind::If) {
return compile_if(f, st); return compile_if(f, st);
@@ -274,7 +259,7 @@ namespace {
return compile_fb_call(f, st); return compile_fb_call(f, st);
} }
if (st.kind != StmtKind::Assign) { if (st.kind != StmtKind::Assign) {
return fail("statement not supported in slice 7"); return fail("statement not supported");
} }
// 内联 FB 体内:左值可能是实例字段(STORE_GLOBAL // 内联 FB 体内:左值可能是实例字段(STORE_GLOBAL
if (inline_fields_) { if (inline_fields_) {
@@ -284,32 +269,20 @@ namespace {
if (!compile_expr(f, *st.value, t)) { if (!compile_expr(f, *st.value, t)) {
return false; 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; return true;
} }
} }
const auto it = f.regs.find(st.target); const auto it = f.regs.find(st.target);
if (it == f.regs.end()) { if (it == f.regs.end()) {
// 全局 / 外部左值 → STORE_* 到数据区
return store_target(f, st.target, *st.value); return store_target(f, st.target, *st.value);
} }
// 函数输入只读(调用约定,见 Doc/compiler/寄存器码.md
if (f.is_function && it->second >= 1 && it->second <= 7) { if (f.is_function && it->second >= 1 && it->second <= 7) {
return fail("cannot write function input '" + st.target + "'"); return fail("cannot write function input '" + st.target + "'");
} }
const uint8_t rd = it->second; return compile_expr(f, *st.value, it->second);
return compile_expr(f, *st.value, rd);
} }
/**
* @brief 编译 FB 调用
* @details 实参求值到临时 → STORE_GLOBAL 到实例字段;
* 内建 TON/TOF/CTU → CAL_* <实例偏移>
* 用户 FB → compile_fb_inline 内联展开
* @param f 当前函数
* @param st FbCall 语句 AST
* @return true 成功;falseerr 已写)
*/
bool compile_fb_call(FuncCtx& f, const Stmt& st) { bool compile_fb_call(FuncCtx& f, const Stmt& st) {
const InstFields* inst = instance_of(f.pou_name, st.instance); const InstFields* inst = instance_of(f.pou_name, st.instance);
if (inst == nullptr) { if (inst == nullptr) {
@@ -324,22 +297,15 @@ namespace {
if (!compile_expr(f, *a.value, t)) { if (!compile_expr(f, *a.value, t)) {
return false; 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" || // 内建 FB:配置里必须有对应 op 行(强校验保证)→ 直接查配置 opcode
inst->type_name == "tp" || inst->type_name == "ctu" || const std::string& tn = inst->type_name;
inst->type_name == "ctd" || inst->type_name == "ctud" || if (cfg_.find_op("CAL_" + uppercase_of(tn)) != nullptr &&
inst->type_name == "r_trig" || inst->type_name == "f_trig") { (tn == "ton" || tn == "tof" || tn == "tp" || tn == "ctu" ||
const isa::Op op = tn == "ctd" || tn == "ctud" || tn == "r_trig" || tn == "f_trig")) {
inst->type_name == "ton" ? isa::Op::CAL_TON const std::string opname = "CAL_" + uppercase_of(tn);
: inst->type_name == "tof" ? isa::Op::CAL_TOF f.code.push_back(E_slot(opname.c_str(), 0, inst->base));
: 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));
return true; return true;
} }
const POU* fb = find_pou(inst->type_name); const POU* fb = find_pou(inst->type_name);
@@ -349,15 +315,14 @@ namespace {
return compile_fb_inline(f, *fb, *inst); return compile_fb_inline(f, *fb, *inst);
} }
/** static std::string uppercase_of(const std::string& s) {
* @brief 内联编译用户 FB 体 std::string out = s;
* @details 切到实例字段上下文(VarRef/赋值左值映射到字段地址); for (char& ch : out) {
* 表达式临时与调用方共用 r8+;字段读写全走 LOAD/STORE_GLOBAL ch = static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
* @param f 当前函数 }
* @param fb FB 类型 POU return out;
* @param inst 本实例字段表 }
* @return true 成功;falseerr 已写)
*/
bool compile_fb_inline(FuncCtx& f, const POU& fb, const InstFields& inst) { bool compile_fb_inline(FuncCtx& f, const POU& fb, const InstFields& inst) {
const InstFields* saved = inline_fields_; const InstFields* saved = inline_fields_;
inline_fields_ = &inst; inline_fields_ = &inst;
@@ -371,14 +336,6 @@ namespace {
return true; return true;
} }
/**
* @brief 编译 WHILE
* @details L_loop: <cond→t> JF t, L_end <body> JMP L_loop L_end:
* 条件 JF 与回环 JMP 双回填
* @param f 当前函数
* @param st WHILE 语句 AST
* @return true 成功;falseerr 已写)
*/
bool compile_while(FuncCtx& f, const Stmt& st) { bool compile_while(FuncCtx& f, const Stmt& st) {
const size_t loop = f.code.size(); const size_t loop = f.code.size();
const uint8_t t = alloc_temp(f); const uint8_t t = alloc_temp(f);
@@ -386,7 +343,7 @@ namespace {
return false; return false;
} }
const size_t jf_idx = f.code.size(); 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) { for (const Stmt& s : st.body) {
begin_stmt(f); begin_stmt(f);
if (!compile_stmt(f, s)) { if (!compile_stmt(f, s)) {
@@ -394,29 +351,20 @@ namespace {
} }
} }
const size_t jmp_idx = f.code.size(); 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, jmp_idx, loop);
patch_jump(f, jf_idx, f.code.size()); patch_jump(f, jf_idx, f.code.size());
return true; return true;
} }
/**
* @brief 编译 IF / ELSIF / ELSE
* @details 每分支:<cond→t> JF t, L_next → <body> → JMP L_end
* 最后一个分支无 else 时 JF 直接落 L_end(不补 JMP
* @param f 当前函数
* @param st IF 语句 AST
* @return true 成功;falseerr 已写)
*/
bool compile_if(FuncCtx& f, const Stmt& st) { bool compile_if(FuncCtx& f, const Stmt& st) {
// 条件链:IF 体 + ELSIF 各体;ELSE 体可选
std::vector<std::pair<const Expr*, const std::vector<Stmt>*>> branches; std::vector<std::pair<const Expr*, const std::vector<Stmt>*>> branches;
branches.push_back({st.cond.get(), &st.body}); branches.push_back({st.cond.get(), &st.body});
for (const IfBranch& b : st.elsifs) { for (const IfBranch& b : st.elsifs) {
branches.push_back({b.cond.get(), &b.body}); branches.push_back({b.cond.get(), &b.body});
} }
const bool has_else = !st.else_body.empty(); const bool has_else = !st.else_body.empty();
std::vector<size_t> end_jmps; // 各分支尾部 JMP(回填到 L_end) std::vector<size_t> end_jmps;
for (size_t i = 0; i < branches.size(); ++i) { for (size_t i = 0; i < branches.size(); ++i) {
const uint8_t t = alloc_temp(f); const uint8_t t = alloc_temp(f);
@@ -424,7 +372,7 @@ namespace {
return false; return false;
} }
const size_t jf_idx = f.code.size(); 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) { for (const Stmt& s : *branches[i].second) {
begin_stmt(f); begin_stmt(f);
if (!compile_stmt(f, s)) { if (!compile_stmt(f, s)) {
@@ -433,10 +381,10 @@ namespace {
} }
if (i + 1 < branches.size() || has_else) { if (i + 1 < branches.size() || has_else) {
const size_t jmp_idx = f.code.size(); 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); 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) { if (has_else) {
for (const Stmt& s : st.else_body) { for (const Stmt& s : st.else_body) {
@@ -453,13 +401,6 @@ namespace {
return true; return true;
} }
/**
* @brief 赋值左值:帧寄存器直写或 STORE_* 到全局槽
* @param f 当前函数
* @param target 左值名(全局 / 外部)
* @param value 右值表达式
* @return true 成功;falseerr 已写)
*/
bool store_target(FuncCtx& f, const std::string& target, const Expr& value) { bool store_target(FuncCtx& f, const std::string& target, const Expr& value) {
const auto git = link_.global_index.find(target); const auto git = link_.global_index.find(target);
if (git == link_.global_index.end()) { if (git == link_.global_index.end()) {
@@ -472,49 +413,36 @@ namespace {
if (!compile_expr(f, value, tmp)) { if (!compile_expr(f, value, tmp)) {
return false; return false;
} }
const uint16_t slot = global_offset(git->second); f.code.push_back(E_slot(store_name(target), tmp, static_cast<uint16_t>(git->second)));
f.code.push_back(isa::enc_slot(store_op(target), tmp, slot));
return true; return true;
} }
/**
* @brief 表达式编译到目标寄存器
* @details 字面量 → LOADK;帧变量 → MOVE;全局/外部 → LOAD_*io.input 用 LOAD_I);
* NOT → 求值后 NOT rd, tAND/OR → 短路跳转(JF/JT 跳过右侧)
* @param f 当前函数
* @param e 表达式 AST
* @param rd 目标寄存器
* @return true 成功;falseerr 已写)
*/
bool compile_expr(FuncCtx& f, const Expr& e, uint8_t rd) { bool compile_expr(FuncCtx& f, const Expr& e, uint8_t rd) {
if (e.kind == ExprKind::LitBool || e.kind == ExprKind::LitInt || if (e.kind == ExprKind::LitBool || e.kind == ExprKind::LitInt ||
e.kind == ExprKind::LitTime) { e.kind == ExprKind::LitTime) {
const isa::types::TypeTag tag = const char* type_name = e.kind == ExprKind::LitBool ? "BOOL"
e.kind == ExprKind::LitBool ? isa::types::Bool : e.kind == ExprKind::LitInt ? "INT"
: e.kind == ExprKind::LitInt ? isa::types::Int : "TIME";
: isa::types::Time; f.code.push_back(E_imm("LOADK", rd, const_id(type_name, e.int_value)));
f.code.push_back(
isa::enc_imm(isa::Op::LOADK, rd, const_id(tag, e.int_value)));
return true; return true;
} }
if (e.kind == ExprKind::VarRef) { if (e.kind == ExprKind::VarRef) {
// 内联 FB 体内:字段优先(实例字段为变量)
if (inline_fields_) { if (inline_fields_) {
const auto fit = inline_fields_->field_addr.find(e.name); const auto fit = inline_fields_->field_addr.find(e.name);
if (fit != inline_fields_->field_addr.end()) { 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; return true;
} }
} }
const auto sit = f.regs.find(e.name); const auto sit = f.regs.find(e.name);
if (sit != f.regs.end()) { 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; return true;
} }
const auto git = link_.global_index.find(e.name); const auto git = link_.global_index.find(e.name);
if (git != link_.global_index.end()) { if (git != link_.global_index.end()) {
const uint16_t slot = global_offset(git->second); f.code.push_back(
f.code.push_back(isa::enc_slot(load_op(e.name), rd, slot)); E_slot(load_name(e.name), rd, static_cast<uint16_t>(git->second)));
return true; return true;
} }
return fail("no register or slot for '" + e.name + "'"); return fail("no register or slot for '" + e.name + "'");
@@ -528,7 +456,7 @@ namespace {
if (fit == inst->field_addr.end()) { if (fit == inst->field_addr.end()) {
return fail("unknown field '" + e.field + "' for '" + e.name + "'"); 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; return true;
} }
if (e.kind == ExprKind::Add || e.kind == ExprKind::Sub || if (e.kind == ExprKind::Add || e.kind == ExprKind::Sub ||
@@ -541,7 +469,7 @@ namespace {
if (!compile_expr(f, *e.rhs, r)) { if (!compile_expr(f, *e.rhs, r)) {
return false; 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; return true;
} }
if (e.kind == ExprKind::Cmp) { if (e.kind == ExprKind::Cmp) {
@@ -553,7 +481,7 @@ namespace {
if (!compile_expr(f, *e.rhs, r)) { if (!compile_expr(f, *e.rhs, r)) {
return false; 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; return true;
} }
if (e.kind == ExprKind::Not) { if (e.kind == ExprKind::Not) {
@@ -561,27 +489,25 @@ namespace {
if (!compile_expr(f, *e.operand, t)) { if (!compile_expr(f, *e.operand, t)) {
return false; 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; return true;
} }
if (e.kind == ExprKind::And || e.kind == ExprKind::Or) { if (e.kind == ExprKind::And || e.kind == ExprKind::Or) {
// 短路:左侧结果在 rd;AND 为假 / OR 为真时跳过右侧
if (!compile_expr(f, *e.lhs, rd)) { if (!compile_expr(f, *e.lhs, rd)) {
return false; 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(); 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); const uint8_t t = alloc_temp(f);
if (!compile_expr(f, *e.rhs, t)) { if (!compile_expr(f, *e.rhs, t)) {
return false; 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()); patch_jump(f, jmp_idx, f.code.size());
return true; return true;
} }
if (e.kind == ExprKind::Call) { if (e.kind == ExprKind::Call) {
// 调用约定:实参求值到 r8+ 临时 → MOVE 到 r1..r7 → CALL → 结果 r0
if (e.args.size() > 7) { if (e.args.size() > 7) {
return fail("too many arguments (max 7)"); return fail("too many arguments (max 7)");
} }
@@ -598,21 +524,15 @@ namespace {
return fail("no fn_id for '" + e.name + "'"); return fail("no fn_id for '" + e.name + "'");
} }
for (size_t i = 0; i < args.size(); ++i) { for (size_t i = 0; i < args.size(); ++i) {
f.code.push_back( f.code.push_back(E_rr("MOVE", static_cast<uint8_t>(1 + i), args[i]));
isa::enc_rr(isa::Op::MOVE, static_cast<uint8_t>(1 + i), args[i]));
} }
f.code.push_back(isa::enc_call(static_cast<uint16_t>(fn_id))); f.code.push_back(E_call(static_cast<uint16_t>(fn_id)));
f.code.push_back(isa::enc_rr(isa::Op::MOVE, rd, 0)); f.code.push_back(E_rr("MOVE", rd, 0));
return true; return true;
} }
return fail("expression not supported in slice 6"); return fail("expression not supported");
} }
/**
* @brief 按名查 fn_idPOU 收集序下标)
* @param name POU 名(小写)
* @return fn_id;未找到返回 -1
*/
int find_fn_id(const std::string& name) const { int find_fn_id(const std::string& name) const {
for (size_t i = 0; i < link_.scopes.size(); ++i) { for (size_t i = 0; i < link_.scopes.size(); ++i) {
if (link_.scopes[i].name == name) { if (link_.scopes[i].name == name) {
@@ -622,87 +542,43 @@ namespace {
return -1; return -1;
} }
/**
* @brief 回填跳转偏移(相对下一条指令)
* @param f 当前函数
* @param idx 跳转指令下标(占位)
* @param target_idx 目标指令下标
*/
void patch_jump(FuncCtx& f, size_t idx, size_t target_idx) { void patch_jump(FuncCtx& f, size_t idx, size_t target_idx) {
const int16_t off = static_cast<int16_t>( const int16_t off = static_cast<int16_t>(
static_cast<int64_t>(target_idx) - (static_cast<int64_t>(idx) + 1)); static_cast<int64_t>(target_idx) - (static_cast<int64_t>(idx) + 1));
const isa::Instr w = f.code[idx]; const Instr w = f.code[idx];
f.code[idx] = isa::pack(isa::op(w), isa::rd(w), const uint16_t u = static_cast<uint16_t>(off);
static_cast<uint8_t>(static_cast<uint16_t>(off) & 0xFFu), f.code[idx] = pack(op_of(w), rd_of(w), static_cast<uint8_t>(u & 0xFFu),
static_cast<uint8_t>((static_cast<uint16_t>(off) >> 8) & 0xFFu)); static_cast<uint8_t>((u >> 8) & 0xFFu));
} }
/** // ---- 操作码名(MachineConfig 已强校验存在)----
* @brief 算术运算 → 操作码 static const char* arith_name(ExprKind k) {
* @param k 表达式种类(Add/Sub/Mul/Div
* @return 对应操作码
*/
isa::Op arith_op(ExprKind k) const {
switch (k) { switch (k) {
case ExprKind::Add: return isa::Op::ADD; case ExprKind::Add: return "ADD";
case ExprKind::Sub: return isa::Op::SUB; case ExprKind::Sub: return "SUB";
case ExprKind::Mul: return isa::Op::MUL; case ExprKind::Mul: return "MUL";
case ExprKind::Div: return isa::Op::DIV; case ExprKind::Div: return "DIV";
default: return isa::Op::ADD; default: return "ADD";
} }
} }
static const char* cmp_name(BinOp op) {
/**
* @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 {
switch (op) { switch (op) {
case BinOp::Eq: return isa::Op::CMP_EQ; case BinOp::Eq: return "CMP_EQ";
case BinOp::Ne: return isa::Op::CMP_NE; case BinOp::Ne: return "CMP_NE";
case BinOp::Lt: return isa::Op::CMP_LT; case BinOp::Lt: return "CMP_LT";
case BinOp::Le: return isa::Op::CMP_LE; case BinOp::Le: return "CMP_LE";
case BinOp::Gt: return isa::Op::CMP_GT; case BinOp::Gt: return "CMP_GT";
case BinOp::Ge: return isa::Op::CMP_GE; case BinOp::Ge: return "CMP_GE";
default: return isa::Op::CMP_EQ; // Add/Sub/Mul/Div 不经此函数 default: return "CMP_EQ";
} }
} }
const char* load_name(const std::string& name) const {
/** return io_input_.count(name) ? "LOAD_I" : "LOAD_GLOBAL";
* @brief 按 io 绑定选读取操作码 }
* @param name 变量名(小写) const char* store_name(const std::string& name) const {
* @return io.input 绑定 → LOAD_I;否则 LOAD_GLOBAL return io_output_.count(name) ? "STORE_Q" : "STORE_GLOBAL";
*/
isa::Op load_op(const std::string& name) const {
return io_input_.count(name) ? isa::Op::LOAD_I : isa::Op::LOAD_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<uint16_t>(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() { bool layout_data() {
for (const Symbol& s : link_.globals) { for (const Symbol& s : link_.globals) {
if (s.address > 0xFFFF) { if (s.address > 0xFFFF) {
@@ -714,30 +590,32 @@ namespace {
const int64_t v = has_init ? init : 0; const int64_t v = has_init ? init : 0;
const size_t off = static_cast<size_t>(s.address) * 8; const size_t off = static_cast<size_t>(s.address) * 8;
data_.resize(off + 8, 0); 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<uint16_t>(v); const uint16_t iv = static_cast<uint16_t>(v);
data_[off] = static_cast<uint8_t>(iv & 0xFFu); data_[off] = static_cast<uint8_t>(iv & 0xFFu);
data_[off + 1] = static_cast<uint8_t>((iv >> 8) & 0xFFu); data_[off + 1] = static_cast<uint8_t>((iv >> 8) & 0xFFu);
} else if (s.type_name == "time") { } else if (tm.width() == 8) {
const uint64_t tv = static_cast<uint64_t>(v); const uint64_t tv = static_cast<uint64_t>(v);
for (int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
data_[off + i] = static_cast<uint8_t>((tv >> (8 * i)) & 0xFFu); data_[off + i] = static_cast<uint8_t>((tv >> (8 * i)) & 0xFFu);
} }
} else { // bool } else {
data_[off] = v ? 1 : 0; data_[off] = v ? 1 : 0;
} }
} }
return true; return true;
} }
/**
* @brief 布局 FB 实例块:数据区全局之后,按 POU 收集序 / 实例声明序
* @details 每实例:字段按布局段序各占一槽(8 字节定宽),
* 字段地址 = 实例基槽 + 字段序号;实例基槽 = 累计槽数
* @return true 成功;falseerr 已写)
*/
bool layout_fb_instances() { bool layout_fb_instances() {
uint32_t cur = static_cast<uint32_t>(data_.size() / 8); // 槽号起点 uint32_t cur = static_cast<uint32_t>(data_.size() / 8);
bool any = false; bool any = false;
for (const LinkResult::PouScope& sc : link_.scopes) { for (const LinkResult::PouScope& sc : link_.scopes) {
for (const Symbol& s : sc.syms) { for (const Symbol& s : sc.syms) {
@@ -749,7 +627,7 @@ namespace {
return fail("no layout for instance '" + s.name + "'"); return fail("no layout for instance '" + s.name + "'");
} }
InstFields inst; InstFields inst;
inst.base = cur; // 实例基槽(CAL_* 操作数) inst.base = cur;
inst.type_name = s.type_name; inst.type_name = s.type_name;
for (size_t i = 0; i < lay->second.fields.size(); ++i) { for (size_t i = 0; i < lay->second.fields.size(); ++i) {
inst.field_addr[lay->second.fields[i].name] = cur + i; inst.field_addr[lay->second.fields[i].name] = cur + i;
@@ -759,31 +637,18 @@ namespace {
any = true; any = true;
} }
} }
// 仅在有实例时把数据区补到实例区终点(无实例则保持全局区原样)
if (any) { if (any) {
data_.resize(static_cast<size_t>(cur) * 8, 0); data_.resize(static_cast<size_t>(cur) * 8, 0);
} }
return true; return true;
} }
/**
* @brief 按 POU + 实例名查实例字段表
* @param pou POU 名
* @param name 实例名
* @return 实例字段表指针;未找到返回 nullptr
*/
const InstFields* instance_of(const std::string& pou, const InstFields* instance_of(const std::string& pou,
const std::string& name) const { const std::string& name) const {
const auto it = instances_.find(pou + "/" + name); const auto it = instances_.find(pou + "/" + name);
return it == instances_.end() ? nullptr : &it->second; 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 { void init_of(const std::string& name, bool* has_init, int64_t* init) const {
for (const SourceUnit& u : units_) { for (const SourceUnit& u : units_) {
for (const VarBlock& b : u.ast.globals) { for (const VarBlock& b : u.ast.globals) {
@@ -802,11 +667,13 @@ namespace {
/** /**
* @brief 取常量表 id(无则追加) * @brief 取常量表 id(无则追加)
* @param tag 类型标记(BOOL/INT/TIME * @param type_name 语言类型名("BOOL"/"INT"/"TIME")→ 配置 tag(契约
* @param value 常量值 * @param value 常量值
* @return const_idu16 * @return const_idu16
*/ */
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) { for (size_t i = 0; i < consts_.size(); ++i) {
if (consts_[i].tag == tag && consts_[i].value == static_cast<uint64_t>(value)) { if (consts_[i].tag == tag && consts_[i].value == static_cast<uint64_t>(value)) {
return static_cast<uint16_t>(i); return static_cast<uint16_t>(i);
@@ -816,33 +683,28 @@ namespace {
return static_cast<uint16_t>(consts_.size() - 1); return static_cast<uint16_t>(consts_.size() - 1);
} }
/**
* @brief 拼映像:头 + 常量表 + 函数表 + 字节码 + 数据段
* @details 段序:const → funcs → code → fb(空)→ data
* 数据段放全局初值(layout_data 已生成字节)
* @return true 成功;falseerr 已写)
*/
bool assemble_image() { bool assemble_image() {
const uint32_t off_const = isa::kHeaderSize; const uint32_t off_const = static_cast<uint32_t>(kHeaderSize);
const uint32_t off_funcs = off_const + const uint32_t off_funcs = off_const +
static_cast<uint32_t>(consts_.size()) * isa::kConstEntrySize; static_cast<uint32_t>(consts_.size()) * static_cast<uint32_t>(kConstEntrySize);
uint32_t off_code = off_funcs + uint32_t off_code = off_funcs +
static_cast<uint32_t>(funcs_.size()) * isa::kFuncRowSize; static_cast<uint32_t>(funcs_.size()) * static_cast<uint32_t>(kFuncRowSize);
uint32_t code_total = 0; uint32_t code_total = 0;
for (const FuncCtx& f : funcs_) { for (const FuncCtx& f : funcs_) {
code_total += static_cast<uint32_t>(f.code.size()) * 4; code_total += static_cast<uint32_t>(f.code.size()) * 4;
} }
const uint32_t off_data = off_code + code_total; const uint32_t off_data = off_code + code_total;
const uint32_t off_end = off_data + static_cast<uint32_t>(data_.size()); const uint32_t off_end = off_data + static_cast<uint32_t>(data_.size()) +
static_cast<uint32_t>(kSha256Size);
std::vector<uint8_t>& b = *image_; std::vector<uint8_t>& b = *image_;
b.assign(off_end, 0); b.assign(off_end, 0);
put_le32(b, 0, isa::kMagic); put_le32(b, 0, kMagic);
put_le32(b, 4, isa::kVersion); put_le32(b, 4, kVersion);
put_le32(b, 8, proj_.cycle_limit); put_le32(b, 8, proj_.cycle_limit);
put_le32(b, 12, proj_.dt_ms); put_le32(b, 12, proj_.dt_ms);
uint64_t hash = isa::kFnvBasis; uint64_t hash = kFnvBasis;
if (!compute_project_hash(proj_, &hash, err_)) { if (!compute_project_hash(proj_, &hash, err_)) {
return false; return false;
} }
@@ -855,80 +717,97 @@ namespace {
} }
} }
put_le32(b, 24, entry); put_le32(b, 24, entry);
put_le32(b, 28, static_cast<uint32_t>(link_.globals.size())); // n_globals put_le32(b, 28, static_cast<uint32_t>(link_.globals.size()));
put_le32(b, 32, 0); // n_i put_le32(b, 32, 0);
put_le32(b, 36, 0); // n_q put_le32(b, 36, 0);
put_le32(b, 40, 0); // n_m put_le32(b, 40, 0);
put_le32(b, 44, static_cast<uint32_t>(consts_.size())); put_le32(b, 44, static_cast<uint32_t>(consts_.size()));
put_le32(b, 48, static_cast<uint32_t>(funcs_.size())); put_le32(b, 48, static_cast<uint32_t>(funcs_.size()));
put_le32(b, 52, off_const); put_le32(b, 52, off_const);
put_le32(b, 56, off_funcs); put_le32(b, 56, off_funcs);
put_le32(b, 60, off_code); put_le32(b, 60, off_code);
put_le32(b, 64, off_data); // offset_fb(空,与数据段起点相同) put_le32(b, 64, off_data);
put_le32(b, 68, off_data); // offset_data put_le32(b, 68, off_data);
// 型号标识[32](偏移 7212.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<uint8_t>(mid[i]);
}
for (size_t i = 0; i < consts_.size(); ++i) { for (size_t i = 0; i < consts_.size(); ++i) {
const size_t o = off_const + i * isa::kConstEntrySize; const size_t o = off_const + i * kConstEntrySize;
put_le32(b, o, static_cast<uint32_t>(consts_[i].tag)); put_le32(b, o, consts_[i].tag);
put_le64(b, o + 4, consts_[i].value); put_le64(b, o + 4, consts_[i].value);
} }
uint32_t c = off_code; uint32_t c = off_code;
for (size_t i = 0; i < funcs_.size(); ++i) { for (size_t i = 0; i < funcs_.size(); ++i) {
const FuncCtx& f = funcs_[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, f.nregs);
put_le32(b, o + 8, static_cast<uint32_t>(f.code.size())); put_le32(b, o + 8, static_cast<uint32_t>(f.code.size()));
for (const isa::Instr in : f.code) { for (const Instr in : f.code) {
put_le32(b, c, in); put_le32(b, c, in);
c += 4; c += 4;
} }
} }
// code_offset 回填(相对字节码段起点)
uint32_t acc = 0; uint32_t acc = 0;
for (size_t i = 0; i < funcs_.size(); ++i) { 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); put_le32(b, o + 4, acc);
acc += static_cast<uint32_t>(funcs_[i].code.size()) * 4; acc += static_cast<uint32_t>(funcs_[i].code.size()) * 4;
} }
// 数据段:全局初值字节
for (size_t i = 0; i < data_.size(); ++i) { for (size_t i = 0; i < data_.size(); ++i) {
b[off_data + i] = data_[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; return true;
} }
static void put_le32(std::vector<uint8_t>& b, size_t off, uint32_t v) {
b[off + 0] = static_cast<uint8_t>(v & 0xFFu);
b[off + 1] = static_cast<uint8_t>((v >> 8) & 0xFFu);
b[off + 2] = static_cast<uint8_t>((v >> 16) & 0xFFu);
b[off + 3] = static_cast<uint8_t>((v >> 24) & 0xFFu);
}
static void put_le64(std::vector<uint8_t>& b, size_t off, uint64_t v) {
for (int i = 0; i < 8; ++i) {
b[off + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFFu);
}
}
// ---- 成员 ---- // ---- 成员 ----
const Project& proj_; const Project& proj_;
const std::vector<SourceUnit>& units_; const std::vector<SourceUnit>& units_;
const LinkResult& link_; const LinkResult& link_;
const MachineConfig& cfg_;
std::vector<uint8_t>* image_; std::vector<uint8_t>* image_;
std::string* err_; std::string* err_;
std::vector<FuncCtx> funcs_; std::vector<FuncCtx> funcs_;
std::vector<isa::ConstEntry> consts_; std::vector<ConstEntry> consts_;
std::map<std::string, bool> io_input_; // io.input 绑定名(小写) std::map<std::string, bool> io_input_;
std::map<std::string, bool> io_output_; // io.output 绑定名(小写) std::map<std::string, bool> io_output_;
std::vector<uint8_t> data_; // 数据区(8 字节定宽槽:全局 + FB 实例块) std::vector<uint8_t> data_;
std::map<std::string, InstFields> instances_; // "POU/实例名" → 字段槽号 std::map<std::string, InstFields> instances_;
const InstFields* inline_fields_ = nullptr; // 内联 FB 体字段上下文(可空) const InstFields* inline_fields_ = nullptr;
}; };
} // namespace } // 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<SourceUnit>& units, bool codegen_project(const Project& proj, const std::vector<SourceUnit>& units,
const LinkResult& link, std::vector<uint8_t>* image, const LinkResult& link, const MachineConfig& cfg,
std::string* err) { std::vector<uint8_t>* image, std::string* err) {
Builder b(proj, units, link, image, err); Builder b(proj, units, link, cfg, image, err);
return b.run(); return b.run();
} }
+1 -1
View File
@@ -662,7 +662,7 @@ namespace {
* @return true 全部绑定合法;falseerr 已写) * @return true 全部绑定合法;falseerr 已写)
*/ */
bool check_io() { bool check_io() {
for (const isa::IoBinding& b : proj_.io) { for (const IoBinding& b : proj_.io) {
std::string key = b.var; std::string key = b.var;
for (char& ch : key) { for (char& ch : key) {
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch))); ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
+396
View File
@@ -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..255class/format 合法;类别-格式互锁(INSTANCE ↔ CAL);
* params 数量与 format 一致
* 5. fb.opcode 与 op 行(instance 类)一致;字段类型命中配置类型表;字段名唯一
*/
#include "compiler/MachineConfig.h"
#include <toml++/toml.hpp>
#include <map>
#include <string>
namespace compiler {
const std::vector<PrimType>& MachineConfig::prims() {
static const std::vector<PrimType> 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<int64_t>(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<bool>(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<uint32_t>(ver);
// ---- type ----
{
const toml::array* arr = root["type"].as_array();
if (!arr || arr->empty()) {
fail(err, "missing 'type' table");
return false;
}
std::map<uint32_t, std::string> 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<uint32_t>(tag))) {
fail(err, "type '" + ct.name + "': tag out of contract (0..2)");
return false;
}
ct.tag = static_cast<uint32_t>(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<int64_t>(0);
ct.range_max = (*r)[1].value_or<int64_t>(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<uint32_t, std::string> code_owner;
std::map<std::string, uint32_t> 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<uint32_t>(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<int>(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<uint32_t>(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<std::string, bool> 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
+4 -4
View File
@@ -16,7 +16,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "isa/Image.h" #include "compiler/Stb.h"
namespace compiler { namespace compiler {
namespace { namespace {
@@ -241,7 +241,7 @@ namespace {
if (!check_keys(sec, is_input ? "io.input" : "io.output", allowed, 3, err)) { if (!check_keys(sec, is_input ? "io.input" : "io.output", allowed, 3, err)) {
return false; return false;
} }
isa::IoBinding b; IoBinding b;
b.is_input = is_input; b.is_input = is_input;
b.slot = 0; // 12.6 链接阶段再解析 b.slot = 0; // 12.6 链接阶段再解析
if (!req_string(sec, is_input ? "io.input" : "io.output", "var", &b.var, err)) { 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::sort(sorted.begin(), sorted.end());
std::filesystem::path base(p.base_dir); std::filesystem::path base(p.base_dir);
uint64_t h = isa::kFnvBasis; uint64_t h = kFnvBasis;
for (const std::string& f : sorted) { for (const std::string& f : sorted) {
std::ifstream in(base / f, std::ios::binary); std::ifstream in(base / f, std::ios::binary);
if (!in) { if (!in) {
@@ -365,7 +365,7 @@ bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err) {
in.read(buf, sizeof buf); in.read(buf, sizeof buf);
const std::streamsize n = in.gcount(); const std::streamsize n = in.gcount();
if (n > 0) { if (n > 0) {
h = isa::fnv1a64_update(h, reinterpret_cast<const uint8_t*>(buf), h = fnv1a64_update(h, reinterpret_cast<const uint8_t*>(buf),
static_cast<size_t>(n)); static_cast<size_t>(n));
} }
} }
+378
View File
@@ -0,0 +1,378 @@
/**
* @file Stb.cpp
* @brief .stb + + FNV-1a + sidecar
* @author
* @date 2026-08-21
*/
#include "compiler/Stb.h"
#include <cstdio>
#include <cstring>
#include <fstream>
#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<uint32_t>(p[0])
| (static_cast<uint32_t>(p[1]) << 8)
| (static_cast<uint32_t>(p[2]) << 16)
| (static_cast<uint32_t>(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<uint64_t>(p[i]) << (8 * i);
}
return v;
}
// ---- SHA-256FIPS 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<uint8_t>((bitlen >> (56 - 8 * i)) & 0xFF)};
update(b2, 1);
}
for (int i = 0; i < 8; ++i) {
out[i * 4 + 0] = static_cast<uint8_t>((h[i] >> 24) & 0xFF);
out[i * 4 + 1] = static_cast<uint8_t>((h[i] >> 16) & 0xFF);
out[i * 4 + 2] = static_cast<uint8_t>((h[i] >> 8) & 0xFF);
out[i * 4 + 3] = static_cast<uint8_t>(h[i] & 0xFF);
}
}
static uint32_t get_be32(const uint8_t* p) {
return (static_cast<uint32_t>(p[0]) << 24) | (static_cast<uint32_t>(p[1]) << 16) |
(static_cast<uint32_t>(p[2]) << 8) | static_cast<uint32_t>(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<size_t>(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<uint64_t>(v.n_consts_) * kConstEntrySize) {
v.err_ = "const table size mismatch";
return v;
}
if (offs[2] - offs[1] != static_cast<uint64_t>(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<uint8_t>& 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<uint32_t>(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<const char*>(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<uint8_t>* 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<char>(in), std::istreambuf_iterator<char>());
return true;
}
bool write_stb_file(const char* path, const std::vector<uint8_t>& 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<IoBinding>& 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<unsigned>(b.slot));
out += buf;
std::snprintf(buf, sizeof buf, "channel = %u\n", static_cast<unsigned>(b.channel));
out += buf;
std::snprintf(buf, sizeof buf, "bit = %u\n", static_cast<unsigned>(b.bit));
out += buf;
out += "\n";
}
return out;
}
bool write_sidecar_file(const char* path, const std::vector<IoBinding>& 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
+41
View File
@@ -0,0 +1,41 @@
/**
* @file TypeInfo.cpp
* @brief machine.toml +
* @author
* @date 2026-08-21
*/
#include "compiler/TypeInfo.h"
#include <cctype>
namespace compiler {
namespace {
std::string lower(const std::string& s) {
std::string out = s;
for (char& ch : out) {
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(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
+85 -41
View File
@@ -12,67 +12,68 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "compiler/Codec.h"
#include "compiler/Codegen.h" #include "compiler/Codegen.h"
#include "compiler/Linker.h" #include "compiler/Linker.h"
#include "compiler/MachineConfig.h"
#include "compiler/Project.h" #include "compiler/Project.h"
#include "compiler/Stb.h"
#include "compiler/Typecheck.h" #include "compiler/Typecheck.h"
#include "isa/Encode.h"
#include "isa/Image.h"
namespace { namespace {
void usage() { void usage() {
std::printf("usage: STCompiler <project.toml> [-o <name>.stb]\n" std::printf("usage: STCompiler <project.toml> [-o <name>.stb] --machine <machine.toml>\n"
" STCompiler <name>.stb --disasm\n" " STCompiler <name>.stb --disasm --machine <machine.toml>\n"
" 解析工程并编译(词法 → 语法 → 链接 → 类型 → 寄存器码)\n" " 解析工程并编译(词法 → 语法 → 链接 → 类型 → 寄存器码)\n"
" -o <name>.stb 编译并写出映像文件\n" " -o <name>.stb 编译并写出映像文件\n"
" 无 -o 只打印文件集合与工程哈希(12.3 阶段\n" " --machine <path> 机器定义(machine.toml,编译路径必填\n"
" --disasm 反汇编一个已编译的 .stb 映像\n" " --disasm 反汇编一个已编译的 .stb 映像(需要 --machine\n"
" --help 打印本帮助\n"); " --help 打印本帮助\n");
} }
// 反汇编一个已编译映像(函数表逐条 + 常量表 + 数据段摘要)。 // 反汇编一个已编译映像(函数表逐条 + 常量表 + 数据段摘要)。
// 映像无符号表,按 fn_id / 槽号显示;指令偏移为文件绝对字节偏移。 // 指令偏移为文件绝对字节偏移;反汇编按 machine.toml 的 format 输出
int dump_image(const char* path) { int dump_image(const char* path, const compiler::MachineConfig& cfg) {
std::vector<uint8_t> bytes; std::vector<uint8_t> bytes;
std::string err; 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()); std::fprintf(stderr, "error: %s\n", err.c_str());
return 1; return 1;
} }
const isa::ImageView v = isa::ImageView::from(bytes); const compiler::StbView v = compiler::StbView::from(bytes);
if (!v.ok()) { if (!v.ok()) {
std::fprintf(stderr, "error: %s\n", v.error().c_str()); std::fprintf(stderr, "error: %s\n", v.error().c_str());
return 1; return 1;
} }
const isa::ImageHeader& h = v.header();
std::printf("image: %s (%zu bytes, %u functions, %u globals, entry fn %u)\n", 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); path, bytes.size(), v.n_funcs(), v.n_globals(), v.entry_fn_id());
std::printf(" dt_ms=%u cycle_limit=%u hash=0x%016llx\n", h.dt_ms, std::printf(" dt_ms=%u cycle_limit=%u hash=0x%016llx model=%s sha=%s\n",
h.cycle_limit, static_cast<unsigned long long>(h.project_hash)); v.dt_ms(), v.cycle_limit(),
static_cast<unsigned long long>(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"); std::printf("constants:\n");
for (uint32_t i = 0; i < h.n_consts; ++i) { for (uint32_t i = 0; i < v.n_consts(); ++i) {
const isa::ConstEntry c = v.const_entry(i); const compiler::ConstEntry c = v.const_entry(i);
const char* tag = c.tag == isa::types::Bool ? "BOOL" const char* tag = c.tag == 0 ? "BOOL" : c.tag == 1 ? "INT" : "TIME";
: c.tag == isa::types::Int ? "INT"
: "TIME";
std::printf(" [%u] %s %llu\n", i, tag, std::printf(" [%u] %s %llu\n", i, tag,
static_cast<unsigned long long>(c.value)); static_cast<unsigned long long>(c.value));
} }
} }
std::printf("functions:\n"); std::printf("functions:\n");
for (uint32_t i = 0; i < h.n_funcs; ++i) { for (uint32_t i = 0; i < v.n_funcs(); ++i) {
const isa::FuncRow r = v.func_row(i); const compiler::StbView::FuncRow r = v.func_row(i);
std::printf(" fn %u: nregs=%u, offset=%u, len=%u\n", i, r.nregs, std::printf(" fn %u: nregs=%u, offset=%u, len=%u\n", i, r.nregs,
r.code_offset, r.code_len); r.code_offset, r.code_len);
const uint8_t* base = v.code_bytes() + r.code_offset; const uint8_t* base = v.code_bytes() + r.code_offset;
for (uint32_t j = 0; j < r.code_len; ++j) { for (uint32_t j = 0; j < r.code_len; ++j) {
char buf[64]; char buf[64];
isa::disasm(reinterpret_cast<const uint32_t*>(base)[j], buf, sizeof buf); compiler::disasm(cfg, reinterpret_cast<const uint32_t*>(base)[j], buf,
sizeof buf);
std::printf(" 0x%04x %s\n", 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; return 0;
} }
// --disasm:第一参数是 .stb 路径,不重新编译 // 参数收集
for (int i = 1; i < argc; ++i) { std::string machine_path;
if (std::strcmp(argv[i], "--disasm") == 0) {
return dump_image(argv[1]);
}
}
const std::string toml_path = argv[1];
std::string out_path; std::string out_path;
bool disasm_mode = false;
for (int i = 2; i + 1 < argc; ++i) { 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]; 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 <machine.toml>\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; compiler::Project proj;
std::string err; std::string err;
if (!compiler::parse_project(toml_path, &proj, &err)) { if (!compiler::parse_project(toml_path, &proj, &err)) {
@@ -125,6 +145,7 @@ int main(int argc, char** argv) {
} }
const std::vector<std::string> files = compiler::compile_files(proj); const std::vector<std::string> files = compiler::compile_files(proj);
// 打印模式(无 -o):不需要机器定义
if (out_path.empty()) { if (out_path.empty()) {
uint64_t hash = 0; uint64_t hash = 0;
if (!compiler::compute_project_hash(proj, &hash, &err)) { if (!compiler::compute_project_hash(proj, &hash, &err)) {
@@ -140,6 +161,18 @@ int main(int argc, char** argv) {
return 0; return 0;
} }
// 编译路径:机器定义必填(编码/类型/FB 布局来自 machine.toml
if (machine_path.empty()) {
std::fprintf(stderr, "error: missing --machine <machine.toml>\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 → 链接 → 类型检查 → 寄存器码 // 完整管线:读 .st → 链接 → 类型检查 → 寄存器码
std::vector<compiler::SourceUnit> units; std::vector<compiler::SourceUnit> units;
for (const std::string& f : files) { for (const std::string& f : files) {
@@ -160,19 +193,30 @@ int main(int argc, char** argv) {
return 1; return 1;
} }
std::vector<uint8_t> image; std::vector<uint8_t> 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()); std::fprintf(stderr, "error: %s\n", err.c_str());
return 1; 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()); std::fprintf(stderr, "error: %s\n", err.c_str());
return 1; 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;
}
// sidecarI/O 绑定 var → 槽号 → channel/bit(不进映像,执行器采样用) // sidecarI/O 绑定 var → 槽号 → channel/bit(不进映像,执行器采样用)
std::vector<isa::IoBinding> bindings; std::vector<compiler::IoBinding> bindings;
for (const isa::IoBinding& b : proj.io) { for (const compiler::IoBinding& b : proj.io) {
isa::IoBinding out_b = b; compiler::IoBinding out_b = b;
std::string key = b.var; std::string key = b.var;
for (char& ch : key) { for (char& ch : key) {
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch))); ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
@@ -185,13 +229,13 @@ int main(int argc, char** argv) {
} }
std::filesystem::path sidecar = std::filesystem::path(out_path); std::filesystem::path sidecar = std::filesystem::path(out_path);
sidecar.replace_extension(".runtime.toml"); 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()); std::fprintf(stderr, "error: %s\n", err.c_str());
return 1; 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", 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; return 0;
} }
+13 -4
View File
@@ -21,7 +21,7 @@
#include <vector> #include <vector>
#include "isa/Encode.h" #include "isa/Encode.h"
#include "isa/Image.h" #include "vm/Image.h"
#include "vm/Machine.h" #include "vm/Machine.h"
namespace { namespace {
@@ -199,8 +199,17 @@ int main(int argc, char** argv) {
std::string err; std::string err;
std::vector<uint8_t> bytes; std::vector<uint8_t> 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<char>(in), std::istreambuf_iterator<char>());
}
const vm::Image img = vm::Image::from(bytes);
if (!img.ok()) {
std::fprintf(stderr, "error: %s\n", img.error().c_str());
return 1; return 1;
} }
vm::Machine machine; 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(), 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); bytes.size(), h.dt_ms, h.cycle_limit);
+1 -2
View File
@@ -7,7 +7,6 @@ project(ISA
DESCRIPTION "") DESCRIPTION "")
add_library(isa STATIC add_library(isa STATIC
./src/Encode.cpp ./src/Encode.cpp)
./src/Image.cpp)
target_include_directories(isa PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) target_include_directories(isa PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
-125
View File
@@ -1,125 +0,0 @@
/**
* @file Image.h
* @brief FNV-1a .stb sidecar
* @author
* @date 2026-08-19
*/
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#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 64basis / 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() == falseerror() == "uninitialized"
ImageView();
static ImageView from(const uint8_t* buf, size_t len);
static ImageView from(const std::vector<uint8_t>& 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_;
};
// 写最小映像:空槽 + MAINfn_id=0+ 一条 RET
std::vector<uint8_t> 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<uint8_t>& image,
std::string* err);
bool read_stb_file(const char* path, std::vector<uint8_t>* image,
std::string* err);
// sidecar 生成与写文件(格式见 Doc/isa/指令与映像.md
std::string make_sidecar(const std::vector<IoBinding>& bindings);
bool write_sidecar_file(const char* path,
const std::vector<IoBinding>& bindings,
std::string* err);
}
+75 -13
View File
@@ -53,20 +53,82 @@ namespace isa {
// 操作码总数 // 操作码总数
static const int kOpCount = 34; static const int kOpCount = 34;
// 助记符:下标与 Op 数值一一对应 // 操作数形态(三层分类第二层,见 Doc/isa/指令配置.md
enum class OpFormat : uint8_t {
RR, // 两寄存器:rd rsMOVE / NOT
RRR, // 三寄存器:rd ra rb(逻辑 / 算术 / 比较)
IMM, // 常量:rd const_idLOADK
SLOT, // 槽:rd slotLOAD_* / STORE_*
JMP, // 偏移:off
JC, // 条件跳转:r offJT / JF
CALL, // 调用:fn_id
CAL, // 实例:instanceCAL_*
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) { 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<int>(op); const int idx = static_cast<int>(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<int>(op);
return (idx >= 0 && idx < kOpCount) ? kOpDefs[idx].format : OpFormat::NONE;
}
inline OpClass op_class(Op op) {
const int idx = static_cast<int>(op);
return (idx >= 0 && idx < kOpCount) ? kOpDefs[idx].cls : OpClass::Plain;
} }
} }
+11 -38
View File
@@ -38,61 +38,34 @@ void disasm(Instr w, char* out, size_t cap) {
} }
const char* m = mnemonic(o); const char* m = mnemonic(o);
switch (o) { // 表驱动:按操作数形态输出(文本与 Doc/isa/指令与映像.md 一致)
case Op::MOVE: switch (format(o)) {
case Op::NOT: case OpFormat::RR:
snprintf(out, cap, "%s r%u, r%u", m, snprintf(out, cap, "%s r%u, r%u", m,
static_cast<unsigned>(rd(w)), static_cast<unsigned>(a(w))); static_cast<unsigned>(rd(w)), static_cast<unsigned>(a(w)));
break; break;
case Op::AND: case OpFormat::RRR:
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:
snprintf(out, cap, "%s r%u, r%u, r%u", m, snprintf(out, cap, "%s r%u, r%u, r%u", m,
static_cast<unsigned>(rd(w)), static_cast<unsigned>(a(w)), static_cast<unsigned>(rd(w)), static_cast<unsigned>(a(w)),
static_cast<unsigned>(b(w))); static_cast<unsigned>(b(w)));
break; break;
case Op::LOADK: case OpFormat::IMM:
case OpFormat::SLOT:
snprintf(out, cap, "%s r%u, %u", m, snprintf(out, cap, "%s r%u, %u", m,
static_cast<unsigned>(rd(w)), static_cast<unsigned>(imm16(w))); static_cast<unsigned>(rd(w)), static_cast<unsigned>(imm16(w)));
break; break;
case Op::JMP: case OpFormat::JMP:
snprintf(out, cap, "%s %+d", m, static_cast<int>(off16(w))); snprintf(out, cap, "%s %+d", m, static_cast<int>(off16(w)));
break; break;
case Op::JT: case OpFormat::JC:
case Op::JF:
snprintf(out, cap, "%s r%u, %+d", m, snprintf(out, cap, "%s r%u, %+d", m,
static_cast<unsigned>(rd(w)), static_cast<int>(off16(w))); static_cast<unsigned>(rd(w)), static_cast<int>(off16(w)));
break; break;
case Op::LOAD_I: case OpFormat::CALL:
case Op::STORE_Q: case OpFormat::CAL:
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<unsigned>(rd(w)), static_cast<unsigned>(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:
snprintf(out, cap, "%s %u", m, static_cast<unsigned>(imm16(w))); snprintf(out, cap, "%s %u", m, static_cast<unsigned>(imm16(w)));
break; break;
case Op::RET: case OpFormat::NONE:
snprintf(out, cap, "%s", m); snprintf(out, cap, "%s", m);
break; break;
} }
-321
View File
@@ -1,321 +0,0 @@
/**
* @file Image.cpp
* @brief FNV-1a .stb sidecar
* @author
* @date 2026-08-19
*/
#include "isa/Image.h"
#include <cstdio>
#include <cstring>
#include "isa/Instr.h"
namespace isa {
// ---- 小端读写(只放 .cpp,不把 packed struct 当 ABI----
static void put_le32(std::vector<uint8_t>& b, size_t off, uint32_t v) {
b[off + 0] = static_cast<uint8_t>(v & 0xFFu);
b[off + 1] = static_cast<uint8_t>((v >> 8) & 0xFFu);
b[off + 2] = static_cast<uint8_t>((v >> 16) & 0xFFu);
b[off + 3] = static_cast<uint8_t>((v >> 24) & 0xFFu);
}
static uint32_t get_le32(const uint8_t* p) {
return static_cast<uint32_t>(p[0])
| (static_cast<uint32_t>(p[1]) << 8)
| (static_cast<uint32_t>(p[2]) << 16)
| (static_cast<uint32_t>(p[3]) << 24);
}
static void put_le64(std::vector<uint8_t>& b, size_t off, uint64_t v) {
for (int i = 0; i < 8; ++i) {
b[off + i] = static_cast<uint8_t>((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<uint64_t>(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<uint8_t> write_ret_main(uint32_t cycle_limit, uint32_t dt_ms,
uint64_t project_hash) {
std::vector<uint8_t> 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<uint32_t>(off_funcs)); // offset_const
put_le32(b, 56, static_cast<uint32_t>(off_funcs)); // offset_funcs
put_le32(b, 60, static_cast<uint32_t>(off_code)); // offset_code
put_le32(b, 64, static_cast<uint32_t>(off_end)); // offset_fb
put_le32(b, 68, static_cast<uint32_t>(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<uint64_t>(h.n_consts) * kConstEntrySize) {
v.err_ = "const table size mismatch";
return v;
}
if (funcs_len != static_cast<uint64_t>(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<uint8_t>& 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<uint32_t>(types::Time)) {
e.tag = static_cast<types::TypeTag>(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<uint8_t>& 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<uint8_t>* 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_t>(size));
const bool ok = size == 0 || std::fread(&(*image)[0], 1, static_cast<size_t>(size), f) == static_cast<size_t>(size);
std::fclose(f);
if (!ok && err) {
*err = std::string("read failed: ") + path;
}
return ok;
}
// ---- sidecar ----
std::string make_sidecar(const std::vector<IoBinding>& 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<unsigned>(b.slot));
out += buf;
std::snprintf(buf, sizeof buf, "channel = %u\n", static_cast<unsigned>(b.channel));
out += buf;
std::snprintf(buf, sizeof buf, "bit = %u\n", static_cast<unsigned>(b.bit));
out += buf;
out += "\n";
}
return out;
}
bool write_sidecar_file(const char* path,
const std::vector<IoBinding>& 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
+22 -1
View File
@@ -4,6 +4,16 @@ add_test(NAME stcompiler_version
add_test(NAME bytecode_executor_version add_test(NAME bytecode_executor_version
COMMAND BytecodeExecutor) COMMAND BytecodeExecutor)
# CLI --machineWILL_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 # isa isa //disasm///sidecar
add_executable(isa_test add_executable(isa_test
./src/isa_test.cpp) ./src/isa_test.cpp)
@@ -72,7 +82,7 @@ add_test(NAME typecheck_types
add_executable(codegen_test add_executable(codegen_test
./src/codegen_test.cpp) ./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 target_compile_definitions(codegen_test PRIVATE
REPO_ROOT="${CMAKE_SOURCE_DIR}") REPO_ROOT="${CMAKE_SOURCE_DIR}")
@@ -100,3 +110,14 @@ target_compile_definitions(cases_test PRIVATE
add_test(NAME cases_all add_test(NAME cases_all
COMMAND cases_test) COMMAND cases_test)
# machine.toml A 3REPO_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)
+6 -1
View File
@@ -119,8 +119,13 @@ namespace {
std::printf("FAIL %s type: %s\n", dir, err.c_str()); std::printf("FAIL %s type: %s\n", dir, err.c_str());
return false; 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<uint8_t> img; std::vector<uint8_t> img;
if (!codegen_project(p, units, link, &img, &err)) { if (!codegen_project(p, units, link, cfg, &img, &err)) {
if (want == Outcome::CompileError) { if (want == Outcome::CompileError) {
++g_checks; ++g_checks;
return true; return true;
+90 -65
View File
@@ -16,7 +16,7 @@
#include "compiler/Project.h" #include "compiler/Project.h"
#include "compiler/Typecheck.h" #include "compiler/Typecheck.h"
#include "isa/Encode.h" #include "isa/Encode.h"
#include "isa/Image.h" #include "compiler/Stb.h"
#ifndef REPO_ROOT #ifndef REPO_ROOT
#define REPO_ROOT "." #define REPO_ROOT "."
@@ -34,8 +34,19 @@ static int g_checks = 0;
} while (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<uint8_t>* image, std::string* err) { static bool compile_case(const char* dir, std::vector<uint8_t>* image, std::string* err) {
using namespace compiler; 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"; const std::string toml = std::string(REPO_ROOT) + "/tests/cases/" + dir + "/project.toml";
Project p; Project p;
if (!parse_project(toml, &p, err)) { if (!parse_project(toml, &p, err)) {
@@ -56,7 +67,7 @@ static bool compile_case(const char* dir, std::vector<uint8_t>* image, std::stri
if (!check_project(p, units, link, err)) { if (!check_project(p, units, link, err)) {
return false; return false;
} }
return codegen_project(p, units, link, image, err); return codegen_project(p, units, link, cfg, image, err);
} }
// 从临时工程(globals.st + main.st + 可含 io 绑定)走完整管线 // 从临时工程(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<uint8_t>* image, const char* main_st, std::vector<uint8_t>* image,
std::string* err) { std::string* err) {
using namespace compiler; using namespace compiler;
compiler::MachineConfig cfg;
if (!load_cfg(&cfg)) return false;
const std::string dir = std::string(REPO_ROOT) + "/build/cg_tmp_" + name; const std::string dir = std::string(REPO_ROOT) + "/build/cg_tmp_" + name;
std::filesystem::remove_all(dir); std::filesystem::remove_all(dir);
std::filesystem::create_directories(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)) { if (!check_project(p, units, link, err)) {
return false; return false;
} }
return codegen_project(p, units, link, image, err); return codegen_project(p, units, link, cfg, image, err);
} }
// ---- 1. 用例 01:空 MAIN + RET ---- // ---- 1. 用例 01:空 MAIN + RET ----
@@ -110,17 +123,17 @@ static bool test_case01() {
std::string err; std::string err;
CHECK(compile_case("01_empty_main", &img, &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.ok());
CHECK(v.header().cycle_limit == 1000); CHECK(v.cycle_limit() == 1000);
CHECK(v.header().dt_ms == 10); CHECK(v.dt_ms() == 10);
CHECK(v.header().entry_fn_id == 0); CHECK(v.entry_fn_id() == 0);
CHECK(v.header().n_funcs == 1); CHECK(v.n_funcs() == 1);
CHECK(v.header().n_consts == 0); CHECK(v.n_consts() == 0);
CHECK(v.header().n_globals == 0); CHECK(v.n_globals() == 0);
CHECK(v.header().project_hash != isa::kFnvBasis); 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.nregs == 8); // 帧基址(调用约定区)
CHECK(r.code_len == 1); CHECK(r.code_len == 1);
@@ -137,12 +150,12 @@ static bool test_case02() {
std::string err; std::string err;
CHECK(compile_case("02_bool_assign", &img, &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.ok());
CHECK(v.header().n_funcs == 1); CHECK(v.n_funcs() == 1);
CHECK(v.header().n_consts == 2); // TRUE 与 FALSE 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, br8 起) CHECK(r.nregs == 10); // a, br8 起)
CHECK(r.code_len == 3); CHECK(r.code_len == 3);
@@ -157,10 +170,10 @@ static bool test_case02() {
CHECK(std::strcmp(buf, "RET") == 0); CHECK(std::strcmp(buf, "RET") == 0);
// 常量表:0 = BOOL 11 = BOOL 0 // 常量表:0 = BOOL 11 = BOOL 0
const isa::ConstEntry c0 = v.const_entry(0); const compiler::ConstEntry c0 = v.const_entry(0);
CHECK(c0.tag == isa::types::Bool && c0.value == 1); CHECK(c0.tag == 0 && c0.value == 1);
const isa::ConstEntry c1 = v.const_entry(1); const compiler::ConstEntry c1 = v.const_entry(1);
CHECK(c1.tag == isa::types::Bool && c1.value == 0); CHECK(c1.tag == 0 && c1.value == 0);
return true; return true;
} }
@@ -171,14 +184,14 @@ static bool test_case09() {
std::string err; std::string err;
CHECK(compile_case("09_gvl_external", &img, &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.ok());
CHECK(v.header().n_globals == 1); CHECK(v.n_globals() == 1);
CHECK(v.data_len() == 8); // 8 字节定宽槽 CHECK(v.data_len() == 8); // 8 字节定宽槽(不含 SHA 尾)
CHECK(v.data_bytes()[0] == 5); // G1 初值 5(小端低位) CHECK(v.data_bytes()[0] == 5); // G1 初值 5(小端低位)
CHECK(v.data_bytes()[1] == 0); 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.nregs == 9); // r8 起:仅局部 x
CHECK(r.code_len == 2); CHECK(r.code_len == 2);
@@ -208,11 +221,11 @@ static bool test_io_ops() {
"END_PROGRAM\n"; "END_PROGRAM\n";
CHECK(compile_src("io", io_extra, globals_st, main_st, &img, &err)); 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.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 临时 CHECK(r.nregs == 10); // r8 起:q + 1 临时
char buf[64]; char buf[64];
@@ -245,9 +258,9 @@ static bool test_layout() {
"END_PROGRAM\n"; "END_PROGRAM\n";
CHECK(compile_src("layout", "", globals_st, main_st, &img, &err)); 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.ok());
CHECK(v.header().n_globals == 3); CHECK(v.n_globals() == 3);
CHECK(v.data_len() == 24); // 3 槽 × 8 字节定宽 CHECK(v.data_len() == 24); // 3 槽 × 8 字节定宽
CHECK(v.data_bytes()[0] == 0); // B 无初值 CHECK(v.data_bytes()[0] == 0); // B 无初值
CHECK(v.data_bytes()[8] == 0x2C && v.data_bytes()[9] == 0x01); // I = 300(槽 1 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; std::string err;
CHECK(compile_case("03_short_circuit", &img, &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()); 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.nregs == 12); // r8 起:a b x + 1 临时
CHECK(r.code_len == 9); CHECK(r.code_len == 9);
@@ -329,9 +342,9 @@ static bool test_not() {
"END_PROGRAM\n"; "END_PROGRAM\n";
CHECK(compile_src("not", "", "", main_st, &img, &err)); 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()); 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.nregs == 11); // r8 起:a x + 1 临时
CHECK(r.code_len == 3); CHECK(r.code_len == 3);
@@ -354,9 +367,9 @@ static bool test_case04() {
std::string err; std::string err;
CHECK(compile_case("04_if_elsif_else", &img, &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()); 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.nregs == 13); // r8 起:sel out + 3 临时
CHECK(r.code_len == 14); CHECK(r.code_len == 14);
@@ -404,9 +417,9 @@ static bool test_case05() {
std::string err; std::string err;
CHECK(compile_case("05_while_normal", &img, &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()); 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.nregs == 12); // r8 起:n + 3 临时(条件结果 r9 + 两操作数)
CHECK(r.code_len == 10); CHECK(r.code_len == 10);
@@ -445,9 +458,9 @@ static bool test_case07() {
std::string err; std::string err;
CHECK(compile_case("07_int_arith", &img, &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()); 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 临时 CHECK(r.nregs == 14); // r8 起:a b c eq + 2 临时
char buf[64]; char buf[64];
@@ -472,15 +485,15 @@ static bool test_case08() {
std::string err; std::string err;
CHECK(compile_case("08_time_literal", &img, &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.ok());
CHECK(v.header().n_consts == 2); CHECK(v.n_consts() == 2);
const isa::ConstEntry c0 = v.const_entry(0); const compiler::ConstEntry c0 = v.const_entry(0);
CHECK(c0.tag == isa::types::Time && c0.value == 10); // T#10ms CHECK(c0.tag == 2 && c0.value == 10); // T#10ms
const isa::ConstEntry c1 = v.const_entry(1); const compiler::ConstEntry c1 = v.const_entry(1);
CHECK(c1.tag == isa::types::Time && c1.value == 1250); // T#1s250ms 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 起 CHECK(r.nregs == 10); // r8 起
char buf[64]; char buf[64];
const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes()); const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes());
@@ -498,15 +511,15 @@ static bool test_case13() {
std::string err; std::string err;
CHECK(compile_case("13_function_call", &img, &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.ok());
CHECK(v.header().n_funcs == 2); CHECK(v.n_funcs() == 2);
CHECK(v.header().entry_fn_id == 1); // MAIN 是第二个 POU CHECK(v.entry_fn_id() == 1); // MAIN 是第二个 POU
char buf[64]; char buf[64];
const uint8_t* base = v.code_bytes(); const uint8_t* base = v.code_bytes();
// Addfn_id 0):结果 r0、输入 r1(a) r2(b)、临时 r8/r9 // Addfn_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.nregs == 10);
CHECK(fa.code_len == 4); CHECK(fa.code_len == 4);
const uint32_t* ia = reinterpret_cast<const uint32_t*>(base); const uint32_t* ia = reinterpret_cast<const uint32_t*>(base);
@@ -520,7 +533,7 @@ static bool test_case13() {
CHECK(std::strcmp(buf, "RET") == 0); CHECK(std::strcmp(buf, "RET") == 0);
// MAINfn_id 1):x 在 r8,实参临时 r9/r10 // MAINfn_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.nregs == 11);
CHECK(fm.code_len == 7); CHECK(fm.code_len == 7);
const uint32_t* im = reinterpret_cast<const uint32_t*>(base) + fm.code_offset / 4; const uint32_t* im = reinterpret_cast<const uint32_t*>(base) + fm.code_offset / 4;
@@ -548,13 +561,13 @@ static bool test_case15() {
std::string err; std::string err;
CHECK(compile_case("15_fb_instance", &img, &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.ok());
CHECK(v.header().n_funcs == 2); // FB 占位 + MAIN CHECK(v.n_funcs() == 2); // FB 占位 + MAIN
CHECK(v.header().entry_fn_id == 1); CHECK(v.entry_fn_id() == 1);
CHECK(v.data_len() == 24); // 实例 3 槽 × 8 字节定宽 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.nregs == 12);
CHECK(fm.code_len == 12); CHECK(fm.code_len == 12);
const uint32_t* im = reinterpret_cast<const uint32_t*>(v.code_bytes()) + fm.code_offset / 4; const uint32_t* im = reinterpret_cast<const uint32_t*>(v.code_bytes()) + fm.code_offset / 4;
@@ -588,10 +601,10 @@ static bool test_case17() {
std::string err; std::string err;
CHECK(compile_case("17_ton", &img, &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.ok());
CHECK(v.data_len() == 32); // in@0 pt@8 q@16 et@24 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); CHECK(r.nregs == 11);
const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes()); const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes());
char buf[64]; char buf[64];
@@ -617,10 +630,10 @@ static bool test_case18() {
std::string err; std::string err;
CHECK(compile_case("18_tof_ctu", &img, &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.ok());
CHECK(v.data_len() == 72); // tf 4 槽 + c 5 槽 = 9 槽 × 8 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); CHECK(r.nregs == 14);
const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes()); const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes());
char buf[64]; char buf[64];
@@ -653,16 +666,18 @@ static bool test_line1() {
LinkResult link; LinkResult link;
CHECK(link_project(p, units, &link, &err)); CHECK(link_project(p, units, &link, &err));
CHECK(check_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.ok());
CHECK(v.header().n_globals == 4); CHECK(v.n_globals() == 4);
CHECK(v.header().n_funcs == 2); CHECK(v.n_funcs() == 2);
CHECK(v.header().entry_fn_id == 1); CHECK(v.entry_fn_id() == 1);
CHECK(v.data_len() == 56); // 7 槽 × 8 字节定宽 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.nregs == 13);
CHECK(fm.code_len == 17); CHECK(fm.code_len == 17);
const uint32_t* im = reinterpret_cast<const uint32_t*>(v.code_bytes()) + fm.code_offset / 4; const uint32_t* im = reinterpret_cast<const uint32_t*>(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_0io.output CHECK(std::strcmp(buf, "STORE_Q r8, 3") == 0); // Q0_0io.output
isa::disasm(im[16], buf, sizeof buf); isa::disasm(im[16], buf, sizeof buf);
CHECK(std::strcmp(buf, "RET") == 0); CHECK(std::strcmp(buf, "RET") == 0);
// 12.13 写侧自检:型号标识 + SHA-256compiler::StbView
const compiler::StbView self = compiler::StbView::from(img);
CHECK(self.ok());
CHECK(self.model_matches("STATOR", 1));
CHECK(self.sha_ok());
std::vector<uint8_t> tampered = img;
tampered[100] ^= 0x01; // 篡改代码段一字节
const compiler::StbView bad = compiler::StbView::from(tampered);
CHECK(bad.ok() && !bad.sha_ok()); // 结构可解析但 SHA 校验失败
return true; return true;
} }
-104
View File
@@ -12,7 +12,6 @@
#include <vector> #include <vector>
#include "isa/Encode.h" #include "isa/Encode.h"
#include "isa/Image.h"
#include "isa/Instr.h" #include "isa/Instr.h"
#include "isa/Op.h" #include "isa/Op.h"
#include "isa/Types.h" #include "isa/Types.h"
@@ -127,114 +126,11 @@ static bool test_encode() {
return true; 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<uint8_t> 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<uint8_t> bad = img;
bad[0] = 'X';
CHECK(!ImageView::from(bad).ok());
std::vector<uint8_t> bad2 = img;
bad2[60] = 255; // offset_code 越界
CHECK(!ImageView::from(bad2).ok());
std::vector<uint8_t> bad3 = img;
bad3[48] = 2; // n_funcs=2 但表只有 1 行
CHECK(!ImageView::from(bad3).ok());
std::vector<uint8_t> bad4 = img;
bad4[24] = 1; // entry_fn_id 越界
CHECK(!ImageView::from(bad4).ok());
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> back;
CHECK(read_stb_file(stb_path, &back, &err));
CHECK(back == img);
CHECK(ImageView::from(back).ok());
std::remove(stb_path);
std::vector<IoBinding> 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() { int main() {
if (!test_sat()) return 1; if (!test_sat()) return 1;
if (!test_op()) return 1; if (!test_op()) return 1;
if (!test_instr()) return 1; if (!test_instr()) return 1;
if (!test_encode()) 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); std::printf("isa_test: %d checks passed\n", g_checks);
return 0; return 0;
} }
+329
View File
@@ -0,0 +1,329 @@
/**
* @file machine_test.cpp
* @brief machine.toml A 3
* @author
* @date 2026-08-21
*/
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <string>
#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));
// BOOLbase=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);
// INTint16 → 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));
// TIMEint64 → 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;
}
+87 -14
View File
@@ -13,9 +13,9 @@
#include "compiler/Codegen.h" #include "compiler/Codegen.h"
#include "compiler/Linker.h" #include "compiler/Linker.h"
#include "compiler/Project.h" #include "compiler/Project.h"
#include "compiler/Stb.h"
#include "compiler/Typecheck.h" #include "compiler/Typecheck.h"
#include "isa/Encode.h" #include "isa/Encode.h"
#include "isa/Image.h"
#include "isa/Instr.h" #include "isa/Instr.h"
#include "isa/Op.h" #include "isa/Op.h"
#include "vm/Machine.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) { static bool make_machine(const char* dir, vm::Machine* m, std::string* err) {
using namespace compiler; 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"; const std::string toml = std::string(REPO_ROOT) + "/" + dir + "/project.toml";
Project p; Project p;
if (!parse_project(toml, &p, err)) { 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; return false;
} }
std::vector<uint8_t> img; std::vector<uint8_t> img;
if (!codegen_project(p, units, link, &img, err)) { if (!codegen_project(p, units, link, cfg, &img, err)) {
return false; return false;
} }
return vm::Machine::create(img, m, err); return vm::Machine::create(img, m, err);
@@ -89,7 +93,7 @@ struct HFunc {
uint32_t nregs = 8; uint32_t nregs = 8;
}; };
static std::vector<uint8_t> hand_image(const std::vector<isa::ConstEntry>& consts, static std::vector<uint8_t> hand_image(const std::vector<vm::ConstEntry>& consts,
const std::vector<HFunc>& funcs, const std::vector<HFunc>& funcs,
uint32_t entry = 0, uint32_t entry = 0,
uint32_t cycle_limit = 100000, uint32_t cycle_limit = 100000,
@@ -98,11 +102,11 @@ static std::vector<uint8_t> hand_image(const std::vector<isa::ConstEntry>& const
for (const HFunc& f : funcs) { for (const HFunc& f : funcs) {
code_total += f.code.size() * 4; 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<uint32_t>(consts.size()) * 12; const uint32_t off_funcs = off_const + static_cast<uint32_t>(consts.size()) * 12;
const uint32_t off_code = off_funcs + static_cast<uint32_t>(funcs.size()) * 12; const uint32_t off_code = off_funcs + static_cast<uint32_t>(funcs.size()) * 12;
const uint32_t off_data = off_code + static_cast<uint32_t>(code_total); const uint32_t off_data = off_code + static_cast<uint32_t>(code_total);
const uint32_t off_end = off_data + static_cast<uint32_t>(data.size()); const uint32_t off_end = off_data + static_cast<uint32_t>(data.size()) + 32; // SHA 尾占位
std::vector<uint8_t> b(off_end, 0); std::vector<uint8_t> b(off_end, 0);
auto put32 = [&](size_t o, uint32_t v) { auto put32 = [&](size_t o, uint32_t v) {
@@ -116,8 +120,8 @@ static std::vector<uint8_t> hand_image(const std::vector<isa::ConstEntry>& const
b[o + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFFu); b[o + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFFu);
} }
}; };
put32(0, isa::kMagic); put32(0, 0x43545353u);
put32(4, isa::kVersion); put32(4, 1u);
put32(8, cycle_limit); put32(8, cycle_limit);
put32(12, 10); // dt_ms put32(12, 10); // dt_ms
put32(24, entry); put32(24, entry);
@@ -152,6 +156,16 @@ static std::vector<uint8_t> hand_image(const std::vector<isa::ConstEntry>& const
for (size_t i = 0; i < data.size(); ++i) { for (size_t i = 0; i < data.size(); ++i) {
b[off_data + i] = data[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<uint8_t>(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; return b;
} }
@@ -159,10 +173,10 @@ static std::vector<uint8_t> hand_image(const std::vector<isa::ConstEntry>& const
static bool test_hand_scalar() { static bool test_hand_scalar() {
// r8 := 5r9 := r8 + 3;槽 0 ← r9;槽 1 → r10;JT 跳过一条 // r8 := 5r9 := r8 + 3;槽 0 ← r9;槽 1 → r10;JT 跳过一条
const std::vector<isa::ConstEntry> consts = { const std::vector<vm::ConstEntry> consts = {
{isa::types::Int, 5}, {1, 5},
{isa::types::Int, 3}, {1, 3},
{isa::types::Int, 1}, {1, 1},
}; };
const std::vector<HFunc> funcs = {{ const std::vector<HFunc> funcs = {{
{ {
@@ -197,9 +211,9 @@ static bool test_hand_scalar() {
static bool test_hand_call() { static bool test_hand_call() {
// fn0(入口):r8=5、r2=7、实参 r1←r8CALL 1;结果 r0 → r8 // fn0(入口):r8=5、r2=7、实参 r1←r8CALL 1;结果 r0 → r8
// fn1r0 = r1 + r2r1/r2 来自调用约定区复制) // fn1r0 = r1 + r2r1/r2 来自调用约定区复制)
const std::vector<isa::ConstEntry> consts = { const std::vector<vm::ConstEntry> consts = {
{isa::types::Int, 5}, {1, 5},
{isa::types::Int, 7}, {1, 7},
}; };
const std::vector<HFunc> funcs = { const std::vector<HFunc> funcs = {
{{ {{
@@ -425,6 +439,64 @@ static bool test_bad_const() {
return true; 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<compiler::SourceUnit> 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<uint8_t> 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<vm::ConstEntry> consts;
const std::vector<HFunc> funcs = {{
{
isa::enc_ret(),
},
8,
}};
std::vector<uint8_t> 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() { int main() {
if (!test_hand_scalar()) return 1; if (!test_hand_scalar()) return 1;
if (!test_hand_call()) return 1; if (!test_hand_call()) return 1;
@@ -435,6 +507,7 @@ int main() {
if (!test_determinism()) return 1; if (!test_determinism()) return 1;
if (!test_step()) return 1; if (!test_step()) return 1;
if (!test_bad_const()) return 1; if (!test_bad_const()) return 1;
if (!test_verify()) return 1;
std::printf("vm_test: %d checks passed\n", g_checks); std::printf("vm_test: %d checks passed\n", g_checks);
return 0; return 0;
} }
+2 -1
View File
@@ -7,7 +7,8 @@ project(VM
DESCRIPTION "") DESCRIPTION "")
add_library(vm STATIC add_library(vm STATIC
./src/Machine.cpp) ./src/Machine.cpp
./src/Image.cpp)
target_include_directories(vm PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) target_include_directories(vm PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(vm PUBLIC isa) target_link_libraries(vm PUBLIC isa)
+87
View File
@@ -0,0 +1,87 @@
/**
* @file Image.h
* @brief vm .stb compiler
* @author
* @date 2026-08-21
*
* @details Doc/isa/.md12.13 104 = 72 + [32]
* SHA-256[32] 7 8 SHA-256
*/
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
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<uint8_t>& 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_;
};
}
+3 -3
View File
@@ -19,7 +19,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "isa/Image.h" #include "vm/Image.h"
#include "isa/Instr.h" #include "isa/Instr.h"
namespace vm { namespace vm {
@@ -67,7 +67,7 @@ namespace vm {
bool ended() const; bool ended() const;
/** @brief 映像头(dt_ms / cycle_limit 等) */ /** @brief 映像头(dt_ms / cycle_limit 等) */
const isa::ImageHeader& header() const; const vm::ImageHeader& header() const;
/** @brief 当前指令下标(相对当前函数字节码段) */ /** @brief 当前指令下标(相对当前函数字节码段) */
uint32_t pc() const; uint32_t pc() const;
@@ -105,7 +105,7 @@ namespace vm {
uint32_t ret_fn_id = 0; // 返回目标函数 uint32_t ret_fn_id = 0; // 返回目标函数
}; };
isa::ImageView image_; // 映像只读视图 vm::Image image_; // 映像只读视图vm 自实现)
std::vector<uint8_t> data_; // 数据区工作副本(8 字节定宽槽) std::vector<uint8_t> data_; // 数据区工作副本(8 字节定宽槽)
std::vector<Frame> frames_; // 调用栈([0] = MAIN,跨周期保留) std::vector<Frame> frames_; // 调用栈([0] = MAIN,跨周期保留)
// 边沿检测上次输入(每槽 2 字节:[slot*2] 第一边沿、[slot*2+1] 第二边沿; // 边沿检测上次输入(每槽 2 字节:[slot*2] 第一边沿、[slot*2+1] 第二边沿;
+300
View File
@@ -0,0 +1,300 @@
/**
* @file Image.cpp
* @brief vm .stb
* @author
* @date 2026-08-21
*/
#include "vm/Image.h"
#include <cstring>
#include <string>
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<uint32_t>(p[0])
| (static_cast<uint32_t>(p[1]) << 8)
| (static_cast<uint32_t>(p[2]) << 16)
| (static_cast<uint32_t>(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<uint64_t>(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<uint8_t>((bitlen >> (56 - 8 * i)) & 0xFF)};
update(b2, 1);
}
for (int i = 0; i < 8; ++i) {
out[i * 4 + 0] = static_cast<uint8_t>((h[i] >> 24) & 0xFF);
out[i * 4 + 1] = static_cast<uint8_t>((h[i] >> 16) & 0xFF);
out[i * 4 + 2] = static_cast<uint8_t>((h[i] >> 8) & 0xFF);
out[i * 4 + 3] = static_cast<uint8_t>(h[i] & 0xFF);
}
}
static uint32_t get_be32(const uint8_t* p) {
return (static_cast<uint32_t>(p[0]) << 24) | (static_cast<uint32_t>(p[1]) << 16) |
(static_cast<uint32_t>(p[2]) << 8) | static_cast<uint32_t>(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<size_t>(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<uint64_t>(h.n_consts) * kConstEntrySize) {
v.err_ = "const table size mismatch";
return v;
}
if (offs[2] - offs[1] != static_cast<uint64_t>(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<uint8_t>& 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<const char*>(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
+21 -7
View File
@@ -25,13 +25,27 @@ namespace vm {
bool Machine::create(const std::vector<uint8_t>& image, Machine* out, bool Machine::create(const std::vector<uint8_t>& image, Machine* out,
std::string* err) { std::string* err) {
out->image_ = isa::ImageView::from(image); out->image_ = vm::Image::from(image);
if (!out->image_.ok()) { if (!out->image_.ok()) {
if (err) { if (err) {
*err = out->image_.error(); *err = out->image_.error();
} }
return false; 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 字节定宽槽) // 数据区工作副本(8 字节定宽槽)
out->data_.assign(out->image_.data_bytes(), out->data_.assign(out->image_.data_bytes(),
out->image_.data_bytes() + out->image_.data_len()); out->image_.data_bytes() + out->image_.data_len());
@@ -39,7 +53,7 @@ bool Machine::create(const std::vector<uint8_t>& image, Machine* out,
// MAIN 帧(跨周期保留) // MAIN 帧(跨周期保留)
const uint32_t entry = out->image_.header().entry_fn_id; 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(); out->frames_.clear();
Frame f; Frame f;
f.fn_id = entry; f.fn_id = entry;
@@ -79,7 +93,7 @@ bool Machine::step() {
Fault Machine::fault() const { return fault_; } Fault Machine::fault() const { return fault_; }
bool Machine::ended() const { return ended_; } 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::pc() const { return pc_; }
uint32_t Machine::fn_id() const { return cur_frame().fn_id; } uint32_t Machine::fn_id() const { return cur_frame().fn_id; }
uint32_t Machine::cycle_count() const { return cycle_count_; } uint32_t Machine::cycle_count() const { return cycle_count_; }
@@ -94,7 +108,7 @@ uint32_t Machine::nregs() const {
} }
isa::Instr Machine::cur_instr() 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; const uint8_t* base = image_.code_bytes() + row.code_offset;
if (pc_ >= row.code_len) { if (pc_ >= row.code_len) {
return isa::pack(isa::Op::RET, 0, 0, 0); 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() { 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) { if (pc_ >= row.code_len) {
fault_ = Fault::BadOp; fault_ = Fault::BadOp;
return false; return false;
@@ -165,7 +179,7 @@ bool Machine::exec_one() {
fault_ = Fault::BadConst; fault_ = Fault::BadConst;
return false; return false;
} }
const isa::ConstEntry c = image_.const_entry(cid); const vm::ConstEntry c = image_.const_entry(cid);
if (c.tag == isa::types::Bool) { if (c.tag == isa::types::Bool) {
regs[rd] = c.value ? 1 : 0; regs[rd] = c.value ? 1 : 0;
} else if (c.tag == isa::types::Int) { } else if (c.tag == isa::types::Int) {
@@ -310,7 +324,7 @@ Fault Machine::do_call(uint32_t fn_id) {
if (frames_.size() >= 64) { if (frames_.size() >= 64) {
return Fault::StackOverflow; return Fault::StackOverflow;
} }
const isa::FuncRow row = image_.func_row(fn_id); const vm::FuncRow row = image_.func_row(fn_id);
Frame f; Frame f;
f.fn_id = fn_id; f.fn_id = fn_id;
f.regs.assign(row.nregs, 0); f.regs.assign(row.nregs, 0);