Compare commits
50
Commits
93a4662d6a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9893299ca9 | ||
|
|
d51dce4ae3 | ||
|
|
351d6ef40c | ||
|
|
afb5d5b94a | ||
|
|
b0aebe264e | ||
|
|
fff7aef968 | ||
|
|
96607cfe75 | ||
|
|
6873b5ee0b | ||
|
|
1168f5ae30 | ||
|
|
0ebf193ff9 | ||
|
|
40bfd175c4 | ||
|
|
a5f49caa94 | ||
|
|
b13585711a | ||
|
|
5fccbadaa8 | ||
|
|
a308ae713d | ||
|
|
e187ed7b03 | ||
|
|
447a8d0119 | ||
|
|
44b5ecd46d | ||
|
|
4dab914e7c | ||
|
|
728ed9e71f | ||
|
|
35dabf4c3b | ||
|
|
5bcab6791f | ||
|
|
b86a9eaf07 | ||
|
|
26256a36b9 | ||
|
|
66d9c6c548 | ||
|
|
00565f035d | ||
|
|
8c0fc4fe98 | ||
|
|
ada9e5f99e | ||
|
|
a84a1a62ce | ||
|
|
efe4dd1a2c | ||
|
|
815eb87a46 | ||
|
|
ce948451e0 | ||
|
|
cd417cd27b | ||
|
|
3c42a72e86 | ||
|
|
ffb64d2063 | ||
|
|
b18fd6014c | ||
|
|
17c62f0a1f | ||
|
|
eb6829b9b9 | ||
|
|
6f5e8ef755 | ||
|
|
0a42d5876e | ||
|
|
0263813a8f | ||
|
|
0a36e36b16 | ||
|
|
31ba557627 | ||
|
|
6f1bf61c7f | ||
|
|
e7dee50cde | ||
|
|
ac411e6a4a | ||
|
|
66359a0a26 | ||
|
|
d296a9215e | ||
|
|
5c140c36ff | ||
|
|
9cd4997a3a |
+2
-1
@@ -13,7 +13,8 @@
|
||||
"binaryDir": "${sourceDir}/build/${presetName}",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": "gcc",
|
||||
"CMAKE_CXX_COMPILER": "g++"
|
||||
"CMAKE_CXX_COMPILER": "g++",
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
# STCompiler 使用说明
|
||||
|
||||
把 ST 工程(`project.toml` + 若干 `.st`)编译成单一映像 `.stb`。执行由 `BytecodeExecutor`(12.9/12.10 实现)负责。
|
||||
|
||||
## 构建
|
||||
|
||||
```bash
|
||||
cmake --preset gcc-debug # 配置
|
||||
cmake --build --preset gcc-debug # 编译
|
||||
ctest --test-dir build/gcc-debug # 测试(当前 9 个用例)
|
||||
```
|
||||
|
||||
可执行文件:`build/gcc-debug/compiler/STCompiler`
|
||||
|
||||
## 用法
|
||||
|
||||
```text
|
||||
STCompiler <project.toml> [-o <name>.stb] --machine <machine.toml>
|
||||
STCompiler <name>.stb --disasm --machine <machine.toml>
|
||||
```
|
||||
|
||||
| 参数 | 作用 |
|
||||
|---|---|
|
||||
| `<project.toml>` | 工程文件(必填) |
|
||||
| `-o <name>.stb` | 编译并写出映像文件(+ sidecar);**缺省时只打印文件集合与工程哈希**(不需 `--machine`) |
|
||||
| `--machine <machine.toml>` | 机器定义(指令 opcode / 类型 / FB 布局,见 `Doc/isa/指令配置.md`);**编译与 `--disasm` 必填**,加载/校验失败报错退出 |
|
||||
| `<name>.stb --disasm` | **反汇编一个已编译的映像**(不重新编译,见下) |
|
||||
| `--help` | 打印帮助 |
|
||||
|
||||
退出码:`0` 成功;`1` 失败(错误信息打到 stderr)。
|
||||
|
||||
## 反汇编(--disasm)
|
||||
|
||||
对已编译的 `.stb` 逐段查看:段摘要(含型号与 SHA 校验)、函数表逐条指令(文件绝对偏移)、常量表、数据段 hex。
|
||||
|
||||
```bash
|
||||
$ STCompiler line1.stb --disasm --machine machine.toml
|
||||
image: line1.stb (288 bytes, 2 functions, 4 globals, entry fn 1)
|
||||
dt_ms=10 cycle_limit=100000 hash=0xc3fe4ae74ad45900 model=STATOR1 sha=ok
|
||||
functions:
|
||||
fn 0: nregs=8, offset=0, len=1
|
||||
0x0080 RET
|
||||
fn 1: nregs=13, offset=4, len=17
|
||||
0x0084 LOAD_I r8, 1
|
||||
0x0088 STORE_GLOBAL r8, 4
|
||||
...
|
||||
data (56 bytes):
|
||||
0x0000: ...
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- 指令偏移是**文件绝对字节偏移**,可直接对照 `xxd line1.stb`
|
||||
- 映像里**没有符号名**(函数表只有 fn_id,数据段无名字映射),因此按 `fn 0` / `fn 1`、槽号显示
|
||||
- `model=` 显示型号标识(`machine.toml [meta]` 生成);`sha=ok|BAD` 显示文件尾 SHA-256 校验结果
|
||||
- 文件损坏或打不开 → `error: ...` 退出码 1
|
||||
|
||||
## 编译产物(.stb 加固)
|
||||
|
||||
- **型号标识[32]**:`[meta] name + version` 拼成(如 `"STATOR1"`),写进头偏移 72
|
||||
- **SHA-256 文件尾[32]**:对文件尾之前全部内容计算;compiler 写侧与 vm 读侧各实现一份
|
||||
- 执行器读取时型号不匹配 / SHA 不符 → 直接拒绝(详见 `Doc/isa/指令与映像.md` 12.13 修订)
|
||||
|
||||
## 示例(line1)
|
||||
|
||||
```bash
|
||||
$ STCompiler examples/line1/project.toml
|
||||
project: line1
|
||||
files:
|
||||
globals.st
|
||||
motor.st
|
||||
main.st
|
||||
hash: 0xc3fe4ae74ad45900
|
||||
|
||||
$ STCompiler examples/line1/project.toml -o line1.stb --machine compiler/machine.toml
|
||||
compiled: line1.stb (288 bytes, 2 functions, 4 globals)
|
||||
```
|
||||
|
||||
产物:`line1.stb`(288 字节:头 104 含型号标识 + 各段 + SHA-256 文件尾)+ `line1.runtime.toml`(sidecar)。指令见 `Doc/isa/指令与映像.md`,结构拆解见 `Doc/isa/stb文件格式.md`。
|
||||
|
||||
## 工程文件(project.toml)
|
||||
|
||||
只做三件事:**列出源文件、指定唯一 GVL、可选地把已声明全局接到硬件**。不声明变量。
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
|---|---|---|
|
||||
| `project.name` | 是 | 工程名 |
|
||||
| `project.entry` | 是 | 第一版必须 `"program MAIN"` |
|
||||
| `project.cycle_limit` | 是 | 每周期指令上限(>0) |
|
||||
| `project.dt_ms` | 是 | 本周期 Δt 毫秒(>0),给内置 FB 定时器 |
|
||||
| `files.st` | 是 | 源文件列表(非空数组) |
|
||||
| `gvl.file` | 否 | 唯一允许 `VAR_GLOBAL` 的文件;已在 `files.st` 则不重复收录 |
|
||||
| `[[io.input]]` / `[[io.output]]` | 否 | `var` + `channel` + `bit`;`var` 必须已在 GVL 声明 |
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "line1"
|
||||
entry = "program MAIN"
|
||||
cycle_limit = 100000
|
||||
dt_ms = 10
|
||||
|
||||
[files]
|
||||
st = ["globals.st", "motor.st", "main.st"]
|
||||
|
||||
[gvl]
|
||||
file = "globals.st"
|
||||
|
||||
[[io.input]]
|
||||
var = "EmergencyStop"
|
||||
channel = 0
|
||||
bit = 2
|
||||
```
|
||||
|
||||
## ST 子集(第一版)
|
||||
|
||||
**POU**:`PROGRAM` / `FUNCTION` / `FUNCTION_BLOCK`(含对应 `END_*`)。
|
||||
|
||||
**变量段**:`VAR` / `VAR_INPUT` / `VAR_OUTPUT` / `VAR_GLOBAL`(仅 `gvl.file` 顶层)/ `VAR_EXTERNAL`。
|
||||
|
||||
**类型**:`BOOL` / `INT` / `TIME`;内建 FB 类型 `TON` / `TOF` / `TP` / `CTU` / `CTD` / `CTUD` / `R_TRIG` / `F_TRIG`(关键字,不可作变量名)。
|
||||
|
||||
**语句**:赋值 `:=`、`IF / ELSIF / ELSE / END_IF`、`WHILE / END_WHILE`、FB 调用 `fb(in := ..., ...);`、字段读 `fb.Q`。
|
||||
|
||||
**表达式**:字面量(`TRUE`/`FALSE`、整数、`T#10ms` 式 TIME)、`NOT`、`AND`/`OR`(**短路**)、比较 `= <> < <= > >=`(不连锁)、算术 `+ - * /`、一元负号、函数调用 `Add(3, 4)`。
|
||||
|
||||
**函数**:结果 = 函数名赋值(`Add := a + b;`);返回类型 `FUNCTION Add : INT`;调用约定:结果 r0、参数 r1..r7(最多 7 个输入,函数内只读)、变量与临时从 r8 起(见 `Doc/compiler/寄存器码.md`)。
|
||||
|
||||
**限制(v1 明确不做)**:指针 / `REF` / `CLASS` / `ANY` / `VAR_IN_OUT`(语法层直接拒绝)、链式比较、FB 字段赋值、`REAL`、字符串、用户类型套娃、隐式类型转换、函数循环调用(链接层拒绝)、`FUNCTION` 写全局(类型检查层拒绝,含经 `VAR_EXTERNAL`)。
|
||||
|
||||
## 编译管线与错误类别
|
||||
|
||||
```
|
||||
project.toml + .st
|
||||
→ 词法(lex error)→ 语法(syntax error)→ 链接(link error)
|
||||
→ 类型检查(type error)→ 寄存器码(codegen error)→ .stb
|
||||
```
|
||||
|
||||
错误消息前缀即阶段:`lex error` / `syntax error` / `link error` / `type error` / `codegen error`,均带文件与行列(`type error` 仅带文件)。
|
||||
|
||||
| 类别 | 示例 |
|
||||
|---|---|
|
||||
| `lex error` | 非法字符、未闭合注释、坏 `T#` 字面量 |
|
||||
| `syntax error` | 缺 `END_*`、`VAR_IN_OUT` 等保留名、链式比较 |
|
||||
| `link error` | 非 GVL 文件写 `VAR_GLOBAL`、同名全局、io.var 未在 GVL、未声明实例、循环调用 |
|
||||
| `type error` | `FUNCTION` 写全局、AND 吃 INT、赋值类型不匹配、FB 输入类型不匹配 |
|
||||
| `codegen error` | 寄存器溢出、写 io.input、写函数输入 |
|
||||
|
||||
## 样例与测试
|
||||
|
||||
- `examples/line1/`:最小闭环(MAIN + MotorStarter FB + I/O + 全局),给人看
|
||||
- `tests/cases/01~20/`:20 个用例(每份 `project.toml` + `.st` + `EXPECTED.md`),编译结果按表
|
||||
- `tests/`:CTest(`compiler_toml` / `lexer_tokens` / `parser_syntax` / `linker_links` / `typecheck_types` / `codegen_slice1` / `machine_config` / `vm_cycles` / `cases_all` / `cli_*`)
|
||||
|
||||
## 当前状态
|
||||
|
||||
- ✅ 可编译:14 个正例用例全部产出 `.stb`(含型号标识 + SHA-256);6 个负例按期望拒绝
|
||||
- ✅ 执行:`BytecodeExecutor` 加载 `.stb` 跑扫描周期(型号/SHA 校验、回放、单步)
|
||||
- ✅ 配置驱动:指令 opcode / 类型元数据 / FB 布局来自 `compiler/machine.toml`(`--machine`)
|
||||
@@ -0,0 +1,135 @@
|
||||
# 寄存器码(12.8)
|
||||
|
||||
compiler 模块的代码生成。输入:`Project` + `Unit`(AST)+ `LinkResult`;输出:`.stb` 映像字节(`std::vector<uint8_t>`)。全工程定案见 [`初步计划.md`](../初步计划.md) 12.8,指令/映像布局见 [`指令与映像.md`](../isa/指令与映像.md)。
|
||||
|
||||
## 做 / 不做
|
||||
|
||||
**做**
|
||||
|
||||
- 寄存器分配:局部/变量固定寄存器,表达式临时值往后编号,`nregs` 写进函数头
|
||||
- 跳转先留洞、后回填;`AND` / `OR` **必须编成跳转**(短路),禁止两边都算
|
||||
- 常量表(`LOADK`)、全局/实例槽(`LOAD_GLOBAL` / `STORE_GLOBAL` 等)
|
||||
- `CALL fn_id` 有界帧;用户 FB 调用**内联展开**;内建 `CAL_TON` / `CAL_TOF` / `CAL_CTU`
|
||||
- 拼装映像:函数表 / 常量表 / 数据段 / 头(`cycle_limit`、`dt_ms`、工程哈希、入口)
|
||||
|
||||
**不做(第一版)**
|
||||
|
||||
- 图染色、SSA、生命周期分析、优化、异常表、闭包
|
||||
- 函数指针、间接跳转、`REAL`
|
||||
|
||||
## 存储布局(v1 定案)
|
||||
|
||||
| 数据 | 放哪 | 说明 |
|
||||
|---|---|---|
|
||||
| PROGRAM / FUNCTION 标量变量与临时 | **寄存器**(函数帧) | 每周期帧保留由 VM 负责(12.9) |
|
||||
| FB 实例(任意 POU 内声明) | **数据区实例块**(绝对地址) | 状态跨周期持久 |
|
||||
| 全局(含 I/Q/M) | 数据区(声明序) | 12.6 已定槽号 |
|
||||
|
||||
- **数据区** = 全局块(声明序,**每槽 8 字节定宽**,见 [`指令与映像.md`](../isa/指令与映像.md))→ FB 实例块(按实例声明序,每字段一槽)
|
||||
- **指令 slot = 槽号**(u16,0..65535);数据偏移 = 槽号 × 8;符号表 `address` 即指令 slot,无映射
|
||||
- 字段访问 `fb.Q`:`LOAD_GLOBAL rd, <实例基槽+字段序号>`(编译期算死);I/Q/M 用 `LOAD_I` / `STORE_Q` / `LOAD_M` / `STORE_M` 同槽号
|
||||
- 函数帧寄存器:`FUNCTION` 结果固定 **r0**,`VAR_INPUT` 从 r1 起按声明序,`VAR` 续后;PROGRAM / FB 变量从 r0 起按声明序
|
||||
|
||||
### 调用约定(v1 冻结,VM 12.9 按此实现)
|
||||
|
||||
| 项 | 约定 |
|
||||
|---|---|
|
||||
| 结果寄存器 | **r0**(FUNCTION 结果写入处) |
|
||||
| 参数寄存器 | **r1..r7**(最多 7 个输入,按声明序) |
|
||||
| 变量 / 临时 | **全部从 r8 起**(调用约定区不受覆盖) |
|
||||
| `CALL` | VM 复制当前帧 **r0..r7 → 新帧 r0..r7** |
|
||||
| `RET` | VM 复制当前帧 **r0..r7 → 调用方帧 r0..r7**(r0=结果,r1..r7 原样返回) |
|
||||
| 函数输入 | 函数体内**只读**(写输入 → codegen error) |
|
||||
|
||||
- 调用点:实参求值到 r8+ 临时 → `MOVE r(1+i), t` → `CALL fn_id` → `MOVE rd, r0`
|
||||
- 所有 POU 的变量/临时统一 r8 起,故 r1..r7 在调用点可安全覆盖
|
||||
|
||||
## 寄存器分配
|
||||
|
||||
- 局部变量固定占用前段(0..k-1)
|
||||
- 表达式临时从 k 起,**语句内递增、语句结束复用基址**(不分析生命周期)
|
||||
- `nregs` = 全函数临时峰值 + 1
|
||||
|
||||
## 表达式与语句的指令模式
|
||||
|
||||
```text
|
||||
字面量 LOADK rd, const_id
|
||||
变量读 LOAD_GLOBAL/LOAD_I/LOAD_M rd, slot (或帧内 MOVE)
|
||||
字段读 LOAD_GLOBAL rd, <基址+偏移>
|
||||
函数调用 <实参求值 → 参数寄存器> CALL fn_id MOVE rd, r0
|
||||
|
||||
NOT <operand → t> NOT rd, t
|
||||
AND/OR 短路(必须跳转):
|
||||
<lhs → rd> JF(AND)/JT(OR) rd, L_end
|
||||
<rhs → t> MOVE rd, t
|
||||
L_end:
|
||||
比较 <l→t1> <r→t2> CMP_xx rd, t1, t2
|
||||
算术 <l→t1> <r→t2> ADD/SUB/MUL/DIV rd, t1, t2
|
||||
负号 <operand → t> SUB rd, r0, t (0 - t,r0 恒 0)
|
||||
|
||||
IF <cond → t> JF t, L_else
|
||||
<then body> JMP L_end
|
||||
L_else: (ELSIF 逐级) <elsif cond → t> JF t, L_next ...
|
||||
L_end:
|
||||
WHILE L_loop: <cond → t> JF t, L_end <body> JMP L_loop L_end:
|
||||
|
||||
FB 调用(用户,内联展开):
|
||||
<每个实参求值 → 临时> STORE_GLOBAL <实例基址+字段偏移>, 临时
|
||||
<内联体:读输入字段、算输出、写输出字段>
|
||||
内建 实参同左 → CAL_TON/CAL_TOF/CAL_CTU <实例偏移>
|
||||
```
|
||||
|
||||
- 常量统一进常量表(BOOL / INT / TIME),`const_id` = 首次出现序
|
||||
- 空条件寄存器约定:`r0` 恒 0(`enc_jmp` 用),`SUB rd, r0, t` 实现取负
|
||||
|
||||
## 用户 FB 调用内联(v1 定案)
|
||||
|
||||
FB 体共享会与「绝对字段地址」冲突(不同实例基址不同),v1 选择**调用点内联展开**:
|
||||
|
||||
- 调用点:实参写入该实例字段 → 内联该 FB 的函数体(字段地址按本实例基址算死)→ 字段读取自然可用
|
||||
- 代价:字节码随调用点数膨胀;v1 规模可接受;以后可升级为「基址寄存器寻址」指令
|
||||
- FB 无递归、无动态实例,内联安全
|
||||
|
||||
## 映像拼装
|
||||
|
||||
```text
|
||||
函数表:收集序 fn_id → { nregs, code_offset(相对字节码段), code_len }
|
||||
常量表:const_id → { tag, value }
|
||||
数据段:全局 + 实例块(见上)
|
||||
头:cycle_limit / dt_ms / 工程哈希(12.3)/ entry=MAIN fn_id / n_globals / n_i=n_q=n_m=0
|
||||
```
|
||||
|
||||
- 用 `isa` 的 `enc_*` 编码指令、`ImageView` 校验回读
|
||||
- 工程哈希用 12.3 的 `compute_project_hash`
|
||||
|
||||
## 错误
|
||||
|
||||
稳定前缀 `codegen error`:
|
||||
|
||||
```text
|
||||
codegen error: register overflow (>256) in function 'f'
|
||||
codegen error: constant table overflow
|
||||
codegen error: slot out of range
|
||||
```
|
||||
|
||||
## 完成标准
|
||||
|
||||
1. `line1` 能写出映像(函数字节码 + 全局/FB 槽 + 工程哈希),`ImageView` 校验通过
|
||||
2. 跳转回填无悬空(disasm 抽查)
|
||||
3. 用例 1~9、13、15、17、18、20 编译出映像;负例(10/11/12/14/16/19)仍在前层被拒
|
||||
4. 全部构建 + `ctest` 无回归
|
||||
|
||||
---
|
||||
|
||||
## 执行计划(8 片,每片验证编译产物)
|
||||
|
||||
1. **`Codegen.h`(新建)+ `Codegen.cpp` 骨架**:帧/槽/常量表/指令缓冲/回填基础设施;`MOVE` `LOADK` `RET` → 用例 1、2 出映像
|
||||
2. **全局与 I/Q/M**:`LOAD_I` / `STORE_Q` / `LOAD_M` / `STORE_M` / `LOAD_GLOBAL` / `STORE_GLOBAL`(用例 9)
|
||||
3. **`NOT` + 短路 `AND` / `OR`**(用例 3,断言产物含跳转)
|
||||
4. **`CMP_*` + `JMP` / `JT` / `JF` → `IF`**(用例 4)
|
||||
5. **`WHILE`**(用例 5;`cycle_limit` 进映像头)
|
||||
6. **`ADD/SUB/MUL/DIV` + TIME 常量**(用例 7、8)
|
||||
7. **`CALL fn_id` + 有界帧**(用例 13)
|
||||
8. **FB 实例块 + 字段偏移;内联用户 FB;`CAL_TON` / `CAL_TOF` / `CAL_CTU`**(用例 15、17、18、20 line1 全链路)
|
||||
|
||||
每片配 `codegen_test` 断言(nregs / 指令序列 disasm / 回填 / 常量表 / 数据区);最终 `ctest` 9/9。
|
||||
@@ -12,7 +12,7 @@ compiler 模块的符号收集与链接。输入:`Project`(toml)+ 各 `.st
|
||||
- `FUNCTION` 标无状态;`FUNCTION_BLOCK` 必须先有实例才能调用
|
||||
- `CALL` 目标解析成 `fn_id`(立即数),无函数指针
|
||||
- 校验:同名全局、未声明 `io.var`、重复 POU 名、未声明 FB 实例、函数循环调用 → 失败
|
||||
- FB 实例布局:按类型算字段清单(输入/输出/内部/内建 TON/TOF/CTU 固定字段)
|
||||
- FB 实例布局:按类型算字段清单(输入/输出/内部/内建 8 个 FB 固定字段(12.11 扩充))
|
||||
|
||||
**不做(第一版)**
|
||||
|
||||
@@ -70,15 +70,20 @@ struct Symbol {
|
||||
## FB 实例布局
|
||||
|
||||
- 用户 FB:字段 = 全部 `VAR_INPUT` + `VAR_OUTPUT` + `VAR`(内部),按声明段序排
|
||||
- 内建(冻结,isa 规格):
|
||||
- 内建(冻结,isa 规格;12.11 扩为 8 个):
|
||||
|
||||
| FB | 字段(名 → 类型) |
|
||||
|---|---|
|
||||
| TON | in BOOL, pt TIME, q BOOL, et TIME |
|
||||
| TOF | in BOOL, pt TIME, q BOOL, et TIME |
|
||||
| TP | in BOOL, pt TIME, q BOOL, et TIME |
|
||||
| CTU | cu BOOL, r BOOL, pv INT, q BOOL, cv INT |
|
||||
| CTD | cd BOOL, ld BOOL, pv INT, q BOOL, cv INT |
|
||||
| CTUD | cu BOOL, cd BOOL, r BOOL, lu BOOL, pv INT, qu BOOL, qd BOOL, cv INT |
|
||||
| R_TRIG | clk BOOL, q BOOL |
|
||||
| F_TRIG | clk BOOL, q BOOL |
|
||||
|
||||
- 实例布局 = 类型字段表的一份拷贝;字段偏移 12.8 按对齐规则算
|
||||
- 实例布局 = 类型字段表的一份拷贝;字段偏移 = 字段序号 × 8(8 字节定宽槽)
|
||||
|
||||
## 错误
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# 类型检查(12.7)
|
||||
|
||||
compiler 模块的类型规则检查。输入:`Project` + 各 `.st` 的 `Unit`(AST)+ `LinkResult`(符号已解析);输出:通过或 `type error`。全工程定案见 [`初步计划.md`](../初步计划.md) 12.7。
|
||||
|
||||
## 做 / 不做
|
||||
|
||||
**做**
|
||||
|
||||
- `:=`、比较、算术的类型规则;`AND` / `OR` / `NOT` 只吃 `BOOL`
|
||||
- `FUNCTION` 禁止写全局(含经 `VAR_EXTERNAL`)——用例 14
|
||||
- FB 调用:命名输入类型匹配;`fb.Q` 字段存在且类型对
|
||||
- 表达式求值类型:`BOOL` / `INT` / `TIME` 三型
|
||||
- 无隐式宽化;`INT` 与 `TIME` 不混用
|
||||
|
||||
**不做(第一版)**
|
||||
|
||||
- 常量折叠、溢出区间检查(INT 字面量范围,饱和在 VM 层)
|
||||
- `FUNCTION` 实参与形参的个数/类型核对(12.7 未要求,只核对 FB 命名输入)
|
||||
- 变量初始化表达式的类型核对(语法层已限字面量)
|
||||
|
||||
## 表达式求值类型
|
||||
|
||||
```cpp
|
||||
enum class TType { Bool, Int, Time };
|
||||
```
|
||||
|
||||
| 节点 | 规则 | 结果 |
|
||||
|---|---|---|
|
||||
| `LitBool` / `LitInt` / `LitTime` | — | Bool / Int / Time |
|
||||
| `VarRef` | 查符号(局部/输入/输出/外部/全局)类型 | 符号类型 |
|
||||
| `Field` | 实例字段类型(布局已由 12.6 提供) | 字段类型 |
|
||||
| `Not` | 操作数必须 Bool | Bool |
|
||||
| `And` / `Or` | 两侧必须 Bool(短路已由语法层保留) | Bool |
|
||||
| `Cmp` | 两侧同型(Bool/Int/Time 均可比较) | Bool |
|
||||
| `Add/Sub/Mul/Div` | 两侧必须 Int(**TIME 无算术**,INT 与 TIME 不混用) | Int |
|
||||
| `Neg` | 操作数必须 Int | Int |
|
||||
| `Call` | 函数存在(12.6 已验);结果 = 函数返回类型 | 返回类型 |
|
||||
| `VarRef` 指向 FB 实例 | 实例不能当值 | type error |
|
||||
|
||||
## 语句规则
|
||||
|
||||
| 语句 | 规则 |
|
||||
|---|---|
|
||||
| `Assign`(普通) | 左值类型 == 表达式类型;左值可为局部/输入/输出/外部/全局 |
|
||||
| `Assign`(函数名,FUNCTION 内) | 表达式类型 == 函数返回类型(用例 13 约定) |
|
||||
| **`FUNCTION` 内左值是全局/外部** | **拒绝:`function cannot write global`(用例 14,含经 VAR_EXTERNAL)** |
|
||||
| `FbCall` | 每个命名实参表达式类型 == 实例对应字段类型(布局 12.6 已校验存在性) |
|
||||
| `If` / `While` | 条件表达式必须 Bool |
|
||||
|
||||
## 错误
|
||||
|
||||
稳定前缀 `type error`,带文件与行列(尽量带):
|
||||
|
||||
```text
|
||||
type error: AND operands must be BOOL (main.st, line 5, col 9)
|
||||
type error: type mismatch in assignment to 'q0_0' (INT vs BOOL) (main.st, line 6, col 13)
|
||||
type error: function cannot write global 'g1' (main.st, line 3, col 5)
|
||||
type error: TIME has no arithmetic (main.st, line 4, col 12)
|
||||
type error: FB input 'pt' expects TIME (main.st, line 5, col 16)
|
||||
```
|
||||
|
||||
## 完成标准
|
||||
|
||||
1. 用例 14(FUNCTION 写全局)→ `type error`(12.6 已放行,本阶段点亮)
|
||||
2. line1 / 用例 07 / 13 / 15 / 17 等合法工程类型检查通过
|
||||
3. 负例:AND 吃 INT、赋值类型不匹配、INT+TIME、NOT INT、FB 输入类型不匹配、比较异型 → `type error`
|
||||
4. 全部构建 + `ctest` 无回归
|
||||
|
||||
---
|
||||
|
||||
## 执行计划(单步确认)
|
||||
|
||||
1. **`Typecheck.h`(新建 `compiler/include/compiler/Typecheck.h`)**:`TType` + `check_project` 声明
|
||||
2. **`Typecheck.cpp` 前半**:符号类型解析(VarRef/Field/Call/字面量)+ 表达式递归定类型
|
||||
3. **`Typecheck.cpp` 后半**:语句规则(Assign 含函数名/FUNCTION 禁写全局、FbCall 实参、IF/WHILE 条件)
|
||||
4. **`typecheck_test`(`tests/src/typecheck_test.cpp` + CMake)**:用例 14 + 负例 + 正例
|
||||
5. **验证**:`cmake --build` + `ctest`(新增 `typecheck_types` 后 8/8 全绿)
|
||||
6. **提交**:`Typecheck.h/cpp` + `typecheck_test` + 文档
|
||||
@@ -1,27 +1,28 @@
|
||||
# 编译管线
|
||||
|
||||
compiler 模块:ST / toml 编译器。CMake 目标:`compiler`(`STATIC`),依赖 [`指令与映像.md`](../isa/指令与映像.md)。可执行入口 `STCompiler`(本模块 `src/main.cpp`,链 `compiler` + `isa`)。
|
||||
compiler 模块:ST / toml 编译器。CMake 目标:`compiler`(`STATIC`),**不依赖 isa**(指令 opcode / 类型元数据 / FB 布局来自 `compiler/machine.toml`,见 [`指令配置.md`](../isa/指令配置.md))。可执行入口 `STCompiler`(本模块 `src/main.cpp`,链 `compiler`)。
|
||||
|
||||
读齐工程后走直通 pass,产出单一映像文件,不上 LLVM。词法、语法、符号表、检查、codegen、链接都留在本库,不再拆 CMake 子库。
|
||||
|
||||
语言规则、toml 字段、链接与第 12 节阶段见 [`初步计划.md`](../初步计划.md)。指令和映像只认 [`指令与映像.md`](../isa/指令与映像.md)。
|
||||
语言规则、toml 字段、链接与第 12 节阶段见 [`初步计划.md`](../初步计划.md)。`.stb` 格式契约见 [`指令与映像.md`](../isa/指令与映像.md)(compiler 自带写实现 + 型号标识/SHA-256)。
|
||||
|
||||
## 管线
|
||||
|
||||
```text
|
||||
读 toml、收齐 .st
|
||||
加载 machine.toml(--machine,强校验)
|
||||
→ 读 toml、收齐 .st
|
||||
→ 词法 / 递归下降语法
|
||||
→ 收集导出符号
|
||||
→ 解析 VAR_EXTERNAL、链接、定地址
|
||||
→ 类型检查
|
||||
→ 按 PROGRAM / FUNCTION / FB 编寄存器码
|
||||
→ 按 PROGRAM / FUNCTION / FB 编寄存器码(opcode/类型/FB 布局查配置)
|
||||
→ 回填跳转、写映像头与工程哈希
|
||||
→ 写 <name>.stb(映像)与 <name>.runtime.toml(sidecar:io 绑定)
|
||||
→ 写 <name>.stb(头 104 含型号标识 + 段 + SHA-256 文件尾)与 <name>.runtime.toml(sidecar:io 绑定)
|
||||
```
|
||||
|
||||
## 边界
|
||||
|
||||
- toml **不声明变量**;`VAR_GLOBAL` 第一版只允许出现在 `gvl.file`
|
||||
- `[[io.*]]` 的 `var` 必须已在 GVL 中;同名全局链接失败
|
||||
- 第三方 toml 解析放 `third_party/`,本库私有链接
|
||||
- 不写扫描周期,不链 `vm`
|
||||
- 第三方 toml 解析(toml++)放 `third_party/`,本库私有链接
|
||||
- 不写扫描周期,不链 `vm`、不链 `isa`
|
||||
|
||||
+2
-1
@@ -26,11 +26,12 @@ BOOL INT TIME
|
||||
IF ELSIF ELSE END_IF WHILE END_WHILE
|
||||
AND OR NOT
|
||||
TRUE FALSE
|
||||
TON TOF CTU
|
||||
TON TOF TP CTU CTD CTUD R_TRIG F_TRIG
|
||||
```
|
||||
|
||||
> 12.5 修订:`THEN`、`DO` 补入关键字表(`IF ... THEN` / `WHILE ... DO` 语法需要),
|
||||
> 与 12.1 原表合并,其余不变。
|
||||
> 12.11 修订:内置 FB 扩为 8 个(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG)。
|
||||
|
||||
其余单词一律当标识符;`VAR_IN_OUT`、`REF`、`CLASS` 等由语法层(12.5)出明确错误。
|
||||
|
||||
|
||||
+13
-1
@@ -4,11 +4,23 @@ BytecodeExecutor 模块:可执行入口。CMake 目标:`BytecodeExecutor`(
|
||||
|
||||
第一版两个可执行:`STCompiler` 编译出 `<name>.stb` 与 `<name>.runtime.toml`;`BytecodeExecutor` 加载映像按扫描周期跑。总顺序见 [`初步计划.md`](../初步计划.md) 第 12.10 节。
|
||||
|
||||
## 用法
|
||||
|
||||
```text
|
||||
BytecodeExecutor <name>.stb [--cycles N] [--replay <file>] [--step]
|
||||
```
|
||||
|
||||
- `--cycles N`:跑 N 个周期(缺省 10)
|
||||
- `--replay <file>`:读录制的 I 序列(每行空格分隔的 0/1,按 sidecar `io.input` 绑定顺序),逐周期喂入;读尽后保持最后一行
|
||||
- `--step`:单步——采样 I 后逐指令打印(pc/fn/反汇编 + 执行后非零寄存器),跑 1 个周期,最后打印 I/Q
|
||||
- 每周期打印 `cycle N: I=[...] Q=[...]`(按 sidecar 绑定顺序);故障打印 `FAULT(n)` 并退出码 1
|
||||
|
||||
## 职责
|
||||
|
||||
- 加载 `.stb` 映像与 `<name>.runtime.toml` sidecar(I/O 绑定 var → 槽号 → channel/bit)
|
||||
- 读取时**校验型号标识与 SHA-256**:型号不匹配 / 文件被篡改 → 直接报错拒绝(执行器只接受与自身内建型号一致的映像)
|
||||
- 按 sidecar 采样 I、写回 Q(不创造变量)
|
||||
- `dt_ms` / `cycle_limit` 从映像头取,推进 `TON` / `TOF` / `CTU`
|
||||
- `dt_ms` / `cycle_limit` 从映像头取,推进内置 FB(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG)
|
||||
- 单步、看寄存器和映像、按录制的 `I` 回放
|
||||
|
||||
## 边界
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
# .stb 映像文件结构
|
||||
|
||||
编译产物 `.stb` 的**面向使用者**的结构说明。规范以 [`指令与映像.md`](指令与映像.md) 为准,本文用 `line1.stb`(288 字节,12.13 修订后)做真实拆解。
|
||||
|
||||
## 概览
|
||||
|
||||
- 魔数 `STSC`,版本 `1`,**全部整数小端**
|
||||
- 文件 = 头 + 五个段,段序:**常量表 → 函数表 → 字节码 → FB 布局 → 数据**
|
||||
- 段偏移记在头里;`offset_const ≤ offset_funcs ≤ offset_code ≤ offset_fb ≤ offset_data ≤ 文件长度`
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ 头(104 字节,偏移 0,含型号标识[32]) │
|
||||
├──────────────────────────────────────────────────┤
|
||||
│ 常量表段 offset_const .. offset_funcs │
|
||||
├──────────────────────────────────────────────────┤
|
||||
│ 函数表段 offset_funcs .. offset_code │
|
||||
├──────────────────────────────────────────────────┤
|
||||
│ 字节码段 offset_code .. offset_fb │
|
||||
├──────────────────────────────────────────────────┤
|
||||
│ FB 布局段 offset_fb .. offset_data(v1 为空)│
|
||||
├──────────────────────────────────────────────────┤
|
||||
│ 数据段 offset_data .. offset_data+len │
|
||||
├──────────────────────────────────────────────────┤
|
||||
│ SHA-256 文件尾(32 字节,对文件尾之前全部内容) │
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 头(104 字节)
|
||||
|
||||
| 偏移 | 宽 | 字段 | line1 实际值 |
|
||||
|---|---|---|---|
|
||||
| 0 | 4 | 魔数 `STSC` | `53 53 54 43` |
|
||||
| 4 | 4 | 版本 `1` | 1 |
|
||||
| 8 | 4 | `cycle_limit`(每周期指令上限) | 100000 |
|
||||
| 12 | 4 | `dt_ms`(本周期 Δt) | 10 |
|
||||
| 16 | 8 | 工程哈希(FNV-1a 64) | `0xc3fe4ae74ad45900` |
|
||||
| 24 | 4 | 入口 `entry_fn_id` | 1(MAIN) |
|
||||
| 28 | 4 | 全局槽数 `n_globals` | 4 |
|
||||
| 32 | 4 | I 槽数 `n_i` | 0(v1 不拆) |
|
||||
| 36 | 4 | Q 槽数 `n_q` | 0 |
|
||||
| 40 | 4 | M 槽数 `n_m` | 0 |
|
||||
| 44 | 4 | 常量数 `n_consts` | 0 |
|
||||
| 48 | 4 | 函数数 `n_funcs` | 2 |
|
||||
| 52 | 4 | `offset_const` | 104(`0x68`) |
|
||||
| 56 | 4 | `offset_funcs` | 104 |
|
||||
| 60 | 4 | `offset_code` | 128(`0x80`) |
|
||||
| 64 | 4 | `offset_fb` | 200(`0xC8`) |
|
||||
| 68 | 4 | `offset_data` | 200 |
|
||||
| 72 | 32 | **型号标识**(定长 ASCII 含版本,补 `'\0'`) | `"STATOR1"`(`0x48..0x67`) |
|
||||
|
||||
> 12.13 修订:型号标识由 `machine.toml [meta] name + version` 拼成(如 `STATOR + 1` → `"STATOR1"`);
|
||||
> 执行器读取时型号不匹配 → 直接报错。
|
||||
|
||||
## 常量表段(`offset_const` 起)
|
||||
|
||||
一行 12 字节:`[tag:u32][value:u64]`;`tag`:0=BOOL、1=INT、2=TIME。
|
||||
|
||||
line1 无字面量常量 → 段为空(`offset_const == offset_funcs`)。
|
||||
|
||||
## 函数表段(`offset_funcs` 起)
|
||||
|
||||
一行 12 字节:`[nregs:u32][code_offset:u32][code_len:u32]`
|
||||
|
||||
- **行下标即 `fn_id`**;`code_offset` 相对字节码段起点(**字节**);`code_len` 为**指令条数**
|
||||
- 入口 = 头的 `entry_fn_id` 指向的行
|
||||
|
||||
line1(2 行):
|
||||
|
||||
| fn_id | nregs | code_offset | code_len | 内容 |
|
||||
|---|---|---|---|---|
|
||||
| 0 | 8 | 0 | 1 | `MotorStarter` 占位(内联展开,仅 RET) |
|
||||
| 1 | 13 | 4 | 17 | `MAIN` |
|
||||
|
||||
## 字节码段(`offset_code` 起)
|
||||
|
||||
指令 4 字节一条:`[op:8 | rd:8 | a:8 | b:8]`(小端 u32)。
|
||||
|
||||
- `rd` 目的寄存器;`a|b` 视操作码为寄存器、槽号(u16)或跳转偏移(有符号 int16,**相对下一条指令**:目标 = 当前 + 1 + off)
|
||||
- `CALL` 的 `fn_id` = 函数表行下标
|
||||
- 调用约定:结果 r0、参数 r1..r7、变量与临时 r8 起
|
||||
|
||||
line1 `MAIN` 前 5 条(`0x84` 起;`0x80` 是 `fn 0` 的占位 `RET`,slot 为 8 字节定宽槽号):
|
||||
|
||||
| 偏移 | 字节 | 反汇编 | 含义 |
|
||||
|---|---|---|---|
|
||||
| 0x84 | `12 08 01 00` | `LOAD_I r8, 1` | I0_0(io.input 绑定 → LOAD_I,槽 1) |
|
||||
| 0x88 | `17 08 04 00` | `STORE_GLOBAL r8, 4` | starter.start(槽 4)← I0_0 |
|
||||
| 0x8C | `16 09 02 00` | `LOAD_GLOBAL r9, 2` | I0_1(槽 2) |
|
||||
| 0x90 | `17 09 05 00` | `STORE_GLOBAL r9, 5` | starter.stop(槽 5)← I0_1 |
|
||||
| 0x94 | `16 08 04 00` | `LOAD_GLOBAL r8, 4` | 内联体:读 starter.start(槽 4) |
|
||||
|
||||
## FB 布局段(`offset_fb` 起)
|
||||
|
||||
v1 为空(用户 FB 内联展开,字段偏移已算进数据段;内建 8 个 FB 布局冻结在编译器中(12.11 扩充))。行格式约定:`[field_count:u32][field_count × (tag:u32, offset:u32)]`,offset 相对实例基址。
|
||||
|
||||
## 数据段(`offset_data` 起)
|
||||
|
||||
**每槽 8 字节定宽**(12.9 方案 a):`BOOL` 用低 1 字节、`INT` 用低 2 字节(小端)、`TIME` 全 8 字节。指令 `slot` 操作数即**槽号**,数据偏移 = 槽号 × 8。
|
||||
|
||||
槽序:**全局块**(`n_globals` 个,声明序)→ **FB 实例块**(POU 收集序、实例声明序,每字段一槽)。
|
||||
|
||||
line1 数据段(56 字节 = 7 槽 × 8B,`0xC8` 起):
|
||||
|
||||
| 槽号 | 偏移 | 内容 |
|
||||
|---|---|---|
|
||||
| 0 | 0xC8 | EmergencyStop(BOOL,初值 0) |
|
||||
| 1 | 0xD0 | I0_0(BOOL) |
|
||||
| 2 | 0xD8 | I0_1(BOOL) |
|
||||
| 3 | 0xE0 | Q0_0(BOOL) |
|
||||
| 4 | 0xE8 | starter.start(BOOL) |
|
||||
| 5 | 0xF0 | starter.stop(BOOL) |
|
||||
| 6 | 0xF8 | starter.q(BOOL) |
|
||||
|
||||
## SHA-256 文件尾(32 字节)
|
||||
|
||||
数据段之后追加 32 字节 SHA-256:**对文件尾之前全部内容计算**(`0x100..0x11F`)。执行器必须校验通过才运行;任一字节被篡改 → 直接拒绝。compiler 写侧与 vm 读侧**各自实现**一份 SHA-256。
|
||||
|
||||
## 用工具验证
|
||||
|
||||
- `ctest`:`codegen_slice1`(写侧 model/sha 自检)、`vm_cycles`(读侧型号/SHA 拒绝)、`machine_config`
|
||||
- `STCompiler x.stb --disasm --machine machine.toml`:显示 `model=` 与 `sha=ok|BAD`
|
||||
- 手工检查:`xxd line1.stb` 对照上表逐段核对
|
||||
|
||||
## 与执行器的关系
|
||||
|
||||
`BytecodeExecutor` 只读本格式:头(`cycle_limit`/`dt_ms`/型号标识)、函数表(帧 `nregs` + 代码定位)、数据段(全局 + FB 实例状态)。读取时**型号匹配 + SHA-256 校验**,任一不符 → 报错拒绝。I/O 绑定**不在**映像里,在 sidecar `<name>.runtime.toml`(var → 槽号 → channel/bit)。
|
||||
+33
-12
@@ -2,16 +2,16 @@
|
||||
|
||||
isa 模块规范:指令集 + 映像 + 定宽类型。不是编译器,不把 `.st` 编成映像。
|
||||
|
||||
`compiler` 按本文件写出字节,`vm` 只认这些字节。两边不互链。
|
||||
`compiler` 写 `.stb`、`vm` 读 `.stb`,两边**各自实现**读写;`isa` 归执行器侧只管指令定义。指令 opcode / 类型 / FB 布局的登记见 [`指令配置.md`](指令配置.md)(`compiler/machine.toml`,与代码强校验)。
|
||||
|
||||
全工程定案见 [`初步计划.md`](../初步计划.md)。**类型、指令编码、映像布局以本文为准**,不要另写一份互相打架的表。头文件实现本文,不另当规范。
|
||||
|
||||
```text
|
||||
STCompiler → compiler → isa
|
||||
BytecodeExecutor → vm → isa
|
||||
STCompiler → compiler (编码查 machine.toml;自带 .stb 写)
|
||||
BytecodeExecutor → vm → isa (isa 只管指令;vm 自带 .stb 读)
|
||||
```
|
||||
|
||||
CMake 目标:`isa`(`STATIC`),无依赖。公开头:`isa/include/isa/`。
|
||||
CMake 目标:`isa`(`STATIC`),无依赖;`vm` PUBLIC 链 `isa`。公开头:`isa/include/isa/`。
|
||||
|
||||
---
|
||||
|
||||
@@ -47,6 +47,14 @@ CMake 目标:`isa`(`STATIC`),无依赖。公开头:`isa/include/isa/`
|
||||
| 寄存器 | 下标 0..255;每函数实际个数写在函数头 `nregs` |
|
||||
| 跳转 | 相对 offset,单位是**指令条数**,不是字节 |
|
||||
| `CALL` | 立即数 `fn_id`(u16),无函数指针 |
|
||||
|
||||
### 调用约定(冻结,compiler 12.8 与 VM 12.9 共同遵守)
|
||||
|
||||
- 结果寄存器 **r0**;参数寄存器 **r1..r7**(最多 7 个输入,按声明序)
|
||||
- 变量与表达式临时**全部从 r8 起**(调用约定区 r0..r7 不受用户数据占用)
|
||||
- `CALL`:VM 复制当前帧 r0..r7 到新帧 r0..r7
|
||||
- `RET`:VM 复制当前帧 r0..r7 回调用方帧 r0..r7(r0 = 结果;r1..r7 原样返回)
|
||||
- 函数输入在函数体内只读(compiler 拒绝写入)
|
||||
| 映像魔数 | `STSC`,版本 `1` |
|
||||
| 工程哈希 | 源文件路径排序后对内容做 FNV-1a 64(非密码学) |
|
||||
| I/O 绑定 | **不进映像**;编译期把 `var` 收成槽号,channel/bit 由 STCompiler 写进 sidecar(`.runtime.toml`),BytecodeExecutor 读 sidecar 采样 |
|
||||
@@ -68,7 +76,7 @@ C++20 定宽:
|
||||
|
||||
- 有符号:`TOF` 剩余时间、两个时间点相减会出负值,免去无符号边界处理
|
||||
- 64 位:为将来 `DATE_AND_TIME`(毫秒时间戳)与 `TON` / `TOF` / `TP` 的 64 位定时器预留;毫秒量程约 2.9 亿年,扫描周期场景永不溢出
|
||||
- 代价:槽位 8 字节对齐。**槽位对齐规则**:`BOOL` 1 字节、`INT` 2 字节、`TIME` 8 字节;连续排列按类型对齐,需要时插 padding,偏移以本规则为准。
|
||||
- 代价:槽位 8 字节对齐。**槽位规则**(12.9 方案 a):每槽 8 字节定宽,`BOOL` 用低 1 字节、`INT` 用低 2 字节(小端)、`TIME` 全 8 字节;槽号 × 8 = 数据偏移,无 padding。
|
||||
|
||||
---
|
||||
|
||||
@@ -82,7 +90,7 @@ C++20 定宽:
|
||||
|
||||
- 三寄存器:`a`/`b` 为 `ra`/`rb`(`MOVE`/`NOT` 只用 `rd`+`a`)
|
||||
- `LOADK`:`a|b` 为 `const_id`(u16)
|
||||
- `JMP` / `JT` / `JF`:`a|b` 为有符号相对 offset(指令条数)
|
||||
- `JMP` / `JT` / `JF`:`a|b` 为有符号相对 offset(指令条数),**相对下一条指令**:目标下标 = 当前下标 + 1 + off
|
||||
- `CALL`:`a|b` 为 `fn_id`(u16)
|
||||
|
||||
操作码一次列全(`CMP_xx` 冻下面六种):
|
||||
@@ -98,11 +106,15 @@ JMP offset
|
||||
JT/JF r, offset
|
||||
LOAD_I / STORE_Q / LOAD_M / STORE_M
|
||||
LOAD_GLOBAL / STORE_GLOBAL
|
||||
CAL_TON / CAL_TOF / CAL_CTU
|
||||
CAL_TON / CAL_TOF / CAL_TP / CAL_CTU / CAL_CTD / CAL_CTUD / CAL_R_TRIG / CAL_F_TRIG
|
||||
CALL fn_id
|
||||
RET
|
||||
```
|
||||
|
||||
> 12.11 修订:内置功能块扩为 8 个(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG),
|
||||
> `CAL_*` 操作码**连续占 24..31**(原 CALL=27、RET=28 后移为 32、33)。
|
||||
> 操作码数值即编码,本表为准。
|
||||
|
||||
局部 / `VAR` = 固定寄存器;表达式临时值往后编号;`nregs` 写进函数头。
|
||||
`I`/`Q`/`M` 走映像指令,不是通用寄存器。
|
||||
FB 实例固定布局,字段偏移编译期算死。
|
||||
@@ -130,9 +142,13 @@ FB 实例固定布局,字段偏移编译期算死。
|
||||
|
||||
## 映像
|
||||
|
||||
魔数 `STSC`,版本 `1`。全部整数小端。文件 = 头 + 五个段,段序:常量表 → 函数表 → 字节码 → FB 布局 → 数据。偏移记在头里,段必须连续、偏移单调不减,末段终点不超过文件长度。
|
||||
魔数 `STSC`,版本 `1`。全部整数小端。文件 = 头 + 五个段 + **文件尾校验**,段序:常量表 → 函数表 → 字节码 → FB 布局 → 数据。偏移记在头里,段必须连续、偏移单调不减,末段终点不超过文件长度(文件尾在数据段之后)。
|
||||
|
||||
### 头(72 字节)
|
||||
> 12.13 修订:头新增 **型号标识[32]**(原头 72 字节 → 104 字节,段偏移基准同步后移);
|
||||
> 文件尾新增 **SHA-256[32]**(对文件尾之前全部内容计算,compiler 写侧 / vm 读侧各自实现)。
|
||||
> 执行器读取时:型号标识与自身支持型号不匹配 → 报错;SHA-256 不匹配 → 报错(损坏/篡改)。
|
||||
|
||||
### 头(104 字节)
|
||||
|
||||
| 偏移 | 宽 | 字段 |
|
||||
|---|---|---|
|
||||
@@ -153,6 +169,11 @@ FB 实例固定布局,字段偏移编译期算死。
|
||||
| 60 | 4 | `offset_code` |
|
||||
| 64 | 4 | `offset_fb` |
|
||||
| 68 | 4 | `offset_data` |
|
||||
| 72 | 32 | **型号标识**(定长 ASCII,含版本,如 `"STATOR" + "1"` 拼 32 字节补 `'\0'`) |
|
||||
|
||||
### 文件尾(SHA-256)
|
||||
|
||||
数据段之后追加 **32 字节 SHA-256**:对文件尾之前全部内容计算。执行器必须校验通过才运行;compiler 写入。
|
||||
|
||||
### 常量表一行(12 字节)
|
||||
|
||||
@@ -172,7 +193,7 @@ FB 实例固定布局,字段偏移编译期算死。
|
||||
|
||||
### 数据段
|
||||
|
||||
槽初值按序排:全局(`n_globals`)→ I → Q → M。槽宽与对齐:`BOOL` 1 字节 / `INT` 2 字节 / `TIME` 8 字节,每个槽起点对齐到自身宽度(需要时插 padding)。
|
||||
槽初值按序排:全局(`n_globals`,v1 不拆 I/Q/M)→ FB 实例块(每字段一槽)。**每槽 8 字节定宽**(12.9 方案 a):`BOOL` 用低 1 字节、`INT` 用低 2 字节(小端)、`TIME` 全 8 字节。指令 `slot` 操作数即**槽号**,数据偏移 = 槽号 × 8;符号表 `address` 与指令 slot 一致,无映射。
|
||||
|
||||
工程哈希:收录源文件路径排序后对内容做 **FNV-1a 64**,只保证回放槽位一致,不是密码学哈希。FNV-1a 64:basis `0xcbf29ce484222325`,prime `0x100000001b3`。
|
||||
|
||||
@@ -219,11 +240,11 @@ isa/
|
||||
```
|
||||
|
||||
```cmake
|
||||
add_library(isa STATIC src/Encode.cpp src/Image.cpp)
|
||||
add_library(isa STATIC src/Encode.cpp)
|
||||
target_include_directories(isa PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
```
|
||||
|
||||
`compiler` / `vm`:`target_link_libraries(... PUBLIC isa)`。
|
||||
`vm`:`target_link_libraries(vm PUBLIC isa)`;`compiler` 不链 `isa`。
|
||||
|
||||
---
|
||||
|
||||
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
# 机器定义文件(machine.toml)
|
||||
|
||||
指令集与类型的**唯一事实来源**,**只给编译器**(STCompiler/compiler)使用。执行器(vm)不读本文件——它是下位机程序,内建全部指令。
|
||||
|
||||
## 架构(最终定案)
|
||||
|
||||
```text
|
||||
machine.toml(只给编译器:meta + type 别名表 + op 表 + fb 表)
|
||||
│ 运行时加载(toml++)+ 强校验
|
||||
▼
|
||||
STCompiler → compiler(编码 opcode / 类型元数据 / FB 布局 全部来自配置)
|
||||
│ 写 .stb(compiler 自带写实现:型号标识[32] + SHA-256 文件尾)
|
||||
▼
|
||||
.stb ──→ BytecodeExecutor → vm(执行 switch 在 vm;读 .stb 自实现)
|
||||
│ + 型号匹配校验 + SHA-256 校验
|
||||
▼
|
||||
isa(归执行器侧,只管指令定义)
|
||||
```
|
||||
|
||||
| 模块 | 职责 |
|
||||
|---|---|
|
||||
| `compiler` | 配置驱动编译;自带 `.stb` 写实现;`--disasm` 用配置参数名/format 自格式化 |
|
||||
| `vm` | 内建全指令集(执行 switch 在 vm);自带 `.stb` 读实现 + 型号/SHA-256 校验;无屏蔽、不读配置 |
|
||||
| `isa` | **归执行器侧**(vm 依赖),**只管指令定义**:Op 枚举/Instr/Encode/OpFormat/OpClass/类型/饱和/disasm。指令的具体执行实现在 vm |
|
||||
| `machine.toml` | 编译器唯一知识来源 |
|
||||
|
||||
## 决策清单(定案)
|
||||
|
||||
1. 指令两大分类:**PLAIN**(无实例,26 条)/ **INSTANCE**(有实例,8 条 `CAL_*`)
|
||||
2. 三层分类:`class`(操作对象)→ `format`(操作数形态)→ `参数名`(语义)
|
||||
3. isa 归执行器侧、只管指令定义;执行 switch 留在 vm
|
||||
4. 配置改 TOML、改名 machine.toml,只给编译器;vm 不需要配置
|
||||
5. 一致性靠 `.stb`:**型号标识[32]**(定长字符串含版本)+ **SHA-256 文件尾**(compiler/vm 各实现一份)+ 强校验
|
||||
6. 类型:**基元内置**(编译器内建 bit/int8..uint64/float32/float64,元数据在代码);配置类型行 = `name + base + range? + tag`(别名);**值域约束进配置**(如 BOOL range=[0,1])
|
||||
7. 字符串:**方案 A**(v1 不做;"组合类型"(含定长字符串 STRING(n))为未来扩展,需时再扩 .stb 格式)
|
||||
8. `.stb` 读写:compiler 写、vm 读,**各自实现**(无共享 image 模块)
|
||||
9. 冻结哈希测试更新(接受)
|
||||
|
||||
## 指令三层分类
|
||||
|
||||
```text
|
||||
class(操作对象) → format(操作数形态) → 参数名(操作数语义)
|
||||
PLAIN/INSTANCE RR/RRR/IMM/SLOT/ rd/rs/const_id/
|
||||
JMP/JC/CALL/CAL/NONE slot/off/fn_id/instance
|
||||
```
|
||||
|
||||
| 大类 | 操作对象 | 现有指令 |
|
||||
|---|---|---|
|
||||
| **PLAIN**(无实例) | 寄存器 / 立即数 / 槽号 / 分支 / 调用 | MOVE/LOADK/NOT/AND/OR/算术/CMP_* / JMP/JT/JF / LOAD_*/STORE_* / CALL/RET(26 条) |
|
||||
| **INSTANCE**(有实例) | 数据区实例块(内建 FB) | CAL_TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG(8 条) |
|
||||
|
||||
format 不可省:同是"第二个操作数是数字",`LOADK`/`LOAD_I`/`CALL` 渲染相同但语义不同(常量 id / 槽号 / fn_id,由参数名表达);"数字 vs 寄存器 vs 偏移"的渲染差异(`r%d` / `%u` / `%+d`)只有 format 能表达。
|
||||
|
||||
## 配置文件格式(TOML)
|
||||
|
||||
**全属性必填,不可省略**:
|
||||
|
||||
```toml
|
||||
[meta]
|
||||
name = "STATOR" # 型号名(参与 .stb 型号标识)
|
||||
version = 1 # 指令集版本(参与 .stb 型号标识)
|
||||
|
||||
# ---- 类型:编译器内建基元(bit/int8/int16/int32/int64/uint8/uint16/uint32/uint64/float32/float64)
|
||||
# 配置类型 = 基元别名(+ 值域约束 + .stb 契约 tag)
|
||||
[[type]]
|
||||
name = "BOOL"
|
||||
base = "uint8"
|
||||
range = [0, 1] # 值域约束(语言语义数据化;缺省 = 基元全范围)
|
||||
tag = 0 # 常量表契约(强校验)
|
||||
|
||||
[[type]]
|
||||
name = "INT"
|
||||
base = "int16"
|
||||
tag = 1
|
||||
|
||||
[[type]]
|
||||
name = "TIME"
|
||||
base = "int64"
|
||||
tag = 2
|
||||
|
||||
# ---- 指令(全属性必填)
|
||||
[[op]]
|
||||
name = "MOVE"
|
||||
opcode = 0
|
||||
class = "plain" # plain / instance
|
||||
format = "RR"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CAL_TON"
|
||||
opcode = 24
|
||||
class = "instance"
|
||||
format = "CAL"
|
||||
params = ["instance"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "RET"
|
||||
opcode = 33
|
||||
class = "plain"
|
||||
format = "NONE"
|
||||
params = []
|
||||
enabled = true
|
||||
|
||||
# ---- 内建 FB(布局登记;cal_opcode 与 op 行强校验一致)
|
||||
[[fb]]
|
||||
name = "ton"
|
||||
opcode = 24
|
||||
fields = [["in", "BOOL"], ["pt", "TIME"], ["q", "BOOL"], ["et", "TIME"]]
|
||||
|
||||
[[fb]]
|
||||
name = "ctud"
|
||||
opcode = 29
|
||||
fields = [["cu", "BOOL"], ["cd", "BOOL"], ["r", "BOOL"], ["lu", "BOOL"],
|
||||
["pv", "INT"], ["qu", "BOOL"], ["qd", "BOOL"], ["cv", "INT"]]
|
||||
```
|
||||
|
||||
## 类型系统
|
||||
|
||||
- **基元内置**:编译器内建 `bit/int8/int16/int32/int64/uint8/uint16/uint32/uint64/float32/float64`(宽度/有无符号/是否浮点元数据在代码,C 语义)
|
||||
- **别名**:配置类型行只写 `name + base + range? + tag`——宽度/符号/浮点从 base 继承
|
||||
- **值域约束**:`range`(如 BOOL [0,1])供编译器做常量/赋值检查;缺省 = 基元全范围
|
||||
- **tag 契约**:`tag`(0/1/2)与 `.stb` 常量表格式强校验(防 type 表改序破坏旧 .stb)
|
||||
- **字符串**:v1 不做(方案 A);组合类型(含 `STRING(n)`)为未来扩展,文档预留,需时再扩 `.stb` 格式
|
||||
- 半数据化边界:**类型定义数据化**(配置),**运算/类型检查语义代码化**(如 AND 只吃 BOOL 是语言规则,在 compiler 代码;饱和算术在 vm/isa)
|
||||
|
||||
## .stb 加固(格式冻结更新)
|
||||
|
||||
见 [`指令与映像.md`](指令与映像.md):头新增 **型号标识[32]**(定长 ASCII 字符串,含版本,不足补 `'\0'`);文件尾新增 **SHA-256[32]**(对前面全部内容计算,compiler 写侧 / vm 读侧各自实现)。执行器读取时:
|
||||
|
||||
```text
|
||||
型号标识与自身支持型号不匹配 → 直接报错
|
||||
SHA-256 不匹配 → 报错(文件损坏/篡改)
|
||||
```
|
||||
|
||||
## 强校验清单(加载/读取时,任一不符 → 报错退出)
|
||||
|
||||
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
|
||||
加普通指令:machine.toml 加 [[op]] 一行 + vm 一个语义 case → 重建 vm
|
||||
加内建 FB :machine.toml 加 [[fb]] 一行 + vm 一个语义 case → 重建 vm(compiler 免重建)
|
||||
加语言类型:machine.toml 加 [[type]] 一行(别名+tag)+ 类型检查/VM 语义代码 → 重建
|
||||
改型号版本:machine.toml [meta] version +1 → 旧 .stb 被旧/新执行器按型号校验决定是否接受
|
||||
```
|
||||
|
||||
## 执行计划(分支 dev_ops_config)
|
||||
|
||||
```
|
||||
0. 文档定案(本文 + 指令与映像.md 格式冻结更新 + stb文件格式.md 修订注记) ← 当前
|
||||
1. ✅ OpFormat/OpClass 显式化 + disasm 表驱动(已完成,35dabf4)
|
||||
2. machine.toml 落地:meta + type 表 + op 34 条 + fb 8 条
|
||||
3. compiler 配置驱动改造:toml++ 加载 + 强校验;编码 opcode / 类型元数据 /
|
||||
FB 布局来自配置(compiler 不再依赖 isa)
|
||||
4. .stb 写侧:compiler 实现 型号标识[32] + SHA-256 文件尾
|
||||
5. .stb 读侧:vm 自实现读 + 型号匹配 + SHA-256 校验(isa 的 Image 拆出)
|
||||
6. CLI:STCompiler --machine <path>(编译路径必填)
|
||||
7. 测试:machine 解析/校验负例、型号不匹配报错、SHA-256 篡改检测、
|
||||
冻结哈希更新、全量回归
|
||||
8. 文档 + 提交
|
||||
```
|
||||
|
||||
## 实现前确认项(4 项,未定案)
|
||||
|
||||
1. 配置文件名与位置:建议 `compiler/machine.toml`(只归编译器用)
|
||||
2. 型号标识生成规则:建议 `[meta] name + version` 拼成 32 字节(如 `"STATOR" + "1"` → 补 `'\0'`);vm 内建"支持型号列表"比对
|
||||
3. compiler 内部 `TypeKind`(Bool/Int/Time):保留为语言语义枚举(类型检查规则),元数据从配置 base 查
|
||||
4. 依赖方向确认:`compiler` 不再依赖 `isa`(`compiler → toml++` 自有;`STCompiler → compiler`;`vm → isa`)
|
||||
+74
-2
@@ -10,12 +10,13 @@ vm 模块:寄存器虚拟机。CMake 目标:`vm`(`STATIC`),只依赖 [
|
||||
1. 按 sidecar 的 io 绑定,采样 → I / 输入全局
|
||||
2. 从 PROGRAM MAIN 的 pc=0 执行到 RET
|
||||
3. 写回 Q / 输出全局
|
||||
4. 用本周期 Δt 推进 TON / TOF / CTU(不用 wall clock)
|
||||
4. 用本周期 Δt 推进内置 FB(不用 wall clock)
|
||||
```
|
||||
|
||||
## 边界
|
||||
|
||||
- 只认 `isa` 映像;类型与溢出按 [`指令与映像.md`](../isa/指令与映像.md)(`INT` 饱和)
|
||||
- 只认 `.stb` 映像(`vm` 自带读实现);读取时**型号标识匹配 + SHA-256 校验**,不符/篡改 → 拒绝
|
||||
- 类型与溢出按 [`指令与映像.md`](../isa/指令与映像.md)(`INT` 饱和);指令定义归 `isa`
|
||||
- 局部 / `VAR` = 固定寄存器;`nregs` 在函数头
|
||||
- `CALL` 目标是立即数 `fn_id`,有界调用栈
|
||||
- 无 GC、无堆、无线程;每周期受 `cycle_limit` 限制
|
||||
@@ -48,3 +49,74 @@ const Snapshot& snapshot() const;
|
||||
|
||||
- 寄存器 ≤256、映像很小,快照拷贝成本可忽略。
|
||||
- 单步、回放、dump 都建立在同一套观察 API 上。
|
||||
|
||||
---
|
||||
|
||||
## 12.9 执行计划
|
||||
|
||||
指令执行语义(帧/调用栈、指令表、定时器、故障、line1 推演)见 [`指令执行.md`](指令执行.md)。
|
||||
|
||||
### 步骤 0:数据段改 8 字节定宽槽(方案 a,前置)
|
||||
|
||||
VM 需要无歧义地访问数据区,当前变宽布局(BOOL 1B / INT 2B / TIME 8B 对齐)无法从槽号推断宽度。定案:**每槽 8 字节定宽**,指令 slot 语义 = **槽号**(数据偏移 = slot × 8)。
|
||||
|
||||
- 文档修订:`Doc/isa/指令与映像.md`(数据段规则 + slot 语义)、`Doc/compiler/寄存器码.md`(存储布局,`global_offset` 映射取消)、`Doc/isa/stb文件格式.md`(line1 数据段 16B → 56B)
|
||||
- `Codegen.cpp` 简化:`width_of`/`align_up` 删除、`layout_data`/`layout_fb_instances` 按槽号布局、指令 slot = 符号表槽号
|
||||
- `codegen_test` 断言更新(slot 从字节偏移变槽号、数据区字节数变化)
|
||||
|
||||
### 步骤 1:`Machine.h`(已落地 `vm/include/vm/Machine.h`)
|
||||
|
||||
```cpp
|
||||
namespace vm {
|
||||
enum class Fault { None, CycleLimit, StackOverflow, BadOp, BadSlot, BadConst };
|
||||
|
||||
class Machine {
|
||||
public:
|
||||
static bool create(const std::vector<uint8_t>& image, Machine* out, std::string* err);
|
||||
Fault run_cycle(); // MAIN pc=0 → RET(MAIN 帧跨周期保留)
|
||||
// 可观察性(v1 预留,见上节):
|
||||
bool step(); // 执行一条指令,停在指令边界
|
||||
uint32_t pc() / fn_id() / cycle_count() / call_depth();
|
||||
uint8_t* data(); // 数据区(I 采样 / Q 读回由外部做)
|
||||
int64_t reg(uint8_t i); // 当前帧寄存器
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
- `Fault` 枚举 + 帧结构 `{ fn_id, regs[nregs], ret_pc, ret_fn_id }`;调用栈深度上限 64
|
||||
- 只依赖 `isa`(指令定义)+ 自带 `vm::Image`(.stb 读)
|
||||
|
||||
### 步骤 2:`Machine.cpp` 译码 switch(已落地)
|
||||
|
||||
- 取指:函数表 → `code_offset`(字节)→ 读 u32 → 拆 `op/rd/a/b`
|
||||
- 标量:`MOVE` / `LOADK`(常量表 tag 定值表示)/ `NOT` / `AND` / `OR`(布尔)/ `ADD/SUB/MUL/DIV`(`sat_*`)/ `CMP_xx`(全宽比较)
|
||||
- 跳转:`JMP` / `JT` / `JF`(`pc += off`,相对下一条语义)
|
||||
- 数据区:`LOAD_I/LOAD_M/LOAD_GLOBAL`、`STORE_Q/STORE_M/STORE_GLOBAL`(同一实现,slot × 8)
|
||||
|
||||
### 步骤 3:CALL / RET 帧栈 + 故障(已落地)
|
||||
|
||||
- `CALL fn_id`:压新帧(复制 r0..r7)→ pc=0;`RET`:复制 r0..r7 回调用方 → 弹栈(MAIN 的 RET = 周期结束)
|
||||
- `cycle_limit` 超限(用例 6)、栈深 > 64、`BadOp` / `BadSlot` / `BadConst` → 周期中止返回故障
|
||||
|
||||
### 步骤 4:定时器与边沿(已落地)
|
||||
|
||||
- 执行时用映像 `dt_ms` 推进(每周期一次调用 = 推进一次),不读系统时钟
|
||||
- TON:in 真 → et += dt(到 pt 停)、q = et ≥ pt;in 假 → et = 0
|
||||
- TOF:in 真 → q=1、et=0;掉电 → et += dt、et ≥ pt → q=0
|
||||
- CTU:cu 上升沿(**上次 cu 存 VM 侧 vector**,按实例基槽索引)→ cv+1;r → 复位;q = cv ≥ pv
|
||||
|
||||
### 步骤 5:可观察性基础(已落地)
|
||||
|
||||
- `step()` / `pc` / `fn_id` / `cycle_count` / `call_depth` / `reg` 落基础实现;`snapshot()` 留 12.11 补全
|
||||
|
||||
### 步骤 6:`vm_test`(已落地)
|
||||
|
||||
- 手工拼小映像(`isa` 编码函数):赋值、跳转、CALL/RET 帧复制、cycle_limit
|
||||
- 编译器产物全链路:用例 01/02/04/05/07/09/13/15/17/18/20 跑周期断言寄存器与数据区
|
||||
- 用例 06:`cycle_limit` 打满 → `CycleLimit` 故障
|
||||
- TON:dt=10、pt=30 → 第 3 周期 q=1;CTU:上升沿计数
|
||||
- **确定性**:同一 I 序列跑两遍,数据区完全一致
|
||||
|
||||
### 步骤 7:验证 + 提交(已落地,ctest 14/14)
|
||||
|
||||
- `cmake --build` + `ctest`(新增 `vm_cycles` 后 10/10 全绿);20 用例扫描:14 正例执行通过
|
||||
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
# 指令执行流程(12.9)
|
||||
|
||||
`vm` 模块的指令执行语义:取指、译码、执行、故障。规范以 [`指令与映像.md`](../isa/指令与映像.md) 为准,本文描述**执行器怎么跑一条指令**。执行模型见 [`初步计划.md`](../初步计划.md) 第 2 节。
|
||||
|
||||
## 执行模型
|
||||
|
||||
```text
|
||||
加载(Machine::create):解析 .stb(vm 自读实现)→ 型号匹配(内建 STATOR1)→ SHA-256 校验
|
||||
任一不符 → 直接拒绝(不创建机器)
|
||||
|
||||
每个扫描周期(run_cycle):
|
||||
1. pc = 0,从入口函数(MAIN)开始逐条执行
|
||||
2. 执行到 MAIN 的 RET → 周期结束
|
||||
3. (I 采样 / Q 写回由 executor 按 sidecar 做,VM 只管数据区)
|
||||
4. 定时器在 CAL_* 指令执行时用映像 dt_ms 推进
|
||||
```
|
||||
|
||||
- **pc**:当前指令在函数字节码段内的下标(指令条数单位,从 0 起)
|
||||
- **取指**:函数表 `func_row(fn_id)` → `code_offset`(字节)→ `code_bytes[code_offset/4 + pc]` 读 u32(小端)
|
||||
- **译码**:拆 `op / rd / a / b` 四字段,`a|b` 按操作码解释为寄存器对、槽号(u16)、或跳转偏移(int16)
|
||||
|
||||
## 帧与调用栈
|
||||
|
||||
```cpp
|
||||
struct Frame {
|
||||
uint32_t fn_id; // 当前函数
|
||||
std::vector<int64_t> regs; // 寄存器文件,大小 = 函数头 nregs
|
||||
uint32_t ret_pc; // CALL 后应恢复的 pc(仅非 MAIN 帧有意义)
|
||||
uint32_t ret_fn_id; // 返回目标函数(调用方)
|
||||
};
|
||||
```
|
||||
|
||||
- 调用栈 `std::vector<Frame>`,**深度上限 64**;超限 → `StackOverflow` 故障
|
||||
- **MAIN 帧跨周期保留**(PROGRAM 变量状态持久);周期结束只清调用栈(保留栈底 MAIN 帧)
|
||||
- 帧寄存器初值 0
|
||||
|
||||
### CALL / RET(调用约定,冻结于 12.8)
|
||||
|
||||
```text
|
||||
CALL fn_id:
|
||||
1. 新帧:regs 大小 = callee 的 nregs,全部 0
|
||||
2. 复制当前帧 r0..r7 → 新帧 r0..r7(实参已在调用点 MOVE 进 r1..r7)
|
||||
3. 新帧 ret_pc = 当前 pc + 1(CALL 的下一条)、ret_fn_id = 当前函数
|
||||
4. 压栈,pc = 0,fn_id = callee
|
||||
|
||||
RET:
|
||||
1. 若当前是 MAIN(栈深 0)→ 周期结束
|
||||
2. 否则:复制当前帧 r0..r7 → 调用方帧 r0..r7(r0 = 结果,r1..r7 原样返回)
|
||||
3. 弹栈,pc = ret_pc,fn_id = ret_fn_id
|
||||
```
|
||||
|
||||
## 值表示(寄存器 / 槽统一约定)
|
||||
|
||||
| 类型 | 表示 | 说明 |
|
||||
|---|---|---|
|
||||
| BOOL | `0` / `1` | 逻辑指令产出、字面量 TRUE=1 |
|
||||
| INT | `int16_t` 符号扩展 | `sat_*` 返回 int16,赋给 int64 寄存器自动符号扩展 |
|
||||
| TIME | 全 64 位 | 毫秒 |
|
||||
|
||||
推论:**全宽 int64 比较等价于各类型自身比较**(INT 符号扩展后 -5 < 3 正确)。类型语义由编译期类型检查保证,VM 不查类型。
|
||||
|
||||
## 数据区访问(8 字节定宽槽,方案 a)
|
||||
|
||||
```text
|
||||
槽号 slot(指令操作数)→ 数据偏移 = slot × 8
|
||||
LOAD_* rd, slot → rd = 读 data[slot*8 .. slot*8+8)(8 字节原样)
|
||||
STORE_* slot, rs → 写 data[slot*8 .. slot*8+8) = rs(8 字节原样)
|
||||
```
|
||||
|
||||
`LOAD_I` / `LOAD_M` / `LOAD_GLOBAL` / `STORE_Q` / `STORE_M` / `STORE_GLOBAL` 执行逻辑完全相同(v1 单一数据区,操作码只作语义标签)。
|
||||
|
||||
## 指令执行表
|
||||
|
||||
| 指令 | 流程 |
|
||||
|---|---|
|
||||
| `MOVE rd, rs` | `reg[rd] = reg[rs]` |
|
||||
| `LOADK rd, cid` | 查常量表 `const_entry(cid)`:BOOL → 0/1;INT → `(int16_t)value` 符号扩展;TIME → 全宽 |
|
||||
| `NOT rd, rs` | `reg[rd] = (reg[rs] == 0) ? 1 : 0` |
|
||||
| `AND/OR rd, ra, rb` | `reg[rd] = (reg[ra] != 0 && reg[rb] != 0) ? 1 : 0`(或 OR) |
|
||||
| `ADD/SUB/MUL/DIV rd, ra, rb` | `sat_add((int16)ra, (int16)rb)` 等,结果符号扩展入 rd |
|
||||
| `CMP_xx rd, ra, rb` | 全宽 int64 比较 → `1` / `0` |
|
||||
| `JMP off` | `pc += off`(见跳转语义) |
|
||||
| `JT rd, off` / `JF rd, off` | `reg[rd] != 0`(JT)/ `== 0`(JF)时 `pc += off`,否则正常 +1 |
|
||||
| `LOAD_* rd, slot` | 见数据区访问 |
|
||||
| `STORE_* slot, rs` | 见数据区访问 |
|
||||
| `CAL_TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG slot` | 见定时器与边沿推进 |
|
||||
| `CALL fn_id` / `RET` | 见帧与调用栈 |
|
||||
|
||||
### 跳转语义
|
||||
|
||||
```text
|
||||
指令执行后 pc 先 +1(指向下一条),跳转指令再 pc += off
|
||||
⇒ 目标下标 = 当前 + 1 + off(与编译端 patch_jump 一致)
|
||||
```
|
||||
|
||||
### 周期指令计数
|
||||
|
||||
- 每条指令执行后 `cycle_count++`;超过映像头 `cycle_limit` → `CycleLimit` 故障(周期中止)
|
||||
- `JMP` / `JT` / `JF` / `CALL` 只算 1 条(cycle_limit 以"执行的指令数"计)
|
||||
|
||||
## 定时器与边沿推进(CAL_TON / CAL_TOF / CAL_TP / CAL_CTU / CAL_CTD / CAL_CTUD / CAL_R_TRIG / CAL_F_TRIG)
|
||||
|
||||
`CAL_* slot` 的 `slot` 是实例**基槽号**;字段偏移 = 字段序号 × 8(布局冻结:TON/TOF/TP = in/pt/q/et;CTU = cu/r/pv/q/cv;CTD = cd/ld/pv/q/cv;CTUD = cu/cd/r/lu/pv/qu/qd/cv;R_TRIG/F_TRIG = clk/q)。每次执行 = 推进一次,Δt = 映像头 `dt_ms`(不读系统时钟)。边沿检测的"上次输入"存 VM 侧 `edge_prev_`(每实例基槽 2 字节),跨周期保留:
|
||||
|
||||
```text
|
||||
CAL_TON:
|
||||
in 为真:et += dt(et ≥ pt 时停在 pt);q = (et ≥ pt) ? 1 : 0
|
||||
in 为假:et = 0;q = 0
|
||||
|
||||
CAL_TOF:
|
||||
in 为真:q = 1;et = 0
|
||||
in 掉电:et += dt;et ≥ pt 时 q = 0
|
||||
|
||||
CAL_TP(脉冲):
|
||||
in 上升沿启动 PT 时长脉冲(期间 in 变化不影响)
|
||||
计时中:et += dt;et ≥ pt → q = 0、et 归零(脉冲结束);否则 q = 1
|
||||
非计时:et = 0;q = 0
|
||||
|
||||
CAL_CTU:cu 上升沿 → cv += 1;r → cv = 0;q = (cv ≥ pv) ? 1 : 0
|
||||
CAL_CTD:cd 上升沿 → cv -= 1;ld → cv = pv;q = (cv ≤ 0) ? 1 : 0
|
||||
CAL_CTUD:cu 上升沿 → cv += 1;cd 上升沿 → cv -= 1;r → cv = 0;lu → cv = pv;
|
||||
qu = (cv ≥ pv) ? 1 : 0;qd = (cv ≤ 0) ? 1 : 0
|
||||
CAL_R_TRIG:q = clk 且上次 clk 假(上升沿)
|
||||
CAL_F_TRIG:q = !clk 且上次 clk 真(下降沿)
|
||||
```
|
||||
|
||||
## 故障
|
||||
|
||||
| 故障 | 触发 | 周期行为 |
|
||||
|---|---|---|
|
||||
| `None` | — | 正常完成 |
|
||||
| `CycleLimit` | 周期指令数 > `cycle_limit`(用例 6) | 中止,返回故障 |
|
||||
| `StackOverflow` | 调用栈深度 > 64 | 中止 |
|
||||
| `BadOp` | op ≥ 29 | 中止 |
|
||||
| `BadSlot` | slot × 8 + 8 > 数据区长度 | 中止 |
|
||||
| `BadConst` | const_id ≥ n_consts | 中止 |
|
||||
|
||||
## 示例:line1 MAIN 逐指令推演
|
||||
|
||||
映像:`dt_ms=10`,`cycle_limit=100000`,数据区 56 字节(7 槽 × 8B,方案 a)。
|
||||
|
||||
```text
|
||||
初始:MAIN 帧 regs[13] 全 0;槽 0..6 全 0(初值 0)
|
||||
假定 executor 已采样:槽 0 = EmergencyStop = 0、槽 1 = I0_0 = 1、槽 2 = I0_1 = 0
|
||||
|
||||
pc 指令 执行后
|
||||
── ───────────────────── ──────────────────────────────
|
||||
0 LOAD_I r8, 1 r8 = data[8..16) 低字节 = 1(I0_0)
|
||||
1 STORE_GLOBAL r8, 4 槽 4(starter.start)= 1
|
||||
2 LOAD_GLOBAL r9, 2 r9 = 0(I0_1)
|
||||
3 STORE_GLOBAL r9, 5 槽 5(starter.stop)= 0
|
||||
4 LOAD_GLOBAL r8, 8 内联体:r8 = 槽 4 = 1(start)
|
||||
5 JF r8, +3 r8≠0 不跳;pc = 6
|
||||
6 LOAD_GLOBAL r10, 9 r10 = 槽 5 = 0(stop)
|
||||
7 NOT r9, r10 r9 = 1
|
||||
8 MOVE r8, r9 r8 = 1
|
||||
9 JF r8, +3 r8≠0 不跳;pc = 10
|
||||
10 LOAD_I r12, 0 r12 = 槽 0 = 0(EmergencyStop)
|
||||
11 NOT r11, r12 r11 = 1
|
||||
12 MOVE r8, r11 r8 = 1
|
||||
13 STORE_GLOBAL r8, 10 槽 6(starter.q)= 1
|
||||
14 LOAD_GLOBAL r8, 10 r8 = 槽 6 = 1
|
||||
15 STORE_Q r8, 3 槽 3(Q0_0)= 1
|
||||
16 RET 周期结束;executor 读槽 3 → 硬件
|
||||
```
|
||||
|
||||
结果:`Q0_0 = 1`(start=1、stop=0、急停=0 → 电机启动)——与 `EXPECTED.md` 真值表一致。
|
||||
+17
-13
@@ -45,7 +45,7 @@
|
||||
1. 按 toml 的 io 绑定,采样 → I / 输入全局
|
||||
2. 从 PROGRAM MAIN 的 pc=0 执行到 RET
|
||||
3. 写回 Q / 输出全局
|
||||
4. 用本周期 Δt 推进 TON / TOF / CTU(不用 wall clock)
|
||||
4. 用本周期 Δt 推进内置 FB(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG,不用 wall clock)
|
||||
```
|
||||
|
||||
运行时是 **一台 VM、一个入口、一份映像**。多文件只影响源码怎么切、怎么链接。
|
||||
@@ -219,7 +219,7 @@ JMP offset
|
||||
JT/JF r, offset
|
||||
LOAD_I / STORE_Q / LOAD_M / STORE_M
|
||||
LOAD_GLOBAL / STORE_GLOBAL
|
||||
CAL_TON / CAL_TOF / CAL_CTU
|
||||
CAL_TON / CAL_TOF / CAL_TP / CAL_CTU / CAL_CTD / CAL_CTUD / CAL_R_TRIG / CAL_F_TRIG
|
||||
CALL fn_id
|
||||
RET
|
||||
```
|
||||
@@ -354,7 +354,7 @@ CMake 三个库能空编译:`isa`、`compiler`、`vm`;两个可执行 `STCom
|
||||
| `project.name` | 工程名 | 必填 |
|
||||
| `project.entry` | 入口 | 第一版必须是 `program MAIN` |
|
||||
| `project.cycle_limit` | 每周期指令上限 | 如 `100000`;超时是故障 |
|
||||
| `project.dt_ms` | 本周期 Δt | 给 TON / TOF / CTU,不是 wall clock |
|
||||
| `project.dt_ms` | 本周期 Δt | 给内置 FB 定时器,不是 wall clock |
|
||||
| `files.st` | 源文件列表 | 编译前全部已知 |
|
||||
| `gvl.file` | 唯一允许 `VAR_GLOBAL` 的文件 | 已在 `files.st` 则不重复收录 |
|
||||
| `[[io.input]]` / `[[io.output]]` | `var` + `channel` + `bit` | 可选;`var` 必须已在 GVL |
|
||||
@@ -370,7 +370,7 @@ CMake 三个库能空编译:`isa`、`compiler`、`vm`;两个可执行 `STCom
|
||||
`THEN` `DO` (12.5 修订补入:`IF…THEN` / `WHILE…DO` 语法需要)
|
||||
`AND` `OR` `NOT`
|
||||
`TRUE` `FALSE`
|
||||
`TON` `TOF` `CTU`
|
||||
`TON` `TOF` `TP` `CTU` `CTD` `CTUD` `R_TRIG` `F_TRIG` (12.11 修订:内置 FB 扩为 8 个)
|
||||
|
||||
其余一律当标识符或直接拒绝(`VAR_IN_OUT`、`REF`、`CLASS` 等出明确错误)。
|
||||
|
||||
@@ -449,7 +449,7 @@ CMake 三个库能空编译:`isa`、`compiler`、`vm`;两个可执行 `STCom
|
||||
|
||||
1. POU 外壳:`PROGRAM` / `FUNCTION` / `FUNCTION_BLOCK` 与对应 `END_*`
|
||||
2. 变量段:`VAR` / `VAR_INPUT` / `VAR_OUTPUT` / `VAR_GLOBAL` / `VAR_EXTERNAL`
|
||||
3. 类型名只接受 `BOOL` `INT` `TIME`;内置 FB 名 `TON` `TOF` `CTU`
|
||||
3. 类型名只接受 `BOOL` `INT` `TIME`;内置 FB 名 `TON` `TOF` `TP` `CTU` `CTD` `CTUD` `R_TRIG` `F_TRIG` (12.11 修订:内置 FB 扩为 8 个)
|
||||
4. 语句:赋值、`IF`、`WHILE`、FB 调用 `fb(in := …)`、字段 `fb.Q`
|
||||
5. 表达式:`NOT`、`AND`/`OR`(AST 里保留短路语义)、比较、加减乘除
|
||||
|
||||
@@ -512,7 +512,7 @@ CMake 三个库能空编译:`isa`、`compiler`、`vm`;两个可执行 `STCom
|
||||
此时才写执行循环。只认 `isa` 映像。
|
||||
|
||||
```text
|
||||
采样 I → pc=0 执行到 RET → 写回 Q → 用本周期 Δt 推进 TON/TOF/CTU
|
||||
采样 I → pc=0 执行到 RET → 写回 Q → 用本周期 Δt 推进内置 FB
|
||||
```
|
||||
|
||||
- 译码 + 大 `switch`;寄存器文件按函数头 `nregs` 分配。
|
||||
@@ -530,17 +530,21 @@ CMake 三个库能空编译:`isa`、`compiler`、`vm`;两个可执行 `STCom
|
||||
编译与执行拆成两个可执行,映像 `.stb` 是中间产物:
|
||||
|
||||
```text
|
||||
STCompiler <project.toml> -o <name>.stb # 编译:读 toml + .st → 映像 + sidecar
|
||||
BytecodeExecutor <name>.stb [--cycles N] # 执行:加载 .stb + sidecar,跑 N 周期
|
||||
STCompiler <project.toml> -o <name>.stb --machine <machine.toml>
|
||||
BytecodeExecutor <name>.stb [--cycles N]
|
||||
```
|
||||
|
||||
- `STCompiler` 链 `compiler` + `isa`;`BytecodeExecutor` 链 `vm` + `isa`,**不链 `compiler`**。
|
||||
- 产物两个文件:`<name>.stb`(映像,魔数 `STSC`)+ `<name>.runtime.toml`(sidecar:I/O 绑定 var → 槽号 → channel/bit)。`[[io.*]]` 不进映像,sidecar 由 STCompiler 生成,不创造变量。
|
||||
- `STCompiler` 链 `compiler`(**不链 `isa`**,编码查 `machine.toml`);`BytecodeExecutor` 链 `vm` + `isa`,**不链 `compiler`**。
|
||||
- 产物两个文件:`<name>.stb`(头 104 含型号标识 + 段 + SHA-256 文件尾)+ `<name>.runtime.toml`(sidecar:I/O 绑定 var → 槽号 → channel/bit)。`[[io.*]]` 不进映像,sidecar 由 STCompiler 生成,不创造变量。
|
||||
- `dt_ms` / `cycle_limit` 从映像头取,sidecar 不重复配置。
|
||||
- 执行器读取时**型号匹配 + SHA-256 校验**,不匹配/篡改 → 拒绝。
|
||||
- 最小可观测:BytecodeExecutor 打印本周期 I/Q,或把寄存器/映像 dump 出来。
|
||||
- 回放:读预先录制的 I 序列(文本即可),不接真实硬件。
|
||||
|
||||
完成:`STCompiler examples/line1/project.toml -o line1.stb` 产出两个文件;`BytecodeExecutor line1.stb` 能跑若干周期。
|
||||
完成:`STCompiler examples/line1/project.toml -o line1.stb --machine compiler/machine.toml` 产出两个文件;`BytecodeExecutor line1.stb` 能跑若干周期。
|
||||
|
||||
> 12.13 修订:指令 opcode / 类型 / FB 布局的登记移入 `compiler/machine.toml`(见 [`指令配置.md`](isa/指令配置.md)),
|
||||
> `STCompiler` 编译路径必填 `--machine`。
|
||||
|
||||
---
|
||||
|
||||
@@ -575,8 +579,8 @@ BytecodeExecutor <name>.stb [--cycles N] # 执行:加载 .stb + sidecar
|
||||
### 12.13 依赖方向(全程遵守)
|
||||
|
||||
```text
|
||||
STCompiler → compiler → isa
|
||||
BytecodeExecutor → vm → isa
|
||||
STCompiler → compiler (编码查 machine.toml;自带 .stb 写)
|
||||
BytecodeExecutor → vm → isa (isa 只管指令;vm 自带 .stb 读)
|
||||
```
|
||||
|
||||
- 指令或映像布局有变:先改 `isa` 和编解码测试,再改两端。
|
||||
|
||||
@@ -7,11 +7,17 @@ doc/
|
||||
索引.md
|
||||
初步计划.md
|
||||
isa/指令与映像.md
|
||||
isa/stb文件格式.md
|
||||
isa/指令配置.md
|
||||
compiler/编译管线.md
|
||||
compiler/STCompiler使用说明.md
|
||||
compiler/词法.md
|
||||
compiler/语法.md
|
||||
compiler/符号表与链接.md
|
||||
compiler/类型检查.md
|
||||
compiler/寄存器码.md
|
||||
vm/扫描周期.md
|
||||
vm/指令执行.md
|
||||
executor/执行器入口.md
|
||||
```
|
||||
|
||||
@@ -19,11 +25,17 @@ doc/
|
||||
|---|---|
|
||||
| [`初步计划.md`](初步计划.md) | 全工程定案:子集 ST、toml、执行模型、第 12 节阶段 |
|
||||
| [`isa/指令与映像.md`](isa/指令与映像.md) | 指令、映像、定宽类型、饱和;**规范以此为准** |
|
||||
| [`isa/stb文件格式.md`](isa/stb文件格式.md) | `.stb` 结构说明(用 line1 真实字节拆解) |
|
||||
| [`isa/指令配置.md`](isa/指令配置.md) | ops.txt:指令登记唯一事实来源、OpTable、屏蔽机制 |
|
||||
| [`compiler/编译管线.md`](compiler/编译管线.md) | 编译器边界与管线 |
|
||||
| [`compiler/STCompiler使用说明.md`](compiler/STCompiler使用说明.md) | 构建、用法、工程文件、ST 子集、错误类别 |
|
||||
| [`compiler/词法.md`](compiler/词法.md) | 12.4 词法:关键字表、token、大小写、TIME 字面量 |
|
||||
| [`compiler/语法.md`](compiler/语法.md) | 12.5 语法:AST 结构、文法、拒绝清单 |
|
||||
| [`compiler/符号表与链接.md`](compiler/符号表与链接.md) | 12.6 符号表与链接:数据区定址、FB 布局、错误类别 |
|
||||
| [`compiler/类型检查.md`](compiler/类型检查.md) | 12.7 类型检查:表达式类型、语句规则、错误类别 |
|
||||
| [`compiler/寄存器码.md`](compiler/寄存器码.md) | 12.8 寄存器码:布局、指令模式、内联 FB、8 片计划 |
|
||||
| [`vm/扫描周期.md`](vm/扫描周期.md) | 扫描周期与 VM 边界 |
|
||||
| [`vm/指令执行.md`](vm/指令执行.md) | 12.9 指令执行流程:帧/调用栈、指令表、定时器、故障 |
|
||||
| [`executor/执行器入口.md`](executor/执行器入口.md) | 可执行入口:加载 `.stb` + sidecar,跑扫描周期 |
|
||||
|
||||
代码目录里的 README 只作入口;类型、指令、映像以 `isa/指令与映像.md` 为准。
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
方案见 [`Doc/初步计划.md`](Doc/初步计划.md),模块文档见 [`Doc/索引.md`](Doc/索引.md)。
|
||||
|
||||
```text
|
||||
多个 .st
|
||||
↑ project.toml 列出
|
||||
多个 .st + machine.toml(指令/类型/FB 布局)
|
||||
↑ project.toml 列出源文件;machine.toml 唯一事实来源
|
||||
↓
|
||||
STCompiler(compiler 库:词法 / 语法 → 符号表 → 类型检查 → 寄存器码 → 链接)
|
||||
STCompiler(compiler 库:词法 / 语法 → 符号表 → 类型检查 → 寄存器码 → 链接;编码查 machine.toml)
|
||||
↓
|
||||
<name>.stb 映像 + <name>.runtime.toml sidecar
|
||||
<name>.stb 映像(头 104 含型号标识 + 段 + SHA-256 文件尾)+ <name>.runtime.toml sidecar
|
||||
↓
|
||||
BytecodeExecutor(vm 库按扫描周期执行;喂 I、收 Q、给 Δt、回放)
|
||||
BytecodeExecutor(vm 库按扫描周期执行;型号/SHA 校验;喂 I、收 Q、给 Δt、回放)
|
||||
```
|
||||
|
||||
## 目录
|
||||
@@ -40,33 +40,33 @@ BytecodeExecutor(vm 库按扫描周期执行;喂 I、收 Q、给 Δt、回
|
||||
|
||||
## 模块与 CMake 目标
|
||||
|
||||
三个静态库与两个可执行,与目录一一对应。`compiler` 与 `vm` 不互相链接,合同放在 `isa`。依赖方向:`STCompiler → compiler → isa`、`BytecodeExecutor → vm → isa`。
|
||||
三个静态库与两个可执行,与目录一一对应。`compiler` 与 `vm` 不互相链接。指令/类型/FB 布局的**唯一事实来源是 `compiler/machine.toml`**(编译器运行时加载);`isa` 归执行器侧(`vm` 依赖),只管指令定义。依赖方向:`STCompiler → compiler`、`BytecodeExecutor → vm → isa`。
|
||||
|
||||
| 目录 | 目标 | 类型 | 依赖 | 职责 |
|
||||
|---|---|---|---|---|
|
||||
| `isa/` | `isa` | `STATIC` | 无 | 约 20 条指令编码、映像头、工程哈希、`BOOL` / `INT` / `TIME` 宽度 |
|
||||
| `compiler/` | `compiler` | `STATIC` | `isa` | 读 toml、收齐 `.st`、解析、链接、定址、类型检查、编寄存器码、写映像 |
|
||||
| `vm/` | `vm` | `STATIC` | `isa` | 一台 VM、一个入口、一份映像;扫描周期、寄存器、FB、有界 `CALL` / `RET` |
|
||||
| `compiler/` | `STCompiler` | `EXECUTABLE` | `compiler`、`isa` | `STCompiler <project.toml> -o <name>.stb`:编译并写映像 + sidecar;`--disasm` 可反汇编 |
|
||||
| `isa/` | `isa` | `STATIC` | 无 | 指令定义(Op/Instr/Encode/Types/饱和/disasm),**归执行器侧** |
|
||||
| `compiler/` | `compiler` | `STATIC` | toml++(私有) | 读 toml、收齐 `.st`、解析、链接、定址、类型检查、编寄存器码、写 `.stb`(指令 opcode/类型/FB 布局全部来自 `machine.toml`) |
|
||||
| `vm/` | `vm` | `STATIC` | `isa` | 一台 VM、一个入口、一份映像;扫描周期、寄存器、FB、有界 `CALL` / `RET`;自带 `.stb` 读实现 + 型号/SHA-256 校验 |
|
||||
| `compiler/` | `STCompiler` | `EXECUTABLE` | `compiler` | `STCompiler <project.toml> -o <name>.stb --machine <machine.toml>`:编译并写映像 + sidecar;`--disasm` 可反汇编 |
|
||||
| `executor/` | `BytecodeExecutor` | `EXECUTABLE` | `vm`、`isa` | `BytecodeExecutor <name>.stb`:加载映像 + sidecar,采样 I、执行 `PROGRAM MAIN`、写回 Q、用 Δt 推进定时器 / 计数器、回放 |
|
||||
|
||||
词法、语法、符号表、检查、codegen、链接都留在 `compiler` 一个库里,不再拆 CMake 子库。
|
||||
|
||||
### `isa`
|
||||
|
||||
编译器写映像、虚拟机读映像,两边共用同一套编码。放这里避免循环依赖。
|
||||
归执行器侧(`vm` 依赖),**只管指令定义**:Op 枚举 / Instr 打包 / Encode(编解码·disasm)/ Types(饱和)。执行器执行 switch 在 `vm`。
|
||||
|
||||
### `compiler`
|
||||
|
||||
toml **不声明变量**,只列出源文件、指定唯一 GVL、可选地把已有全局接到硬件。`VAR_GLOBAL` 第一版只允许出现在 `gvl.file`(如 `globals.st`)。
|
||||
toml **不声明变量**,只列出源文件、指定唯一 GVL、可选地把已有全局接到硬件。`VAR_GLOBAL` 第一版只允许出现在 `gvl.file`(如 `globals.st`)。指令 opcode / 类型元数据 / FB 布局**全部来自 `compiler/machine.toml`**(`STCompiler --machine` 加载,强校验后编译)。
|
||||
|
||||
### `vm`
|
||||
|
||||
每个扫描周期:采样 I → 从 `PROGRAM MAIN` 的 `pc=0` 执行到 `RET` → 写回 Q → 用本周期 Δt 推进 `TON` / `TOF` / `CTU`。无 GC、无堆、无线程;变量编译期分配。
|
||||
每个扫描周期:采样 I → 从 `PROGRAM MAIN` 的 `pc=0` 执行到 `RET` → 写回 Q → 用本周期 Δt 推进内置 FB。无 GC、无堆、无线程;变量编译期分配。读取 `.stb` 时**校验型号标识与 SHA-256**(不匹配/篡改 → 拒绝)。
|
||||
|
||||
### `executor`(BytecodeExecutor)
|
||||
|
||||
第一版两个可执行:`STCompiler` 编译出 `<name>.stb`(映像)+ `<name>.runtime.toml`(sidecar:I/O 绑定);`BytecodeExecutor` 加载后按扫描周期跑。不再有"一条命令编译 + 跑"。`dt_ms` / `cycle_limit` 从映像头取。
|
||||
`STCompiler` 编译出 `<name>.stb`(含型号标识 + SHA-256)+ `<name>.runtime.toml`(sidecar:I/O 绑定);`BytecodeExecutor` 加载后按扫描周期跑。`dt_ms` / `cycle_limit` 从映像头取。
|
||||
|
||||
### `examples/` 与 `tests/`
|
||||
|
||||
|
||||
@@ -7,15 +7,21 @@ project(Compiler
|
||||
DESCRIPTION "ST / toml 编译器")
|
||||
|
||||
# 词法、语法、符号表、类型检查、codegen、链接都留在 compiler 一个库里
|
||||
# 指令 opcode / 类型 / FB 布局来自 machine.toml(MachineConfig),不依赖 isa
|
||||
add_library(compiler STATIC
|
||||
./src/Lexer.cpp
|
||||
./src/Project.cpp
|
||||
./src/Parser.cpp
|
||||
./src/Linker.cpp)
|
||||
./src/Linker.cpp
|
||||
./src/Typecheck.cpp
|
||||
./src/Codegen.cpp
|
||||
./src/MachineConfig.cpp
|
||||
./src/Stb.cpp
|
||||
./src/Codec.cpp
|
||||
./src/TypeInfo.cpp)
|
||||
|
||||
target_include_directories(compiler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_include_directories(compiler PRIVATE ${CMAKE_SOURCE_DIR}/third_party/tomlplusplus/include)
|
||||
target_link_libraries(compiler PUBLIC isa)
|
||||
|
||||
# 可执行入口:STCompiler <project.toml> -o <name>.stb
|
||||
add_executable(STCompiler
|
||||
|
||||
+4
-2
@@ -1,5 +1,7 @@
|
||||
# compiler
|
||||
|
||||
ST / toml 编译器。CMake 目标:`compiler`(`STATIC`),依赖 `isa`。
|
||||
ST / toml 编译器。CMake 目标:`compiler`(`STATIC`),**不依赖 `isa`**——指令 opcode / 类型元数据 / FB 布局来自 `compiler/machine.toml`(见 [`指令配置.md`](../doc/isa/指令配置.md));自带 `.stb` 写实现。可执行入口 `STCompiler`。
|
||||
|
||||
边界与管线见 [`doc/compiler/编译管线.md`](../doc/compiler/编译管线.md)。语言规则见 [`doc/初步计划.md`](../doc/初步计划.md)。
|
||||
- **使用说明**(构建 / 用法 / 工程文件 / ST 子集 / 错误类别):[`doc/compiler/STCompiler使用说明.md`](../doc/compiler/STCompiler使用说明.md)
|
||||
- 边界与管线:[`doc/compiler/编译管线.md`](../doc/compiler/编译管线.md)
|
||||
- 语言规则:[`doc/初步计划.md`](../doc/初步计划.md)
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* @brief 打包:opcode + 三个 8 位字段拼成一条指令字。
|
||||
* @param opcode 操作码(低 8 位)
|
||||
* @param rd 目标寄存器 / 条件寄存器(第 8..15 位)
|
||||
* @param a 源寄存器 / 立即数低 8 位 / 偏移低 8 位(第 16..23 位)
|
||||
* @param b 源寄存器 / 立即数高 8 位 / 偏移高 8 位(第 24..31 位)
|
||||
* @return 打包后的指令字(小端 u32)
|
||||
* @details 布局与执行器 isa 一致,保证编译器产出的字节可被执行。
|
||||
*/
|
||||
Instr pack(uint8_t opcode, uint8_t rd, uint8_t a, uint8_t b);
|
||||
|
||||
/// @brief 取操作码(低 8 位)。
|
||||
uint8_t op_of(Instr w);
|
||||
/// @brief 取 rd 字段(第 8..15 位)。
|
||||
uint8_t rd_of(Instr w);
|
||||
/// @brief 取 a 字段(第 16..23 位)。
|
||||
uint8_t a_of(Instr w);
|
||||
/// @brief 取 b 字段(第 24..31 位)。
|
||||
uint8_t b_of(Instr w);
|
||||
/// @brief a|b 拼 16 位无符号数(const_id / slot / fn_id)。
|
||||
uint16_t imm16_of(Instr w);
|
||||
/// @brief a|b 为有符号相对偏移(单位:指令条数,相对下一条指令)。
|
||||
int16_t off16_of(Instr w);
|
||||
|
||||
/**
|
||||
* @brief 配置驱动反汇编:按 machine.toml 的 format 输出一行文本。
|
||||
* @param cfg 机器配置(opcode 名 / format / 参数名来源)
|
||||
* @param w 指令字
|
||||
* @param out 输出缓冲
|
||||
* @param cap 缓冲容量(含 '\0');cap==0 时直接返回、不写 out
|
||||
* @details 文本格式与 Doc/isa/指令与映像.md 一致(如 `MOVE r1, r2`、
|
||||
* `LOADK r0, 3`、`JMP +4`、`JT r0, -1`、`CALL 1`、`CAL_TON 0`、`RET`);
|
||||
* 未知操作码输出 `??? 0x<hex>`。
|
||||
*/
|
||||
void disasm(const MachineConfig& cfg, Instr w, char* out, size_t cap);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @file Codegen.h
|
||||
* @brief 寄存器码生成(12.8,切片 1:MOVE / LOADK / RET)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 代码生成:输入 Project + Unit(AST)+ LinkResult,输出 .stb 映像字节。
|
||||
* 寄存器分配:r0 结果、r1..r7 参数、r8+ 变量/临时;跳转偏移相对下一条。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "compiler/Linker.h"
|
||||
#include "compiler/MachineConfig.h"
|
||||
#include "compiler/Parser.h"
|
||||
#include "compiler/Project.h"
|
||||
|
||||
namespace compiler {
|
||||
|
||||
/**
|
||||
* @brief 编译工程为 .stb 映像字节(在链接 + 类型检查成功后调用)。
|
||||
* @param proj 工程定义(cycle_limit / dt_ms / 哈希)
|
||||
* @param units 全部源文件的 AST
|
||||
* @param link 链接结果(POU 顺序 / 全局符号 / FB 布局)
|
||||
* @param cfg 机器定义(machine.toml;指令 opcode / 类型 tag / FB 布局)
|
||||
* @param image 输出映像字节
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false 失败(err 前缀 "codegen error")
|
||||
* @details 指令 opcode / 类型 tag / FB 布局全部来自 machine.toml(cfg)。
|
||||
*/
|
||||
bool codegen_project(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
const LinkResult& link, const MachineConfig& cfg,
|
||||
std::vector<uint8_t>* image, std::string* err);
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
/**
|
||||
* @file Lexer.h
|
||||
* @brief ST 子集词法分析
|
||||
* @details 本文件是词法层对外接口:定义 token 类型(Tok)、token 载体
|
||||
* (Token)与两个入口(lex / lex_file)。扫描器实现见 Lexer.cpp,
|
||||
* 关键字与 token 集见 Doc/compiler/词法.md。
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*/
|
||||
@@ -13,79 +16,105 @@
|
||||
|
||||
namespace compiler {
|
||||
|
||||
// token 类型。关键字与 token 集见 Doc/compiler/词法.md。
|
||||
/**
|
||||
* @brief token 类型枚举。
|
||||
* @details 关键字与 token 集见 Doc/compiler/词法.md;关键字共 25 个,
|
||||
* 与 12.1 冻结表一致(12.5 修订补入 THEN/DO)。顺序冻结后
|
||||
* 不得插值改动,只能追加到末尾。
|
||||
*/
|
||||
enum class Tok {
|
||||
// 关键字(25 个,与 12.1 冻结表一致)
|
||||
PROGRAM,
|
||||
FUNCTION,
|
||||
FUNCTION_BLOCK,
|
||||
END_PROGRAM,
|
||||
END_FUNCTION,
|
||||
END_FUNCTION_BLOCK,
|
||||
VAR,
|
||||
VAR_INPUT,
|
||||
VAR_OUTPUT,
|
||||
VAR_GLOBAL,
|
||||
VAR_EXTERNAL,
|
||||
END_VAR,
|
||||
BOOL,
|
||||
INT,
|
||||
TIME,
|
||||
IF,
|
||||
ELSIF,
|
||||
ELSE,
|
||||
END_IF,
|
||||
WHILE,
|
||||
END_WHILE,
|
||||
THEN, // 12.5 修订:IF/WHILE 语法需要,补入关键字表
|
||||
DO, // 同上
|
||||
AND,
|
||||
OR,
|
||||
NOT,
|
||||
TRUE,
|
||||
FALSE,
|
||||
TON,
|
||||
TOF,
|
||||
CTU,
|
||||
PROGRAM, ///< PROGRAM 声明起始
|
||||
FUNCTION, ///< FUNCTION 声明起始
|
||||
FUNCTION_BLOCK, ///< FUNCTION_BLOCK 声明起始
|
||||
END_PROGRAM, ///< PROGRAM 结束
|
||||
END_FUNCTION, ///< FUNCTION 结束
|
||||
END_FUNCTION_BLOCK, ///< FUNCTION_BLOCK 结束
|
||||
VAR, ///< 局部变量段(VAR)起始
|
||||
VAR_INPUT, ///< 输入变量段起始
|
||||
VAR_OUTPUT, ///< 输出变量段起始
|
||||
VAR_GLOBAL, ///< 全局变量段起始
|
||||
VAR_EXTERNAL, ///< 外部变量段起始
|
||||
END_VAR, ///< 变量段结束
|
||||
BOOL, ///< 布尔类型
|
||||
INT, ///< 整数类型
|
||||
TIME, ///< 时间类型
|
||||
IF, ///< IF 关键字
|
||||
ELSIF, ///< ELSIF 关键字
|
||||
ELSE, ///< ELSE 关键字
|
||||
END_IF, ///< IF 结束
|
||||
WHILE, ///< WHILE 关键字
|
||||
END_WHILE, ///< WHILE 结束
|
||||
THEN, ///< IF/ELSIF 分支引导(12.5 修订补入关键字表)
|
||||
DO, ///< WHILE 循环体引导(12.5 修订补入关键字表)
|
||||
AND, ///< 逻辑与
|
||||
OR, ///< 逻辑或
|
||||
NOT, ///< 逻辑非(前缀)
|
||||
TRUE, ///< 布尔真字面量
|
||||
FALSE, ///< 布尔假字面量
|
||||
TON, ///< 内建 FB 类型名
|
||||
TOF, ///< 内建 FB 类型名
|
||||
TP, ///< 内建 FB 类型名
|
||||
CTU, ///< 内建 FB 类型名
|
||||
CTD, ///< 内建 FB 类型名
|
||||
CTUD, ///< 内建 FB 类型名
|
||||
R_TRIG, ///< 内建 FB 类型名
|
||||
F_TRIG, ///< 内建 FB 类型名
|
||||
// 字面量
|
||||
IDENT, // text = 小写名
|
||||
INT_LIT, // int_value
|
||||
TIME_LIT, // int_value = 毫秒
|
||||
IDENT, ///< 标识符(text = 小写名)
|
||||
INT_LIT, ///< 整数字面量(int_value)
|
||||
TIME_LIT, ///< 时间字面量(int_value = 毫秒)
|
||||
// 符号
|
||||
ASSIGN, // :=
|
||||
EQ, // =
|
||||
NE, // <>
|
||||
LT, // <
|
||||
LE, // <=
|
||||
GT, // >
|
||||
GE, // >=
|
||||
PLUS, // +
|
||||
MINUS, // -
|
||||
STAR, // *
|
||||
SLASH, // /
|
||||
LPAREN, // (
|
||||
RPAREN, // )
|
||||
COMMA, // ,
|
||||
SEMI, // ;
|
||||
COLON, // :
|
||||
DOT, // .
|
||||
END, // 文件结束
|
||||
ASSIGN, ///< :=(赋值)
|
||||
EQ, ///< =(等于)
|
||||
NE, ///< <>(不等)
|
||||
LT, ///< <(小于)
|
||||
LE, ///< <=(小于等于)
|
||||
GT, ///< >(大于)
|
||||
GE, ///< >=(大于等于)
|
||||
PLUS, ///< +(加)
|
||||
MINUS, ///< -(减)
|
||||
STAR, ///< *(乘)
|
||||
SLASH, ///< /(除)
|
||||
LPAREN, ///< ((左括号)
|
||||
RPAREN, ///< )(右括号)
|
||||
COMMA, ///< ,(逗号)
|
||||
SEMI, ///< ;(分号)
|
||||
COLON, ///< :(冒号)
|
||||
DOT, ///< .(点,FB 字段访问)
|
||||
END, ///< 文件结束哨兵
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 单个 token:类型 + 文本/字面量值 + 源位置。
|
||||
* @details line/col 从 1 起,记录 token 起始字符的位置。
|
||||
*/
|
||||
struct Token {
|
||||
Tok type;
|
||||
std::string text; // 标识符/关键字的小写文本;字面量原文
|
||||
int64_t int_value; // INT_LIT / TIME_LIT(毫秒)
|
||||
std::string source_file;
|
||||
uint32_t line; // 从 1 起
|
||||
uint32_t col; // 从 1 起
|
||||
Tok type; ///< token 类型
|
||||
std::string text; ///< 标识符/关键字的小写文本;字面量原文
|
||||
int64_t int_value; ///< INT_LIT / TIME_LIT(毫秒)的字面量值
|
||||
std::string source_file; ///< 来源文件名
|
||||
uint32_t line; ///< 起始行(从 1 起)
|
||||
uint32_t col; ///< 起始列(从 1 起)
|
||||
};
|
||||
|
||||
// 对源文本 lex 成 token 流(含末尾 END)。失败返回 false,
|
||||
// err 前缀 "lex error",带文件与行列。
|
||||
/**
|
||||
* @brief 对源文本做词法分析,产出 token 流(含末尾 END)。
|
||||
* @param source_file 源文件名(写入每个 token 的 source_file 并用于报错)
|
||||
* @param content 源文本
|
||||
* @param out 输出 token 流
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false 失败(err 前缀 "lex error",带文件与行列)
|
||||
*/
|
||||
bool lex(const std::string& source_file, const std::string& content,
|
||||
std::vector<Token>* out, std::string* err);
|
||||
|
||||
// 读文件后 lex(文件不存在 / 读取失败也算 lex error)
|
||||
/**
|
||||
* @brief 读取文件后做词法分析。
|
||||
* @param path 文件路径
|
||||
* @param out 输出 token 流
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false 失败(文件不存在 / 读取失败也算 lex error)
|
||||
*/
|
||||
bool lex_file(const std::string& path, std::vector<Token>* out, std::string* err);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
/**
|
||||
* @file Linker.h
|
||||
* @brief 符号表与链接
|
||||
* @details 本文件定义链接阶段的输入 / 输出数据结构与对外接口(实现见 Linker.cpp):
|
||||
* - Symbol / FbField / FbLayout / SourceUnit / LinkResult:符号与布局数据结构
|
||||
* - link_project:链接主入口(校验 GVL、编全局槽号、解析 VAR_EXTERNAL、
|
||||
* FB 实例与字段、函数循环调用检测、io 校验),失败 err 前缀 "link error"
|
||||
* - load_unit:读取并解析一个 .st 文件成 SourceUnit
|
||||
* - dump_symbols:符号表文本输出(全局表 + 各 POU 局部表)
|
||||
* 流程与规则详见 Doc/compiler/符号表与链接.md。
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*/
|
||||
@@ -17,66 +24,106 @@
|
||||
|
||||
namespace compiler {
|
||||
|
||||
// 符号种类(与 Doc/初步计划.md 12.1 一致)
|
||||
/**
|
||||
* @brief 符号种类(与 Doc/初步计划.md 12.1 一致)。
|
||||
* @details 各成员含义:
|
||||
* - Global:全局变量(仅 gvl 顶层,编全局槽号)
|
||||
* - External:VAR_EXTERNAL 引用(接全局同一槽,类型必须一致)
|
||||
* - Local:POU 内部局部变量(12.8 分配槽)
|
||||
* - Input / Output:POU 输入 / 输出参数
|
||||
* - Pou:POU 登记(PROGRAM / FUNCTION / FUNCTION_BLOCK)
|
||||
* - FbInstance:FB 实例(带布局,内建或用户类型)
|
||||
* - Const:常量
|
||||
*/
|
||||
enum class SymbolKind { Global, External, Local, Input, Output, Pou, FbInstance, Const };
|
||||
|
||||
/**
|
||||
* @brief 一个符号(全局槽表项或 POU 局部符号)。
|
||||
*/
|
||||
struct Symbol {
|
||||
SymbolKind kind = SymbolKind::Local;
|
||||
std::string name; // 小写
|
||||
std::string type_name; // bool / int / time / fb 类型名(小写)
|
||||
uint32_t address = 0; // 全局槽号(Global/External);局部暂 0(12.8 分配)
|
||||
std::string source_file;
|
||||
uint32_t line = 0;
|
||||
uint32_t col = 0;
|
||||
SymbolKind kind = SymbolKind::Local; ///< 符号种类
|
||||
std::string name; ///< 符号名(小写)
|
||||
std::string type_name; ///< 类型名:bool / int / time / fb 类型名(小写)
|
||||
uint32_t address = 0; ///< 全局槽号(Global/External);局部暂 0(12.8 分配)
|
||||
std::string source_file; ///< 声明所在源文件
|
||||
uint32_t line = 0; ///< 声明行号
|
||||
uint32_t col = 0; ///< 声明列号
|
||||
};
|
||||
|
||||
// FB 类型字段(实例布局用;v1 字段类型仅 BOOL/INT/TIME)
|
||||
/**
|
||||
* @brief FB 类型字段(实例布局用;v1 字段类型仅 BOOL/INT/TIME)。
|
||||
*/
|
||||
struct FbField {
|
||||
std::string name;
|
||||
TypeKind type = TypeKind::Bool;
|
||||
std::string name; ///< 字段名
|
||||
TypeKind type = TypeKind::Bool; ///< 字段类型(仅标量)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 一个 FB 类型的字段布局(供实例分配与字段引用解析)。
|
||||
*/
|
||||
struct FbLayout {
|
||||
std::string type_name; // 类型名(小写)
|
||||
std::vector<FbField> fields; // 段序:input → output → 内部 var
|
||||
std::string type_name; ///< 类型名(小写)
|
||||
std::vector<FbField> fields; ///< 段序:input → output → 内部 var
|
||||
};
|
||||
|
||||
// 一个源文件的 AST(供链接输入)
|
||||
/**
|
||||
* @brief 一个源文件的 AST(供链接输入)。
|
||||
*/
|
||||
struct SourceUnit {
|
||||
std::string path;
|
||||
Unit ast;
|
||||
std::string path; ///< 源文件路径
|
||||
Unit ast; ///< 解析出的 AST
|
||||
};
|
||||
|
||||
// 链接结果
|
||||
/**
|
||||
* @brief 链接结果:全局槽表 + FB 类型布局 + 各 POU 作用域 + 函数表顺序。
|
||||
*/
|
||||
struct LinkResult {
|
||||
// 全局槽表:下标即槽号(声明顺序)
|
||||
std::vector<Symbol> globals;
|
||||
std::map<std::string, uint32_t> global_index; // 名 → 槽号
|
||||
std::vector<Symbol> globals; ///< 全局槽表:下标即槽号(声明顺序)
|
||||
std::map<std::string, uint32_t> global_index; ///< 名 → 槽号
|
||||
|
||||
// 用户 FB 类型布局(按类型名)
|
||||
std::map<std::string, FbLayout> fb_types;
|
||||
std::map<std::string, FbLayout> fb_types; ///< 用户 FB 类型布局(按类型名)
|
||||
|
||||
// 每 POU 的局部符号与 FB 实例
|
||||
/**
|
||||
* @brief 每 POU 的局部符号与 FB 实例。
|
||||
*/
|
||||
struct PouScope {
|
||||
std::string name;
|
||||
PouKind kind = PouKind::Program;
|
||||
std::vector<Symbol> syms; // local/input/output/external/fb_instance
|
||||
std::map<std::string, FbLayout> fb_instances; // 实例名 → 布局
|
||||
std::string name; ///< POU 名
|
||||
PouKind kind = PouKind::Program; ///< POU 种类
|
||||
std::vector<Symbol> syms; ///< local/input/output/external/fb_instance
|
||||
std::map<std::string, FbLayout> fb_instances; ///< 实例名 → 布局
|
||||
};
|
||||
std::vector<PouScope> scopes;
|
||||
std::vector<PouScope> scopes; ///< 全部 POU 作用域
|
||||
|
||||
// 函数表顺序(PROGRAM / FUNCTION / FUNCTION_BLOCK 名,收集序)
|
||||
std::vector<std::string> fn_order;
|
||||
std::vector<std::string> fn_order; ///< 函数表顺序(PROGRAM / FUNCTION / FUNCTION_BLOCK 名,收集序)
|
||||
};
|
||||
|
||||
// 链接一个工程:校验 GVL、定全局槽、解析 VAR_EXTERNAL、FB 实例与字段、
|
||||
// 函数循环调用检测、io 校验。失败返回 false,err 前缀 "link error"。
|
||||
/**
|
||||
* @brief 链接一个工程。
|
||||
* @details 校验 GVL、定全局槽、解析 VAR_EXTERNAL、FB 实例与字段、函数循环调用检测、
|
||||
* io 校验。失败返回 false,err 前缀 "link error"。
|
||||
* @param proj 工程定义(toml 解析结果)
|
||||
* @param units 全部源文件的 AST(compile_files 集合逐一 load_unit 得到)
|
||||
* @param out 链接结果(各字段先清空后填充)
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false 失败(err 已写)
|
||||
*/
|
||||
bool link_project(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
LinkResult* out, std::string* err);
|
||||
|
||||
// 读取并解析一个 .st 文件成 SourceUnit
|
||||
/**
|
||||
* @brief 读取并解析一个 .st 文件成 SourceUnit。
|
||||
* @param path .st 文件路径
|
||||
* @param out 输出 SourceUnit(path + ast)
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false 失败(透传 lex/syntax error)
|
||||
*/
|
||||
bool load_unit(const std::string& path, SourceUnit* out, std::string* err);
|
||||
|
||||
// 符号表文本:每行 "name kind type address file"(全局表 + 各 POU 局部表)
|
||||
/**
|
||||
* @brief 符号表文本。
|
||||
* @details 每行 "name kind type address file"(全局表 + 各 POU 局部表)。
|
||||
* @param r 链接结果
|
||||
* @return 多行文本:全局表逐行;每个 POU 以 "[名字]" 开头后接其局部符号行
|
||||
*/
|
||||
std::string dump_symbols(const LinkResult& r);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* @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 {
|
||||
|
||||
/**
|
||||
* @brief 内建基元(编译器内建,C 语义)。
|
||||
* @details 元数据写在代码而非 machine.toml;配置类型的 base 必须命中本表。
|
||||
*/
|
||||
struct PrimType {
|
||||
const char* name; ///< 基元名(bit / int8..uint64 / float32 / float64)
|
||||
uint32_t width; ///< 宽度(字节)
|
||||
bool is_signed; ///< 是否带符号
|
||||
bool is_float; ///< 是否浮点
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 配置类型 = 基元别名(+ 值域约束 + .stb 契约 tag)。
|
||||
* @details 对应 machine.toml [[type]] 行:base 必为内建基元名;range 可选且
|
||||
* min ≤ max;tag 与 .stb 常量表契约一致(0=BOOL、1=INT、2=TIME,唯一)。
|
||||
*/
|
||||
struct ConfigType {
|
||||
std::string name; ///< 配置类型名(大写,如 BOOL/INT/TIME)
|
||||
std::string base; ///< 基元名(命中内建基元表)
|
||||
bool has_range = false; ///< 是否有值域约束
|
||||
int64_t range_min = 0; ///< 值域下界(含)
|
||||
int64_t range_max = 0; ///< 值域上界(含)
|
||||
uint32_t tag = 0; ///< .stb 常量表契约 tag
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 配置指令。
|
||||
* @details 对应 machine.toml [[op]] 行:opcode 唯一且 0..255;class 与 format
|
||||
* 互锁(INSTANCE ↔ CAL);params 数量与 format 期望一致。
|
||||
*/
|
||||
struct ConfigOp {
|
||||
std::string name; ///< 助记符(与 .stb / isa 一致)
|
||||
uint32_t opcode = 0; ///< 操作码(0..255,表内唯一)
|
||||
bool is_instance = false; ///< class:instance / plain
|
||||
std::string format; ///< 操作数形态:RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE
|
||||
std::vector<std::string> params; ///< 参数名(数量与 format 一致)
|
||||
bool enabled = false; ///< 是否启用
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 内建 FB 的一个字段。
|
||||
*/
|
||||
struct ConfigFbField {
|
||||
std::string name; ///< 字段名(如 in/pt/q/et/cu/cv)
|
||||
std::string type; ///< 配置类型名(BOOL/INT/TIME)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 内建功能块(FB)布局登记。
|
||||
* @details 对应 machine.toml [[fb]] 行;opcode 必须命中 instance 类 op 行。
|
||||
*/
|
||||
struct ConfigFb {
|
||||
std::string name; ///< FB 名(小写,如 ton/ctu)
|
||||
uint32_t opcode = 0; ///< 对应 op 行 opcode(CAL_*)
|
||||
std::vector<ConfigFbField> fields; ///< 字段列表(名称唯一,类型命中 type 表)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 机器定义:加载 machine.toml 并做强校验。
|
||||
* @details 编译器指令集 / 类型的唯一事实来源;校验失败时 ok() 为 false,
|
||||
* err 前缀 "machine error"。vm 不读配置(内建全指令集)。
|
||||
*/
|
||||
class MachineConfig {
|
||||
public:
|
||||
/**
|
||||
* @brief 加载 + 强校验。
|
||||
* @param path machine.toml 路径
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false(err 已写,前缀 "machine error")
|
||||
* @details 强校验项:字段必填 / type.base 命中内建基元·range 合法·tag 契约 /
|
||||
* op opcode 唯一·类别-格式互锁 / fb.opcode 与 op 一致·字段类型命中
|
||||
* type 表(清单见文件头)。
|
||||
*/
|
||||
bool load(const std::string& path, std::string* err);
|
||||
|
||||
/// @brief 加载是否成功(校验通过)。
|
||||
bool ok() const { return ok_; }
|
||||
/// @brief 型号名(meta.name,参与 .stb 型号标识)。
|
||||
const std::string& model_name() const { return model_name_; }
|
||||
/// @brief 指令集版本(meta.version,参与 .stb 型号标识)。
|
||||
uint32_t version() const { return version_; }
|
||||
|
||||
/// @brief 配置类型表([[type]])。
|
||||
const std::vector<ConfigType>& types() const { return types_; }
|
||||
/// @brief 配置指令表([[op]])。
|
||||
const std::vector<ConfigOp>& ops() const { return ops_; }
|
||||
/// @brief 内建 FB 表([[fb]])。
|
||||
const std::vector<ConfigFb>& fbs() const { return fbs_; }
|
||||
|
||||
/**
|
||||
* @brief 按配置类型名查类型行。
|
||||
* @param name 配置类型名
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigType* find_type(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief 按助记符查指令行。
|
||||
* @param name 助记符(如 "MOVE")
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigOp* find_op(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief 按 opcode 查指令行。
|
||||
* @param opcode 操作码(0..255)
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigOp* find_op_by_code(uint32_t opcode) const;
|
||||
|
||||
/**
|
||||
* @brief 查询某 opcode 是否启用。
|
||||
* @param opcode 操作码
|
||||
* @return 指令存在且 enabled 为 true;未知 opcode 返回 false
|
||||
*/
|
||||
bool op_enabled(uint32_t opcode) const;
|
||||
|
||||
/**
|
||||
* @brief 按名称查内建 FB。
|
||||
* @param name FB 名(小写,如 "ton")
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigFb* find_fb(const std::string& name) const;
|
||||
|
||||
/// @brief 内建基元表(bit / int8..uint64 / float32 / float64)。
|
||||
static const std::vector<PrimType>& prims();
|
||||
|
||||
/**
|
||||
* @brief 按名称查内建基元。
|
||||
* @param name 基元名
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
static const PrimType* find_prim(const std::string& name);
|
||||
|
||||
private:
|
||||
bool ok_ = false; ///< 加载成功标志
|
||||
std::string model_name_; ///< 型号名(meta.name)
|
||||
uint32_t version_ = 0; ///< 指令集版本(meta.version)
|
||||
std::vector<ConfigType> types_; ///< 配置类型表
|
||||
std::vector<ConfigOp> ops_; ///< 配置指令表
|
||||
std::vector<ConfigFb> fbs_; ///< 内建 FB 表
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
/**
|
||||
* @file Parser.h
|
||||
* @brief ST 子集递归下降语法分析(AST 定义 + 入口)
|
||||
* @details 本文件定义语法层全部公开类型(AST 节点与相关枚举)与两个
|
||||
* 解析入口(parse_pous / parse_pous_file)。解析器实现见
|
||||
* Parser.cpp,文法详见 Doc/compiler/语法.md。
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*/
|
||||
@@ -16,118 +19,196 @@ namespace compiler {
|
||||
|
||||
// ---- 类型引用 ----
|
||||
|
||||
/**
|
||||
* @brief 类型种类。
|
||||
* @details 枚举值:Bool=内建 BOOL;Int=内建 INT;Time=内建 TIME;
|
||||
* FbBuiltin=内建 FB 类型(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG);
|
||||
* FbUser=用户 FB 类型(存在性由 12.6 链接层校验)。
|
||||
*/
|
||||
enum class TypeKind { Bool, Int, Time, FbBuiltin, FbUser };
|
||||
|
||||
/**
|
||||
* @brief 类型引用:种类 + (用户 FB 的)类型名。
|
||||
*/
|
||||
struct TypeRef {
|
||||
TypeKind kind = TypeKind::Bool;
|
||||
std::string name; // FbUser 时为用户 FB 类型名(小写)
|
||||
TypeKind kind = TypeKind::Bool; ///< 类型种类
|
||||
std::string name; ///< FbUser 时为用户 FB 类型名(小写)
|
||||
};
|
||||
|
||||
// ---- 变量段 ----
|
||||
|
||||
/**
|
||||
* @brief 单个变量声明。
|
||||
* @details 初值可选(v1 仅字面量);line/col 记录声明处位置(报错用)。
|
||||
*/
|
||||
struct VarDecl {
|
||||
std::string name;
|
||||
TypeRef type;
|
||||
bool has_init = false; // 可选初值(v1 仅字面量)
|
||||
int64_t init_value = 0; // BOOL 0/1、INT 值、TIME 毫秒
|
||||
uint32_t line = 0;
|
||||
uint32_t col = 0;
|
||||
std::string name; ///< 变量名(小写)
|
||||
TypeRef type; ///< 类型引用
|
||||
bool has_init = false; ///< 是否有可选初值(v1 仅字面量)
|
||||
int64_t init_value = 0; ///< BOOL 0/1、INT 值、TIME 毫秒
|
||||
uint32_t line = 0; ///< 声明起始行
|
||||
uint32_t col = 0; ///< 声明起始列
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 变量段种类。
|
||||
* @details 枚举值:Local=VAR、Input=VAR_INPUT、Output=VAR_OUTPUT、
|
||||
* Global=VAR_GLOBAL、External=VAR_EXTERNAL。
|
||||
*/
|
||||
enum class VarSection { Local, Input, Output, Global, External };
|
||||
|
||||
/**
|
||||
* @brief 一段 VAR_* … END_VAR:段种类 + 声明列表。
|
||||
*/
|
||||
struct VarBlock {
|
||||
VarSection section = VarSection::Local;
|
||||
std::vector<VarDecl> vars;
|
||||
VarSection section = VarSection::Local; ///< 段种类
|
||||
std::vector<VarDecl> vars; ///< 段内声明(按出现顺序)
|
||||
};
|
||||
|
||||
// ---- 表达式 ----
|
||||
|
||||
/**
|
||||
* @brief 表达式节点种类。
|
||||
* @details 二目节点(And/Or/Cmp/Add/Sub/Mul/Div)用 lhs/rhs/op;
|
||||
* 一元节点(Not/Neg)用 operand;AND/OR 的短路语义保留在
|
||||
* AST 中,由 12.8 编译为跳转。
|
||||
*/
|
||||
enum class ExprKind {
|
||||
LitBool, // int_value 0/1
|
||||
LitInt, // int_value
|
||||
LitTime, // int_value 毫秒
|
||||
VarRef, // name
|
||||
Field, // name.field
|
||||
Not, // operand
|
||||
And, Or, // lhs, rhs(短路语义保留到 12.8)
|
||||
Cmp, Add, Sub, Mul, Div, // lhs, rhs,op
|
||||
Neg, // operand(一元负号)
|
||||
Call, // name + args(函数调用,有返回值)
|
||||
LitBool, ///< 布尔字面量(int_value 0/1)
|
||||
LitInt, ///< 整数字面量(int_value)
|
||||
LitTime, ///< 时间字面量(int_value 毫秒)
|
||||
VarRef, ///< 变量引用(name)
|
||||
Field, ///< FB 字段访问(name.field)
|
||||
Not, ///< 逻辑非(operand)
|
||||
And, Or, ///< 逻辑与 / 逻辑或(lhs, rhs;短路语义保留到 12.8)
|
||||
Cmp, Add, Sub, Mul, Div, ///< 二目:比较 / 加减 / 乘除(lhs, rhs,op 为运算符)
|
||||
Neg, ///< 一元负号(operand)
|
||||
Call, ///< 函数调用(name + args,有返回值)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 二目运算符种类。
|
||||
* @details 枚举值:Eq/Ne/Lt/Le/Gt/Ge=比较符(= <> < <= > >=);
|
||||
* Add/Sub/Mul/Div=算术符(+ - * /)。
|
||||
*/
|
||||
enum class BinOp { Eq, Ne, Lt, Le, Gt, Ge, Add, Sub, Mul, Div };
|
||||
|
||||
/**
|
||||
* @brief 表达式 AST 节点(带 kind 标签的联合式结构)。
|
||||
* @details 按 kind 解释字段:字面量用 int_value;VarRef/Field/Call 用
|
||||
* name(+field、args);二目用 lhs/rhs/op;一元 Not/Neg 用
|
||||
* operand。
|
||||
*/
|
||||
struct Expr {
|
||||
ExprKind kind = ExprKind::LitBool;
|
||||
BinOp op = BinOp::Eq; // 二目运算
|
||||
std::string name; // VarRef / Field 对象 / Call 函数名
|
||||
std::string field; // Field 字段名
|
||||
int64_t int_value = 0; // 字面量值
|
||||
std::unique_ptr<Expr> lhs;
|
||||
std::unique_ptr<Expr> rhs;
|
||||
std::unique_ptr<Expr> operand; // Not / Neg
|
||||
std::vector<std::unique_ptr<Expr>> args; // Call 实参
|
||||
ExprKind kind = ExprKind::LitBool; ///< 节点种类
|
||||
BinOp op = BinOp::Eq; ///< 二目运算(Cmp/Add/Sub/Mul/Div 用)
|
||||
std::string name; ///< VarRef / Field 对象 / Call 函数名
|
||||
std::string field; ///< Field 字段名
|
||||
int64_t int_value = 0; ///< 字面量值(BOOL 0/1、INT、TIME 毫秒)
|
||||
std::unique_ptr<Expr> lhs; ///< 二目左操作数
|
||||
std::unique_ptr<Expr> rhs; ///< 二目右操作数
|
||||
std::unique_ptr<Expr> operand; ///< Not / Neg 的操作数
|
||||
std::vector<std::unique_ptr<Expr>> args; ///< Call 实参
|
||||
};
|
||||
|
||||
// ---- 语句 ----
|
||||
|
||||
/**
|
||||
* @brief 语句节点种类。
|
||||
* @details 枚举值:Assign=赋值;If=IF 语句(含 ELSIF/ELSE);
|
||||
* While=WHILE 语句;FbCall=FB 调用语句(无返回值)。
|
||||
*/
|
||||
enum class StmtKind { Assign, If, While, FbCall };
|
||||
|
||||
struct Stmt; // 前置声明(IfBranch / Stmt 相互引用)
|
||||
/** @brief 语句节点(前置声明;Stmt 与 IfBranch 相互引用)。 */
|
||||
struct Stmt;
|
||||
|
||||
/**
|
||||
* @brief FB 调用实参:命名参数(名 := 表达式)。
|
||||
*/
|
||||
struct FbArg {
|
||||
std::string name;
|
||||
std::unique_ptr<Expr> value;
|
||||
std::string name; ///< 形参名(小写)
|
||||
std::unique_ptr<Expr> value; ///< 实参表达式
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief IF 语句的一个 ELSIF 分支:条件 + 分支体。
|
||||
*/
|
||||
struct IfBranch {
|
||||
std::unique_ptr<Expr> cond;
|
||||
std::vector<Stmt> body;
|
||||
std::unique_ptr<Expr> cond; ///< 分支条件
|
||||
std::vector<Stmt> body; ///< 分支体(条件成立时执行)
|
||||
};
|
||||
|
||||
// 前置声明:Stmt 内部自引用
|
||||
/**
|
||||
* @brief 语句 AST 节点:按 kind 解释各字段组。
|
||||
* @details Assign 用 target(+field);If/While 用 cond/body
|
||||
* (+elsifs、else_body);FbCall 用 instance/args。
|
||||
* fb.field 左值 v1 禁止赋值,在语法层拒绝。
|
||||
*/
|
||||
struct Stmt {
|
||||
StmtKind kind = StmtKind::Assign;
|
||||
StmtKind kind = StmtKind::Assign; ///< 语句种类
|
||||
// Assign
|
||||
std::string target; // 左值标识符
|
||||
bool target_is_field = false; // 左值 fb.field(v1 禁止赋值,语法层拒绝)
|
||||
std::string field;
|
||||
std::unique_ptr<Expr> value;
|
||||
std::string target; ///< 左值标识符
|
||||
bool target_is_field = false; ///< 左值 fb.field(v1 禁止赋值,语法层拒绝)
|
||||
std::string field; ///< 左值字段名
|
||||
std::unique_ptr<Expr> value; ///< 右值表达式
|
||||
// If / While
|
||||
std::unique_ptr<Expr> cond;
|
||||
std::vector<Stmt> body;
|
||||
std::vector<IfBranch> elsifs;
|
||||
std::vector<Stmt> else_body;
|
||||
std::unique_ptr<Expr> cond; ///< 条件表达式
|
||||
std::vector<Stmt> body; ///< 主分支体
|
||||
std::vector<IfBranch> elsifs; ///< ELSIF 分支列表
|
||||
std::vector<Stmt> else_body; ///< ELSE 分支体
|
||||
// FbCall
|
||||
std::string instance;
|
||||
std::vector<FbArg> args;
|
||||
std::string instance; ///< FB 实例名
|
||||
std::vector<FbArg> args; ///< 命名实参列表
|
||||
};
|
||||
|
||||
// ---- POU ----
|
||||
|
||||
/**
|
||||
* @brief POU 种类。
|
||||
* @details 枚举值:Program=PROGRAM;Function=FUNCTION(可有返回类型);
|
||||
* FunctionBlock=FUNCTION_BLOCK。
|
||||
*/
|
||||
enum class PouKind { Program, Function, FunctionBlock };
|
||||
|
||||
/**
|
||||
* @brief 程序组织单元(POU):外壳 + 变量段 + 语句体。
|
||||
*/
|
||||
struct POU {
|
||||
PouKind kind = PouKind::Program;
|
||||
std::string name;
|
||||
TypeRef result_type; // FUNCTION 才有
|
||||
std::vector<VarBlock> blocks;
|
||||
std::vector<Stmt> body;
|
||||
std::string source_file;
|
||||
PouKind kind = PouKind::Program; ///< POU 种类
|
||||
std::string name; ///< POU 名(小写)
|
||||
TypeRef result_type; ///< 返回类型(仅 FUNCTION 才有)
|
||||
std::vector<VarBlock> blocks; ///< 变量段(按声明顺序)
|
||||
std::vector<Stmt> body; ///< 语句体
|
||||
std::string source_file; ///< 来源文件名
|
||||
};
|
||||
|
||||
// 一个 .st 文件的解析结果:GVL 文件形态(无 POU)时只有 globals 非空
|
||||
/**
|
||||
* @brief 一个 .st 文件的解析结果。
|
||||
* @details GVL 文件形态(无 POU)时只有 globals 非空。
|
||||
*/
|
||||
struct Unit {
|
||||
std::vector<VarBlock> globals; // 顶层 VAR_GLOBAL 段(仅 GVL 文件)
|
||||
std::vector<POU> pous;
|
||||
std::vector<VarBlock> globals; ///< 顶层 VAR_GLOBAL 段(仅 GVL 文件)
|
||||
std::vector<POU> pous; ///< POU 列表(按出现顺序)
|
||||
};
|
||||
|
||||
// 解析一个 .st 文件的全部内容(顶层 VAR_GLOBAL 段或 POU)。
|
||||
// 失败返回 false,err 前缀 "syntax error",带文件与行列。
|
||||
/**
|
||||
* @brief 解析一个 .st 文件的全部内容(顶层 VAR_GLOBAL 段或 POU)。
|
||||
* @param source_file 源文件名(报错用)
|
||||
* @param content 源文本
|
||||
* @param out 输出 Unit(globals / pous)
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false 失败(err 前缀 "syntax error",带文件与行列)
|
||||
*/
|
||||
bool parse_pous(const std::string& source_file, const std::string& content,
|
||||
Unit* out, std::string* err);
|
||||
|
||||
// 读文件后解析
|
||||
/**
|
||||
* @brief 读取文件后解析。
|
||||
* @param path 文件路径
|
||||
* @param out 输出 Unit(globals / pous)
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false 失败(文件打不开也算 syntax error)
|
||||
*/
|
||||
bool parse_pous_file(const std::string& path, Unit* out, std::string* err);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
/**
|
||||
* @file Project.h
|
||||
* @brief 工程定义与 project.toml 解析(schema 校验)
|
||||
* @details 本文件定义工程数据结构与解析接口(实现见 Project.cpp):
|
||||
* - IoBinding / Project:project.toml 的解析结果(I/O 绑定不创造变量,
|
||||
* slot 由链接结果解析)
|
||||
* - parse_project:解析并校验 project.toml,错误带稳定类别前缀 + 行号
|
||||
* - compile_files:编译文件集合(files.st ∪ gvl.file,去重)
|
||||
* - compute_project_hash:校验文件存在并计算工程哈希,缺文件前缀 "file missing"
|
||||
* 字段约定与 Doc/初步计划.md 12.1 的 toml 字段表一一对应。
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*/
|
||||
@@ -11,32 +18,63 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "isa/Image.h"
|
||||
|
||||
namespace compiler {
|
||||
|
||||
// 与 Doc/初步计划.md 12.1 的 toml 字段表一一对应。
|
||||
// 字段白名单 / 必填 / io 完整性在 parse_project 内校验。
|
||||
struct Project {
|
||||
std::string name; // [project] 必填
|
||||
std::string entry; // 必填,第一版必须 "program MAIN"
|
||||
uint32_t cycle_limit = 0; // 必填 > 0
|
||||
uint32_t dt_ms = 0; // 必填 > 0
|
||||
std::vector<std::string> files_st; // [files] 必填非空
|
||||
std::string gvl_file; // [gvl] 可选;无则空串
|
||||
std::vector<isa::IoBinding> io; // [[io.*]] 可选;slot 12.6 才解析,此处填 0
|
||||
std::string base_dir; // toml 所在目录(解析相对路径用)
|
||||
/**
|
||||
* @brief I/O 绑定(project.toml [[io.*]];不创造变量,slot 由链接结果解析)。
|
||||
*/
|
||||
struct IoBinding {
|
||||
std::string var; ///< 绑定的全局变量名(须已在 GVL 声明)
|
||||
uint32_t slot = 0; ///< 全局槽号(12.6 链接阶段解析,此处填 0)
|
||||
uint32_t channel = 0; ///< 通道号
|
||||
uint32_t bit = 0; ///< 位号
|
||||
bool is_input = false; ///< true = [[io.input]],false = [[io.output]]
|
||||
};
|
||||
|
||||
// 解析并校验 project.toml。成功返回 true;失败返回 false 并写 err,
|
||||
// err 带稳定类别前缀(unknown key / missing field / invalid value / bad entry)+ 行号。
|
||||
/**
|
||||
* @brief 工程定义。
|
||||
* @details 与 Doc/初步计划.md 12.1 的 toml 字段表一一对应;
|
||||
* 字段白名单 / 必填 / io 完整性在 parse_project 内校验。
|
||||
*/
|
||||
struct Project {
|
||||
std::string name; ///< [project] 必填
|
||||
std::string entry; ///< 必填,第一版必须 "program MAIN"
|
||||
uint32_t cycle_limit = 0; ///< 必填 > 0
|
||||
uint32_t dt_ms = 0; ///< 必填 > 0
|
||||
std::vector<std::string> files_st; ///< [files] 必填非空
|
||||
std::string gvl_file; ///< [gvl] 可选;无则空串
|
||||
std::vector<IoBinding> io; ///< [[io.*]] 可选;slot 12.6 才解析,此处填 0
|
||||
std::string base_dir; ///< toml 所在目录(解析相对路径用)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 解析并校验 project.toml。
|
||||
* @details 成功返回 true;失败返回 false 并写 err,err 带稳定类别前缀
|
||||
* (parse error / unknown key / missing field / invalid value / bad entry)
|
||||
* + 行号。
|
||||
* @param toml_path project.toml 路径
|
||||
* @param out 输出 Project(base_dir 先填,其余字段由各段解析填充)
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false 失败(err 已写)
|
||||
*/
|
||||
bool parse_project(const std::string& toml_path, Project* out, std::string* err);
|
||||
|
||||
// 编译文件集合:files.st ∪ gvl.file,去重(gvl 已在 files.st 则跳过)。
|
||||
// 保持 files.st 顺序,gvl 追加在后;相对路径以 base_dir 为基准解析。
|
||||
/**
|
||||
* @brief 编译文件集合:files.st ∪ gvl.file,去重(gvl 已在 files.st 则跳过)。
|
||||
* @details 保持 files.st 顺序,gvl 追加在后;相对路径以 base_dir 为基准解析。
|
||||
* @param p 工程定义
|
||||
* @return 编译文件路径集合
|
||||
*/
|
||||
std::vector<std::string> compile_files(const Project& p);
|
||||
|
||||
// 校验全部文件存在并计算工程哈希:集合按路径排序,对内容做 FNV-1a 64 增量;
|
||||
// 空集合 = basis(isa::kFnvBasis)。缺文件报错前缀 "file missing"。
|
||||
/**
|
||||
* @brief 校验全部文件存在并计算工程哈希。
|
||||
* @details 集合按路径排序,对内容做 FNV-1a 64 增量;空集合 = basis(Stb::kFnvBasis)。
|
||||
* 缺文件报错前缀 "file missing"。
|
||||
* @param p 工程定义
|
||||
* @param hash 输出工程哈希
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false 文件缺失(err 已写)
|
||||
*/
|
||||
bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
/**
|
||||
* @file Stb.h
|
||||
* @brief 编译器自带的 .stb 映像规范(写侧)+ 只读视图 + FNV-1a
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 执行器(vm)各有自己的读写实现;格式契约见 Doc/isa/指令与映像.md。
|
||||
* 12.13 修订:头 104 = 原 72 + 型号标识[32] @72,文件尾 SHA-256[32]。
|
||||
* 常量表 tag 契约:0=BOOL、1=INT、2=TIME;工程哈希用 FNV-1a 64。
|
||||
*/
|
||||
|
||||
#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])----
|
||||
/// 映像魔数:"STSC" 小端。
|
||||
static const uint32_t kMagic = 0x43545353u;
|
||||
/// 映像格式版本。
|
||||
static const uint32_t kVersion = 1;
|
||||
/// 映像头字节数(原 72 字段 + 型号标识[32] @72)。
|
||||
static const size_t kHeaderSize = 104;
|
||||
/// 常量表一行字节数(tag:4 + value:8)。
|
||||
static const size_t kConstEntrySize = 12;
|
||||
/// 函数表一行字节数(nregs / code_offset / code_len 各 4)。
|
||||
static const size_t kFuncRowSize = 12;
|
||||
/// SHA-256 摘要长度(文件尾)。
|
||||
static const size_t kSha256Size = 32;
|
||||
/// 型号标识长度(头内 @72,不足补 '\0')。
|
||||
static const size_t kModelIdSize = 32;
|
||||
/// FNV-1a 64 哈希基值。
|
||||
static const uint64_t kFnvBasis = 0xcbf29ce484222325ull;
|
||||
/// FNV-1a 64 哈希素数。
|
||||
static const uint64_t kFnvPrime = 0x100000001b3ull;
|
||||
|
||||
/**
|
||||
* @brief 常量表一行。
|
||||
* @details tag 契约:0=BOOL、1=INT、2=TIME。
|
||||
*/
|
||||
struct ConstEntry {
|
||||
uint32_t tag = 0; ///< 类型 tag(0=BOOL、1=INT、2=TIME)
|
||||
uint64_t value = 0; ///< 常量值
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief FNV-1a 64 增量哈希更新(工程哈希,非密码学)。
|
||||
* @param h 当前哈希值(首轮传 kFnvBasis)
|
||||
* @param data 输入字节
|
||||
* @param len 字节数
|
||||
* @return 更新后的哈希值
|
||||
*/
|
||||
uint64_t fnv1a64_update(uint64_t h, const uint8_t* data, size_t len);
|
||||
/**
|
||||
* @brief FNV-1a 64 一次性哈希(工程哈希)。
|
||||
* @param data 输入字节
|
||||
* @param len 字节数
|
||||
* @return 哈希值
|
||||
*/
|
||||
uint64_t fnv1a64(const uint8_t* data, size_t len);
|
||||
|
||||
/**
|
||||
* @brief 一次性 SHA-256(文件完整性校验)。
|
||||
* @param data 输入字节
|
||||
* @param len 字节数
|
||||
* @param out 32 字节摘要输出(大端)
|
||||
*/
|
||||
void sha256(const uint8_t* data, size_t len, uint8_t out[kSha256Size]);
|
||||
|
||||
/**
|
||||
* @brief 型号标识:name + version 拼 32 字节 ASCII(如 "STATOR1")。
|
||||
* @param name 型号名
|
||||
* @param version 版本号
|
||||
* @param out 32 字节输出缓冲
|
||||
* @details 不足补 '\0',超长截断。
|
||||
*/
|
||||
void fill_model_id(const std::string& name, uint32_t version, char out[kModelIdSize]);
|
||||
|
||||
/**
|
||||
* @brief .stb 映像只读视图(--disasm 用)。
|
||||
* @details from() 时校验魔数/版本/段边界,之后各访问器只读;
|
||||
* 失败时 ok() == false,error() 取原因。
|
||||
*/
|
||||
class StbView {
|
||||
public:
|
||||
/// @brief 函数表一行(nregs / code_offset / code_len)。
|
||||
struct FuncRow {
|
||||
uint32_t nregs = 0; ///< 帧寄存器数(含变量与临时)
|
||||
uint32_t code_offset = 0; ///< 字节码段内偏移(4 字节对齐)
|
||||
uint32_t code_len = 0; ///< 指令条数
|
||||
};
|
||||
|
||||
/// 从原始字节构造(不拷贝,调用方保证生命周期)。
|
||||
static StbView from(const uint8_t* buf, size_t len);
|
||||
/// 从 vector 构造(转发 from(buf.data(), buf.size()))。
|
||||
static StbView from(const std::vector<uint8_t>& buf);
|
||||
|
||||
/// 解析是否成功。
|
||||
bool ok() const { return ok_; }
|
||||
/// 失败原因(成功时为空串)。
|
||||
const std::string& error() const { return err_; }
|
||||
|
||||
/// 周期上限(头 @8)。
|
||||
uint32_t cycle_limit() const { return cycle_limit_; }
|
||||
/// 扫描周期 dt_ms(头 @12)。
|
||||
uint32_t dt_ms() const { return dt_ms_; }
|
||||
/// 工程哈希(FNV-1a 64,头 @16)。
|
||||
uint64_t project_hash() const { return project_hash_; }
|
||||
/// 入口函数 fn_id(头 @24)。
|
||||
uint32_t entry_fn_id() const { return entry_fn_id_; }
|
||||
/// 全局槽数(头 @28)。
|
||||
uint32_t n_globals() const { return n_globals_; }
|
||||
/// 常量表行数(头 @44)。
|
||||
uint32_t n_consts() const { return n_consts_; }
|
||||
/// 函数表行数(头 @48)。
|
||||
uint32_t n_funcs() const { return n_funcs_; }
|
||||
/// 字节码段偏移(头 @60)。
|
||||
uint32_t offset_code() const { return offset_code_; }
|
||||
/// 数据段(FB 区)偏移(头 @64;即字节码段终点)。
|
||||
uint32_t offset_fb() const { return offset_fb_; }
|
||||
/// 数据段偏移(头 @68)。
|
||||
uint32_t offset_data() const { return offset_data_; }
|
||||
|
||||
/// 读常量表一行;越界时返回全 0。
|
||||
ConstEntry const_entry(size_t i) const;
|
||||
/// 读函数表一行;越界时返回全 0。
|
||||
FuncRow func_row(size_t i) const;
|
||||
/// 字节码段起点。
|
||||
const uint8_t* code_bytes() const;
|
||||
/// 字节码段字节数。
|
||||
size_t code_len() const;
|
||||
/// 数据段起点。
|
||||
const uint8_t* data_bytes() const;
|
||||
/// 数据段字节数(不含文件尾 SHA-256)。
|
||||
size_t data_len() const;
|
||||
|
||||
/// 型号标识字符串(头 72..103,截断到首个 '\0')。
|
||||
std::string model_id() const; // 头 72..103(补 '\0' 后字符串)
|
||||
/// 型号标识是否匹配 name + version。
|
||||
bool model_matches(const std::string& name, uint32_t version) const;
|
||||
/// 文件尾 32 字节 SHA-256 校验(对文件尾之前全部内容重算)。
|
||||
bool sha_ok() const; // 文件尾 32 字节 SHA-256 校验
|
||||
|
||||
private:
|
||||
/// 私有构造(只能经 from() 创建)。
|
||||
StbView() : buf_(0), len_(0) {}
|
||||
|
||||
/// 常量表偏移(from 已校验的段起点)。
|
||||
uint32_t offs_of_const() const; // offset_const(from 已校验段起点)
|
||||
|
||||
const uint8_t* buf_; ///< 映像缓冲(不拥有)
|
||||
size_t len_; ///< 缓冲字节数
|
||||
bool ok_ = false; ///< 解析成功标志
|
||||
std::string err_; ///< 失败原因
|
||||
uint32_t cycle_limit_ = 0; ///< 周期上限
|
||||
uint32_t dt_ms_ = 0; ///< 扫描周期
|
||||
uint64_t project_hash_ = 0; ///< 工程哈希
|
||||
uint32_t entry_fn_id_ = 0; ///< 入口函数 fn_id
|
||||
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; ///< 数据段(FB 区)偏移
|
||||
uint32_t offset_data_ = 0; ///< 数据段偏移
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 读整个文件为字节(纯字节;校验交给 StbView)。
|
||||
* @param path 文件路径
|
||||
* @param out 输出字节
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false(err "cannot open for read: <path>")
|
||||
*/
|
||||
bool read_stb_file(const char* path, std::vector<uint8_t>* out, std::string* err);
|
||||
/**
|
||||
* @brief 写字节为文件(纯字节)。
|
||||
* @param path 文件路径
|
||||
* @param img 映像字节
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false(err "cannot open for write: <path>" / "write failed: <path>")
|
||||
*/
|
||||
bool write_stb_file(const char* path, const std::vector<uint8_t>& img, std::string* err);
|
||||
|
||||
/**
|
||||
* @brief 生成 sidecar 文本(TOML)。
|
||||
* @param bindings I/O 绑定列表
|
||||
* @return TOML 文本(每个绑定一节 [[io.input]] / [[io.output]])
|
||||
* @details I/O 绑定 var → 槽号 → channel/bit(不进映像,执行器采样用)。
|
||||
*/
|
||||
std::string make_sidecar(const std::vector<IoBinding>& bindings);
|
||||
/**
|
||||
* @brief 生成并写 sidecar 文件。
|
||||
* @param path 文件路径
|
||||
* @param bindings I/O 绑定列表
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false(err "cannot open for write: <path>" / "write failed: <path>")
|
||||
*/
|
||||
bool write_sidecar_file(const char* path, const std::vector<IoBinding>& bindings,
|
||||
std::string* err);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @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 {
|
||||
|
||||
/**
|
||||
* @brief 语言类型元数据。
|
||||
* @details 由配置类型行 + 内建基元合成;config 或 prim 未命中时
|
||||
* 对应访问器取安全默认值,operator bool 为 false。
|
||||
*/
|
||||
struct TypeMeta {
|
||||
const ConfigType* config = nullptr; ///< 配置类型行(别名 + range + tag)
|
||||
const PrimType* prim = nullptr; ///< 内建基元(宽度/符号/浮点)
|
||||
|
||||
/// @brief 宽度(字节);无基元返回 0。
|
||||
uint32_t width() const { return prim ? prim->width : 0; }
|
||||
/// @brief 是否带符号;无基元返回 false。
|
||||
bool is_signed() const { return prim ? prim->is_signed : false; }
|
||||
/// @brief 是否浮点;无基元返回 false。
|
||||
bool is_float() const { return prim ? prim->is_float : false; }
|
||||
/// @brief .stb 契约 tag;无配置返回 0。
|
||||
uint32_t tag() const { return config ? config->tag : 0; }
|
||||
/// @brief 是否有值域约束;无配置返回 false。
|
||||
bool has_range() const { return config ? config->has_range : false; }
|
||||
/**
|
||||
* @brief 值是否落在配置值域内。
|
||||
* @param v 待查值
|
||||
* @return 无配置或无 range 时为 true;否则 min ≤ v ≤ max
|
||||
*/
|
||||
bool value_in_range(int64_t v) const {
|
||||
return !config || !config->has_range ||
|
||||
(v >= config->range_min && v <= config->range_max);
|
||||
}
|
||||
|
||||
/// @brief 是否有效(config 与 prim 都已命中)。
|
||||
explicit operator bool() const { return config != nullptr && prim != nullptr; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 按语言类型名查元数据。
|
||||
* @param cfg 机器配置(类型表来源)
|
||||
* @param name 语言类型名
|
||||
* @return 对应 TypeMeta;未找到返回空 TypeMeta(operator bool 为 false)
|
||||
* @details 查询大小写不敏感(配置名大写,Linker type_name 小写)。
|
||||
*/
|
||||
TypeMeta type_meta(const MachineConfig& cfg, const std::string& name);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @file Typecheck.h
|
||||
* @brief 类型检查
|
||||
* @details 本文件定义类型检查的求值类型与对外入口(实现见 Typecheck.cpp):
|
||||
* - TType:表达式求值类型(v1 三型,无隐式宽化)
|
||||
* - check_project:对工程做类型检查(在链接成功后调用),失败 err 前缀 "type error"
|
||||
* 规则详见 Doc/compiler/类型检查.md。
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "compiler/Linker.h"
|
||||
#include "compiler/Parser.h"
|
||||
#include "compiler/Project.h"
|
||||
|
||||
namespace compiler {
|
||||
|
||||
/**
|
||||
* @brief 表达式求值类型(v1 三型,无隐式宽化)。
|
||||
* @details 各成员含义:
|
||||
* - Bool:布尔量(逻辑运算 / 条件表达式)
|
||||
* - Int:整数(算术运算)
|
||||
* - Time:时间量(仅比较,无算术);INT 与 TIME 不混用
|
||||
*/
|
||||
enum class TType { Bool, Int, Time };
|
||||
|
||||
/**
|
||||
* @brief 对工程做类型检查(在链接成功后调用)。
|
||||
* @details 规则见 Doc/compiler/类型检查.md。失败返回 false,err 前缀 "type error"。
|
||||
* @param proj 工程定义
|
||||
* @param units 全部源文件的 AST
|
||||
* @param link 链接结果(符号 / 布局已解析,只读)
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 全部通过;false 失败(err 已写)
|
||||
*/
|
||||
bool check_project(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
const LinkResult& link, std::string* err);
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
# 机器定义:指令集与类型的唯一事实来源(只给编译器)
|
||||
# 格式见 Doc/isa/指令配置.md;全属性必填,与代码强校验一致。
|
||||
|
||||
[meta]
|
||||
name = "STATOR" # 型号名(参与 .stb 型号标识[32])
|
||||
version = 1 # 指令集版本(参与 .stb 型号标识[32])
|
||||
|
||||
# ---- 类型:编译器内建基元(bit/int8/int16/int32/int64/uint8/uint16/uint32/uint64/float32/float64)
|
||||
# 配置类型 = 基元别名(+ 值域约束 + .stb 契约 tag)
|
||||
|
||||
[[type]]
|
||||
name = "BOOL"
|
||||
base = "uint8"
|
||||
range = [0, 1] # 值域约束:只允许 0/1
|
||||
tag = 0
|
||||
|
||||
[[type]]
|
||||
name = "INT"
|
||||
base = "int16"
|
||||
tag = 1
|
||||
|
||||
[[type]]
|
||||
name = "TIME"
|
||||
base = "int64"
|
||||
tag = 2
|
||||
|
||||
# ---- 指令(全属性必填;class: plain/instance;format: RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE)
|
||||
|
||||
[[op]]
|
||||
name = "MOVE"
|
||||
opcode = 0
|
||||
class = "plain"
|
||||
format = "RR"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "LOADK"
|
||||
opcode = 1
|
||||
class = "plain"
|
||||
format = "IMM"
|
||||
params = ["rd", "const_id"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "NOT"
|
||||
opcode = 2
|
||||
class = "plain"
|
||||
format = "RR"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "AND"
|
||||
opcode = 3
|
||||
class = "plain"
|
||||
format = "RRR"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "OR"
|
||||
opcode = 4
|
||||
class = "plain"
|
||||
format = "RRR"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "ADD"
|
||||
opcode = 5
|
||||
class = "plain"
|
||||
format = "RRR"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "SUB"
|
||||
opcode = 6
|
||||
class = "plain"
|
||||
format = "RRR"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "MUL"
|
||||
opcode = 7
|
||||
class = "plain"
|
||||
format = "RRR"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "DIV"
|
||||
opcode = 8
|
||||
class = "plain"
|
||||
format = "RRR"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CMP_EQ"
|
||||
opcode = 9
|
||||
class = "plain"
|
||||
format = "RRR"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CMP_NE"
|
||||
opcode = 10
|
||||
class = "plain"
|
||||
format = "RRR"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CMP_LT"
|
||||
opcode = 11
|
||||
class = "plain"
|
||||
format = "RRR"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CMP_LE"
|
||||
opcode = 12
|
||||
class = "plain"
|
||||
format = "RRR"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CMP_GT"
|
||||
opcode = 13
|
||||
class = "plain"
|
||||
format = "RRR"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CMP_GE"
|
||||
opcode = 14
|
||||
class = "plain"
|
||||
format = "RRR"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "JMP"
|
||||
opcode = 15
|
||||
class = "plain"
|
||||
format = "JMP"
|
||||
params = ["off"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "JT"
|
||||
opcode = 16
|
||||
class = "plain"
|
||||
format = "JC"
|
||||
params = ["r", "off"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "JF"
|
||||
opcode = 17
|
||||
class = "plain"
|
||||
format = "JC"
|
||||
params = ["r", "off"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "LOAD_I"
|
||||
opcode = 18
|
||||
class = "plain"
|
||||
format = "SLOT"
|
||||
params = ["rd", "slot"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "STORE_Q"
|
||||
opcode = 19
|
||||
class = "plain"
|
||||
format = "SLOT"
|
||||
params = ["rs", "slot"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "LOAD_M"
|
||||
opcode = 20
|
||||
class = "plain"
|
||||
format = "SLOT"
|
||||
params = ["rd", "slot"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "STORE_M"
|
||||
opcode = 21
|
||||
class = "plain"
|
||||
format = "SLOT"
|
||||
params = ["rs", "slot"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "LOAD_GLOBAL"
|
||||
opcode = 22
|
||||
class = "plain"
|
||||
format = "SLOT"
|
||||
params = ["rd", "slot"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "STORE_GLOBAL"
|
||||
opcode = 23
|
||||
class = "plain"
|
||||
format = "SLOT"
|
||||
params = ["rs", "slot"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CAL_TON"
|
||||
opcode = 24
|
||||
class = "instance"
|
||||
format = "CAL"
|
||||
params = ["instance"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CAL_TOF"
|
||||
opcode = 25
|
||||
class = "instance"
|
||||
format = "CAL"
|
||||
params = ["instance"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CAL_TP"
|
||||
opcode = 26
|
||||
class = "instance"
|
||||
format = "CAL"
|
||||
params = ["instance"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CAL_CTU"
|
||||
opcode = 27
|
||||
class = "instance"
|
||||
format = "CAL"
|
||||
params = ["instance"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CAL_CTD"
|
||||
opcode = 28
|
||||
class = "instance"
|
||||
format = "CAL"
|
||||
params = ["instance"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CAL_CTUD"
|
||||
opcode = 29
|
||||
class = "instance"
|
||||
format = "CAL"
|
||||
params = ["instance"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CAL_R_TRIG"
|
||||
opcode = 30
|
||||
class = "instance"
|
||||
format = "CAL"
|
||||
params = ["instance"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CAL_F_TRIG"
|
||||
opcode = 31
|
||||
class = "instance"
|
||||
format = "CAL"
|
||||
params = ["instance"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CALL"
|
||||
opcode = 32
|
||||
class = "plain"
|
||||
format = "CALL"
|
||||
params = ["fn_id"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "RET"
|
||||
opcode = 33
|
||||
class = "plain"
|
||||
format = "NONE"
|
||||
params = []
|
||||
enabled = true
|
||||
|
||||
# ---- 内建 FB(布局登记;opcode 与 op 行强校验一致;字段类型命中 type 表)
|
||||
|
||||
[[fb]]
|
||||
name = "ton"
|
||||
opcode = 24
|
||||
fields = [["in", "BOOL"], ["pt", "TIME"], ["q", "BOOL"], ["et", "TIME"]]
|
||||
|
||||
[[fb]]
|
||||
name = "tof"
|
||||
opcode = 25
|
||||
fields = [["in", "BOOL"], ["pt", "TIME"], ["q", "BOOL"], ["et", "TIME"]]
|
||||
|
||||
[[fb]]
|
||||
name = "tp"
|
||||
opcode = 26
|
||||
fields = [["in", "BOOL"], ["pt", "TIME"], ["q", "BOOL"], ["et", "TIME"]]
|
||||
|
||||
[[fb]]
|
||||
name = "ctu"
|
||||
opcode = 27
|
||||
fields = [["cu", "BOOL"], ["r", "BOOL"], ["pv", "INT"], ["q", "BOOL"], ["cv", "INT"]]
|
||||
|
||||
[[fb]]
|
||||
name = "ctd"
|
||||
opcode = 28
|
||||
fields = [["cd", "BOOL"], ["ld", "BOOL"], ["pv", "INT"], ["q", "BOOL"], ["cv", "INT"]]
|
||||
|
||||
[[fb]]
|
||||
name = "ctud"
|
||||
opcode = 29
|
||||
fields = [["cu", "BOOL"], ["cd", "BOOL"], ["r", "BOOL"], ["lu", "BOOL"],
|
||||
["pv", "INT"], ["qu", "BOOL"], ["qd", "BOOL"], ["cv", "INT"]]
|
||||
|
||||
[[fb]]
|
||||
name = "r_trig"
|
||||
opcode = 30
|
||||
fields = [["clk", "BOOL"], ["q", "BOOL"]]
|
||||
|
||||
[[fb]]
|
||||
name = "f_trig"
|
||||
opcode = 31
|
||||
fields = [["clk", "BOOL"], ["q", "BOOL"]]
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @file Codec.cpp
|
||||
* @brief 指令字编解码 + 配置驱动反汇编(编译器侧)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details pack / op_of / imm16_of / off16_of 与执行器 isa 字节布局一致;
|
||||
* disasm 由 MachineConfig 驱动(opcode 名 / format / 参数名来自配置)。
|
||||
*/
|
||||
|
||||
#include "compiler/Codec.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace compiler {
|
||||
|
||||
/**
|
||||
* @brief 打包:四个字段拼成一条指令字。
|
||||
* @param opcode 操作码(低 8 位)
|
||||
* @param rd 目标寄存器 / 条件寄存器(第 8..15 位)
|
||||
* @param a 源寄存器 / 立即数低 8 位 / 偏移低 8 位(第 16..23 位)
|
||||
* @param b 源寄存器 / 立即数高 8 位 / 偏移高 8 位(第 24..31 位)
|
||||
* @return 打包后的指令字(小端 u32)
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/// @brief 取操作码(低 8 位)。
|
||||
uint8_t op_of(Instr w) { return static_cast<uint8_t>(w & 0xFFu); }
|
||||
/// @brief 取 rd 字段(第 8..15 位)。
|
||||
uint8_t rd_of(Instr w) { return static_cast<uint8_t>((w >> 8) & 0xFFu); }
|
||||
/// @brief 取 a 字段(第 16..23 位)。
|
||||
uint8_t a_of(Instr w) { return static_cast<uint8_t>((w >> 16) & 0xFFu); }
|
||||
/// @brief 取 b 字段(第 24..31 位)。
|
||||
uint8_t b_of(Instr w) { return static_cast<uint8_t>((w >> 24) & 0xFFu); }
|
||||
|
||||
/**
|
||||
* @brief a|b 拼成 16 位无符号数。
|
||||
* @param w 指令字
|
||||
* @return 小端拼出的 16 位值(const_id / slot / fn_id)
|
||||
*/
|
||||
uint16_t imm16_of(Instr w) {
|
||||
return static_cast<uint16_t>(a_of(w) | (static_cast<uint16_t>(b_of(w)) << 8));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief a|b 为有符号相对偏移。
|
||||
* @param w 指令字
|
||||
* @return 偏移量(单位:指令条数,相对下一条指令)
|
||||
*/
|
||||
int16_t off16_of(Instr w) { return static_cast<int16_t>(imm16_of(w)); }
|
||||
|
||||
/**
|
||||
* @brief 配置驱动反汇编:按 machine.toml 的 format 输出一行文本。
|
||||
* @param cfg 机器配置(opcode 名 / format / 参数名来源)
|
||||
* @param w 指令字
|
||||
* @param out 输出缓冲
|
||||
* @param cap 缓冲容量(含 '\0');cap==0 时直接返回、不写 out
|
||||
* @details 未知操作码输出 `??? 0x%08x`;format 分支 RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE
|
||||
* 输出文本与 Doc/isa/指令与映像.md 一致。
|
||||
*/
|
||||
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
|
||||
@@ -0,0 +1,974 @@
|
||||
/**
|
||||
* @file Codegen.cpp
|
||||
* @brief 寄存器码生成(配置驱动;compiler 不依赖 isa)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 设计说明(详见 Doc/compiler/寄存器码.md 与 Doc/isa/指令配置.md):
|
||||
* - 指令 opcode / 类型 tag / FB 布局全部来自 machine.toml(MachineConfig)
|
||||
* - 帧/字面量/MOVE/RET;全局数据区;短路 AND/OR;CMP 与 IF;WHILE 与四则;
|
||||
* FUNCTION 与 CALL(调用约定 r0/r1..r7/r8+)
|
||||
* - FB 实例 → 数据区实例块(跨周期持久);字段槽号 = 实例基槽 + 字段序号
|
||||
* - 用户 FB 调用点内联展开;内建 FB → CAL_* <实例基槽>
|
||||
* - 数据段每槽 8 字节定宽,指令 slot = 槽号
|
||||
*
|
||||
* 函数清单:
|
||||
* - Builder::Builder (构造)存工程/源文件/链接结果/机器配置/输出,收集 io 绑定
|
||||
* - Builder::run 布局数据区 → 逐 POU 建函数 → 拼映像
|
||||
* - Builder::fail / opc 错误 / 按名查配置 opcode
|
||||
* - E_rr / E_rrr / E_imm / E_slot / E_jmp / E_jc / E_call / E_ret 指令发射(配置 opcode)
|
||||
* - find_pou / find_fn_id 按名查 POU AST / fn_id
|
||||
* - build_function 编译一个 POU(帧约定分配;FB 占位)
|
||||
* - begin_stmt / alloc_temp 临时寄存器管理
|
||||
* - compile_stmt 语句编译(赋值 / IF / WHILE / FB 调用)
|
||||
* - compile_if / compile_while / compile_fb_call / compile_fb_inline
|
||||
* - store_target / compile_expr 左值存储 / 表达式编译
|
||||
* - arith_name / cmp_name / load_name / store_name 操作码名选择
|
||||
* - patch_jump 回填跳转偏移(相对下一条)
|
||||
* - layout_data / layout_fb_instances / instance_of / init_of
|
||||
* - const_id 取常量表 id(tag 来自配置类型表)
|
||||
* - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + 数据段
|
||||
* - codegen_project 对外入口
|
||||
*/
|
||||
|
||||
#include "compiler/Codegen.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "compiler/Codec.h"
|
||||
#include "compiler/MachineConfig.h"
|
||||
#include "compiler/Stb.h"
|
||||
#include "compiler/TypeInfo.h"
|
||||
|
||||
namespace compiler {
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief 代码生成器(寄存器码)。
|
||||
* @details 流程:布局数据区 → 布局 FB 实例 → 逐 POU 建函数 → 拼映像。
|
||||
* 寄存器分配:r0 结果、r1..r7 参数(调用约定区,输入只读)、r8+ 变量/临时;
|
||||
* 跳转偏移相对下一条(目标 = 当前 + 1 + off)。失败统一经 fail() 写
|
||||
* err(前缀 "codegen error")。
|
||||
*/
|
||||
class Builder {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造生成器
|
||||
* @param proj 工程定义(cycle_limit / dt_ms / 哈希用)
|
||||
* @param units 全部源文件的 AST
|
||||
* @param link 链接结果(POU 顺序 / 符号)
|
||||
* @param cfg 机器定义(machine.toml 强校验后;指令 opcode / 类型 tag / FB 布局)
|
||||
* @param image 输出映像字节
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
*/
|
||||
Builder(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
const LinkResult& link, const MachineConfig& cfg,
|
||||
std::vector<uint8_t>* image, std::string* err)
|
||||
: proj_(proj), units_(units), link_(link), cfg_(cfg), image_(image), err_(err) {
|
||||
// io 绑定分类(名已折小写;不创造变量,只影响操作码选择)
|
||||
for (const IoBinding& b : proj_.io) {
|
||||
std::string key = b.var;
|
||||
for (char& ch : key) {
|
||||
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||
}
|
||||
if (b.is_input) {
|
||||
io_input_[key] = true;
|
||||
} else {
|
||||
io_output_[key] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 布局数据区 → 布局 FB 实例 → 逐 POU 建函数 → 拼映像
|
||||
* @return true 成功;false(err 已写,前缀 "codegen error")
|
||||
*/
|
||||
bool run() {
|
||||
if (!layout_data()) {
|
||||
return false;
|
||||
}
|
||||
if (!layout_fb_instances()) {
|
||||
return false;
|
||||
}
|
||||
for (const LinkResult::PouScope& sc : link_.scopes) {
|
||||
const POU* pou = find_pou(sc.name);
|
||||
if (pou == nullptr) {
|
||||
continue;
|
||||
}
|
||||
FuncCtx f;
|
||||
f.name = sc.name;
|
||||
f.pou_name = sc.name;
|
||||
if (!build_function(*pou, &f)) {
|
||||
return false;
|
||||
}
|
||||
funcs_.push_back(std::move(f));
|
||||
}
|
||||
return assemble_image();
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 记录错误并返回失败。
|
||||
* @param msg 错误消息(自动加前缀 "codegen error: ")
|
||||
* @return 恒 false(便于 return fail(...) 连写)
|
||||
*/
|
||||
bool fail(const std::string& msg) {
|
||||
if (err_) {
|
||||
*err_ = "codegen error: " + msg;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按名查配置 opcode(MachineConfig 强校验保证存在)
|
||||
* @param name 指令名(大写,如 "ADD")
|
||||
* @return opcode(找不到返回 0,不应发生)
|
||||
*/
|
||||
uint8_t opc(const char* name) const {
|
||||
const ConfigOp* op = cfg_.find_op(name);
|
||||
return op ? static_cast<uint8_t>(op->opcode) : 0;
|
||||
}
|
||||
|
||||
// ---- 指令发射(配置 opcode)----
|
||||
/// 双寄存器指令(rd, rs)。
|
||||
Instr E_rr(const char* name, uint8_t rd, uint8_t rs) {
|
||||
return pack(opc(name), rd, rs, 0);
|
||||
}
|
||||
/// 三寄存器指令(rd, ra, rb)。
|
||||
Instr E_rrr(const char* name, uint8_t rd, uint8_t ra, uint8_t rb) {
|
||||
return pack(opc(name), rd, ra, rb);
|
||||
}
|
||||
/// 立即数指令(imm 拆低/高字节入字段)。
|
||||
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));
|
||||
}
|
||||
/// 槽号指令(slot 复用 imm 字段,u16)。
|
||||
Instr E_slot(const char* name, uint8_t rd, uint16_t slot) {
|
||||
return E_imm(name, rd, slot);
|
||||
}
|
||||
/// 无条件跳转(off 相对下一条指令)。
|
||||
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));
|
||||
}
|
||||
/// 条件跳转(寄存器 r 为真则跳;off 相对下一条指令)。
|
||||
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));
|
||||
}
|
||||
/// CALL 指令(fn_id 为函数表下标)。
|
||||
Instr E_call(uint16_t fn_id) { return E_imm("CALL", 0, fn_id); }
|
||||
/// RET 指令。
|
||||
Instr E_ret() { return pack(opc("RET"), 0, 0, 0); }
|
||||
|
||||
/**
|
||||
* @brief 按名查 POU AST。
|
||||
* @param name POU 名
|
||||
* @return 找到返回指针;否则 nullptr
|
||||
*/
|
||||
const POU* find_pou(const std::string& name) const {
|
||||
for (const SourceUnit& u : units_) {
|
||||
for (const POU& p : u.ast.pous) {
|
||||
if (p.name == name) {
|
||||
return &p;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 每函数的编译态
|
||||
/**
|
||||
* @brief 单个函数的编译状态。
|
||||
* @details 寄存器分配(调用约定):结果 r0、输入 r1..r7、变量与临时 r8+。
|
||||
*/
|
||||
struct FuncCtx {
|
||||
std::string name; ///< 函数名(函数表行用)
|
||||
std::string pou_name; ///< 所属 POU 名(实例查找键)
|
||||
std::vector<Instr> code; ///< 字节码
|
||||
std::map<std::string, uint8_t> regs; ///< 变量名 → 帧寄存器
|
||||
uint8_t nlocals = 0; ///< 变量区终点 = 临时寄存器起始
|
||||
uint8_t nregs = 0; ///< 寄存器峰值(变量 + 临时)
|
||||
uint8_t temp_used = 0; ///< 本语句已用临时数(语句结束清零)
|
||||
bool is_function = false; ///< FUNCTION(结果 r0 / 输入只读)
|
||||
std::string result_name; ///< FUNCTION 名(结果寄存器映射)
|
||||
};
|
||||
|
||||
// FB 实例:字段名 → 数据区槽号
|
||||
/**
|
||||
* @brief FB 实例布局。
|
||||
* @details 字段槽号 = 实例基槽 + 字段序号(跨周期持久)。
|
||||
*/
|
||||
struct InstFields {
|
||||
std::map<std::string, uint32_t> field_addr; ///< 字段名 → 数据区槽号
|
||||
uint32_t base = 0; ///< 实例基槽
|
||||
std::string type_name; ///< 实例的 FB 类型名(内建 / 用户)
|
||||
};
|
||||
|
||||
/// 语句开始:清零临时寄存器计数。
|
||||
void begin_stmt(FuncCtx& f) { f.temp_used = 0; }
|
||||
|
||||
/**
|
||||
* @brief 分配一个临时寄存器。
|
||||
* @param f 函数编译态
|
||||
* @return 临时寄存器号(nlocals + 已用数;随用抬高 nregs 峰值)
|
||||
*/
|
||||
uint8_t alloc_temp(FuncCtx& f) {
|
||||
const uint8_t r = f.nlocals + f.temp_used;
|
||||
++f.temp_used;
|
||||
if (static_cast<uint16_t>(f.nlocals) + f.temp_used > f.nregs) {
|
||||
f.nregs = f.nlocals + f.temp_used;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 编译一个 POU
|
||||
* @details 支持 PROGRAM / FUNCTION / FUNCTION_BLOCK:
|
||||
* FB 不生成业务字节码(调用点内联),只出空函数占位保持 fn_id 一致;
|
||||
* 帧约定(见 Doc/compiler/寄存器码.md):结果 r0、输入 r1..r7(只读)、
|
||||
* 变量与临时全部从 r8 起
|
||||
*/
|
||||
bool build_function(const POU& pou, FuncCtx* f) {
|
||||
if (pou.kind == PouKind::FunctionBlock) {
|
||||
f->nlocals = 8;
|
||||
f->nregs = 8;
|
||||
f->code.push_back(E_ret());
|
||||
return true;
|
||||
}
|
||||
f->is_function = pou.kind == PouKind::Function;
|
||||
f->result_name = pou.name;
|
||||
if (f->is_function) {
|
||||
f->regs[pou.name] = 0; // 结果寄存器 r0
|
||||
uint8_t idx = 1;
|
||||
for (const VarBlock& b : pou.blocks) {
|
||||
if (b.section != VarSection::Input) {
|
||||
continue;
|
||||
}
|
||||
for (const VarDecl& d : b.vars) {
|
||||
f->regs[d.name] = idx++; // 输入 r1..r7
|
||||
}
|
||||
}
|
||||
}
|
||||
uint8_t r = 8; // 变量/临时基址(调用约定区 r0..r7 不占用)
|
||||
for (const VarBlock& b : pou.blocks) {
|
||||
if (b.section == VarSection::External || b.section == VarSection::Global) {
|
||||
continue;
|
||||
}
|
||||
if (f->is_function && b.section == VarSection::Input) {
|
||||
continue;
|
||||
}
|
||||
for (const VarDecl& d : b.vars) {
|
||||
if (d.type.kind == TypeKind::FbUser || d.type.kind == TypeKind::FbBuiltin) {
|
||||
continue;
|
||||
}
|
||||
f->regs[d.name] = r++;
|
||||
}
|
||||
}
|
||||
f->nlocals = r;
|
||||
f->nregs = r;
|
||||
for (const Stmt& st : pou.body) {
|
||||
begin_stmt(*f);
|
||||
if (!compile_stmt(*f, st)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
f->code.push_back(E_ret());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 编译一条语句。
|
||||
* @param f 函数编译态
|
||||
* @param st 语句 AST
|
||||
* @return true 成功;false(err 已写)
|
||||
* @details 支持 IF / WHILE / FB 调用 / 赋值;其他语句报
|
||||
* "statement not supported"。内联 FB 体内左值可能是实例字段
|
||||
* (走 STORE_GLOBAL);写 FUNCTION 输入报 "cannot write function input"。
|
||||
*/
|
||||
bool compile_stmt(FuncCtx& f, const Stmt& st) {
|
||||
if (st.kind == StmtKind::If) {
|
||||
return compile_if(f, st);
|
||||
}
|
||||
if (st.kind == StmtKind::While) {
|
||||
return compile_while(f, st);
|
||||
}
|
||||
if (st.kind == StmtKind::FbCall) {
|
||||
return compile_fb_call(f, st);
|
||||
}
|
||||
if (st.kind != StmtKind::Assign) {
|
||||
return fail("statement not supported");
|
||||
}
|
||||
// 内联 FB 体内:左值可能是实例字段(STORE_GLOBAL)
|
||||
if (inline_fields_) {
|
||||
const auto fit = inline_fields_->field_addr.find(st.target);
|
||||
if (fit != inline_fields_->field_addr.end()) {
|
||||
const uint8_t t = alloc_temp(f);
|
||||
if (!compile_expr(f, *st.value, t)) {
|
||||
return false;
|
||||
}
|
||||
f.code.push_back(E_slot("STORE_GLOBAL", t, fit->second));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const auto it = f.regs.find(st.target);
|
||||
if (it == f.regs.end()) {
|
||||
return store_target(f, st.target, *st.value);
|
||||
}
|
||||
if (f.is_function && it->second >= 1 && it->second <= 7) {
|
||||
return fail("cannot write function input '" + st.target + "'");
|
||||
}
|
||||
return compile_expr(f, *st.value, it->second);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 编译 FB 调用语句。
|
||||
* @param f 函数编译态
|
||||
* @param st FB 调用语句 AST
|
||||
* @return true 成功;false(err 已写)
|
||||
* @details 实参逐项编译后 STORE_GLOBAL 写实例字段;内建 FB
|
||||
* 发 CAL_<TYPE> <实例基槽>;用户 FB 调用点内联展开。
|
||||
* 错误:"no instance" / "unknown input" / "no FB type"。
|
||||
*/
|
||||
bool compile_fb_call(FuncCtx& f, const Stmt& st) {
|
||||
const InstFields* inst = instance_of(f.pou_name, st.instance);
|
||||
if (inst == nullptr) {
|
||||
return fail("no instance '" + st.instance + "' in '" + f.pou_name + "'");
|
||||
}
|
||||
for (const FbArg& a : st.args) {
|
||||
const auto it = inst->field_addr.find(a.name);
|
||||
if (it == inst->field_addr.end()) {
|
||||
return fail("unknown input '" + a.name + "' for '" + st.instance + "'");
|
||||
}
|
||||
const uint8_t t = alloc_temp(f);
|
||||
if (!compile_expr(f, *a.value, t)) {
|
||||
return false;
|
||||
}
|
||||
f.code.push_back(E_slot("STORE_GLOBAL", t, it->second));
|
||||
}
|
||||
// 内建 FB:配置里必须有对应 op 行(强校验保证)→ 直接查配置 opcode
|
||||
const std::string& tn = inst->type_name;
|
||||
if (cfg_.find_op("CAL_" + uppercase_of(tn)) != nullptr &&
|
||||
(tn == "ton" || tn == "tof" || tn == "tp" || tn == "ctu" ||
|
||||
tn == "ctd" || tn == "ctud" || tn == "r_trig" || tn == "f_trig")) {
|
||||
const std::string opname = "CAL_" + uppercase_of(tn);
|
||||
f.code.push_back(E_slot(opname.c_str(), 0, inst->base));
|
||||
return true;
|
||||
}
|
||||
const POU* fb = find_pou(inst->type_name);
|
||||
if (fb == nullptr) {
|
||||
return fail("no FB type '" + inst->type_name + "'");
|
||||
}
|
||||
return compile_fb_inline(f, *fb, *inst);
|
||||
}
|
||||
|
||||
/// 转大写(内建 FB 名 → 操作码名)。
|
||||
static std::string uppercase_of(const std::string& s) {
|
||||
std::string out = s;
|
||||
for (char& ch : out) {
|
||||
ch = static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 用户 FB 调用点内联展开。
|
||||
* @param f 函数编译态
|
||||
* @param fb 用户 FB 的 POU AST
|
||||
* @param inst 实例布局
|
||||
* @return true 成功;false(err 已写)
|
||||
* @details 临时置 inline_fields_ 使体内变量引用改查实例字段
|
||||
* (编译结束后恢复)。
|
||||
*/
|
||||
bool compile_fb_inline(FuncCtx& f, const POU& fb, const InstFields& inst) {
|
||||
const InstFields* saved = inline_fields_;
|
||||
inline_fields_ = &inst;
|
||||
for (const Stmt& s : fb.body) {
|
||||
begin_stmt(f);
|
||||
if (!compile_stmt(f, s)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
inline_fields_ = saved;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 编译 WHILE 循环。
|
||||
* @param f 函数编译态
|
||||
* @param st WHILE 语句 AST
|
||||
* @return true 成功;false(err 已写)
|
||||
* @details 结构:条件 → JF 跳出 → 体 → JMP 回条件;偏移经
|
||||
* patch_jump 回填(相对下一条)。
|
||||
*/
|
||||
bool compile_while(FuncCtx& f, const Stmt& st) {
|
||||
const size_t loop = f.code.size();
|
||||
const uint8_t t = alloc_temp(f);
|
||||
if (!compile_expr(f, *st.cond, t)) {
|
||||
return false;
|
||||
}
|
||||
const size_t jf_idx = f.code.size();
|
||||
f.code.push_back(E_jc("JF", t, 0));
|
||||
for (const Stmt& s : st.body) {
|
||||
begin_stmt(f);
|
||||
if (!compile_stmt(f, s)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const size_t jmp_idx = f.code.size();
|
||||
f.code.push_back(E_jmp(0));
|
||||
patch_jump(f, jmp_idx, loop);
|
||||
patch_jump(f, jf_idx, f.code.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 编译 IF / ELSIF / ELSE。
|
||||
* @param f 函数编译态
|
||||
* @param st IF 语句 AST
|
||||
* @return true 成功;false(err 已写)
|
||||
* @details 每条分支:条件 → JF 跳下一条 → 体;分支间 JMP 跳
|
||||
* 公共结束点;偏移经 patch_jump 回填。
|
||||
*/
|
||||
bool compile_if(FuncCtx& f, const Stmt& st) {
|
||||
std::vector<std::pair<const Expr*, const std::vector<Stmt>*>> branches;
|
||||
branches.push_back({st.cond.get(), &st.body});
|
||||
for (const IfBranch& b : st.elsifs) {
|
||||
branches.push_back({b.cond.get(), &b.body});
|
||||
}
|
||||
const bool has_else = !st.else_body.empty();
|
||||
std::vector<size_t> end_jmps;
|
||||
|
||||
for (size_t i = 0; i < branches.size(); ++i) {
|
||||
const uint8_t t = alloc_temp(f);
|
||||
if (!compile_expr(f, *branches[i].first, t)) {
|
||||
return false;
|
||||
}
|
||||
const size_t jf_idx = f.code.size();
|
||||
f.code.push_back(E_jc("JF", t, 0));
|
||||
for (const Stmt& s : *branches[i].second) {
|
||||
begin_stmt(f);
|
||||
if (!compile_stmt(f, s)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (i + 1 < branches.size() || has_else) {
|
||||
const size_t jmp_idx = f.code.size();
|
||||
f.code.push_back(E_jmp(0));
|
||||
end_jmps.push_back(jmp_idx);
|
||||
}
|
||||
patch_jump(f, jf_idx, f.code.size());
|
||||
}
|
||||
if (has_else) {
|
||||
for (const Stmt& s : st.else_body) {
|
||||
begin_stmt(f);
|
||||
if (!compile_stmt(f, s)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
const size_t end = f.code.size();
|
||||
for (const size_t j : end_jmps) {
|
||||
patch_jump(f, j, end);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 编译左值存储(全局槽)。
|
||||
* @param f 函数编译态
|
||||
* @param target 目标名
|
||||
* @param value 表达式
|
||||
* @return true 成功;false(err 已写)
|
||||
* @details 目标槽号在 link_.global_index 中查;I/O 输入禁止写
|
||||
* ("cannot write to input");存储操作码经 store_name 选择
|
||||
* (I/O 输出 STORE_Q,其余 STORE_GLOBAL)。
|
||||
* 错误:"no storage for ..." / "cannot write to input ..."。
|
||||
*/
|
||||
bool store_target(FuncCtx& f, const std::string& target, const Expr& value) {
|
||||
const auto git = link_.global_index.find(target);
|
||||
if (git == link_.global_index.end()) {
|
||||
return fail("no storage for '" + target + "'");
|
||||
}
|
||||
if (io_input_.count(target)) {
|
||||
return fail("cannot write to input '" + target + "'");
|
||||
}
|
||||
const uint8_t tmp = alloc_temp(f);
|
||||
if (!compile_expr(f, value, tmp)) {
|
||||
return false;
|
||||
}
|
||||
f.code.push_back(E_slot(store_name(target), tmp, static_cast<uint16_t>(git->second)));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 编译表达式到目标寄存器。
|
||||
* @param f 函数编译态
|
||||
* @param e 表达式 AST
|
||||
* @param rd 目标寄存器号
|
||||
* @return true 成功;false(err 已写)
|
||||
* @details 支持:字面量(LOADK,tag 来自配置类型表)、变量/字段引用、
|
||||
* 四则、比较(cmp_name)、NOT、短路 AND/OR(JF/JT)、函数调用
|
||||
* (实参 MOVE 到 r1..r7 后 CALL,结果 MOVE 回 rd)。
|
||||
* 错误:"no register or slot" / "no instance" / "unknown field" /
|
||||
* "too many arguments (max 7)" / "no fn_id" / "expression not supported"。
|
||||
*/
|
||||
bool compile_expr(FuncCtx& f, const Expr& e, uint8_t rd) {
|
||||
if (e.kind == ExprKind::LitBool || e.kind == ExprKind::LitInt ||
|
||||
e.kind == ExprKind::LitTime) {
|
||||
const char* type_name = e.kind == ExprKind::LitBool ? "BOOL"
|
||||
: e.kind == ExprKind::LitInt ? "INT"
|
||||
: "TIME";
|
||||
f.code.push_back(E_imm("LOADK", rd, const_id(type_name, e.int_value)));
|
||||
return true;
|
||||
}
|
||||
if (e.kind == ExprKind::VarRef) {
|
||||
if (inline_fields_) {
|
||||
const auto fit = inline_fields_->field_addr.find(e.name);
|
||||
if (fit != inline_fields_->field_addr.end()) {
|
||||
f.code.push_back(E_slot("LOAD_GLOBAL", rd, fit->second));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const auto sit = f.regs.find(e.name);
|
||||
if (sit != f.regs.end()) {
|
||||
f.code.push_back(E_rr("MOVE", rd, sit->second));
|
||||
return true;
|
||||
}
|
||||
const auto git = link_.global_index.find(e.name);
|
||||
if (git != link_.global_index.end()) {
|
||||
f.code.push_back(
|
||||
E_slot(load_name(e.name), rd, static_cast<uint16_t>(git->second)));
|
||||
return true;
|
||||
}
|
||||
return fail("no register or slot for '" + e.name + "'");
|
||||
}
|
||||
if (e.kind == ExprKind::Field) {
|
||||
const InstFields* inst = instance_of(f.pou_name, e.name);
|
||||
if (inst == nullptr) {
|
||||
return fail("no instance '" + e.name + "' in '" + f.pou_name + "'");
|
||||
}
|
||||
const auto fit = inst->field_addr.find(e.field);
|
||||
if (fit == inst->field_addr.end()) {
|
||||
return fail("unknown field '" + e.field + "' for '" + e.name + "'");
|
||||
}
|
||||
f.code.push_back(E_slot("LOAD_GLOBAL", rd, fit->second));
|
||||
return true;
|
||||
}
|
||||
if (e.kind == ExprKind::Add || e.kind == ExprKind::Sub ||
|
||||
e.kind == ExprKind::Mul || e.kind == ExprKind::Div) {
|
||||
const uint8_t l = alloc_temp(f);
|
||||
if (!compile_expr(f, *e.lhs, l)) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t r = alloc_temp(f);
|
||||
if (!compile_expr(f, *e.rhs, r)) {
|
||||
return false;
|
||||
}
|
||||
f.code.push_back(E_rrr(arith_name(e.kind), rd, l, r));
|
||||
return true;
|
||||
}
|
||||
if (e.kind == ExprKind::Cmp) {
|
||||
const uint8_t l = alloc_temp(f);
|
||||
if (!compile_expr(f, *e.lhs, l)) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t r = alloc_temp(f);
|
||||
if (!compile_expr(f, *e.rhs, r)) {
|
||||
return false;
|
||||
}
|
||||
f.code.push_back(E_rrr(cmp_name(e.op), rd, l, r));
|
||||
return true;
|
||||
}
|
||||
if (e.kind == ExprKind::Not) {
|
||||
const uint8_t t = alloc_temp(f);
|
||||
if (!compile_expr(f, *e.operand, t)) {
|
||||
return false;
|
||||
}
|
||||
f.code.push_back(E_rr("NOT", rd, t));
|
||||
return true;
|
||||
}
|
||||
if (e.kind == ExprKind::And || e.kind == ExprKind::Or) {
|
||||
if (!compile_expr(f, *e.lhs, rd)) {
|
||||
return false;
|
||||
}
|
||||
const char* jname = (e.kind == ExprKind::And) ? "JF" : "JT";
|
||||
const size_t jmp_idx = f.code.size();
|
||||
f.code.push_back(E_jc(jname, rd, 0));
|
||||
const uint8_t t = alloc_temp(f);
|
||||
if (!compile_expr(f, *e.rhs, t)) {
|
||||
return false;
|
||||
}
|
||||
f.code.push_back(E_rr("MOVE", rd, t));
|
||||
patch_jump(f, jmp_idx, f.code.size());
|
||||
return true;
|
||||
}
|
||||
if (e.kind == ExprKind::Call) {
|
||||
if (e.args.size() > 7) {
|
||||
return fail("too many arguments (max 7)");
|
||||
}
|
||||
std::vector<uint8_t> args;
|
||||
for (const auto& a : e.args) {
|
||||
const uint8_t t = alloc_temp(f);
|
||||
if (!compile_expr(f, *a, t)) {
|
||||
return false;
|
||||
}
|
||||
args.push_back(t);
|
||||
}
|
||||
const int fn_id = find_fn_id(e.name);
|
||||
if (fn_id < 0) {
|
||||
return fail("no fn_id for '" + e.name + "'");
|
||||
}
|
||||
for (size_t i = 0; i < args.size(); ++i) {
|
||||
f.code.push_back(E_rr("MOVE", static_cast<uint8_t>(1 + i), args[i]));
|
||||
}
|
||||
f.code.push_back(E_call(static_cast<uint16_t>(fn_id)));
|
||||
f.code.push_back(E_rr("MOVE", rd, 0));
|
||||
return true;
|
||||
}
|
||||
return fail("expression not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按名查 fn_id(函数表下标)。
|
||||
* @param name 函数名
|
||||
* @return 找到返回下标;否则 -1
|
||||
*/
|
||||
int find_fn_id(const std::string& name) const {
|
||||
for (size_t i = 0; i < link_.scopes.size(); ++i) {
|
||||
if (link_.scopes[i].name == name) {
|
||||
return static_cast<int>(i);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 回填跳转偏移。
|
||||
* @param f 函数编译态
|
||||
* @param idx 跳转指令下标
|
||||
* @param target_idx 目标指令下标
|
||||
* @details 偏移相对下一条:目标 = 当前 + 1 + off(off 为
|
||||
* int16,重写指令的低 16 位)。
|
||||
*/
|
||||
void patch_jump(FuncCtx& f, size_t idx, size_t target_idx) {
|
||||
const int16_t off = static_cast<int16_t>(
|
||||
static_cast<int64_t>(target_idx) - (static_cast<int64_t>(idx) + 1));
|
||||
const Instr w = f.code[idx];
|
||||
const uint16_t u = static_cast<uint16_t>(off);
|
||||
f.code[idx] = pack(op_of(w), rd_of(w), static_cast<uint8_t>(u & 0xFFu),
|
||||
static_cast<uint8_t>((u >> 8) & 0xFFu));
|
||||
}
|
||||
|
||||
// ---- 操作码名(MachineConfig 已强校验存在)----
|
||||
/// 四则运算操作码名(Add→"ADD" 等;未知回退 "ADD")。
|
||||
static const char* arith_name(ExprKind k) {
|
||||
switch (k) {
|
||||
case ExprKind::Add: return "ADD";
|
||||
case ExprKind::Sub: return "SUB";
|
||||
case ExprKind::Mul: return "MUL";
|
||||
case ExprKind::Div: return "DIV";
|
||||
default: return "ADD";
|
||||
}
|
||||
}
|
||||
/// 比较操作码名(Eq→"CMP_EQ" 等;未知回退 "CMP_EQ")。
|
||||
static const char* cmp_name(BinOp op) {
|
||||
switch (op) {
|
||||
case BinOp::Eq: return "CMP_EQ";
|
||||
case BinOp::Ne: return "CMP_NE";
|
||||
case BinOp::Lt: return "CMP_LT";
|
||||
case BinOp::Le: return "CMP_LE";
|
||||
case BinOp::Gt: return "CMP_GT";
|
||||
case BinOp::Ge: return "CMP_GE";
|
||||
default: return "CMP_EQ";
|
||||
}
|
||||
}
|
||||
/// 加载操作码选择:I/O 输入走 LOAD_I,其余 LOAD_GLOBAL。
|
||||
const char* load_name(const std::string& name) const {
|
||||
return io_input_.count(name) ? "LOAD_I" : "LOAD_GLOBAL";
|
||||
}
|
||||
/// 存储操作码选择:I/O 输出走 STORE_Q,其余 STORE_GLOBAL。
|
||||
const char* store_name(const std::string& name) const {
|
||||
return io_output_.count(name) ? "STORE_Q" : "STORE_GLOBAL";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 布局全局数据区。
|
||||
* @return true 成功;false(err 已写)
|
||||
* @details 每槽 8 字节定宽小端,槽号 × 8 定位;初值按类型元数据
|
||||
* 写(宽度 2 写 u16、8 写 u64、否则写 0/1;别名先经 type_meta 解析)。
|
||||
* 错误:"data area exceeds slot range" / "no type in machine.toml" /
|
||||
* "float initializer not supported yet"。
|
||||
*/
|
||||
bool layout_data() {
|
||||
for (const Symbol& s : link_.globals) {
|
||||
if (s.address > 0xFFFF) {
|
||||
return fail("data area exceeds slot range");
|
||||
}
|
||||
bool has_init = false;
|
||||
int64_t init = 0;
|
||||
init_of(s.name, &has_init, &init);
|
||||
const int64_t v = has_init ? init : 0;
|
||||
const size_t off = static_cast<size_t>(s.address) * 8;
|
||||
data_.resize(off + 8, 0);
|
||||
// 初值按类型元数据写(配置别名 → 基元宽度/浮点)
|
||||
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);
|
||||
data_[off] = static_cast<uint8_t>(iv & 0xFFu);
|
||||
data_[off + 1] = static_cast<uint8_t>((iv >> 8) & 0xFFu);
|
||||
} else if (tm.width() == 8) {
|
||||
const uint64_t tv = static_cast<uint64_t>(v);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
data_[off + i] = static_cast<uint8_t>((tv >> (8 * i)) & 0xFFu);
|
||||
}
|
||||
} else {
|
||||
data_[off] = v ? 1 : 0;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 布局 FB 实例块(全局区之后顺序追加)。
|
||||
* @return true 成功;false(err 已写)
|
||||
* @details 实例 key 为 "POU名/实例名";字段槽号 = 基槽 + 字段序号。
|
||||
* 错误:"no layout for instance ..."。
|
||||
*/
|
||||
bool layout_fb_instances() {
|
||||
uint32_t cur = static_cast<uint32_t>(data_.size() / 8);
|
||||
bool any = false;
|
||||
for (const LinkResult::PouScope& sc : link_.scopes) {
|
||||
for (const Symbol& s : sc.syms) {
|
||||
if (s.kind != SymbolKind::FbInstance) {
|
||||
continue;
|
||||
}
|
||||
const auto lay = sc.fb_instances.find(s.name);
|
||||
if (lay == sc.fb_instances.end()) {
|
||||
return fail("no layout for instance '" + s.name + "'");
|
||||
}
|
||||
InstFields inst;
|
||||
inst.base = cur;
|
||||
inst.type_name = s.type_name;
|
||||
for (size_t i = 0; i < lay->second.fields.size(); ++i) {
|
||||
inst.field_addr[lay->second.fields[i].name] = cur + i;
|
||||
}
|
||||
cur += static_cast<uint32_t>(lay->second.fields.size());
|
||||
instances_[sc.name + "/" + s.name] = inst;
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
if (any) {
|
||||
data_.resize(static_cast<size_t>(cur) * 8, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 按 "POU名/实例名" 查实例布局;找不到返回 nullptr。
|
||||
const InstFields* instance_of(const std::string& pou,
|
||||
const std::string& name) const {
|
||||
const auto it = instances_.find(pou + "/" + name);
|
||||
return it == instances_.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查全局变量初值(源文件 globals 段)。
|
||||
* @param name 变量名
|
||||
* @param has_init 输出:是否有初值
|
||||
* @param init 输出:初值
|
||||
*/
|
||||
void init_of(const std::string& name, bool* has_init, int64_t* init) const {
|
||||
for (const SourceUnit& u : units_) {
|
||||
for (const VarBlock& b : u.ast.globals) {
|
||||
for (const VarDecl& d : b.vars) {
|
||||
if (d.name == name) {
|
||||
*has_init = d.has_init;
|
||||
*init = d.init_value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*has_init = false;
|
||||
*init = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取常量表 id(无则追加)
|
||||
* @param type_name 语言类型名("BOOL"/"INT"/"TIME")→ 配置 tag(契约)
|
||||
* @param value 常量值
|
||||
* @return const_id(u16)
|
||||
*/
|
||||
uint16_t const_id(const char* type_name, int64_t value) {
|
||||
const TypeMeta tm = type_meta(cfg_, type_name);
|
||||
const uint32_t tag = tm ? tm.tag() : 0;
|
||||
for (size_t i = 0; i < consts_.size(); ++i) {
|
||||
if (consts_[i].tag == tag && consts_[i].value == static_cast<uint64_t>(value)) {
|
||||
return static_cast<uint16_t>(i);
|
||||
}
|
||||
}
|
||||
consts_.push_back({tag, static_cast<uint64_t>(value)});
|
||||
return static_cast<uint16_t>(consts_.size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 拼装最终映像。
|
||||
* @return true 成功;false(err 已写)
|
||||
* @details 布局:头(104) + 常量表 + 函数表 + 字节码 + 数据段 +
|
||||
* SHA-256 文件尾;型号标识 @72;头内各段偏移(52..68)与函数表
|
||||
* code_offset 小端写入;SHA-256 对文件尾之前全部内容计算。
|
||||
*/
|
||||
bool assemble_image() {
|
||||
const uint32_t off_const = static_cast<uint32_t>(kHeaderSize);
|
||||
const uint32_t off_funcs = off_const +
|
||||
static_cast<uint32_t>(consts_.size()) * static_cast<uint32_t>(kConstEntrySize);
|
||||
uint32_t off_code = off_funcs +
|
||||
static_cast<uint32_t>(funcs_.size()) * static_cast<uint32_t>(kFuncRowSize);
|
||||
uint32_t code_total = 0;
|
||||
for (const FuncCtx& f : funcs_) {
|
||||
code_total += static_cast<uint32_t>(f.code.size()) * 4;
|
||||
}
|
||||
const uint32_t off_data = off_code + code_total;
|
||||
const uint32_t off_end = off_data + static_cast<uint32_t>(data_.size()) +
|
||||
static_cast<uint32_t>(kSha256Size);
|
||||
|
||||
std::vector<uint8_t>& b = *image_;
|
||||
b.assign(off_end, 0);
|
||||
put_le32(b, 0, kMagic);
|
||||
put_le32(b, 4, kVersion);
|
||||
put_le32(b, 8, proj_.cycle_limit);
|
||||
put_le32(b, 12, proj_.dt_ms);
|
||||
|
||||
uint64_t hash = kFnvBasis;
|
||||
if (!compute_project_hash(proj_, &hash, err_)) {
|
||||
return false;
|
||||
}
|
||||
put_le64(b, 16, hash);
|
||||
|
||||
uint32_t entry = 0;
|
||||
for (size_t i = 0; i < funcs_.size(); ++i) {
|
||||
if (funcs_[i].name == "main") {
|
||||
entry = static_cast<uint32_t>(i);
|
||||
}
|
||||
}
|
||||
put_le32(b, 24, entry);
|
||||
put_le32(b, 28, static_cast<uint32_t>(link_.globals.size()));
|
||||
put_le32(b, 32, 0);
|
||||
put_le32(b, 36, 0);
|
||||
put_le32(b, 40, 0);
|
||||
put_le32(b, 44, static_cast<uint32_t>(consts_.size()));
|
||||
put_le32(b, 48, static_cast<uint32_t>(funcs_.size()));
|
||||
put_le32(b, 52, off_const);
|
||||
put_le32(b, 56, off_funcs);
|
||||
put_le32(b, 60, off_code);
|
||||
put_le32(b, 64, off_data);
|
||||
put_le32(b, 68, off_data);
|
||||
|
||||
// 型号标识[32](偏移 72;12.13 修订)
|
||||
char mid[kModelIdSize];
|
||||
fill_model_id(cfg_.model_name(), cfg_.version(), mid);
|
||||
for (size_t i = 0; i < kModelIdSize; ++i) {
|
||||
b[72 + i] = static_cast<uint8_t>(mid[i]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < consts_.size(); ++i) {
|
||||
const size_t o = off_const + i * kConstEntrySize;
|
||||
put_le32(b, o, consts_[i].tag);
|
||||
put_le64(b, o + 4, consts_[i].value);
|
||||
}
|
||||
|
||||
uint32_t c = off_code;
|
||||
for (size_t i = 0; i < funcs_.size(); ++i) {
|
||||
const FuncCtx& f = funcs_[i];
|
||||
const size_t o = off_funcs + i * kFuncRowSize;
|
||||
put_le32(b, o, f.nregs);
|
||||
put_le32(b, o + 8, static_cast<uint32_t>(f.code.size()));
|
||||
for (const Instr in : f.code) {
|
||||
put_le32(b, c, in);
|
||||
c += 4;
|
||||
}
|
||||
}
|
||||
uint32_t acc = 0;
|
||||
for (size_t i = 0; i < funcs_.size(); ++i) {
|
||||
const size_t o = off_funcs + i * kFuncRowSize;
|
||||
put_le32(b, o + 4, acc);
|
||||
acc += static_cast<uint32_t>(funcs_[i].code.size()) * 4;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < data_.size(); ++i) {
|
||||
b[off_data + i] = data_[i];
|
||||
}
|
||||
|
||||
// 文件尾 SHA-256(对文件尾之前全部内容计算)
|
||||
const size_t content_len = off_data + data_.size();
|
||||
uint8_t digest[kSha256Size];
|
||||
sha256(&b[0], content_len, digest);
|
||||
for (size_t i = 0; i < kSha256Size; ++i) {
|
||||
b[content_len + i] = digest[i];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 小端写 32 位。
|
||||
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);
|
||||
}
|
||||
/// 小端写 64 位。
|
||||
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_; ///< 工程定义(cycle_limit / dt_ms / 哈希)
|
||||
const std::vector<SourceUnit>& units_; ///< 全部源文件 AST
|
||||
const LinkResult& link_; ///< 链接结果(POU 顺序 / 全局符号 / FB 布局)
|
||||
const MachineConfig& cfg_; ///< 机器定义(opcode / tag / FB 布局)
|
||||
std::vector<uint8_t>* image_; ///< 输出映像字节
|
||||
std::string* err_; ///< 错误输出(可为 nullptr)
|
||||
std::vector<FuncCtx> funcs_; ///< 已编译函数
|
||||
std::vector<ConstEntry> consts_; ///< 常量表(tag + value)
|
||||
std::map<std::string, bool> io_input_; ///< I/O 输入变量名集合(小写;只影响操作码选择)
|
||||
std::map<std::string, bool> io_output_; ///< I/O 输出变量名集合(小写;只影响操作码选择)
|
||||
std::vector<uint8_t> data_; ///< 全局数据区字节(每槽 8 字节定宽)
|
||||
std::map<std::string, InstFields> instances_; ///< "POU名/实例名" → 实例布局
|
||||
const InstFields* inline_fields_ = nullptr; ///< 内联 FB 字段表(非空 = 正在内联 FB 体)
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief 编译工程为 .stb 映像字节(对外入口)。
|
||||
* @param proj 工程定义
|
||||
* @param units 全部源文件的 AST
|
||||
* @param link 链接结果
|
||||
* @param cfg 机器定义(machine.toml)
|
||||
* @param image 输出映像字节
|
||||
* @param err 错误输出;可为 nullptr
|
||||
* @return true 成功;false(err 前缀 "codegen error")
|
||||
*/
|
||||
bool codegen_project(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
const LinkResult& link, const MachineConfig& cfg,
|
||||
std::vector<uint8_t>* image, std::string* err) {
|
||||
Builder b(proj, units, link, cfg, image, err);
|
||||
return b.run();
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
+20
-13
@@ -37,9 +37,11 @@
|
||||
namespace compiler {
|
||||
namespace {
|
||||
|
||||
// 关键字表:小写键 → Tok。
|
||||
// 与 Doc/compiler/词法.md 的冻结表一致(12.5 修订补入 then/do);
|
||||
// 数值即 token 类型,只追加不删改。
|
||||
/**
|
||||
* @brief 关键字表:小写键 → Tok。
|
||||
* @details 与 Doc/compiler/词法.md 的冻结表一致(12.5 修订补入
|
||||
* then/do);数值即 token 类型,只追加不删改。
|
||||
*/
|
||||
const struct {
|
||||
const char* key;
|
||||
Tok tok;
|
||||
@@ -78,10 +80,15 @@ namespace {
|
||||
// 字面量
|
||||
{"true", Tok::TRUE},
|
||||
{"false", Tok::FALSE},
|
||||
// 内建 FB 类型名(v1 冻结三件套)
|
||||
// 内置 FB 类型名(12.11 扩充为 8 个)
|
||||
{"ton", Tok::TON},
|
||||
{"tof", Tok::TOF},
|
||||
{"tp", Tok::TP},
|
||||
{"ctu", Tok::CTU},
|
||||
{"ctd", Tok::CTD},
|
||||
{"ctud", Tok::CTUD},
|
||||
{"r_trig", Tok::R_TRIG},
|
||||
{"f_trig", Tok::F_TRIG},
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -428,15 +435,15 @@ namespace {
|
||||
}
|
||||
|
||||
// ---- 状态 ----
|
||||
const std::string& src_; // 源文本(外部所有,不拷贝)
|
||||
const std::string& sf_; // 源文件名(报错用)
|
||||
std::vector<Token>* out_; // token 流输出
|
||||
std::string* err_; // 错误输出(可空)
|
||||
size_t pos_; // 当前字符下标
|
||||
uint32_t line_; // 当前行(1 起)
|
||||
uint32_t col_; // 当前列(1 起)
|
||||
uint32_t tok_line_ = 1; // 本 token 起始行(push 时用)
|
||||
uint32_t tok_col_ = 1; // 本 token 起始列(push 时用)
|
||||
const std::string& src_; ///< 源文本(外部所有,不拷贝)
|
||||
const std::string& sf_; ///< 源文件名(报错用)
|
||||
std::vector<Token>* out_; ///< token 流输出
|
||||
std::string* err_; ///< 错误输出(可空)
|
||||
size_t pos_; ///< 当前字符下标
|
||||
uint32_t line_; ///< 当前行(1 起)
|
||||
uint32_t col_; ///< 当前列(1 起)
|
||||
uint32_t tok_line_ = 1; ///< 本 token 起始行(push 时用)
|
||||
uint32_t tok_col_ = 1; ///< 本 token 起始列(push 时用)
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
+25
-9
@@ -96,24 +96,40 @@ namespace {
|
||||
return k == TypeKind::Bool || k == TypeKind::Int || k == TypeKind::Time;
|
||||
}
|
||||
|
||||
// 内建 FB 布局(冻结,见 Doc/compiler/符号表与链接.md)
|
||||
/// 内建 FB 布局(冻结,见 Doc/compiler/符号表与链接.md;12.11 扩为 8 个)
|
||||
const FbLayout kTonLayout{"ton", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time},
|
||||
{"q", TypeKind::Bool}, {"et", TypeKind::Time}}};
|
||||
const FbLayout kTofLayout{"tof", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time},
|
||||
{"q", TypeKind::Bool}, {"et", TypeKind::Time}}};
|
||||
const FbLayout kTpLayout{"tp", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time},
|
||||
{"q", TypeKind::Bool}, {"et", TypeKind::Time}}};
|
||||
const FbLayout kCtuLayout{"ctu", {{"cu", TypeKind::Bool}, {"r", TypeKind::Bool},
|
||||
{"pv", TypeKind::Int}, {"q", TypeKind::Bool},
|
||||
{"cv", TypeKind::Int}}};
|
||||
const FbLayout kCtdLayout{"ctd", {{"cd", TypeKind::Bool}, {"ld", TypeKind::Bool},
|
||||
{"pv", TypeKind::Int}, {"q", TypeKind::Bool},
|
||||
{"cv", TypeKind::Int}}};
|
||||
const FbLayout kCtudLayout{"ctud", {{"cu", TypeKind::Bool}, {"cd", TypeKind::Bool},
|
||||
{"r", TypeKind::Bool}, {"lu", TypeKind::Bool},
|
||||
{"pv", TypeKind::Int}, {"qu", TypeKind::Bool},
|
||||
{"qd", TypeKind::Bool}, {"cv", TypeKind::Int}}};
|
||||
const FbLayout kRTrigLayout{"r_trig", {{"clk", TypeKind::Bool}, {"q", TypeKind::Bool}}};
|
||||
const FbLayout kFTrigLayout{"f_trig", {{"clk", TypeKind::Bool}, {"q", TypeKind::Bool}}};
|
||||
|
||||
/**
|
||||
* @brief 内建 FB 名 → 冻结布局
|
||||
* @param name FB 类型名(小写)
|
||||
* @return 布局指针(ton / tof / ctu);未知名字返回 nullptr
|
||||
* @return 布局指针(ton/tof/tp/ctu/ctd/ctud/r_trig/f_trig);未知返回 nullptr
|
||||
*/
|
||||
const FbLayout* builtin_layout(const std::string& name) {
|
||||
if (name == "ton") return &kTonLayout;
|
||||
if (name == "tof") return &kTofLayout;
|
||||
if (name == "tp") return &kTpLayout;
|
||||
if (name == "ctu") return &kCtuLayout;
|
||||
if (name == "ctd") return &kCtdLayout;
|
||||
if (name == "ctud") return &kCtudLayout;
|
||||
if (name == "r_trig") return &kRTrigLayout;
|
||||
if (name == "f_trig") return &kFTrigLayout;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -646,7 +662,7 @@ namespace {
|
||||
* @return true 全部绑定合法;false(err 已写)
|
||||
*/
|
||||
bool check_io() {
|
||||
for (const isa::IoBinding& b : proj_.io) {
|
||||
for (const IoBinding& b : proj_.io) {
|
||||
std::string key = b.var;
|
||||
for (char& ch : key) {
|
||||
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||
@@ -675,12 +691,12 @@ namespace {
|
||||
}
|
||||
|
||||
// ---- 成员 ----
|
||||
const Project& proj_; // 工程定义(toml)
|
||||
const std::vector<SourceUnit>& units_; // 全部源文件 AST
|
||||
LinkResult* out_; // 链接结果
|
||||
std::string* err_; // 错误输出(可空)
|
||||
std::filesystem::path gvl_path_; // 规范化后的 gvl 路径
|
||||
std::map<std::string, std::set<std::string>> call_edges_; // 调用者 → 被调函数集
|
||||
const Project& proj_; ///< 工程定义(toml)
|
||||
const std::vector<SourceUnit>& units_; ///< 全部源文件 AST
|
||||
LinkResult* out_; ///< 链接结果
|
||||
std::string* err_; ///< 错误输出(可空)
|
||||
std::filesystem::path gvl_path_; ///< 规范化后的 gvl 路径
|
||||
std::map<std::string, std::set<std::string>> call_edges_; ///< 调用者 → 被调函数集
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -0,0 +1,478 @@
|
||||
/**
|
||||
* @file MachineConfig.cpp
|
||||
* @brief 机器定义(machine.toml)加载与强校验
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 校验项(见 Doc/isa/指令配置.md 强校验清单 1~5):
|
||||
* 1. TOML 语法与字段完整性(全属性必填)
|
||||
* 2. type.base 命中内建基元;range 合法(min ≤ max)
|
||||
* 3. tag 与 .stb 常量表契约一致(BOOL=0、INT=1、TIME=2,唯一)
|
||||
* 4. op.opcode 唯一、0..255;class/format 合法;类别-格式互锁(INSTANCE ↔ CAL);
|
||||
* params 数量与 format 一致
|
||||
* 5. fb.opcode 与 op 行(instance 类)一致;字段类型命中配置类型表;字段名唯一
|
||||
*/
|
||||
|
||||
#include "compiler/MachineConfig.h"
|
||||
|
||||
#include <toml++/toml.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace compiler {
|
||||
|
||||
/**
|
||||
* @brief 内建基元表。
|
||||
* @return 静态基元表(bit / int8..uint64 / float32 / float64,元数据在代码)
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按名称查内建基元。
|
||||
* @param name 基元名
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const PrimType* MachineConfig::find_prim(const std::string& name) {
|
||||
for (const PrimType& p : prims()) {
|
||||
if (p.name == name) {
|
||||
return &p;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief 写错误信息(稳定前缀 "machine error")并原样返回 msg。
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @param msg 错误描述
|
||||
* @return msg(供调用方直接 return)
|
||||
*/
|
||||
std::string fail(std::string* err, const std::string& msg) {
|
||||
if (err) {
|
||||
*err = "machine error: " + msg;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief format → 期望参数数量(解析时校验)。
|
||||
* @param format 操作数形态(RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE)
|
||||
* @return 参数数量;未知 format 返回 -1
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/// @brief format 是否已知(params_of 返回非负)。
|
||||
bool is_known_format(const std::string& f) {
|
||||
return params_of(f) >= 0;
|
||||
}
|
||||
|
||||
/// @brief tag 是否在 .stb 常量表契约内(0=BOOL、1=INT、2=TIME)。
|
||||
bool is_known_tag(uint32_t tag) {
|
||||
return tag <= 2; // .stb 常量表契约:0=BOOL 1=INT 2=TIME
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读必填字符串字段。
|
||||
* @param t 配置表
|
||||
* @param what 表归属描述(用于错误信息,如 "[meta]")
|
||||
* @param key 字段名
|
||||
* @param out 输出值
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false(字段缺失或类型不符,err 已写 "machine error: ...")
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读必填整数字段。
|
||||
* @param t 配置表
|
||||
* @param what 表归属描述(用于错误信息,如 "[meta]")
|
||||
* @param key 字段名
|
||||
* @param out 输出值
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false(字段缺失或类型不符,err 已写 "machine error: ...")
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读必填布尔字段。
|
||||
* @param t 配置表
|
||||
* @param what 表归属描述(用于错误信息,如 "[meta]")
|
||||
* @param key 字段名
|
||||
* @param out 输出值
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false(字段缺失或类型不符,err 已写 "machine error: ...")
|
||||
*/
|
||||
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
|
||||
|
||||
/**
|
||||
* @brief 加载 machine.toml 并做强校验。
|
||||
* @param path machine.toml 路径
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false(err 已写,前缀 "machine error")
|
||||
* @details 校验顺序与文件头清单 1~5 一致:
|
||||
* 1. TOML 语法与字段完整性(meta/type/op/fb 全属性必填)
|
||||
* 2. type.base 命中内建基元;range 合法(min ≤ max)
|
||||
* 3. tag 与 .stb 常量表契约一致(BOOL=0、INT=1、TIME=2,唯一)
|
||||
* 4. op.opcode 唯一、0..255;class/format 合法;类别-格式互锁(INSTANCE ↔ CAL);
|
||||
* params 数量与 format 一致
|
||||
* 5. fb.opcode 与 op 行(instance 类)一致;字段类型命中配置类型表;字段名唯一
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按配置类型名查类型行。
|
||||
* @param name 配置类型名
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigType* MachineConfig::find_type(const std::string& name) const {
|
||||
for (const ConfigType& t : types_) {
|
||||
if (t.name == name) {
|
||||
return &t;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按助记符查指令行。
|
||||
* @param name 助记符(如 "MOVE")
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 opcode 查指令行。
|
||||
* @param opcode 操作码(0..255)
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查询某 opcode 是否启用。
|
||||
* @param opcode 操作码
|
||||
* @return 指令存在且 enabled 为 true;未知 opcode 返回 false
|
||||
*/
|
||||
bool MachineConfig::op_enabled(uint32_t opcode) const {
|
||||
const ConfigOp* op = find_op_by_code(opcode);
|
||||
return op != nullptr && op->enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按名称查内建 FB。
|
||||
* @param name FB 名(小写,如 "ton")
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
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
|
||||
+36
-8
@@ -57,9 +57,12 @@
|
||||
namespace compiler {
|
||||
namespace {
|
||||
|
||||
// 明确拒绝的保留名(小写命中即报错,见 Doc/compiler/语法.md)。
|
||||
// 这些词在词法层不是关键字(lex 成 IDENT),必须在语法层显式拦截,
|
||||
// 避免 VAR_IN_OUT / REF / CLASS 等被静默当作用户标识符。
|
||||
/**
|
||||
* @brief 语法层明确拒绝的保留名清单。
|
||||
* @details 这些词在词法层不是关键字(lex 成 IDENT),必须在语法层
|
||||
* 显式拦截,避免 VAR_IN_OUT / REF / CLASS 等被静默当作
|
||||
* 用户标识符(详见 Doc/compiler/语法.md)。
|
||||
*/
|
||||
const char* const kForbidden[] = {
|
||||
"var_in_out", "var_temp", "ref", "class", "any",
|
||||
"pointer", "interface", "method",
|
||||
@@ -458,11 +461,36 @@ namespace {
|
||||
t->name = "tof";
|
||||
advance();
|
||||
return true;
|
||||
case Tok::TP:
|
||||
t->kind = TypeKind::FbBuiltin;
|
||||
t->name = "tp";
|
||||
advance();
|
||||
return true;
|
||||
case Tok::CTU:
|
||||
t->kind = TypeKind::FbBuiltin;
|
||||
t->name = "ctu";
|
||||
advance();
|
||||
return true;
|
||||
case Tok::CTD:
|
||||
t->kind = TypeKind::FbBuiltin;
|
||||
t->name = "ctd";
|
||||
advance();
|
||||
return true;
|
||||
case Tok::CTUD:
|
||||
t->kind = TypeKind::FbBuiltin;
|
||||
t->name = "ctud";
|
||||
advance();
|
||||
return true;
|
||||
case Tok::R_TRIG:
|
||||
t->kind = TypeKind::FbBuiltin;
|
||||
t->name = "r_trig";
|
||||
advance();
|
||||
return true;
|
||||
case Tok::F_TRIG:
|
||||
t->kind = TypeKind::FbBuiltin;
|
||||
t->name = "f_trig";
|
||||
advance();
|
||||
return true;
|
||||
case Tok::IDENT: {
|
||||
const std::string word = cur().text;
|
||||
if (is_forbidden(word)) {
|
||||
@@ -964,11 +992,11 @@ namespace {
|
||||
|
||||
// ---- 成员 ----
|
||||
|
||||
std::string* err_; // 错误输出(可空)
|
||||
std::vector<Token> tokens_; // 整段 token 流(构造时一次性 lex 完成)
|
||||
Unit* out_; // 解析结果
|
||||
size_t pos_ = 0; // 当前 token 下标
|
||||
bool ok_ = true; // 词法阶段是否成功
|
||||
std::string* err_; ///< 错误输出(可空)
|
||||
std::vector<Token> tokens_; ///< 整段 token 流(构造时一次性 lex 完成)
|
||||
Unit* out_; ///< 解析结果
|
||||
size_t pos_ = 0; ///< 当前 token 下标
|
||||
bool ok_ = true; ///< 词法阶段是否成功
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
+170
-20
@@ -3,6 +3,31 @@
|
||||
* @brief 工程定义与 project.toml 解析(schema 校验)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 设计说明(schema 规则见 Doc/初步计划.md 12.1 的 toml 字段表):
|
||||
* - 解析顺序:root 键白名单 → [project] → [files] → [gvl](可选)→ [io](可选),
|
||||
* 任一段失败立即返回(首个错误优先,不做恢复)
|
||||
* - 错误:稳定类别前缀(parse error / unknown key / missing field / invalid value /
|
||||
* bad entry)+ 行号(toml++ 源位置)
|
||||
* - io 绑定不创造变量:slot 留待 12.6 链接阶段解析,此处填 0
|
||||
*
|
||||
* 函数清单:
|
||||
* - fail 写错误消息;err 为 nullptr 时静默
|
||||
* - at / at_pos toml 节点 / 位置 → " (line N)" 行号后缀
|
||||
* - req_string 取字符串(必填校验)
|
||||
* - req_pos_int 取正整数(必填校验)
|
||||
* - req_uint 取非负整数(必填校验,channel / bit 允许 0)
|
||||
* - check_keys 表内键白名单(报首个未知键)
|
||||
* - parse_project_section [project] 段(name/entry/cycle_limit/dt_ms 全必填)
|
||||
* - parse_files_section [files] 段(st 必填、非空字符串数组)
|
||||
* - parse_gvl_section [gvl] 段(可选;file 必填字符串)
|
||||
* - parse_io_entry [[io.*]] 单个条目(var/channel/bit 全必填)
|
||||
* - parse_io_section [io] 段(可选;input/output 数组可缺省)
|
||||
* - parse_root 顶层:root 键白名单 + 按序解析四段
|
||||
* - dir_of 取路径的目录部分
|
||||
* - parse_project 对外入口:parse_file 捕获 parse_error → parse_root
|
||||
* - compile_files 编译文件集合(files.st ∪ gvl.file,去重)
|
||||
* - compute_project_hash 校验文件存在并计算 FNV-1a 64 工程哈希
|
||||
*/
|
||||
|
||||
#include "compiler/Project.h"
|
||||
@@ -16,35 +41,59 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "isa/Image.h"
|
||||
#include "compiler/Stb.h"
|
||||
|
||||
namespace compiler {
|
||||
namespace {
|
||||
|
||||
// ---- 错误收集:稳定类别前缀 + 行号 ----
|
||||
|
||||
// 写错误消息;err 为 nullptr 时静默忽略(调用方可不关心原因)
|
||||
/**
|
||||
* @brief 写错误消息。
|
||||
* @details err 为 nullptr 时静默忽略(调用方可不关心原因)。
|
||||
* @param err 错误输出(可空)
|
||||
* @param msg 完整错误消息(已带类别前缀与行号)
|
||||
*/
|
||||
void fail(std::string* err, const std::string& msg) {
|
||||
if (err) {
|
||||
*err = msg;
|
||||
}
|
||||
}
|
||||
|
||||
// 给 toml 节点附加行号后缀 " (line N)",用于定位非法值位置
|
||||
/**
|
||||
* @brief 给 toml 节点附加行号后缀。
|
||||
* @param n toml 节点
|
||||
* @return " (line N)",用于定位非法值位置
|
||||
*/
|
||||
std::string at(const toml::node& n) {
|
||||
char buf[32];
|
||||
std::snprintf(buf, sizeof buf, " (line %u)", n.source().begin.line);
|
||||
return buf;
|
||||
}
|
||||
|
||||
// parse_error 的位置可能是空的(如文件打不开):只在有位置时带行号
|
||||
/**
|
||||
* @brief 给 parse_error 位置附加行号后缀。
|
||||
* @details parse_error 的位置可能是空的(如文件打不开):只在有位置时带行号。
|
||||
* @param pos toml 源位置
|
||||
* @return " (line N)";位置为空时返回空串
|
||||
*/
|
||||
std::string at_pos(const toml::source_position& pos) {
|
||||
char buf[32];
|
||||
std::snprintf(buf, sizeof buf, " (line %u)", pos.line);
|
||||
return static_cast<bool>(pos) ? buf : std::string();
|
||||
}
|
||||
|
||||
// 取字符串,必填校验
|
||||
/**
|
||||
* @brief 取字符串字段(必填校验)。
|
||||
* @details 缺失报 "missing field 'key' in [sec]";类型非字符串报
|
||||
* "invalid value for 'key' in [sec] (expect string)" + 行号。
|
||||
* @param tbl 当前 toml 表
|
||||
* @param sec 段名(报错用)
|
||||
* @param key 字段名
|
||||
* @param out 输出字符串值
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 成功;false 缺失或类型错误(err 已写)
|
||||
*/
|
||||
bool req_string(const toml::table& tbl, const char* sec, const char* key,
|
||||
std::string* out, std::string* err) {
|
||||
if (const auto nv = tbl[key]) {
|
||||
@@ -60,7 +109,17 @@ namespace {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 取正整数,必填校验
|
||||
/**
|
||||
* @brief 取正整数字段(必填校验)。
|
||||
* @details 缺失报 "missing field";非整数报 "invalid value ... (expect integer)";
|
||||
* v <= 0 报 "(expect > 0)",均带行号。
|
||||
* @param tbl 当前 toml 表
|
||||
* @param sec 段名(报错用)
|
||||
* @param key 字段名
|
||||
* @param out 输出整数值
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 成功;false 缺失或非法(err 已写)
|
||||
*/
|
||||
bool req_pos_int(const toml::table& tbl, const char* sec, const char* key,
|
||||
uint32_t* out, std::string* err) {
|
||||
if (const auto nv = tbl[key]) {
|
||||
@@ -82,7 +141,16 @@ namespace {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 取非负整数,必填校验(channel / bit 允许 0)
|
||||
/**
|
||||
* @brief 取非负整数字段(必填校验)。
|
||||
* @details 与 req_pos_int 相同校验,但允许 0(channel / bit 可为 0)。
|
||||
* @param tbl 当前 toml 表
|
||||
* @param sec 段名(报错用)
|
||||
* @param key 字段名
|
||||
* @param out 输出整数值
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 成功;false 缺失或非法(err 已写)
|
||||
*/
|
||||
bool req_uint(const toml::table& tbl, const char* sec, const char* key,
|
||||
uint32_t* out, std::string* err) {
|
||||
if (const auto nv = tbl[key]) {
|
||||
@@ -104,7 +172,16 @@ namespace {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 表内键白名单;返回 false 并报首个未知键
|
||||
/**
|
||||
* @brief 表内键白名单校验。
|
||||
* @details 发现首个未知键即失败,报 "unknown key 'key' in [sec]" + 行号。
|
||||
* @param tbl 当前 toml 表
|
||||
* @param sec 段名(报错用)
|
||||
* @param allowed 允许的键名数组
|
||||
* @param n_allowed 键名数量
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 全部合法;false 有未知键(err 已写)
|
||||
*/
|
||||
bool check_keys(const toml::table& tbl, const char* sec,
|
||||
const char* const* allowed, size_t n_allowed,
|
||||
std::string* err) {
|
||||
@@ -127,8 +204,14 @@ namespace {
|
||||
|
||||
// ---- [project] ----
|
||||
|
||||
// [project] 段:name/entry/cycle_limit/dt_ms 全必填;
|
||||
// 第一版 entry 只接受 "program MAIN"
|
||||
/**
|
||||
* @brief 解析 [project] 段。
|
||||
* @details name/entry/cycle_limit/dt_ms 全必填;第一版 entry 只接受 "program MAIN"。
|
||||
* @param root 根表
|
||||
* @param out 输出 Project(填充 name/entry/cycle_limit/dt_ms)
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool parse_project_section(const toml::table& root, Project* out, std::string* err) {
|
||||
static const char* const allowed[] = {"name", "entry", "cycle_limit", "dt_ms"};
|
||||
|
||||
@@ -166,7 +249,14 @@ namespace {
|
||||
|
||||
// ---- [files] ----
|
||||
|
||||
// [files] 段:st 必填、非空数组,元素必须全为字符串,按声明顺序收集
|
||||
/**
|
||||
* @brief 解析 [files] 段。
|
||||
* @details st 必填、非空数组,元素必须全为字符串,按声明顺序收集。
|
||||
* @param root 根表
|
||||
* @param out 输出 Project(填充 files_st)
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool parse_files_section(const toml::table& root, Project* out, std::string* err) {
|
||||
static const char* const allowed[] = {"st"};
|
||||
|
||||
@@ -208,7 +298,14 @@ namespace {
|
||||
|
||||
// ---- [gvl](可选)----
|
||||
|
||||
// [gvl] 段:可选;存在时 file 必填字符串,缺失整段不报错
|
||||
/**
|
||||
* @brief 解析 [gvl] 段(可选)。
|
||||
* @details 存在时 file 必填字符串;整段缺失不报错。
|
||||
* @param root 根表
|
||||
* @param out 输出 Project(填充 gvl_file)
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool parse_gvl_section(const toml::table& root, Project* out, std::string* err) {
|
||||
static const char* const allowed[] = {"file"};
|
||||
|
||||
@@ -228,7 +325,16 @@ namespace {
|
||||
|
||||
// ---- [[io.input]] / [[io.output]](可选)----
|
||||
|
||||
// 单个 I/O 条目:var/channel/bit 全必填、全非负整数;slot 留待 12.6 链接阶段
|
||||
/**
|
||||
* @brief 解析单个 I/O 条目([[io.input]] / [[io.output]] 数组元素)。
|
||||
* @details var/channel/bit 全必填,channel/bit 为非负整数;类型非表报
|
||||
* "bad entry in [[io.xxx]] (expect table)";slot 留待 12.6 链接阶段解析。
|
||||
* @param el 条目节点
|
||||
* @param is_input true = [[io.input]],false = [[io.output]]
|
||||
* @param out 输出 Project(追加 io 条目)
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool parse_io_entry(const toml::node& el, bool is_input, Project* out, std::string* err) {
|
||||
static const char* const allowed[] = {"var", "channel", "bit"};
|
||||
|
||||
@@ -241,7 +347,7 @@ namespace {
|
||||
if (!check_keys(sec, is_input ? "io.input" : "io.output", allowed, 3, err)) {
|
||||
return false;
|
||||
}
|
||||
isa::IoBinding b;
|
||||
IoBinding b;
|
||||
b.is_input = is_input;
|
||||
b.slot = 0; // 12.6 链接阶段再解析
|
||||
if (!req_string(sec, is_input ? "io.input" : "io.output", "var", &b.var, err)) {
|
||||
@@ -257,7 +363,14 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
// [io] 段:可选;input/output 子表为数组时逐条解析,两数组都可缺省
|
||||
/**
|
||||
* @brief 解析 [io] 段(可选)。
|
||||
* @details input/output 子表为数组时逐条解析,两数组都可缺省;整段缺失不报错。
|
||||
* @param root 根表
|
||||
* @param out 输出 Project(填充 io)
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool parse_io_section(const toml::table& root, Project* out, std::string* err) {
|
||||
static const char* const allowed[] = {"input", "output"};
|
||||
|
||||
@@ -292,8 +405,15 @@ namespace {
|
||||
|
||||
// ---- 顶层 ----
|
||||
|
||||
// 顶层:root 键白名单 + 按 project → files → gvl → io 顺序解析,
|
||||
// 任一段失败立即返回 false(首个错误优先,不做恢复)
|
||||
/**
|
||||
* @brief 顶层解析入口。
|
||||
* @details root 键白名单 + 按 project → files → gvl → io 顺序解析,
|
||||
* 任一段失败立即返回 false(首个错误优先,不做恢复)。
|
||||
* @param root 根表
|
||||
* @param out 输出 Project
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool parse_root(const toml::table& root, Project* out, std::string* err) {
|
||||
static const char* const allowed[] = {"project", "files", "gvl", "io"};
|
||||
if (!check_keys(root, "root", allowed, 4, err)) {
|
||||
@@ -311,7 +431,12 @@ namespace {
|
||||
return parse_io_section(root, out, err);
|
||||
}
|
||||
|
||||
// 取路径的目录部分:最后一个分隔符之前;无分隔符返回 "."(相对当前目录)
|
||||
/**
|
||||
* @brief 取路径的目录部分。
|
||||
* @details 最后一个分隔符之前;无分隔符返回 "."(相对当前目录)。
|
||||
* @param path 文件路径
|
||||
* @return 目录部分
|
||||
*/
|
||||
std::string dir_of(const std::string& path) {
|
||||
const size_t slash = path.find_last_of("/\\");
|
||||
return (slash == std::string::npos) ? "." : path.substr(0, slash);
|
||||
@@ -319,6 +444,16 @@ namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief 解析并校验 project.toml(对外入口)。
|
||||
* @details 先定 base_dir(toml 所在目录),再 parse_file;toml++ 默认 TOML_EXCEPTIONS=1,
|
||||
* parse_file 失败直接抛 parse_error,捕获后报 "parse error: <描述>" + 行号
|
||||
* (位置空时不带),随后交由 parse_root 按段解析。
|
||||
* @param toml_path project.toml 路径
|
||||
* @param out 输出 Project
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false 失败(err 已写)
|
||||
*/
|
||||
bool parse_project(const std::string& toml_path, Project* out, std::string* err) {
|
||||
out->base_dir = dir_of(toml_path);
|
||||
|
||||
@@ -336,6 +471,12 @@ bool parse_project(const std::string& toml_path, Project* out, std::string* err)
|
||||
|
||||
// ---- 文件集合与工程哈希 ----
|
||||
|
||||
/**
|
||||
* @brief 编译文件集合:files.st ∪ gvl.file,去重(gvl 已在 files.st 则跳过)。
|
||||
* @details 保持 files.st 顺序,gvl 追加在后;相对路径以 base_dir 为基准解析。
|
||||
* @param p 工程定义
|
||||
* @return 编译文件路径集合
|
||||
*/
|
||||
std::vector<std::string> compile_files(const Project& p) {
|
||||
std::vector<std::string> out = p.files_st;
|
||||
if (!p.gvl_file.empty() &&
|
||||
@@ -345,6 +486,15 @@ std::vector<std::string> compile_files(const Project& p) {
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 校验全部文件存在并计算工程哈希。
|
||||
* @details 集合按路径排序(规格:路径只当排序键),对内容做 FNV-1a 64 增量;
|
||||
* 空集合 = basis(kFnvBasis);缺文件报错前缀 "file missing"。
|
||||
* @param p 工程定义
|
||||
* @param hash 输出工程哈希
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false 文件缺失(err 已写)
|
||||
*/
|
||||
bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err) {
|
||||
std::vector<std::string> files = compile_files(p);
|
||||
|
||||
@@ -353,7 +503,7 @@ bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err) {
|
||||
std::sort(sorted.begin(), sorted.end());
|
||||
|
||||
std::filesystem::path base(p.base_dir);
|
||||
uint64_t h = isa::kFnvBasis;
|
||||
uint64_t h = kFnvBasis;
|
||||
for (const std::string& f : sorted) {
|
||||
std::ifstream in(base / f, std::ios::binary);
|
||||
if (!in) {
|
||||
@@ -365,7 +515,7 @@ bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err) {
|
||||
in.read(buf, sizeof buf);
|
||||
const std::streamsize n = in.gcount();
|
||||
if (n > 0) {
|
||||
h = isa::fnv1a64_update(h, reinterpret_cast<const uint8_t*>(buf),
|
||||
h = fnv1a64_update(h, reinterpret_cast<const uint8_t*>(buf),
|
||||
static_cast<size_t>(n));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,481 @@
|
||||
/**
|
||||
* @file Stb.cpp
|
||||
* @brief 编译器自带的 .stb 映像规范(写侧)+ 只读视图 + FNV-1a + sidecar
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 与 vm 侧各一份实现;格式契约见 Doc/isa/指令与映像.md。
|
||||
* 12.13 修订已落地:头 104 = 原 72 + 型号标识[32] @72,文件尾 SHA-256[32];
|
||||
* 工程哈希为 FNV-1a 64。
|
||||
*/
|
||||
|
||||
#include "compiler/Stb.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
||||
#include "compiler/Project.h"
|
||||
|
||||
namespace compiler {
|
||||
|
||||
/**
|
||||
* @brief FNV-1a 64 增量哈希更新(工程哈希,非密码学)。
|
||||
* @param h 当前哈希(首轮传 kFnvBasis)
|
||||
* @param data 输入字节
|
||||
* @param len 字节数
|
||||
* @return 更新后的哈希值
|
||||
* @details 每字节:h ^= byte; h *= kFnvPrime。
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief FNV-1a 64 一次性哈希(工程哈希)。
|
||||
* @param data 输入字节
|
||||
* @param len 字节数
|
||||
* @return 哈希值(从 kFnvBasis 起)
|
||||
*/
|
||||
uint64_t fnv1a64(const uint8_t* data, size_t len) {
|
||||
return fnv1a64_update(kFnvBasis, data, len);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/// 小端读 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);
|
||||
}
|
||||
|
||||
/// 小端读 64 位。
|
||||
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(FIPS 180-4)----
|
||||
|
||||
/// SHA-256 轮常量 K[0..63]。
|
||||
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)); }
|
||||
|
||||
/**
|
||||
* @brief SHA-256 增量状态机。
|
||||
* @details 标准 FIPS 180-4 实现:update() 吸收任意长度字节流,final() 输出
|
||||
* 32 字节大端摘要。按 64 字节块 process()。
|
||||
*/
|
||||
struct Sha256 {
|
||||
uint32_t h[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; ///< 初始哈希值
|
||||
uint64_t total = 0; ///< 已吸收字节数(final 时编码进长度域)
|
||||
uint8_t block[64]; ///< 当前块缓冲
|
||||
size_t block_len = 0; ///< 块缓冲已用字节数
|
||||
|
||||
/**
|
||||
* @brief 吸收数据。
|
||||
* @param data 输入字节
|
||||
* @param len 字节数
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 压缩一个满块(64 字节:w[0..63] 展开 + 64 轮)。
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 结束并输出摘要。
|
||||
* @param out 32 字节输出缓冲(大端)
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// 大端读 32 位。
|
||||
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
|
||||
|
||||
/**
|
||||
* @brief 一次性 SHA-256(文件完整性校验)。
|
||||
* @param data 输入字节
|
||||
* @param len 字节数
|
||||
* @param out 32 字节摘要输出(大端)
|
||||
*/
|
||||
void sha256(const uint8_t* data, size_t len, uint8_t out[kSha256Size]) {
|
||||
Sha256 s;
|
||||
s.update(data, len);
|
||||
s.final(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按型号标识约定填充 32 字节:name + version(如 "STATOR1")。
|
||||
* @param name 型号名
|
||||
* @param version 版本号
|
||||
* @param out 32 字节输出缓冲
|
||||
* @details 不足补 '\0',超长截断。
|
||||
*/
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从原始字节构造只读视图(不拷贝,调用方保证生命周期)。
|
||||
* @param buf 映像缓冲;可为 nullptr
|
||||
* @param len 缓冲字节数
|
||||
* @return 校验结果;ok() 判成功,error() 取失败原因
|
||||
* @details 校验顺序:长度 ≥ 头 → 魔数 → 版本 → 段偏移范围/单调 →
|
||||
* 常量表/函数表大小 → 代码段 4 字节对齐 → 入口 fn_id 范围;
|
||||
* 错误消息:"null buffer" / "image too short" / "bad magic" /
|
||||
* "bad version" / "missing sha256 tail" / "segment offset out of range" /
|
||||
* "segment offsets not monotonic" / "const table size mismatch" /
|
||||
* "function table size mismatch" / "code segment not 4-byte aligned" /
|
||||
* "entry fn_id out of range"。
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/// 从 vector 构造(转发 from(buf.data(), buf.size()))。
|
||||
StbView StbView::from(const std::vector<uint8_t>& buf) {
|
||||
return from(buf.data(), buf.size());
|
||||
}
|
||||
|
||||
/// 读常量表一行;越界或未 ok() 时返回全 0。
|
||||
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;
|
||||
}
|
||||
|
||||
/// 常量表段起点(头 @52;由 from() 已校验的段起点)。
|
||||
uint32_t StbView::offs_of_const() const {
|
||||
// offset_const = offs[0],由 from() 已校验的段起点
|
||||
return static_cast<uint32_t>(get_le32(buf_ + 52));
|
||||
}
|
||||
|
||||
/// 读函数表一行;越界或未 ok() 时返回全 0。
|
||||
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;
|
||||
}
|
||||
|
||||
/// 字节码段起点(未 ok() 时返回 nullptr)。
|
||||
const uint8_t* StbView::code_bytes() const {
|
||||
return ok_ ? buf_ + offset_code_ : nullptr;
|
||||
}
|
||||
|
||||
/// 字节码段字节数(未 ok() 时返回 0)。
|
||||
size_t StbView::code_len() const {
|
||||
return ok_ ? offset_fb_ - offset_code_ : 0;
|
||||
}
|
||||
|
||||
/// 数据段起点(未 ok() 时返回 nullptr)。
|
||||
const uint8_t* StbView::data_bytes() const {
|
||||
return ok_ ? buf_ + offset_data_ : nullptr;
|
||||
}
|
||||
|
||||
/// 数据段字节数(不含文件尾 SHA-256;未 ok() 时返回 0)。
|
||||
size_t StbView::data_len() const {
|
||||
return ok_ ? (len_ - kSha256Size) - offset_data_ : 0;
|
||||
}
|
||||
|
||||
/// 型号标识字符串(头 72..103,截断到首个 '\0';未 ok() 时返回空串)。
|
||||
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;
|
||||
}
|
||||
|
||||
/// 型号标识是否匹配 name + version(未 ok() 时返回 false)。
|
||||
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;
|
||||
}
|
||||
|
||||
/// 文件尾 32 字节 SHA-256 校验(对文件尾之前全部内容重算;未 ok() 时返回 false)。
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读整个文件为字节(纯字节;校验交给 StbView)。
|
||||
* @param path 文件路径
|
||||
* @param out 输出字节
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false(err "cannot open for read: <path>")
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 写字节为文件(纯字节)。
|
||||
* @param path 文件路径
|
||||
* @param img 映像字节
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false(err "cannot open for write: <path>" / "write failed: <path>")
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 生成 sidecar 文本(TOML)。
|
||||
* @param bindings I/O 绑定列表
|
||||
* @return TOML 文本(每个绑定一节 [[io.input]] / [[io.output]],含
|
||||
* var / slot / channel / bit 四字段)
|
||||
* @details I/O 绑定 var → 槽号 → channel/bit(执行器采样用)。
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 生成并写 sidecar 文件。
|
||||
* @param path 文件路径
|
||||
* @param bindings I/O 绑定列表
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false(err "cannot open for write: <path>" / "write failed: <path>")
|
||||
*/
|
||||
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
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @file TypeInfo.cpp
|
||||
* @brief 语言类型元数据(machine.toml 别名 + 内建基元解析)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details type_meta 实现:先按名称(大小写不敏感)命中配置类型行,
|
||||
* 再由 base 解析出内建基元,合成 TypeMeta。
|
||||
*/
|
||||
|
||||
#include "compiler/TypeInfo.h"
|
||||
|
||||
#include <cctype>
|
||||
|
||||
namespace compiler {
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief 转小写。
|
||||
* @param s 输入串
|
||||
* @return 全小写副本(逐字符 tolower,逐字节安全)
|
||||
*/
|
||||
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
|
||||
|
||||
/**
|
||||
* @brief 按语言类型名查元数据。
|
||||
* @param cfg 机器配置(类型表来源)
|
||||
* @param name 语言类型名
|
||||
* @return 对应 TypeMeta;未找到返回空 TypeMeta(operator bool 为 false)
|
||||
* @details 比较前两侧都转小写(配置名大写 BOOL/INT/TIME,Linker type_name 小写);
|
||||
* 命中配置行后再用 base 解析内建基元,两者都命中才算有效。
|
||||
*/
|
||||
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
|
||||
@@ -0,0 +1,494 @@
|
||||
/**
|
||||
* @file Typecheck.cpp
|
||||
* @brief 类型检查
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 设计说明(详见 Doc/compiler/类型检查.md):
|
||||
* - 在链接成功后运行:符号/字段/函数存在性已由 12.6 保证,本阶段只查类型
|
||||
* - 表达式求值类型三型:BOOL / INT / TIME;无隐式宽化,INT 与 TIME 不混用
|
||||
* - 语句规则:赋值类型相等、FUNCTION 禁写全局(含 EXTERNAL,用例 14)、
|
||||
* FB 命名实参匹配字段类型、IF/WHILE 条件必须 BOOL
|
||||
* - 错误:稳定前缀 "type error" + 文件(Expr/Stmt 无行列信息,只带文件)
|
||||
*
|
||||
* 函数清单:
|
||||
* - tname TType → 文本名(BOOL/INT/TIME)
|
||||
* - to_type(TypeKind) TypeKind → TType(FB 类型返回 false)
|
||||
* - to_type(string) 符号 type_name 字符串 → TType
|
||||
* - Checker::Checker (构造)存工程/源文件/链接结果/错误输出
|
||||
* - Checker::run 逐 POU 逐语句检查
|
||||
* - Checker::fail 组装 "type error: <msg> (file)" 返回 false
|
||||
* - find_scope 按 POU 名取作用域
|
||||
* - find_sym 在作用域里按名查符号
|
||||
* - find_global 按名查全局符号
|
||||
* - find_pou 按名查 POU AST(函数返回类型用)
|
||||
* - check_stmt 语句规则分发(赋值/FB 调用/IF/WHILE)
|
||||
* - assign_target_type 解析赋值左值类型(局部/全局/函数名),FUNCTION 禁写全局
|
||||
* - check_expr 表达式定类型(递归)
|
||||
* - check_project 对外入口:逐 POU 检查
|
||||
*/
|
||||
|
||||
#include "compiler/Typecheck.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace compiler {
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief TType → 文本名(错误报文用)
|
||||
* @param t 求值类型
|
||||
* @return "BOOL" / "INT" / "TIME"
|
||||
*/
|
||||
const char* tname(TType t) {
|
||||
switch (t) {
|
||||
case TType::Bool: return "BOOL";
|
||||
case TType::Int: return "INT";
|
||||
case TType::Time: return "TIME";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief TypeKind → TType
|
||||
* @param k 类型种类
|
||||
* @param out 输出求值类型
|
||||
* @return true 标量(BOOL/INT/TIME);FB 类型返回 false
|
||||
*/
|
||||
bool to_type(TypeKind k, TType* out) {
|
||||
switch (k) {
|
||||
case TypeKind::Bool: *out = TType::Bool; return true;
|
||||
case TypeKind::Int: *out = TType::Int; return true;
|
||||
case TypeKind::Time: *out = TType::Time; return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 符号 type_name 字符串 → TType
|
||||
* @param name "bool" / "int" / "time"(小写)
|
||||
* @param out 输出求值类型
|
||||
* @return true 标量;FB 类型名返回 false
|
||||
*/
|
||||
bool to_type(const std::string& name, TType* out) {
|
||||
if (name == "bool") { *out = TType::Bool; return true; }
|
||||
if (name == "int") { *out = TType::Int; return true; }
|
||||
if (name == "time") { *out = TType::Time; return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 类型检查器
|
||||
*
|
||||
* @details 链接结果只读;不修改 AST 与符号表。
|
||||
*/
|
||||
class Checker {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造检查器
|
||||
* @param proj 工程定义(未用,保留接口对称;io 类型映射留待 12.8)
|
||||
* @param units 全部源文件的 AST
|
||||
* @param link 链接结果(符号/布局已解析)
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
*/
|
||||
Checker(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
const LinkResult& link, std::string* err)
|
||||
: proj_(proj), units_(units), link_(link), err_(err) {}
|
||||
|
||||
/**
|
||||
* @brief 逐 POU 逐语句做类型检查
|
||||
* @return true 全部通过;false 首个类型错误(err 已写)
|
||||
*/
|
||||
bool run() {
|
||||
for (const SourceUnit& u : units_) {
|
||||
for (const POU& p : u.ast.pous) {
|
||||
const LinkResult::PouScope* sc = find_scope(p.name);
|
||||
if (sc == nullptr) {
|
||||
continue; // 不应发生(12.6 已登记)
|
||||
}
|
||||
for (const Stmt& st : p.body) {
|
||||
if (!check_stmt(u.path, *sc, p, st)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 组装 "type error: <msg> (file)" 写入 err
|
||||
* @details Expr/Stmt 不携带行列,只报文件;err_ 为 nullptr 时静默
|
||||
* @param file 出错文件
|
||||
* @param msg 错误描述(不含前缀)
|
||||
* @return 恒 false(便于 return fail(...) 一行退出)
|
||||
*/
|
||||
bool fail(const std::string& file, const std::string& msg) {
|
||||
if (err_) {
|
||||
*err_ = "type error: " + msg + " (" + file + ")";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 POU 名取作用域
|
||||
* @param name POU 名(小写)
|
||||
* @return 作用域指针;未找到返回 nullptr
|
||||
*/
|
||||
const LinkResult::PouScope* find_scope(const std::string& name) const {
|
||||
for (const LinkResult::PouScope& sc : link_.scopes) {
|
||||
if (sc.name == name) {
|
||||
return ≻
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 在作用域里按名查符号
|
||||
* @param sc 作用域
|
||||
* @param name 符号名(小写)
|
||||
* @return 符号指针;未找到返回 nullptr
|
||||
*/
|
||||
const Symbol* find_sym(const LinkResult::PouScope& sc,
|
||||
const std::string& name) const {
|
||||
for (const Symbol& s : sc.syms) {
|
||||
if (s.name == name) {
|
||||
return &s;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按名查全局符号
|
||||
* @param name 全局名(小写)
|
||||
* @return 符号指针;未找到返回 nullptr
|
||||
*/
|
||||
const Symbol* find_global(const std::string& name) const {
|
||||
const auto it = link_.global_index.find(name);
|
||||
if (it == link_.global_index.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &link_.globals[it->second];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按名查 POU AST(函数返回类型用)
|
||||
* @param name POU 名(小写)
|
||||
* @return POU 指针;未找到返回 nullptr
|
||||
*/
|
||||
const POU* find_pou(const std::string& name) const {
|
||||
for (const SourceUnit& u : units_) {
|
||||
for (const POU& p : u.ast.pous) {
|
||||
if (p.name == name) {
|
||||
return &p;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 语句规则分发
|
||||
* @param file 出错文件
|
||||
* @param sc 当前作用域
|
||||
* @param pou 当前 POU(函数名赋值 / 禁写全局判定用)
|
||||
* @param st 语句 AST
|
||||
* @return true 合法;false(err 已写)
|
||||
*/
|
||||
bool check_stmt(const std::string& file, const LinkResult::PouScope& sc,
|
||||
const POU& pou, const Stmt& st) {
|
||||
switch (st.kind) {
|
||||
case StmtKind::Assign: {
|
||||
TType lhs;
|
||||
if (!assign_target_type(file, sc, pou, st.target, &lhs)) {
|
||||
return false;
|
||||
}
|
||||
TType rhs;
|
||||
if (!check_expr(file, sc, *st.value, &rhs)) {
|
||||
return false;
|
||||
}
|
||||
if (lhs != rhs) {
|
||||
return fail(file, "type mismatch in assignment to '" + st.target +
|
||||
"' (" + tname(lhs) + " vs " + tname(rhs) + ")");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case StmtKind::FbCall: {
|
||||
const auto it = sc.fb_instances.find(st.instance);
|
||||
if (it == sc.fb_instances.end()) {
|
||||
return fail(file, "no layout for FB instance '" + st.instance + "'");
|
||||
}
|
||||
for (const FbArg& a : st.args) {
|
||||
TType want = TType::Bool;
|
||||
bool found = false;
|
||||
for (const FbField& f : it->second.fields) {
|
||||
if (f.name == a.name) {
|
||||
found = to_type(f.type, &want);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return fail(file, "unknown input '" + a.name + "' for FB '" +
|
||||
st.instance + "'");
|
||||
}
|
||||
TType got;
|
||||
if (!check_expr(file, sc, *a.value, &got)) {
|
||||
return false;
|
||||
}
|
||||
if (got != want) {
|
||||
return fail(file, "FB input '" + a.name + "' expects " +
|
||||
tname(want));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case StmtKind::If:
|
||||
if (!check_cond(file, sc, *st.cond)) {
|
||||
return false;
|
||||
}
|
||||
for (const Stmt& s : st.body) {
|
||||
if (!check_stmt(file, sc, pou, s)) return false;
|
||||
}
|
||||
for (const IfBranch& b : st.elsifs) {
|
||||
if (!check_cond(file, sc, *b.cond)) return false;
|
||||
for (const Stmt& s : b.body) {
|
||||
if (!check_stmt(file, sc, pou, s)) return false;
|
||||
}
|
||||
}
|
||||
for (const Stmt& s : st.else_body) {
|
||||
if (!check_stmt(file, sc, pou, s)) return false;
|
||||
}
|
||||
return true;
|
||||
case StmtKind::While:
|
||||
if (!check_cond(file, sc, *st.cond)) {
|
||||
return false;
|
||||
}
|
||||
for (const Stmt& s : st.body) {
|
||||
if (!check_stmt(file, sc, pou, s)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 条件表达式必须为 BOOL(IF / WHILE 共用)
|
||||
* @param file 出错文件
|
||||
* @param sc 当前作用域
|
||||
* @param e 条件表达式
|
||||
* @return true 合法;false(err 已写)
|
||||
*/
|
||||
bool check_cond(const std::string& file, const LinkResult::PouScope& sc,
|
||||
const Expr& e) {
|
||||
TType t;
|
||||
if (!check_expr(file, sc, e, &t)) {
|
||||
return false;
|
||||
}
|
||||
if (t != TType::Bool) {
|
||||
return fail(file, "condition must be BOOL, got " + std::string(tname(t)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析赋值左值类型
|
||||
* @details 普通左值:局部/输入/输出/外部/全局 → 符号类型;
|
||||
* FUNCTION 内函数名 → 返回类型;**FUNCTION 内左值是全局/外部 → 拒绝**
|
||||
* (用例 14:FUNCTION 禁止写全局,含经 VAR_EXTERNAL)
|
||||
* @param file 出错文件
|
||||
* @param sc 当前作用域
|
||||
* @param pou 当前 POU
|
||||
* @param target 左值标识符
|
||||
* @param out 输出左值类型
|
||||
* @return true 合法;false(err 已写)
|
||||
*/
|
||||
bool assign_target_type(const std::string& file,
|
||||
const LinkResult::PouScope& sc, const POU& pou,
|
||||
const std::string& target, TType* out) {
|
||||
// FUNCTION 内对函数名赋值 = 结果值写入(用例 13 约定)
|
||||
if (pou.kind == PouKind::Function && target == pou.name) {
|
||||
return to_type(pou.result_type.kind, out) ||
|
||||
fail(file, "function result must be scalar");
|
||||
}
|
||||
|
||||
const Symbol* s = find_sym(sc, target);
|
||||
if (s == nullptr) {
|
||||
s = find_global(target);
|
||||
}
|
||||
if (s == nullptr) {
|
||||
return fail(file, "undeclared identifier '" + target + "'");
|
||||
}
|
||||
|
||||
// FUNCTION 禁写全局(含经 VAR_EXTERNAL)
|
||||
if (pou.kind == PouKind::Function &&
|
||||
(s->kind == SymbolKind::Global || s->kind == SymbolKind::External)) {
|
||||
return fail(file, "function cannot write global '" + target + "'");
|
||||
}
|
||||
|
||||
if (!to_type(s->type_name, out)) {
|
||||
return fail(file, "'" + target + "' has no scalar type");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 表达式定类型(递归)
|
||||
* @param file 出错文件
|
||||
* @param sc 当前作用域
|
||||
* @param e 表达式 AST
|
||||
* @param out 输出求值类型
|
||||
* @return true 合法;false(err 已写)
|
||||
*/
|
||||
bool check_expr(const std::string& file, const LinkResult::PouScope& sc,
|
||||
const Expr& e, TType* out) {
|
||||
switch (e.kind) {
|
||||
case ExprKind::LitBool:
|
||||
*out = TType::Bool;
|
||||
return true;
|
||||
case ExprKind::LitInt:
|
||||
*out = TType::Int;
|
||||
return true;
|
||||
case ExprKind::LitTime:
|
||||
*out = TType::Time;
|
||||
return true;
|
||||
case ExprKind::VarRef: {
|
||||
const Symbol* s = find_sym(sc, e.name);
|
||||
if (s == nullptr) {
|
||||
s = find_global(e.name);
|
||||
}
|
||||
if (s == nullptr) {
|
||||
return fail(file, "undeclared identifier '" + e.name + "'");
|
||||
}
|
||||
if (s->kind == SymbolKind::FbInstance) {
|
||||
return fail(file, "FB instance '" + e.name + "' cannot be used as a value");
|
||||
}
|
||||
if (!to_type(s->type_name, out)) {
|
||||
return fail(file, "'" + e.name + "' has no scalar type");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case ExprKind::Field: {
|
||||
const auto it = sc.fb_instances.find(e.name);
|
||||
if (it == sc.fb_instances.end()) {
|
||||
return fail(file, "no layout for FB instance '" + e.name + "'");
|
||||
}
|
||||
for (const FbField& f : it->second.fields) {
|
||||
if (f.name == e.field) {
|
||||
return to_type(f.type, out) ||
|
||||
fail(file, "field '" + e.field + "' has no scalar type");
|
||||
}
|
||||
}
|
||||
return fail(file, "unknown field '" + e.field + "' for FB '" + e.name + "'");
|
||||
}
|
||||
case ExprKind::Not: {
|
||||
TType t;
|
||||
if (!check_expr(file, sc, *e.operand, &t)) {
|
||||
return false;
|
||||
}
|
||||
if (t != TType::Bool) {
|
||||
return fail(file, "NOT operand must be BOOL");
|
||||
}
|
||||
*out = TType::Bool;
|
||||
return true;
|
||||
}
|
||||
case ExprKind::And:
|
||||
case ExprKind::Or: {
|
||||
TType l, r;
|
||||
if (!check_expr(file, sc, *e.lhs, &l) || !check_expr(file, sc, *e.rhs, &r)) {
|
||||
return false;
|
||||
}
|
||||
if (l != TType::Bool || r != TType::Bool) {
|
||||
const char* op = (e.kind == ExprKind::And) ? "AND" : "OR";
|
||||
return fail(file, std::string(op) + " operands must be BOOL");
|
||||
}
|
||||
*out = TType::Bool;
|
||||
return true;
|
||||
}
|
||||
case ExprKind::Cmp: {
|
||||
TType l, r;
|
||||
if (!check_expr(file, sc, *e.lhs, &l) || !check_expr(file, sc, *e.rhs, &r)) {
|
||||
return false;
|
||||
}
|
||||
if (l != r) {
|
||||
return fail(file, "comparison of mismatched types (" +
|
||||
std::string(tname(l)) + " vs " +
|
||||
std::string(tname(r)) + ")");
|
||||
}
|
||||
*out = TType::Bool;
|
||||
return true;
|
||||
}
|
||||
case ExprKind::Add:
|
||||
case ExprKind::Sub:
|
||||
case ExprKind::Mul:
|
||||
case ExprKind::Div: {
|
||||
TType l, r;
|
||||
if (!check_expr(file, sc, *e.lhs, &l) || !check_expr(file, sc, *e.rhs, &r)) {
|
||||
return false;
|
||||
}
|
||||
if (l != TType::Int || r != TType::Int) {
|
||||
return fail(file, "arithmetic operands must be INT (TIME has no arithmetic)");
|
||||
}
|
||||
*out = TType::Int;
|
||||
return true;
|
||||
}
|
||||
case ExprKind::Neg: {
|
||||
TType t;
|
||||
if (!check_expr(file, sc, *e.operand, &t)) {
|
||||
return false;
|
||||
}
|
||||
if (t != TType::Int) {
|
||||
return fail(file, "unary minus operand must be INT");
|
||||
}
|
||||
*out = TType::Int;
|
||||
return true;
|
||||
}
|
||||
case ExprKind::Call: {
|
||||
const POU* f = find_pou(e.name);
|
||||
if (f == nullptr) {
|
||||
return fail(file, "undeclared function '" + e.name + "'");
|
||||
}
|
||||
for (const auto& a : e.args) {
|
||||
TType at;
|
||||
if (!check_expr(file, sc, *a, &at)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!to_type(f->result_type.kind, out)) {
|
||||
return fail(file, "function '" + e.name + "' result must be scalar");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return fail(file, "unsupported expression");
|
||||
}
|
||||
|
||||
// ---- 成员 ----
|
||||
const Project& proj_; ///< 工程定义(本阶段未用)
|
||||
const std::vector<SourceUnit>& units_; ///< 全部源文件 AST
|
||||
const LinkResult& link_; ///< 链接结果(只读)
|
||||
std::string* err_; ///< 错误输出(可空)
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief 对工程做类型检查(对外入口,链接成功后调用)
|
||||
* @details 逐 POU 逐语句检查;规则见 Doc/compiler/类型检查.md
|
||||
* @param proj 工程定义(本阶段未用,保留接口对称)
|
||||
* @param units 全部源文件的 AST
|
||||
* @param link 链接结果(符号/布局已解析)
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 全部通过;false 失败(err 前缀 "type error")
|
||||
*/
|
||||
bool check_project(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
const LinkResult& link, std::string* err) {
|
||||
Checker c(proj, units, link, err);
|
||||
return c.run();
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
+220
-9
@@ -3,23 +3,119 @@
|
||||
* @brief STCompiler 可执行入口
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 用法:STCompiler <project.toml> [-o out.stb] --machine <machine.toml>;
|
||||
* --disasm 模式反汇编已编译映像。编译/反汇编必填 --machine(缺失报
|
||||
* "error: missing --machine <machine.toml>");打印模式(无 -o)不需要
|
||||
* 机器定义。错误统一打到 stderr,前缀 "error: "。
|
||||
*/
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "compiler/Codec.h"
|
||||
#include "compiler/Codegen.h"
|
||||
#include "compiler/Linker.h"
|
||||
#include "compiler/MachineConfig.h"
|
||||
#include "compiler/Project.h"
|
||||
#include "compiler/Stb.h"
|
||||
#include "compiler/Typecheck.h"
|
||||
|
||||
namespace {
|
||||
/// 打印命令行用法(三种模式 + -o / --machine / --disasm / --help)。
|
||||
void usage() {
|
||||
std::printf("usage: STCompiler <project.toml>\n"
|
||||
" 解析工程并打印文件集合与工程哈希(12.3 阶段)\n"
|
||||
std::printf("usage: STCompiler <project.toml> [-o <name>.stb] --machine <machine.toml>\n"
|
||||
" STCompiler <name>.stb --disasm --machine <machine.toml>\n"
|
||||
" 解析工程并编译(词法 → 语法 → 链接 → 类型 → 寄存器码)\n"
|
||||
" -o <name>.stb 编译并写出映像文件\n"
|
||||
" --machine <path> 机器定义(machine.toml,编译路径必填)\n"
|
||||
" --disasm 反汇编一个已编译的 .stb 映像(需要 --machine)\n"
|
||||
" --help 打印本帮助\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 反汇编一个已编译映像。
|
||||
* @param path .stb 文件路径
|
||||
* @param cfg 机器定义(machine.toml;反汇编按 format 输出)
|
||||
* @return 0 成功;1 失败(错误打到 stderr,前缀 "error: ")
|
||||
* @details 输出:映像摘要(大小 / 函数数 / 全局数 / 入口 fn_id /
|
||||
* dt_ms / cycle_limit / hash / model / sha)、常量表、函数表逐条
|
||||
* 反汇编、数据段摘要。指令偏移为文件绝对字节偏移。
|
||||
* 错误:"cannot open for read" / 解析失败消息(见 StbView::from)。
|
||||
*/
|
||||
int dump_image(const char* path, const compiler::MachineConfig& cfg) {
|
||||
std::vector<uint8_t> bytes;
|
||||
std::string err;
|
||||
if (!compiler::read_stb_file(path, &bytes, &err)) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
const compiler::StbView v = compiler::StbView::from(bytes);
|
||||
if (!v.ok()) {
|
||||
std::fprintf(stderr, "error: %s\n", v.error().c_str());
|
||||
return 1;
|
||||
}
|
||||
std::printf("image: %s (%zu bytes, %u functions, %u globals, entry fn %u)\n",
|
||||
path, bytes.size(), v.n_funcs(), v.n_globals(), v.entry_fn_id());
|
||||
std::printf(" dt_ms=%u cycle_limit=%u hash=0x%016llx model=%s sha=%s\n",
|
||||
v.dt_ms(), v.cycle_limit(),
|
||||
static_cast<unsigned long long>(v.project_hash()),
|
||||
v.model_id().c_str(), v.sha_ok() ? "ok" : "BAD");
|
||||
|
||||
if (v.n_consts()) {
|
||||
std::printf("constants:\n");
|
||||
for (uint32_t i = 0; i < v.n_consts(); ++i) {
|
||||
const compiler::ConstEntry c = v.const_entry(i);
|
||||
const char* tag = c.tag == 0 ? "BOOL" : c.tag == 1 ? "INT" : "TIME";
|
||||
std::printf(" [%u] %s %llu\n", i, tag,
|
||||
static_cast<unsigned long long>(c.value));
|
||||
}
|
||||
}
|
||||
|
||||
std::printf("functions:\n");
|
||||
for (uint32_t i = 0; i < v.n_funcs(); ++i) {
|
||||
const compiler::StbView::FuncRow r = v.func_row(i);
|
||||
std::printf(" fn %u: nregs=%u, offset=%u, len=%u\n", i, r.nregs,
|
||||
r.code_offset, r.code_len);
|
||||
const uint8_t* base = v.code_bytes() + r.code_offset;
|
||||
for (uint32_t j = 0; j < r.code_len; ++j) {
|
||||
char buf[64];
|
||||
compiler::disasm(cfg, reinterpret_cast<const uint32_t*>(base)[j], buf,
|
||||
sizeof buf);
|
||||
std::printf(" 0x%04x %s\n",
|
||||
v.offset_code() + r.code_offset + j * 4, buf);
|
||||
}
|
||||
}
|
||||
|
||||
if (v.data_len()) {
|
||||
std::printf("data (%zu bytes):\n", v.data_len());
|
||||
const uint8_t* d = v.data_bytes();
|
||||
for (size_t i = 0; i < v.data_len(); i += 16) {
|
||||
std::printf(" 0x%04x:", static_cast<unsigned>(i));
|
||||
for (size_t j = 0; j < 16 && i + j < v.data_len(); ++j) {
|
||||
std::printf(" %02x", d[i + j]);
|
||||
}
|
||||
std::printf("\n");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief STCompiler 程序入口。
|
||||
* @param argc 参数个数
|
||||
* @param argv 参数列表
|
||||
* @return 0 成功;1 失败(错误 stderr 前缀 "error: ")
|
||||
* @details 参数:argv[1] 为 project.toml 或 .stb(--disasm 时);
|
||||
* -o 指定输出(打印模式判定依据);--machine 编译/反汇编必填。
|
||||
* 编译管线:读 .st → 链接 → 类型检查 → 寄存器码 → 写映像 →
|
||||
* 写后自检(型号标识 + SHA-256)→ sidecar(I/O 绑定 var → 槽号)。
|
||||
*/
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
std::printf("STCompiler 0.1\n");
|
||||
@@ -31,24 +127,139 @@ int main(int argc, char** argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 参数收集
|
||||
std::string machine_path;
|
||||
std::string out_path;
|
||||
bool disasm_mode = false;
|
||||
for (int i = 2; i + 1 < argc; ++i) {
|
||||
if (std::strcmp(argv[i], "--machine") == 0) {
|
||||
machine_path = argv[i + 1];
|
||||
} else if (std::strcmp(argv[i], "-o") == 0) {
|
||||
out_path = argv[i + 1];
|
||||
}
|
||||
}
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (std::strcmp(argv[i], "--disasm") == 0) {
|
||||
disasm_mode = true;
|
||||
}
|
||||
}
|
||||
|
||||
// --disasm:需要机器定义(反汇编按 machine.toml 的 format 输出)
|
||||
if (disasm_mode) {
|
||||
if (machine_path.empty()) {
|
||||
std::fprintf(stderr, "error: missing --machine <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;
|
||||
std::string err;
|
||||
if (!compiler::parse_project(argv[1], &proj, &err)) {
|
||||
if (!compiler::parse_project(toml_path, &proj, &err)) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
const std::vector<std::string> files = compiler::compile_files(proj);
|
||||
uint64_t hash = 0;
|
||||
if (!compiler::compute_project_hash(proj, &hash, &err)) {
|
||||
|
||||
// 打印模式(无 -o):不需要机器定义
|
||||
if (out_path.empty()) {
|
||||
uint64_t hash = 0;
|
||||
if (!compiler::compute_project_hash(proj, &hash, &err)) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::printf("project: %s\n", proj.name.c_str());
|
||||
std::printf("files:\n");
|
||||
for (const std::string& f : files) {
|
||||
std::printf(" %s\n", f.c_str());
|
||||
}
|
||||
std::printf("hash: 0x%016llx\n", static_cast<unsigned long long>(hash));
|
||||
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;
|
||||
}
|
||||
|
||||
std::printf("project: %s\n", proj.name.c_str());
|
||||
std::printf("files:\n");
|
||||
// 完整管线:读 .st → 链接 → 类型检查 → 寄存器码
|
||||
std::vector<compiler::SourceUnit> units;
|
||||
for (const std::string& f : files) {
|
||||
std::printf(" %s\n", f.c_str());
|
||||
compiler::SourceUnit u;
|
||||
if (!compiler::load_unit(proj.base_dir + "/" + f, &u, &err)) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
std::printf("hash: 0x%016llx\n", static_cast<unsigned long long>(hash));
|
||||
compiler::LinkResult link;
|
||||
if (!compiler::link_project(proj, units, &link, &err)) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (!compiler::check_project(proj, units, link, &err)) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::vector<uint8_t> image;
|
||||
if (!compiler::codegen_project(proj, units, link, cfg, &image, &err)) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (!compiler::write_stb_file(out_path.c_str(), image, &err)) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 写后自检:型号标识与 SHA-256
|
||||
const compiler::StbView self = compiler::StbView::from(image);
|
||||
if (!self.ok() || !self.sha_ok()) {
|
||||
std::fprintf(stderr, "error: self-check failed: %s\n", self.error().c_str());
|
||||
return 1;
|
||||
}
|
||||
if (!self.model_matches(cfg.model_name(), cfg.version())) {
|
||||
std::fprintf(stderr, "error: self-check model mismatch\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// sidecar:I/O 绑定 var → 槽号 → channel/bit(不进映像,执行器采样用)
|
||||
std::vector<compiler::IoBinding> bindings;
|
||||
for (const compiler::IoBinding& b : proj.io) {
|
||||
compiler::IoBinding out_b = b;
|
||||
std::string key = b.var;
|
||||
for (char& ch : key) {
|
||||
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||
}
|
||||
const auto it = link.global_index.find(key);
|
||||
if (it != link.global_index.end()) {
|
||||
out_b.slot = it->second; // 12.6 已校验存在
|
||||
}
|
||||
bindings.push_back(out_b);
|
||||
}
|
||||
std::filesystem::path sidecar = std::filesystem::path(out_path);
|
||||
sidecar.replace_extension(".runtime.toml");
|
||||
if (!compiler::write_sidecar_file(sidecar.string().c_str(), bindings, &err)) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
const compiler::StbView v = compiler::StbView::from(image);
|
||||
std::printf("compiled: %s (%zu bytes, %u functions, %u globals)\n",
|
||||
out_path.c_str(), image.size(), v.n_funcs(), v.n_globals());
|
||||
return 0;
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# executor
|
||||
|
||||
BytecodeExecutor 模块:可执行入口。CMake 目标:`BytecodeExecutor`(`EXECUTABLE`),链接 `vm` 和 `isa`,**不链 `compiler`**。
|
||||
BytecodeExecutor 模块:可执行入口。CMake 目标:`BytecodeExecutor`(`EXECUTABLE`),链接 `vm` 和 `isa`,**不链 `compiler`**。读取 `.stb` 时校验型号标识与 SHA-256。
|
||||
|
||||
职责与边界见 [`doc/executor/执行器入口.md`](../doc/executor/执行器入口.md)。
|
||||
|
||||
+350
-4
@@ -2,14 +2,360 @@
|
||||
* @file main.cpp
|
||||
* @brief BytecodeExecutor 可执行入口
|
||||
* @author
|
||||
* @date 2026-08-19
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 12.10:加载 .stb + sidecar,按扫描周期执行。
|
||||
* - `BytecodeExecutor <name>.stb [--cycles N] [--replay <file>] [--step]`
|
||||
* - 只链 vm + isa,不链 compiler;dt_ms / cycle_limit 从映像头取
|
||||
* - I 采样 / Q 写回按 sidecar(var → 槽号 → channel/bit),不创造变量
|
||||
* - --replay:读录制文本(每行空格分隔的 0/1,按 io.input 绑定顺序),
|
||||
* 逐周期喂入;文件读尽后保持最后一行
|
||||
* - 每周期打印 I/Q(最小可观测)
|
||||
*/
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "isa/Encode.h"
|
||||
#include "vm/Image.h"
|
||||
#include "vm/Machine.h"
|
||||
|
||||
namespace {
|
||||
|
||||
/// 打印命令行用法。
|
||||
void usage() {
|
||||
std::printf("usage: BytecodeExecutor <name>.stb [--cycles N] [--replay <file>] [--step]\n"
|
||||
" 加载 .stb + <name>.runtime.toml,按扫描周期执行\n"
|
||||
" --cycles N 跑 N 个周期(缺省 10)\n"
|
||||
" --replay <file> 读录制的 I 序列(每行按 io.input 顺序的 0/1)\n"
|
||||
" --step 单步:逐指令打印 pc/fn/反汇编/寄存器(跑 1 个周期)\n"
|
||||
" --help 打印本帮助\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief sidecar 中的一条 I/O 绑定。
|
||||
* @details TOML 子集 `var`/`slot`/`channel`/`bit`,格式冻结于
|
||||
* Doc/isa/指令与映像.md;不创造变量,只把外部通道映射到数据区槽。
|
||||
*/
|
||||
struct Binding {
|
||||
bool is_input = true; ///< true = [[io.input]],false = [[io.output]]
|
||||
std::string var; ///< 绑定的全局变量名
|
||||
uint32_t slot = 0; ///< 数据区槽号(槽 × 8 = 字节偏移)
|
||||
uint32_t channel = 0; ///< 通道号(展示用)
|
||||
uint32_t bit = 0; ///< 位号(展示用)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 解析 <name>.runtime.toml(逐行手写解析,仅冻结子集)。
|
||||
* @param path sidecar 路径
|
||||
* @param out 输出绑定列表(按文件中 [[io.input]] / [[io.output]] 出现顺序)
|
||||
* @param err 错误输出
|
||||
* @return true 成功;false(文件打不开,err 已写)
|
||||
* @details 识别行:`[[io.input]]` / `[[io.output]]` 分段,`key = "value"` 或
|
||||
* `key = 数字` 字段(var/slot/channel/bit);空行与 `#` 注释跳过。
|
||||
*/
|
||||
bool parse_sidecar(const std::string& path, std::vector<Binding>* out,
|
||||
std::string* err) {
|
||||
std::ifstream in(path);
|
||||
if (!in) {
|
||||
*err = "cannot open sidecar: " + path;
|
||||
return false;
|
||||
}
|
||||
std::string line;
|
||||
bool is_input = true;
|
||||
bool in_entry = false;
|
||||
Binding cur;
|
||||
while (std::getline(in, line)) {
|
||||
// 去首尾空白
|
||||
size_t b = line.find_first_not_of(" \t\r");
|
||||
if (b == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
line = line.substr(b);
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
if (line == "[[io.input]]") {
|
||||
if (in_entry) out->push_back(cur);
|
||||
cur = Binding();
|
||||
is_input = true;
|
||||
in_entry = true;
|
||||
continue;
|
||||
}
|
||||
if (line == "[[io.output]]") {
|
||||
if (in_entry) out->push_back(cur);
|
||||
cur = Binding();
|
||||
is_input = false;
|
||||
in_entry = true;
|
||||
continue;
|
||||
}
|
||||
const size_t eq = line.find('=');
|
||||
if (eq == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
const std::string key = line.substr(0, eq);
|
||||
std::string val = line.substr(eq + 1);
|
||||
const size_t b2 = key.find_first_not_of(" \t");
|
||||
const size_t e2 = key.find_last_not_of(" \t");
|
||||
const size_t b3 = val.find_first_not_of(" \t");
|
||||
const size_t e3 = val.find_last_not_of(" \t\r");
|
||||
const std::string k = key.substr(b2, e2 - b2 + 1);
|
||||
const std::string v = val.substr(b3, e3 - b3 + 1);
|
||||
if (k == "var") {
|
||||
cur.var = v.substr(1, v.size() - 2); // 去引号
|
||||
} else if (k == "slot") {
|
||||
cur.slot = static_cast<uint32_t>(std::stoul(v));
|
||||
} else if (k == "channel") {
|
||||
cur.channel = static_cast<uint32_t>(std::stoul(v));
|
||||
} else if (k == "bit") {
|
||||
cur.bit = static_cast<uint32_t>(std::stoul(v));
|
||||
}
|
||||
cur.is_input = is_input;
|
||||
}
|
||||
if (in_entry) {
|
||||
out->push_back(cur);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读一个槽的 BOOL 值。
|
||||
* @param m 机器实例
|
||||
* @param slot 槽号
|
||||
* @return 低字节非零 → 1,否则 0
|
||||
*/
|
||||
int slot_bool(vm::Machine& m, uint32_t slot) {
|
||||
return m.data()[static_cast<size_t>(slot) * 8] ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 写一个槽的 BOOL 值。
|
||||
* @param m 机器实例
|
||||
* @param slot 槽号
|
||||
* @param v 0/1(非零归一化为 1)
|
||||
*/
|
||||
void put_bool(vm::Machine& m, uint32_t slot, int v) {
|
||||
m.data()[static_cast<size_t>(slot) * 8] = v ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 本周期 I 采样:喂入数据区。
|
||||
* @param m 机器实例
|
||||
* @param bindings sidecar 绑定列表
|
||||
* @param replay_in 回放文件流(未打开则无回放)
|
||||
* @param replay_line 当前回放行(0/1 按 io.input 绑定顺序)
|
||||
* @details 有回放:读一行,按 io.input 顺序填槽,缺位补 0;
|
||||
* 文件读尽后保持最后一行(replay_line 不清空)。
|
||||
* 无回放:所有 input 槽填 0。
|
||||
*/
|
||||
void sample_inputs(vm::Machine& m, const std::vector<Binding>& bindings,
|
||||
std::ifstream& replay_in, std::vector<int>& replay_line) {
|
||||
if (replay_in.is_open()) {
|
||||
std::string line;
|
||||
if (std::getline(replay_in, line)) {
|
||||
replay_line.clear();
|
||||
size_t pos = 0;
|
||||
while (pos < line.size()) {
|
||||
while (pos < line.size() &&
|
||||
std::isspace(static_cast<unsigned char>(line[pos]))) {
|
||||
++pos;
|
||||
}
|
||||
if (pos >= line.size()) break;
|
||||
const size_t s = pos;
|
||||
while (pos < line.size() &&
|
||||
!std::isspace(static_cast<unsigned char>(line[pos]))) {
|
||||
++pos;
|
||||
}
|
||||
replay_line.push_back(std::stoi(line.substr(s, pos - s)));
|
||||
}
|
||||
}
|
||||
size_t idx = 0;
|
||||
for (const Binding& b : bindings) {
|
||||
if (!b.is_input) continue;
|
||||
put_bool(m, b.slot, idx < replay_line.size() ? replay_line[idx] : 0);
|
||||
++idx;
|
||||
}
|
||||
} else {
|
||||
for (const Binding& b : bindings) {
|
||||
if (b.is_input) {
|
||||
put_bool(m, b.slot, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 sidecar 绑定顺序生成 I / Q 文本。
|
||||
* @param m 机器实例
|
||||
* @param bindings sidecar 绑定列表
|
||||
* @param is 输出:input 槽值(空格分隔)
|
||||
* @param qs 输出:output 槽值(空格分隔)
|
||||
* @return is(与参数 3 同对象)
|
||||
*/
|
||||
std::string iq_string(vm::Machine& m, const std::vector<Binding>& bindings,
|
||||
std::string* is, std::string* qs) {
|
||||
for (const Binding& b : bindings) {
|
||||
if (b.is_input) {
|
||||
if (!is->empty()) *is += " ";
|
||||
*is += std::to_string(slot_bool(m, b.slot));
|
||||
} else {
|
||||
if (!qs->empty()) *qs += " ";
|
||||
*qs += std::to_string(slot_bool(m, b.slot));
|
||||
}
|
||||
}
|
||||
return *is;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief 程序入口。
|
||||
* @param argc 参数个数
|
||||
* @param argv 参数表
|
||||
* @return 0 成功;1 失败
|
||||
* @details 流程:解析参数(--cycles/--replay/--step)→ 读 .stb → Image 解析
|
||||
* → Machine::create(型号/SHA 校验)→ 解析 sidecar → 按周期循环:
|
||||
* 采样 I → run_cycle → 打印 I/Q。--step 模式跑 1 个周期并逐指令
|
||||
* 打印 pc/fn/反汇编/非零寄存器。所有错误打印 `error: ...` 到 stderr。
|
||||
*/
|
||||
int main(int argc, char** argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
std::printf("BytecodeExecutor 0.1\n");
|
||||
if (argc < 2) {
|
||||
std::printf("BytecodeExecutor 0.1\n");
|
||||
usage();
|
||||
return 0;
|
||||
}
|
||||
if (std::strcmp(argv[1], "--help") == 0) {
|
||||
usage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
const std::string stb_path = argv[1];
|
||||
// 参数:--cycles N / --replay <file> / --step(缺省 cycles=10、无回放、非单步)
|
||||
uint32_t cycles = 10;
|
||||
bool step_mode = false;
|
||||
std::string replay;
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
if (std::strcmp(argv[i], "--cycles") == 0 && i + 1 < argc) {
|
||||
cycles = static_cast<uint32_t>(std::stoul(argv[i + 1]));
|
||||
} else if (std::strcmp(argv[i], "--replay") == 0 && i + 1 < argc) {
|
||||
replay = argv[i + 1];
|
||||
} else if (std::strcmp(argv[i], "--step") == 0) {
|
||||
step_mode = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 读 .stb 映像(二进制)到字节缓冲
|
||||
std::string err;
|
||||
std::vector<uint8_t> bytes;
|
||||
{
|
||||
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>());
|
||||
}
|
||||
// 结构校验(Image::from)+ 型号/SHA-256 校验(Machine::create)
|
||||
const vm::Image img = vm::Image::from(bytes);
|
||||
if (!img.ok()) {
|
||||
std::fprintf(stderr, "error: %s\n", img.error().c_str());
|
||||
return 1;
|
||||
}
|
||||
vm::Machine machine;
|
||||
if (!vm::Machine::create(bytes, &machine, &err)) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// sidecar 路径推导:<name>.stb → <name>.runtime.toml
|
||||
std::string sidecar_path = stb_path;
|
||||
const size_t dot = sidecar_path.find_last_of('.');
|
||||
if (dot != std::string::npos) {
|
||||
sidecar_path = sidecar_path.substr(0, dot);
|
||||
}
|
||||
sidecar_path += ".runtime.toml";
|
||||
std::vector<Binding> bindings;
|
||||
if (!parse_sidecar(sidecar_path, &bindings, &err)) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 回放缓冲(当前行 0/1 序列)+ 打开回放文件;打不开 → 报错退出
|
||||
std::vector<int> replay_line;
|
||||
std::ifstream replay_in;
|
||||
if (!replay.empty()) {
|
||||
replay_in.open(replay);
|
||||
if (!replay_in) {
|
||||
std::fprintf(stderr, "error: cannot open replay: %s\n", replay.c_str());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
const vm::ImageHeader& h = machine.header();
|
||||
std::printf("image: %s (%zu bytes, dt_ms=%u, cycle_limit=%u)\n", stb_path.c_str(),
|
||||
bytes.size(), h.dt_ms, h.cycle_limit);
|
||||
|
||||
// 单步模式:采样 I → 逐指令打印(指令 + 执行后寄存器)→ I/Q
|
||||
if (step_mode) {
|
||||
std::printf("-- single step (cycle 1) --\n");
|
||||
sample_inputs(machine, bindings, replay_in, replay_line);
|
||||
uint32_t guard = 0;
|
||||
while (machine.fault() == vm::Fault::None && !machine.ended()) {
|
||||
char buf[64];
|
||||
isa::disasm(machine.cur_instr(), buf, sizeof buf);
|
||||
const uint32_t pc = machine.pc();
|
||||
const uint32_t fn = machine.fn_id();
|
||||
if (!machine.step()) {
|
||||
break;
|
||||
}
|
||||
// 执行后状态:指令 + 非零寄存器
|
||||
std::printf(" pc=%u fn=%u %s", pc, fn, buf);
|
||||
std::string regs;
|
||||
const uint32_t n = machine.nregs() < 32 ? machine.nregs() : 32;
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
const int64_t v = machine.reg(static_cast<uint8_t>(i));
|
||||
if (v != 0) {
|
||||
if (!regs.empty()) regs += " ";
|
||||
regs += "r" + std::to_string(i) + "=" + std::to_string(v);
|
||||
}
|
||||
}
|
||||
std::printf(" [%s]\n", regs.c_str());
|
||||
if (++guard > 1000000) {
|
||||
std::printf(" ...step limit\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (machine.fault() != vm::Fault::None) {
|
||||
std::printf(" FAULT(%d)\n", static_cast<int>(machine.fault()));
|
||||
return 1;
|
||||
}
|
||||
std::string is, qs;
|
||||
iq_string(machine, bindings, &is, &qs);
|
||||
std::printf("cycle 1: I=[%s] Q=[%s]\n", is.c_str(), qs.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (uint32_t c = 1; c <= cycles; ++c) {
|
||||
// 采样:回放行按 io.input 顺序填,无回放则全 0
|
||||
sample_inputs(machine, bindings, replay_in, replay_line);
|
||||
|
||||
const vm::Fault f = machine.run_cycle();
|
||||
|
||||
// 打印 I/Q(按 sidecar 绑定顺序)
|
||||
std::string is, qs;
|
||||
iq_string(machine, bindings, &is, &qs);
|
||||
std::printf("cycle %u: I=[%s] Q=[%s]", c, is.c_str(), qs.c_str());
|
||||
if (f != vm::Fault::None) {
|
||||
std::printf(" FAULT(%d)", static_cast<int>(f));
|
||||
}
|
||||
std::printf("\n");
|
||||
if (f != vm::Fault::None) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
+1
-2
@@ -7,7 +7,6 @@ project(ISA
|
||||
DESCRIPTION "指令集架构")
|
||||
|
||||
add_library(isa STATIC
|
||||
./src/Encode.cpp
|
||||
./src/Image.cpp)
|
||||
./src/Encode.cpp)
|
||||
|
||||
target_include_directories(isa PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
+21
-112
@@ -1,15 +1,15 @@
|
||||
# isa
|
||||
|
||||
指令集 + 映像 + 定宽类型。CMake 目标:`isa`(`STATIC`),无依赖。
|
||||
**归执行器侧**,只管指令定义。CMake 目标:`isa`(`STATIC`),无依赖;被 `vm` 依赖。
|
||||
|
||||
`compiler` 写映像、`vm` 读映像,两边只依赖本目录,不互链。
|
||||
`compiler` **不依赖 isa**——指令 opcode / 类型元数据 / FB 布局全部来自 `compiler/machine.toml`(见 [`指令配置.md`](../doc/isa/指令配置.md))。`.stb` 映像读写由 `compiler`(写侧)与 `vm`(读侧)**各自实现**。
|
||||
|
||||
```text
|
||||
STCompiler → compiler → isa
|
||||
BytecodeExecutor → vm → isa
|
||||
STCompiler → compiler (编码查 machine.toml,自带 .stb 写)
|
||||
BytecodeExecutor → vm → isa (isa 只管指令;vm 自带 .stb 读)
|
||||
```
|
||||
|
||||
**类型、操作码、指令字、映像布局以** [`doc/isa/指令与映像.md`](../doc/isa/指令与映像.md) **为准。** 头文件实现该文,本 README 不另当规范。全工程阶段见 [`doc/初步计划.md`](../doc/初步计划.md) 12.0~12.1。
|
||||
**操作码、指令字以** [`doc/isa/指令与映像.md`](../doc/isa/指令与映像.md) **为准。** 头文件实现该文,本 README 不另当规范。`.stb` 结构拆解见 [`doc/isa/stb文件格式.md`](../doc/isa/stb文件格式.md)。指令/类型/FB 登记见 [`doc/isa/指令配置.md`](../doc/isa/指令配置.md)。
|
||||
|
||||
公开头:`isa/include/isa/`。
|
||||
|
||||
@@ -17,139 +17,48 @@ BytecodeExecutor → vm → isa
|
||||
|
||||
## 做 / 不做
|
||||
|
||||
**做:** 操作码与 32-bit 指令、映像头与段、`BOOL`/`INT`/`TIME` 宽度与饱和、编解码、工程哈希、一条指令的文本转储。
|
||||
**做:** 操作码与 32-bit 指令字、`BOOL`/`INT`/`TIME` 定宽与饱和、编解码、一条指令的文本转储。
|
||||
|
||||
**不做:** toml、关键字、符号表、AST、VM `switch`、扫描周期、I/O 采样。`AND`/`OR` 只组合已算好的值;短路由 `compiler` 编成跳转。
|
||||
**不做:** 映像读写(compiler/vm 各自实现)、toml/关键字/符号表/AST、VM 执行 switch(在 `vm`)、扫描周期、I/O 采样。
|
||||
|
||||
---
|
||||
|
||||
## 实现步骤
|
||||
|
||||
原则:先冻合同,再头文件,再 `.cpp`,再最小测试。每步能编过再进下一步。改编码或映像字段时,先改规范文和本模块,再动 `compiler` / `vm`。
|
||||
|
||||
目标目录(与规范一致):
|
||||
## 目录与 CMake
|
||||
|
||||
```text
|
||||
isa/
|
||||
CMakeLists.txt
|
||||
README.md
|
||||
include/isa/
|
||||
Types.h # 宽度、饱和、槽值
|
||||
Op.h # 操作码枚举 + 助记符
|
||||
Types.h # 宽度、饱和
|
||||
Op.h # 操作码枚举 + OpFormat/OpClass + kOpDefs 表 + 助记符
|
||||
Instr.h # 32-bit 打包/拆字段(inline)
|
||||
Encode.h # encode / decode / disasm
|
||||
Image.h # 头、段、FNV、读写缓冲
|
||||
src/
|
||||
Encode.cpp
|
||||
Image.cpp
|
||||
```
|
||||
|
||||
```cmake
|
||||
add_library(isa STATIC src/Encode.cpp src/Image.cpp)
|
||||
add_library(isa STATIC src/Encode.cpp)
|
||||
target_include_directories(isa PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
```
|
||||
|
||||
`compiler` / `vm`:`target_link_libraries(... PUBLIC isa)`。
|
||||
`vm`:`target_link_libraries(vm PUBLIC isa)`;`compiler` 不链 `isa`。
|
||||
|
||||
---
|
||||
|
||||
### 0. CMake 骨架
|
||||
## 实现说明
|
||||
|
||||
根 `CMakeLists.txt` 已 `add_subdirectory` 了 `compiler` / `vm` / `executor`。这三个目录若没有 `CMakeLists.txt`,顶层 configure 失败,`isa` 也编不过。
|
||||
- **`Types.h`**:`Bool` = `uint8_t`、`Int` = `int16_t`、`Time` = `int64_t`(毫秒,理由见规范文);`sat_add/sub/mul/div`(饱和规则见规范文);`TypeTag`(0/1/2,`.stb` 常量表契约)
|
||||
- **`Op.h`**:`enum Op : uint8_t`(数值即编码,与 `machine.toml` 强校验一致);`OpFormat`(RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE)与 `OpClass`(Plain/Instance);34 条 `kOpDefs{ name, format, cls }`;`mnemonic/format/op_class` 查表
|
||||
- **`Encode.cpp`**:`encode/decode` 往返 + `disasm`(按 format 表驱动,文本与规范文一致)
|
||||
|
||||
本步只让工程能配置、能链,不写业务。
|
||||
## 测试
|
||||
|
||||
- 写本目录 `CMakeLists.txt`(上面那段即可)
|
||||
- `compiler` / `vm`:空 `.cpp` + `PUBLIC` 链 `isa`
|
||||
- `executor`:`BytecodeExecutor`,`main` 打印版本,链 `vm`、`isa`;`STCompiler` 入口在 `compiler/src/main.cpp`,链 `compiler`、`isa`
|
||||
- 根 CMake:`enable_testing()`(测试文件下一步再加)
|
||||
|
||||
完成:`cmake --build` 过。
|
||||
|
||||
---
|
||||
|
||||
### 1. 冻合同(先改规范文)
|
||||
|
||||
[`指令与映像.md`](../doc/isa/指令与映像.md) 已有助记符和 `[ op:8 | rd:8 | a:8 | b:8 ]`,还缺**数值和映像字节**。头文件不得自己发明一份表。
|
||||
|
||||
把下面这些写进规范文,再写代码(细则以该文最终落笔为准):
|
||||
|
||||
- 指令字:小端 `uint32_t`;`op`/`rd`/`a`/`b` 各 8 bit;`imm16 = a | (b << 8)`;`off16` 为有符号、单位是指令条数
|
||||
- 操作码 0..28 连续赋值,以后只追加不插队
|
||||
- 各指令操作数字面(`LOADK`/`JMP`/`CALL`/`LOAD_*`/`STORE_*`/`CAL_*`/`RET`)
|
||||
- `INT` 饱和、`INT_MIN / -1`、除零、`BOOL` 只允许 0/1
|
||||
- FNV-1a 64 的 basis/prime;路径只当排序键,对文件内容 update;空集合 = basis
|
||||
- 映像魔数 `STSC`、版本 1、头字段偏移与长度、常量表/函数表/FB 表一行的字节
|
||||
- 第一版可写映像:空槽、空常量表/FB、`MAIN`(`fn_id=0`)、一条 `RET`
|
||||
|
||||
完成:规范文能当唯一合同;本 README 仍只指向它。
|
||||
|
||||
---
|
||||
|
||||
### 2. `Types.h`
|
||||
|
||||
`include/isa/Types.h`,inline,无对应 `.cpp`。命名空间 `isa`。C++20;本模块保持简单结构体,不依赖花哨特性。
|
||||
|
||||
- `Bool` = `uint8_t`,`Int` = `int16_t`,`Time` = `int64_t`(毫秒;理由见规范文)
|
||||
- `TypeTag`:`Bool` / `Int` / `Time`
|
||||
- `sat_add` / `sat_sub` / `sat_mul` / `sat_div`(规则见规范文)
|
||||
- `pack_bool` / `pack_int` / `pack_time` → 4 字节 cell;对应 unpack
|
||||
|
||||
完成:单独包含该头能编译。
|
||||
|
||||
---
|
||||
|
||||
### 3. `Op.h` + `Instr.h`
|
||||
|
||||
- `Op.h`:`enum Op : uint8_t`,取值与规范文一致;`mnemonic(Op)` 助记符(已在头内落地)
|
||||
- `Instr.h`:`typedef uint32_t Instr`;`pack`、取 `op`/`rd`/`a`/`b`、`imm16`/`off16`;按形态 `enc_rr` / `enc_rrr` / `enc_imm` / `enc_jmp` / `enc_jc`
|
||||
|
||||
完成:能打包/拆开一条 32-bit 指令,不依赖 `.cpp`。
|
||||
|
||||
---
|
||||
|
||||
### 4. `Encode.h` + `Encode.cpp`
|
||||
|
||||
给 `STCompiler --disasm` 和测试往返打印:
|
||||
|
||||
- `Decoded { Op op; uint8_t rd, a, b; }`
|
||||
- `Instr encode(Decoded)` / `Decoded decode(Instr)`
|
||||
- `disasm(Instr, char* out, size_t cap)`
|
||||
|
||||
`disasm` 格式冻在规范文,例如:`RET`、`MOVE r1, r2`、`LOADK r0, 3`、`ADD r1, r2, r3`、`JMP +4`、`JT r0, -1`、`CALL 1`、`LOAD_I r0, 2`、`CAL_TON 0`。
|
||||
|
||||
完成:一条指令能编、能解、能打出一行文本。
|
||||
|
||||
---
|
||||
|
||||
### 5. `Image.h` + `Image.cpp`
|
||||
|
||||
- `fnv1a64` / `fnv1a64_update`
|
||||
- `ImageView`:只读视图,指向外部缓冲
|
||||
- `read_image`:校验魔数、版本、头大小、各段不越界、`entry_fn_id` 落在函数表内
|
||||
- `write_ret_main(cycle_limit, dt_ms, hash)` → `std::vector<uint8_t>`:空槽 + `MAIN` + 一条 `RET`
|
||||
- `.stb` 文件读写与 `<name>.runtime.toml` sidecar(I/O 绑定 var → 槽号 → channel/bit)
|
||||
|
||||
小端 `put_*` / `get_*` 只放 `.cpp`,不把 packed struct 当 ABI。本阶段不写完整 `ImageBuilder`(等 codegen)。
|
||||
|
||||
完成:能写出并读回「空映像 + 一条 `RET`」。
|
||||
|
||||
---
|
||||
|
||||
### 6. 最小测试
|
||||
|
||||
在 `tests/` 加一个只链 `isa` 的 CTest 可执行文件(不是第 12.2 节那 20 个用例):
|
||||
|
||||
- `RET` / `ADD` / `LOADK` / `JMP -1`:encode 后再 decode,字段一致;`disasm` 非空
|
||||
- `write_ret_main` 再 `read_image`:魔数、version=1、`n_funcs=1`、入口是 `RET`
|
||||
- `fnv1a64` 空输入等于 basis
|
||||
|
||||
完成:`ctest` 过上述用例。
|
||||
|
||||
---
|
||||
`tests/isa_test`(链 `isa`):饱和、操作码冻结值、指令打包、encode/decode 往返、disasm 文本、OpClass 分类。
|
||||
|
||||
## 验收
|
||||
|
||||
- `cmake --build` 过;`ctest` 过 isa 往返
|
||||
- `compiler` / `vm` 只 `#include <isa/...>`,不互相包含
|
||||
- 操作码或映像有变:先改 [`指令与映像.md`](../doc/isa/指令与映像.md) + 本目录 + 本测试,再改两端
|
||||
- `cmake --build` 过;`ctest` 过(`isa_roundtrip` 等 14 个用例全绿)
|
||||
- `vm` 只 `#include <isa/...>`;`compiler` 不引用 `isa`
|
||||
- 操作码有变:先改 `compiler/machine.toml` + 本目录 + 测试,再改两端
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* @file Encode.h
|
||||
* @brief 指令编解码与反汇编
|
||||
* @details 给 STCompiler --disasm 与测试往返使用;文本格式见 Doc/isa/指令与映像.md。
|
||||
* @author
|
||||
* @date 2026-08-19
|
||||
*/
|
||||
@@ -14,18 +15,40 @@
|
||||
#include "isa/Op.h"
|
||||
|
||||
namespace isa {
|
||||
// 解码后的字段视图
|
||||
/**
|
||||
* @brief 解码后的字段视图。
|
||||
* @details 各字段是 8 位原始值,含义按操作数形态(OpFormat)解释,
|
||||
* a|b 可用 imm16 / off16 组合成 16 位视图。
|
||||
*/
|
||||
struct Decoded {
|
||||
Op op;
|
||||
uint8_t rd;
|
||||
uint8_t a;
|
||||
uint8_t b;
|
||||
Op op; ///< 操作码
|
||||
uint8_t rd; ///< 目标寄存器 / 条件寄存器
|
||||
uint8_t a; ///< 源寄存器 / 立即数低 8 位 / 偏移低 8 位
|
||||
uint8_t b; ///< 源寄存器 / 立即数高 8 位 / 偏移高 8 位
|
||||
};
|
||||
|
||||
// 编解码往返:encode(decode(w)) == w
|
||||
/**
|
||||
* @brief 编码:字段视图打包成一条 32 位指令字。
|
||||
* @param d 解码后的字段视图
|
||||
* @return 打包后的指令字;满足 encode(decode(w)) == w
|
||||
*/
|
||||
Instr encode(Decoded d);
|
||||
|
||||
/**
|
||||
* @brief 解码:指令字拆成字段视图。
|
||||
* @param w 32 位指令字
|
||||
* @return 各字段视图
|
||||
*/
|
||||
Decoded decode(Instr w);
|
||||
|
||||
// 反汇编一行文本,写入 out,至多 cap 字节(含 '\0')。格式见 Doc/isa/指令与映像.md。
|
||||
/**
|
||||
* @brief 反汇编一条指令为一行文本。
|
||||
* @param w 指令字
|
||||
* @param out 输出缓冲
|
||||
* @param cap 缓冲容量(含 '\0');cap==0 时直接返回、不写 out
|
||||
* @details 按操作数形态表驱动输出(如 `RET`、`MOVE r1, r2`、`LOADK r0, 3`、
|
||||
* `ADD r1, r2, r3`、`JMP +4`、`JT r0, -1`、`CALL 1`、`CAL_TON 0`);
|
||||
* 未知操作码输出 `??? 0x<w>`。
|
||||
*/
|
||||
void disasm(Instr w, char* out, size_t cap);
|
||||
}
|
||||
|
||||
@@ -1,123 +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 64:basis / prime(见 Doc/isa/指令与映像.md)
|
||||
static const uint64_t kFnvBasis = 0xcbf29ce484222325ull;
|
||||
static const uint64_t kFnvPrime = 0x100000001b3ull;
|
||||
|
||||
// FNV-1a 64 增量与一次性
|
||||
uint64_t fnv1a64_update(uint64_t h, const uint8_t* data, size_t len);
|
||||
uint64_t fnv1a64(const uint8_t* data, size_t len);
|
||||
|
||||
// 映像头(字节布局见 Doc/isa/指令与映像.md)
|
||||
struct ImageHeader {
|
||||
uint32_t cycle_limit;
|
||||
uint32_t dt_ms;
|
||||
uint64_t project_hash;
|
||||
uint32_t entry_fn_id;
|
||||
uint32_t n_globals;
|
||||
uint32_t n_i;
|
||||
uint32_t n_q;
|
||||
uint32_t n_m;
|
||||
uint32_t n_consts;
|
||||
uint32_t n_funcs;
|
||||
uint32_t offset_const;
|
||||
uint32_t offset_funcs;
|
||||
uint32_t offset_code;
|
||||
uint32_t offset_fb;
|
||||
uint32_t offset_data;
|
||||
};
|
||||
|
||||
// 常量表一行
|
||||
struct ConstEntry {
|
||||
types::TypeTag tag;
|
||||
uint64_t value;
|
||||
};
|
||||
|
||||
// 函数表一行
|
||||
struct FuncRow {
|
||||
uint32_t nregs;
|
||||
uint32_t code_offset; // 相对字节码段起点
|
||||
uint32_t code_len; // 指令条数
|
||||
};
|
||||
|
||||
// I/O 绑定(sidecar 一行)
|
||||
struct IoBinding {
|
||||
std::string var;
|
||||
uint32_t slot;
|
||||
uint32_t channel;
|
||||
uint32_t bit;
|
||||
bool is_input; // true = [[io.input]],false = [[io.output]]
|
||||
};
|
||||
|
||||
// 只读视图:校验过的映像
|
||||
class ImageView {
|
||||
public:
|
||||
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:
|
||||
ImageView();
|
||||
|
||||
const uint8_t* buf_;
|
||||
size_t len_;
|
||||
bool ok_;
|
||||
std::string err_;
|
||||
ImageHeader hdr_;
|
||||
};
|
||||
|
||||
// 写最小映像:空槽 + MAIN(fn_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);
|
||||
}
|
||||
+95
-13
@@ -1,6 +1,8 @@
|
||||
/**
|
||||
* @file Instr.h
|
||||
* @brief 32-bit 指令打包 / 拆字段
|
||||
* @brief 32-bit 指令字打包 / 拆字段
|
||||
* @details 指令字布局:`[ op:8 | rd:8 | a:8 | b:8 ]`,整体是一个小端 uint32_t。
|
||||
* 字段含义按操作码形态解释,见 Doc/isa/指令与映像.md。
|
||||
* @author
|
||||
* @date 2026-08-19
|
||||
*/
|
||||
@@ -12,11 +14,17 @@
|
||||
#include "isa/Op.h"
|
||||
|
||||
namespace isa {
|
||||
// 指令字:[ op:8 | rd:8 | a:8 | b:8 ],整体是一个小端 uint32_t。
|
||||
// 字段含义按操作码形态解释,见 Doc/isa/指令与映像.md。
|
||||
/// 指令字:`[ op:8 | rd:8 | a:8 | b:8 ]`,小端 uint32_t。
|
||||
typedef uint32_t Instr;
|
||||
|
||||
// 打包 / 拆字段
|
||||
/**
|
||||
* @brief 打包:四个字段拼成一条指令字。
|
||||
* @param op 操作码(低 8 位)
|
||||
* @param rd 目标寄存器 / 条件寄存器(第 8..15 位)
|
||||
* @param a 源寄存器 / 立即数低 8 位 / 偏移低 8 位(第 16..23 位)
|
||||
* @param b 源寄存器 / 立即数高 8 位 / 偏移高 8 位(第 24..31 位)
|
||||
* @return 打包后的指令字
|
||||
*/
|
||||
inline Instr pack(Op op, uint8_t rd, uint8_t a, uint8_t b) {
|
||||
return static_cast<uint32_t>(static_cast<uint8_t>(op))
|
||||
| (static_cast<uint32_t>(rd) << 8)
|
||||
@@ -24,69 +32,143 @@ namespace isa {
|
||||
| (static_cast<uint32_t>(b) << 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取操作码。
|
||||
* @param w 指令字
|
||||
* @return 低 8 位解释为 Op
|
||||
*/
|
||||
inline Op op(Instr w) {
|
||||
return static_cast<Op>(w & 0xFFu);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取目标寄存器字段。
|
||||
* @param w 指令字
|
||||
* @return rd(第 8..15 位)
|
||||
*/
|
||||
inline uint8_t rd(Instr w) {
|
||||
return static_cast<uint8_t>((w >> 8) & 0xFFu);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取 a 字段。
|
||||
* @param w 指令字
|
||||
* @return a(第 16..23 位)
|
||||
*/
|
||||
inline uint8_t a(Instr w) {
|
||||
return static_cast<uint8_t>((w >> 16) & 0xFFu);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取 b 字段。
|
||||
* @param w 指令字
|
||||
* @return b(第 24..31 位)
|
||||
*/
|
||||
inline uint8_t b(Instr w) {
|
||||
return static_cast<uint8_t>((w >> 24) & 0xFFu);
|
||||
}
|
||||
|
||||
// 组合视图:a|b 拼成 16 位(const_id / fn_id / slot)
|
||||
/**
|
||||
* @brief 组合视图:a|b 拼成 16 位无符号数。
|
||||
* @param w 指令字
|
||||
* @return 小端拼出的 16 位值,用作 const_id / fn_id / slot 号
|
||||
*/
|
||||
inline uint16_t imm16(Instr w) {
|
||||
return static_cast<uint16_t>(a(w) | (static_cast<uint16_t>(b(w)) << 8));
|
||||
}
|
||||
|
||||
// 组合视图:a|b 为有符号相对偏移,单位是指令条数
|
||||
/**
|
||||
* @brief 组合视图:a|b 为有符号相对偏移。
|
||||
* @param w 指令字
|
||||
* @return 偏移量,单位是指令条数(跳转目标 = 下一条指令 + off)
|
||||
*/
|
||||
inline int16_t off16(Instr w) {
|
||||
return static_cast<int16_t>(imm16(w));
|
||||
}
|
||||
|
||||
// 按形态的便捷编码
|
||||
/**
|
||||
* @brief 按 RR 形态编码。
|
||||
* @param op 操作码(MOVE / NOT)
|
||||
* @param rd 目标寄存器
|
||||
* @param rs 源寄存器(放进 a)
|
||||
* @return 指令字
|
||||
*/
|
||||
inline Instr enc_rr(Op op, uint8_t rd, uint8_t rs) {
|
||||
return pack(op, rd, rs, 0); // MOVE / NOT:a = rs
|
||||
return pack(op, rd, rs, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 RRR 形态编码。
|
||||
* @param op 操作码(AND/OR/ADD/.../CMP_xx)
|
||||
* @param rd 目标寄存器
|
||||
* @param ra 第一源寄存器
|
||||
* @param rb 第二源寄存器
|
||||
* @return 指令字
|
||||
*/
|
||||
inline Instr enc_rrr(Op op, uint8_t rd, uint8_t ra, uint8_t rb) {
|
||||
return pack(op, rd, ra, rb); // AND/OR/ADD/.../CMP_xx
|
||||
return pack(op, rd, ra, rb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 IMM 形态编码。
|
||||
* @param op 操作码(LOADK)
|
||||
* @param rd 目标寄存器
|
||||
* @param imm 16 位立即数(const_id),拆成 a|b 存放
|
||||
* @return 指令字
|
||||
*/
|
||||
inline Instr enc_imm(Op op, uint8_t rd, uint16_t imm) {
|
||||
return pack(op, rd, static_cast<uint8_t>(imm & 0xFFu),
|
||||
static_cast<uint8_t>((imm >> 8) & 0xFFu)); // LOADK
|
||||
static_cast<uint8_t>((imm >> 8) & 0xFFu));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 JMP 形态编码。
|
||||
* @param off 有符号相对偏移(条数),拆成 a|b 存放
|
||||
* @return 指令字
|
||||
*/
|
||||
inline Instr enc_jmp(int16_t off) {
|
||||
return pack(Op::JMP, 0,
|
||||
static_cast<uint8_t>(static_cast<uint16_t>(off) & 0xFFu),
|
||||
static_cast<uint8_t>((static_cast<uint16_t>(off) >> 8) & 0xFFu));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 JC 形态编码(条件跳转)。
|
||||
* @param op 操作码(JT / JF)
|
||||
* @param r 条件寄存器(放进 rd)
|
||||
* @param off 有符号相对偏移(条数),放进 a|b
|
||||
* @return 指令字
|
||||
*/
|
||||
inline Instr enc_jc(Op op, uint8_t r, int16_t off) {
|
||||
// JT / JF:条件寄存器在 rd,偏移在 a|b
|
||||
return pack(op, r,
|
||||
static_cast<uint8_t>(static_cast<uint16_t>(off) & 0xFFu),
|
||||
static_cast<uint8_t>((static_cast<uint16_t>(off) >> 8) & 0xFFu));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 SLOT 形态编码。
|
||||
* @param op 操作码(LOAD_I / STORE_Q / LOAD_M / STORE_M / LOAD_GLOBAL / STORE_GLOBAL / CAL_*)
|
||||
* @param rd 目标寄存器(CAL_* 时置 0)
|
||||
* @param slot 槽号 / 实例号,放进 a|b
|
||||
* @return 指令字
|
||||
*/
|
||||
inline Instr enc_slot(Op op, uint8_t rd, uint16_t slot) {
|
||||
// LOAD_I / STORE_Q / LOAD_M / STORE_M / LOAD_GLOBAL / STORE_GLOBAL / CAL_*
|
||||
// 槽号 / 实例号在 a|b
|
||||
return enc_imm(op, rd, slot);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 CALL 形态编码。
|
||||
* @param fn_id 函数表下标,放进 a|b
|
||||
* @return 指令字
|
||||
*/
|
||||
inline Instr enc_call(uint16_t fn_id) {
|
||||
return enc_imm(Op::CALL, 0, fn_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 NONE 形态编码(RET)。
|
||||
* @return 指令字
|
||||
*/
|
||||
inline Instr enc_ret() {
|
||||
return pack(Op::RET, 0, 0, 0);
|
||||
}
|
||||
|
||||
+120
-20
@@ -1,6 +1,8 @@
|
||||
/**
|
||||
* @file Op.h
|
||||
* @brief 操作码枚举 + 助记符
|
||||
* @brief 操作码枚举、操作数形态与编译期指令表
|
||||
* @details 操作码数值即 .stb 指令编码。执行器(vm)只依赖本表;
|
||||
* 编译器侧的登记见 Doc/isa/指令配置.md(compiler/machine.toml,与之强校验)。
|
||||
* @author
|
||||
* @date 2026-08-19
|
||||
*/
|
||||
@@ -10,8 +12,11 @@
|
||||
#include <cstdint>
|
||||
|
||||
namespace isa {
|
||||
// 操作码一次列全。数值即编码:冻结后不得改顺序、不得插值,
|
||||
// 增加指令只能追加到末尾,并同步 Doc/isa/指令与映像.md 与测试。
|
||||
/**
|
||||
* @brief 操作码枚举。
|
||||
* @details 数值即编码:冻结后不得改顺序、不得插值,增加指令只能追加到末尾,
|
||||
* 并同步 Doc/isa/指令与映像.md、compiler/machine.toml 与测试。
|
||||
*/
|
||||
enum class Op : uint8_t {
|
||||
MOVE = 0, // MOVE rd, rs
|
||||
LOADK = 1, // LOADK rd, const_id
|
||||
@@ -37,29 +42,124 @@ namespace isa {
|
||||
STORE_M = 21, // STORE_M rs, slot
|
||||
LOAD_GLOBAL = 22, // LOAD_GLOBAL rd, slot
|
||||
STORE_GLOBAL = 23, // STORE_GLOBAL rs, slot
|
||||
// 内置功能块(8 个连续,12.11 扩充:TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG)
|
||||
CAL_TON = 24, // CAL_TON instance_slot
|
||||
CAL_TOF = 25, // CAL_TOF instance_slot
|
||||
CAL_CTU = 26, // CAL_CTU instance_slot
|
||||
CALL = 27, // CALL fn_id
|
||||
RET = 28, // RET
|
||||
CAL_TP = 26, // CAL_TP instance_slot
|
||||
CAL_CTU = 27, // CAL_CTU instance_slot
|
||||
CAL_CTD = 28, // CAL_CTD instance_slot
|
||||
CAL_CTUD = 29, // CAL_CTUD instance_slot
|
||||
CAL_R_TRIG = 30, // CAL_R_TRIG instance_slot
|
||||
CAL_F_TRIG = 31, // CAL_F_TRIG instance_slot
|
||||
CALL = 32, // CALL fn_id
|
||||
RET = 33, // RET
|
||||
};
|
||||
|
||||
// 操作码总数
|
||||
static const int kOpCount = 29;
|
||||
/// 操作码总数(= 最大 opcode + 1,kOpDefs 的下标上界)。
|
||||
static const int kOpCount = 34;
|
||||
|
||||
// 助记符:下标与 Op 数值一一对应
|
||||
/**
|
||||
* @brief 操作数形态(三层分类第二层,见 Doc/isa/指令配置.md)。
|
||||
* @details 决定操作数 a|b 的解释方式与反汇编文本格式。
|
||||
*/
|
||||
enum class OpFormat : uint8_t {
|
||||
RR, // 两寄存器:rd rs(MOVE / NOT)
|
||||
RRR, // 三寄存器:rd ra rb(逻辑 / 算术 / 比较)
|
||||
IMM, // 常量:rd const_id(LOADK)
|
||||
SLOT, // 槽:rd slot(LOAD_* / STORE_*)
|
||||
JMP, // 偏移:off
|
||||
JC, // 条件跳转:r off(JT / JF)
|
||||
CALL, // 调用:fn_id
|
||||
CAL, // 实例:instance(CAL_*)
|
||||
NONE, // 无操作数(RET)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 操作对象大类(三层分类第一层)。
|
||||
*/
|
||||
enum class OpClass : uint8_t {
|
||||
Plain, // 无实例:寄存器 / 立即数 / 槽 / 分支 / 调用
|
||||
Instance, // 有实例:操作数据区实例块(内建 FB)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 编译期指令表条目。
|
||||
* @details 一条指令的名称、操作数形态与大类;下标与 Op 数值一一对应。
|
||||
*/
|
||||
struct OpDef {
|
||||
const char* name; ///< 助记符(与 .stb / machine.toml 一致)
|
||||
OpFormat format; ///< 操作数形态
|
||||
OpClass cls; ///< 操作对象大类
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 编译期指令表:下标与 Op 数值一一对应(machine.toml 强校验的基准)。
|
||||
* @details 表与 Op 枚举同步维护;查询入口见 mnemonic / format / op_class。
|
||||
*/
|
||||
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
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 查助记符。
|
||||
* @param op 操作码
|
||||
* @return 对应助记符;越界返回哨兵 "???"
|
||||
*/
|
||||
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_CTU",
|
||||
"CALL", "RET",
|
||||
};
|
||||
const int idx = static_cast<int>(op);
|
||||
return (idx >= 0 && idx < kOpCount) ? kMnemonic[idx] : "???";
|
||||
return (idx >= 0 && idx < kOpCount) ? kOpDefs[idx].name : "???";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查操作数形态。
|
||||
* @param op 操作码
|
||||
* @return 对应 OpFormat;越界返回 OpFormat::NONE
|
||||
*/
|
||||
inline OpFormat format(Op op) {
|
||||
const int idx = static_cast<int>(op);
|
||||
return (idx >= 0 && idx < kOpCount) ? kOpDefs[idx].format : OpFormat::NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查操作对象大类。
|
||||
* @param op 操作码
|
||||
* @return 对应 OpClass;越界返回 OpClass::Plain
|
||||
*/
|
||||
inline OpClass op_class(Op op) {
|
||||
const int idx = static_cast<int>(op);
|
||||
return (idx >= 0 && idx < kOpCount) ? kOpDefs[idx].cls : OpClass::Plain;
|
||||
}
|
||||
}
|
||||
|
||||
+51
-10
@@ -1,6 +1,8 @@
|
||||
/**
|
||||
* @file Types.h
|
||||
* @brief 类型定义
|
||||
* @brief 定宽类型与饱和算术
|
||||
* @details 虚拟机字长与类型契约的唯一来源。compiler 与 vm 共用同一套规则,
|
||||
* 详见 Doc/isa/指令与映像.md。
|
||||
* @author
|
||||
* @date 2026-08-18
|
||||
*/
|
||||
@@ -12,15 +14,28 @@
|
||||
|
||||
namespace isa {
|
||||
namespace types {
|
||||
// 定宽类型。整数溢出规则:饱和(夹到 INT 最小/最大),
|
||||
// 由 compiler 与 vm 按同一规则实现,见 Doc/isa/指令与映像.md。
|
||||
using BOOL = uint8_t; // 只允许 0/1
|
||||
using INT = int16_t; // IEC INT
|
||||
// 毫秒,有符号。64 位理由见 Doc/isa/指令与映像.md:
|
||||
// 差值可负、为 DATE_AND_TIME 与 64 位定时器预留。槽位按 8 字节对齐。
|
||||
/**
|
||||
* @brief 布尔值:8 位,只允许 0/1(0=FALSE,1=TRUE)。
|
||||
*/
|
||||
using BOOL = uint8_t;
|
||||
|
||||
/**
|
||||
* @brief IEC INT:16 位有符号整数,溢出按饱和规则处理。
|
||||
*/
|
||||
using INT = int16_t;
|
||||
|
||||
/**
|
||||
* @brief 时间:64 位有符号整数,单位毫秒。
|
||||
* @details 取 64 位的理由见 Doc/isa/指令与映像.md:差值可负、
|
||||
* 为 DATE_AND_TIME 与 64 位定时器预留。槽位按 8 字节对齐。
|
||||
*/
|
||||
using TIME = int64_t;
|
||||
|
||||
// 槽 / 常量表类型标记,数值冻结:0=BOOL、1=INT、2=TIME(见映像规范)
|
||||
/**
|
||||
* @brief 槽 / 常量表类型标记。
|
||||
* @details 数值冻结并写入 .stb 常量表:0=BOOL、1=INT、2=TIME(见映像规范),
|
||||
* 不得改顺序、不得插值。
|
||||
*/
|
||||
enum TypeTag : uint8_t {
|
||||
Bool = 0,
|
||||
Int = 1,
|
||||
@@ -29,6 +44,13 @@ namespace isa {
|
||||
|
||||
// 饱和算术:夹到 INT 最小/最大,编译期与 VM 共用同一规则。
|
||||
|
||||
/**
|
||||
* @brief 饱和加法。
|
||||
* @param a 加数
|
||||
* @param b 加数
|
||||
* @return a+b,超出 INT 范围时夹到最小/最大值
|
||||
* @details 中间量用 int32 计算,避免 int16 溢出未定义行为。
|
||||
*/
|
||||
inline INT sat_add(INT a, INT b) {
|
||||
const int32_t sum = static_cast<int32_t>(a) + static_cast<int32_t>(b);
|
||||
return static_cast<INT>(sum < std::numeric_limits<INT>::min() ? std::numeric_limits<INT>::min()
|
||||
@@ -36,6 +58,13 @@ namespace isa {
|
||||
: sum);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 饱和减法。
|
||||
* @param a 被减数
|
||||
* @param b 减数
|
||||
* @return a-b,超出 INT 范围时夹到最小/最大值
|
||||
* @details 中间量用 int32 计算,避免 int16 溢出未定义行为。
|
||||
*/
|
||||
inline INT sat_sub(INT a, INT b) {
|
||||
const int32_t diff = static_cast<int32_t>(a) - static_cast<int32_t>(b);
|
||||
return static_cast<INT>(diff < std::numeric_limits<INT>::min() ? std::numeric_limits<INT>::min()
|
||||
@@ -43,6 +72,13 @@ namespace isa {
|
||||
: diff);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 饱和乘法。
|
||||
* @param a 乘数
|
||||
* @param b 乘数
|
||||
* @return a*b,超出 INT 范围时夹到最小/最大值
|
||||
* @details 中间量用 int32 计算,避免 int16 溢出未定义行为。
|
||||
*/
|
||||
inline INT sat_mul(INT a, INT b) {
|
||||
const int32_t prod = static_cast<int32_t>(a) * static_cast<int32_t>(b);
|
||||
return static_cast<INT>(prod < std::numeric_limits<INT>::min() ? std::numeric_limits<INT>::min()
|
||||
@@ -50,12 +86,17 @@ namespace isa {
|
||||
: prod);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 饱和除法。
|
||||
* @param a 被除数
|
||||
* @param b 除数
|
||||
* @return a/b;b==0 视为除以 1 返回 a;INT_MIN / -1 溢出饱和到 INT_MAX
|
||||
* @details b==0 返回 a 是为避免未定义行为;除零的语义由 compiler/VM 层报错。
|
||||
*/
|
||||
inline INT sat_div(INT a, INT b) {
|
||||
// b == 0 视为除以 1,避免未定义行为;除零的语义由 compiler/VM 层报错
|
||||
if (b == 0) {
|
||||
return a;
|
||||
}
|
||||
// INT_MIN / -1 溢出:饱和到 INT_MAX
|
||||
if (a == std::numeric_limits<INT>::min() && b == -1) {
|
||||
return std::numeric_limits<INT>::max();
|
||||
}
|
||||
|
||||
+30
-34
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @file Encode.cpp
|
||||
* @brief 指令编解码与反汇编
|
||||
* @brief 指令编解码与反汇编实现
|
||||
* @author
|
||||
* @date 2026-08-19
|
||||
*/
|
||||
@@ -11,10 +11,20 @@
|
||||
|
||||
namespace isa {
|
||||
|
||||
/**
|
||||
* @brief 编码:字段视图打包成一条 32 位指令字。
|
||||
* @param d 解码后的字段视图
|
||||
* @return 打包后的指令字;满足 encode(decode(w)) == w
|
||||
*/
|
||||
Instr encode(Decoded d) {
|
||||
return pack(d.op, d.rd, d.a, d.b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解码:指令字拆成字段视图。
|
||||
* @param w 32 位指令字
|
||||
* @return 各字段视图
|
||||
*/
|
||||
Decoded decode(Instr w) {
|
||||
Decoded d;
|
||||
d.op = op(w);
|
||||
@@ -24,6 +34,14 @@ Decoded decode(Instr w) {
|
||||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 反汇编一条指令为一行文本。
|
||||
* @param w 指令字
|
||||
* @param out 输出缓冲
|
||||
* @param cap 缓冲容量(含 '\0');cap==0 时直接返回、不写 out
|
||||
* @details 按操作数形态表驱动输出,文本与 Doc/isa/指令与映像.md 一致;
|
||||
* 未知操作码输出 `??? 0x<w>`。
|
||||
*/
|
||||
void disasm(Instr w, char* out, size_t cap) {
|
||||
if (cap == 0) {
|
||||
return;
|
||||
@@ -38,56 +56,34 @@ void disasm(Instr w, char* out, size_t cap) {
|
||||
}
|
||||
const char* m = mnemonic(o);
|
||||
|
||||
switch (o) {
|
||||
case Op::MOVE:
|
||||
case Op::NOT:
|
||||
// 表驱动:按操作数形态输出(文本与 Doc/isa/指令与映像.md 一致)
|
||||
switch (format(o)) {
|
||||
case OpFormat::RR:
|
||||
snprintf(out, cap, "%s r%u, r%u", m,
|
||||
static_cast<unsigned>(rd(w)), static_cast<unsigned>(a(w)));
|
||||
break;
|
||||
case Op::AND:
|
||||
case Op::OR:
|
||||
case Op::ADD:
|
||||
case Op::SUB:
|
||||
case Op::MUL:
|
||||
case Op::DIV:
|
||||
case Op::CMP_EQ:
|
||||
case Op::CMP_NE:
|
||||
case Op::CMP_LT:
|
||||
case Op::CMP_LE:
|
||||
case Op::CMP_GT:
|
||||
case Op::CMP_GE:
|
||||
case OpFormat::RRR:
|
||||
snprintf(out, cap, "%s r%u, r%u, r%u", m,
|
||||
static_cast<unsigned>(rd(w)), static_cast<unsigned>(a(w)),
|
||||
static_cast<unsigned>(b(w)));
|
||||
break;
|
||||
case Op::LOADK:
|
||||
case OpFormat::IMM:
|
||||
case OpFormat::SLOT:
|
||||
snprintf(out, cap, "%s r%u, %u", m,
|
||||
static_cast<unsigned>(rd(w)), static_cast<unsigned>(imm16(w)));
|
||||
break;
|
||||
case Op::JMP:
|
||||
case OpFormat::JMP:
|
||||
snprintf(out, cap, "%s %+d", m, static_cast<int>(off16(w)));
|
||||
break;
|
||||
case Op::JT:
|
||||
case Op::JF:
|
||||
case OpFormat::JC:
|
||||
snprintf(out, cap, "%s r%u, %+d", m,
|
||||
static_cast<unsigned>(rd(w)), static_cast<int>(off16(w)));
|
||||
break;
|
||||
case Op::LOAD_I:
|
||||
case Op::STORE_Q:
|
||||
case Op::LOAD_M:
|
||||
case Op::STORE_M:
|
||||
case Op::LOAD_GLOBAL:
|
||||
case Op::STORE_GLOBAL:
|
||||
snprintf(out, cap, "%s r%u, %u", m,
|
||||
static_cast<unsigned>(rd(w)), static_cast<unsigned>(imm16(w)));
|
||||
break;
|
||||
case Op::CAL_TON:
|
||||
case Op::CAL_TOF:
|
||||
case Op::CAL_CTU:
|
||||
case Op::CALL:
|
||||
case OpFormat::CALL:
|
||||
case OpFormat::CAL:
|
||||
snprintf(out, cap, "%s %u", m, static_cast<unsigned>(imm16(w)));
|
||||
break;
|
||||
case Op::RET:
|
||||
case OpFormat::NONE:
|
||||
snprintf(out, cap, "%s", m);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -4,6 +4,16 @@ add_test(NAME stcompiler_version
|
||||
add_test(NAME bytecode_executor_version
|
||||
COMMAND BytecodeExecutor)
|
||||
|
||||
# CLI 级:编译需 --machine;缺参必须失败(WILL_FAIL)
|
||||
add_test(NAME cli_compile
|
||||
COMMAND STCompiler ${CMAKE_SOURCE_DIR}/tests/cases/01_empty_main/project.toml
|
||||
-o ${CMAKE_CURRENT_BINARY_DIR}/cli_case01.stb
|
||||
--machine ${CMAKE_SOURCE_DIR}/compiler/machine.toml)
|
||||
add_test(NAME cli_missing_machine
|
||||
COMMAND STCompiler ${CMAKE_SOURCE_DIR}/tests/cases/01_empty_main/project.toml
|
||||
-o ${CMAKE_CURRENT_BINARY_DIR}/cli_nope.stb)
|
||||
set_tests_properties(cli_missing_machine PROPERTIES WILL_FAIL TRUE)
|
||||
|
||||
# isa 合同测试:链接 isa 库,断言覆盖饱和/编解码/disasm/哈希/映像/sidecar
|
||||
add_executable(isa_test
|
||||
./src/isa_test.cpp)
|
||||
@@ -56,3 +66,58 @@ target_compile_definitions(linker_test PRIVATE
|
||||
|
||||
add_test(NAME linker_links
|
||||
COMMAND linker_test)
|
||||
|
||||
# 类型检查测试:用例 14 + 负例 + 正例(REPO_ROOT 注入源目录绝对路径)
|
||||
add_executable(typecheck_test
|
||||
./src/typecheck_test.cpp)
|
||||
|
||||
target_link_libraries(typecheck_test PRIVATE compiler)
|
||||
target_compile_definitions(typecheck_test PRIVATE
|
||||
REPO_ROOT="${CMAKE_SOURCE_DIR}")
|
||||
|
||||
add_test(NAME typecheck_types
|
||||
COMMAND typecheck_test)
|
||||
|
||||
# 寄存器码测试(12.8 切片 1):用例 01/02 出映像(REPO_ROOT 注入源目录绝对路径)
|
||||
add_executable(codegen_test
|
||||
./src/codegen_test.cpp)
|
||||
|
||||
target_link_libraries(codegen_test PRIVATE compiler isa)
|
||||
target_compile_definitions(codegen_test PRIVATE
|
||||
REPO_ROOT="${CMAKE_SOURCE_DIR}")
|
||||
|
||||
add_test(NAME codegen_slice1
|
||||
COMMAND codegen_test)
|
||||
|
||||
# VM 测试:手工映像 + 编译器产物 + 定时器 + 确定性(REPO_ROOT 注入源目录绝对路径)
|
||||
add_executable(vm_test
|
||||
./src/vm_test.cpp)
|
||||
|
||||
target_link_libraries(vm_test PRIVATE compiler vm)
|
||||
target_compile_definitions(vm_test PRIVATE
|
||||
REPO_ROOT="${CMAKE_SOURCE_DIR}")
|
||||
|
||||
add_test(NAME vm_cycles
|
||||
COMMAND vm_test)
|
||||
|
||||
# 12.2 二十用例点亮(12.11):按 EXPECTED.md 期望跑完整管线(REPO_ROOT 注入源目录绝对路径)
|
||||
add_executable(cases_test
|
||||
./src/cases_test.cpp)
|
||||
|
||||
target_link_libraries(cases_test PRIVATE compiler vm)
|
||||
target_compile_definitions(cases_test PRIVATE
|
||||
REPO_ROOT="${CMAKE_SOURCE_DIR}")
|
||||
|
||||
add_test(NAME cases_all
|
||||
COMMAND cases_test)
|
||||
|
||||
# machine.toml 加载与强校验(阶段 A 步骤 3;REPO_ROOT 注入源目录绝对路径)
|
||||
add_executable(machine_test
|
||||
./src/machine_test.cpp)
|
||||
|
||||
target_link_libraries(machine_test PRIVATE compiler)
|
||||
target_compile_definitions(machine_test PRIVATE
|
||||
REPO_ROOT="${CMAKE_SOURCE_DIR}")
|
||||
|
||||
add_test(NAME machine_config
|
||||
COMMAND machine_test)
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* @file cases_test.cpp
|
||||
* @brief 12.2 二十用例点亮(12.11):每例按 EXPECTED.md 期望跑完整管线
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 对 tests/cases/01..20 逐一执行:解析 → 链接 → 类型检查 → 寄存器码 → VM 周期,
|
||||
* 断言期望结果(编译运行通过 / 报错类别 / cycle_limit 故障),与各用例 EXPECTED.md 一致。
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "compiler/Codegen.h"
|
||||
#include "compiler/Linker.h"
|
||||
#include "compiler/Project.h"
|
||||
#include "compiler/Typecheck.h"
|
||||
#include "vm/Machine.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)
|
||||
|
||||
namespace {
|
||||
|
||||
enum class Outcome { Ok, CompileError, CycleLimit };
|
||||
|
||||
// 期望表(与 tests/cases/*/EXPECTED.md 一致;负例断言错误类别关键词)
|
||||
struct CaseSpec {
|
||||
const char* dir;
|
||||
Outcome out;
|
||||
const char* err_keyword; // CompileError 时须出现在错误消息里
|
||||
};
|
||||
|
||||
const CaseSpec kCases[] = {
|
||||
{"01_empty_main", Outcome::Ok, ""},
|
||||
{"02_bool_assign", Outcome::Ok, ""},
|
||||
{"03_short_circuit", Outcome::Ok, ""},
|
||||
{"04_if_elsif_else", Outcome::Ok, ""},
|
||||
{"05_while_normal", Outcome::Ok, ""},
|
||||
{"06_while_cycle_limit", Outcome::CycleLimit, ""},
|
||||
{"07_int_arith", Outcome::Ok, ""},
|
||||
{"08_time_literal", Outcome::Ok, ""},
|
||||
{"09_gvl_external", Outcome::Ok, ""},
|
||||
{"10_gvl_wrong_file", Outcome::CompileError, "VAR_GLOBAL only allowed in gvl file"},
|
||||
{"11_duplicate_global", Outcome::CompileError, "duplicate global"},
|
||||
{"12_io_unknown_var", Outcome::CompileError, "io.var"},
|
||||
{"13_function_call", Outcome::Ok, ""},
|
||||
{"14_function_write_global", Outcome::CompileError, "function cannot write global"},
|
||||
{"15_fb_instance", Outcome::Ok, ""},
|
||||
{"16_fb_undeclared", Outcome::CompileError, "undeclared FB instance"},
|
||||
{"17_ton", Outcome::Ok, ""},
|
||||
{"18_tof_ctu", Outcome::Ok, ""},
|
||||
{"19_recursive_call", Outcome::CompileError, "recursive call"},
|
||||
{"20_line1", Outcome::Ok, ""},
|
||||
};
|
||||
|
||||
bool run_case(const char* dir, Outcome want, const char* keyword) {
|
||||
using namespace compiler;
|
||||
const std::string toml = std::string(REPO_ROOT) + "/tests/cases/" + dir + "/project.toml";
|
||||
std::string err;
|
||||
|
||||
Project p;
|
||||
if (!parse_project(toml, &p, &err)) {
|
||||
if (want == Outcome::CompileError) {
|
||||
++g_checks;
|
||||
return true;
|
||||
}
|
||||
std::printf("FAIL %s parse: %s\n", dir, err.c_str());
|
||||
return false;
|
||||
}
|
||||
std::vector<SourceUnit> units;
|
||||
for (const std::string& f : compile_files(p)) {
|
||||
SourceUnit u;
|
||||
if (!load_unit(p.base_dir + "/" + f, &u, &err)) {
|
||||
if (want == Outcome::CompileError) {
|
||||
++g_checks;
|
||||
return true;
|
||||
}
|
||||
std::printf("FAIL %s load: %s\n", dir, err.c_str());
|
||||
return false;
|
||||
}
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
LinkResult link;
|
||||
if (!link_project(p, units, &link, &err)) {
|
||||
if (want == Outcome::CompileError) {
|
||||
if (err.find(keyword) == std::string::npos) {
|
||||
std::printf("FAIL %s: want '%s', got '%s'\n", dir, keyword, err.c_str());
|
||||
return false;
|
||||
}
|
||||
++g_checks;
|
||||
return true;
|
||||
}
|
||||
std::printf("FAIL %s link: %s\n", dir, err.c_str());
|
||||
return false;
|
||||
}
|
||||
if (!check_project(p, units, link, &err)) {
|
||||
if (want == Outcome::CompileError) {
|
||||
if (err.find(keyword) == std::string::npos) {
|
||||
std::printf("FAIL %s: want '%s', got '%s'\n", dir, keyword, err.c_str());
|
||||
return false;
|
||||
}
|
||||
++g_checks;
|
||||
return true;
|
||||
}
|
||||
std::printf("FAIL %s type: %s\n", dir, err.c_str());
|
||||
return false;
|
||||
}
|
||||
MachineConfig cfg;
|
||||
if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)) {
|
||||
std::printf("FAIL %s machine load\n", dir);
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> img;
|
||||
if (!codegen_project(p, units, link, cfg, &img, &err)) {
|
||||
if (want == Outcome::CompileError) {
|
||||
++g_checks;
|
||||
return true;
|
||||
}
|
||||
std::printf("FAIL %s codegen: %s\n", dir, err.c_str());
|
||||
return false;
|
||||
}
|
||||
if (want == Outcome::CompileError) {
|
||||
std::printf("FAIL %s: expected compile error\n", dir);
|
||||
return false;
|
||||
}
|
||||
|
||||
vm::Machine m;
|
||||
if (!vm::Machine::create(img, &m, &err)) {
|
||||
std::printf("FAIL %s vm create: %s\n", dir, err.c_str());
|
||||
return false;
|
||||
}
|
||||
const vm::Fault f = m.run_cycle();
|
||||
if (want == Outcome::CycleLimit) {
|
||||
if (f == vm::Fault::CycleLimit) {
|
||||
++g_checks;
|
||||
return true;
|
||||
}
|
||||
std::printf("FAIL %s: want CycleLimit, got %d\n", dir, static_cast<int>(f));
|
||||
return false;
|
||||
}
|
||||
if (f != vm::Fault::None) {
|
||||
std::printf("FAIL %s: fault %d\n", dir, static_cast<int>(f));
|
||||
return false;
|
||||
}
|
||||
// 可重复性:再跑一个周期
|
||||
if (m.run_cycle() != vm::Fault::None) {
|
||||
std::printf("FAIL %s: 2nd cycle fault\n", dir);
|
||||
return false;
|
||||
}
|
||||
++g_checks;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
for (const CaseSpec& c : kCases) {
|
||||
if (!run_case(c.dir, c.out, c.err_keyword)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
std::printf("cases_test: %d cases lit up\n", g_checks);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,734 @@
|
||||
/**
|
||||
* @file codegen_test.cpp
|
||||
* @brief 寄存器码测试(12.8 切片 1):用例 01/02 出映像
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "compiler/Codegen.h"
|
||||
#include "compiler/Linker.h"
|
||||
#include "compiler/Project.h"
|
||||
#include "compiler/Typecheck.h"
|
||||
#include "isa/Encode.h"
|
||||
#include "compiler/Stb.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)
|
||||
|
||||
// 从用例目录走完整编译管线(解析 → 链接 → 类型检查 → 代码生成)
|
||||
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) {
|
||||
using namespace compiler;
|
||||
compiler::MachineConfig cfg;
|
||||
if (!load_cfg(&cfg)) return false;
|
||||
const std::string toml = std::string(REPO_ROOT) + "/tests/cases/" + dir + "/project.toml";
|
||||
Project p;
|
||||
if (!parse_project(toml, &p, err)) {
|
||||
return false;
|
||||
}
|
||||
std::vector<SourceUnit> units;
|
||||
for (const std::string& f : compile_files(p)) {
|
||||
SourceUnit u;
|
||||
if (!load_unit(p.base_dir + "/" + f, &u, err)) {
|
||||
return false;
|
||||
}
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
LinkResult link;
|
||||
if (!link_project(p, units, &link, err)) {
|
||||
return false;
|
||||
}
|
||||
if (!check_project(p, units, link, err)) {
|
||||
return false;
|
||||
}
|
||||
return codegen_project(p, units, link, cfg, image, err);
|
||||
}
|
||||
|
||||
// 从临时工程(globals.st + main.st + 可含 io 绑定)走完整管线
|
||||
static bool compile_src(const char* name, const char* io_extra, const char* globals_st,
|
||||
const char* main_st, std::vector<uint8_t>* image,
|
||||
std::string* err) {
|
||||
using namespace compiler;
|
||||
compiler::MachineConfig cfg;
|
||||
if (!load_cfg(&cfg)) return false;
|
||||
const std::string dir = std::string(REPO_ROOT) + "/build/cg_tmp_" + name;
|
||||
std::filesystem::remove_all(dir);
|
||||
std::filesystem::create_directories(dir);
|
||||
const std::string toml_path = dir + "/project.toml";
|
||||
std::FILE* f = std::fopen(toml_path.c_str(), "w");
|
||||
std::fprintf(f, "[project]\nname = \"cg\"\nentry = \"program MAIN\"\n"
|
||||
"cycle_limit = 1000\ndt_ms = 10\n"
|
||||
"[files]\nst = [\"globals.st\", \"main.st\"]\n"
|
||||
"[gvl]\nfile = \"globals.st\"\n%s", io_extra);
|
||||
std::fclose(f);
|
||||
f = std::fopen((dir + "/globals.st").c_str(), "w");
|
||||
std::fputs(globals_st, f);
|
||||
std::fclose(f);
|
||||
f = std::fopen((dir + "/main.st").c_str(), "w");
|
||||
std::fputs(main_st, f);
|
||||
std::fclose(f);
|
||||
|
||||
Project p;
|
||||
if (!parse_project(toml_path, &p, err)) {
|
||||
return false;
|
||||
}
|
||||
std::vector<SourceUnit> units;
|
||||
for (const std::string& file : compile_files(p)) {
|
||||
SourceUnit u;
|
||||
if (!load_unit(p.base_dir + "/" + file, &u, err)) {
|
||||
return false;
|
||||
}
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
LinkResult link;
|
||||
if (!link_project(p, units, &link, err)) {
|
||||
return false;
|
||||
}
|
||||
if (!check_project(p, units, link, err)) {
|
||||
return false;
|
||||
}
|
||||
return codegen_project(p, units, link, cfg, image, err);
|
||||
}
|
||||
|
||||
// ---- 1. 用例 01:空 MAIN + RET ----
|
||||
|
||||
static bool test_case01() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
CHECK(compile_case("01_empty_main", &img, &err));
|
||||
|
||||
const compiler::StbView v = compiler::StbView::from(img);
|
||||
CHECK(v.ok());
|
||||
CHECK(v.cycle_limit() == 1000);
|
||||
CHECK(v.dt_ms() == 10);
|
||||
CHECK(v.entry_fn_id() == 0);
|
||||
CHECK(v.n_funcs() == 1);
|
||||
CHECK(v.n_consts() == 0);
|
||||
CHECK(v.n_globals() == 0);
|
||||
CHECK(v.project_hash() != compiler::kFnvBasis);
|
||||
|
||||
const compiler::StbView::FuncRow r = v.func_row(0);
|
||||
CHECK(r.nregs == 8); // 帧基址(调用约定区)
|
||||
CHECK(r.code_len == 1);
|
||||
|
||||
char buf[32];
|
||||
isa::disasm(v.code_bytes()[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "RET") == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 2. 用例 02:BOOL 赋值(TRUE / FALSE → LOADK)----
|
||||
|
||||
static bool test_case02() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
CHECK(compile_case("02_bool_assign", &img, &err));
|
||||
|
||||
const compiler::StbView v = compiler::StbView::from(img);
|
||||
CHECK(v.ok());
|
||||
CHECK(v.n_funcs() == 1);
|
||||
CHECK(v.n_consts() == 2); // TRUE 与 FALSE
|
||||
|
||||
const compiler::StbView::FuncRow r = v.func_row(0);
|
||||
CHECK(r.nregs == 10); // a, b(r8 起)
|
||||
CHECK(r.code_len == 3);
|
||||
|
||||
// 从字节码段取指令(按函数行 code_offset 定位)
|
||||
const uint8_t* base = v.code_bytes();
|
||||
char buf[64];
|
||||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r8, 0") == 0); // a := TRUE(常量 0)
|
||||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[1], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r9, 1") == 0); // b := FALSE(常量 1)
|
||||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[2], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "RET") == 0);
|
||||
|
||||
// 常量表:0 = BOOL 1,1 = BOOL 0
|
||||
const compiler::ConstEntry c0 = v.const_entry(0);
|
||||
CHECK(c0.tag == 0 && c0.value == 1);
|
||||
const compiler::ConstEntry c1 = v.const_entry(1);
|
||||
CHECK(c1.tag == 0 && c1.value == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 3. 用例 09:GVL + VAR_EXTERNAL(全局读取 LOAD_GLOBAL + 初值数据段)----
|
||||
|
||||
static bool test_case09() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
CHECK(compile_case("09_gvl_external", &img, &err));
|
||||
|
||||
const compiler::StbView v = compiler::StbView::from(img);
|
||||
CHECK(v.ok());
|
||||
CHECK(v.n_globals() == 1);
|
||||
CHECK(v.data_len() == 8); // 8 字节定宽槽(不含 SHA 尾)
|
||||
CHECK(v.data_bytes()[0] == 5); // G1 初值 5(小端低位)
|
||||
CHECK(v.data_bytes()[1] == 0);
|
||||
|
||||
const compiler::StbView::FuncRow r = v.func_row(0);
|
||||
CHECK(r.nregs == 9); // r8 起:仅局部 x
|
||||
CHECK(r.code_len == 2);
|
||||
|
||||
char buf[64];
|
||||
const uint8_t* base = v.code_bytes();
|
||||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 0") == 0); // x := G1(槽 0 偏移 0)
|
||||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[1], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "RET") == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 4. io 绑定:LOAD_I / STORE_Q ----
|
||||
|
||||
static bool test_io_ops() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
const char* io_extra =
|
||||
"[[io.input]]\nvar = \"I0_0\"\nchannel = 0\nbit = 0\n"
|
||||
"[[io.output]]\nvar = \"Q0_0\"\nchannel = 0\nbit = 0\n";
|
||||
const char* globals_st =
|
||||
"VAR_GLOBAL\n I0_0 : BOOL;\n Q0_0 : BOOL;\nEND_VAR\n";
|
||||
const char* main_st =
|
||||
"PROGRAM MAIN\nVAR\n q : BOOL;\nEND_VAR\n"
|
||||
" q := I0_0;\n"
|
||||
" Q0_0 := q;\n"
|
||||
"END_PROGRAM\n";
|
||||
CHECK(compile_src("io", io_extra, globals_st, main_st, &img, &err));
|
||||
|
||||
const compiler::StbView v = compiler::StbView::from(img);
|
||||
CHECK(v.ok());
|
||||
CHECK(v.n_globals() == 2);
|
||||
|
||||
const compiler::StbView::FuncRow r = v.func_row(0);
|
||||
CHECK(r.nregs == 10); // r8 起:q + 1 临时
|
||||
|
||||
char buf[64];
|
||||
const uint8_t* base = v.code_bytes();
|
||||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_I r8, 0") == 0); // q := I0_0(io.input → LOAD_I)
|
||||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[1], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "MOVE r9, r8") == 0); // 临时 ← q
|
||||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[2], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_Q r9, 1") == 0); // Q0_0 := 临时(io.output → STORE_Q)
|
||||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[3], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "RET") == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 5. 混合类型布局:BOOL@0 INT@2 TIME@8,初值入数据段 ----
|
||||
|
||||
static bool test_layout() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
const char* globals_st =
|
||||
"VAR_GLOBAL\n"
|
||||
" B : BOOL;\n"
|
||||
" I : INT := 300;\n"
|
||||
" T : TIME := T#10ms;\n"
|
||||
"END_VAR\n";
|
||||
const char* main_st =
|
||||
"PROGRAM MAIN\nVAR\n x : INT;\nEND_VAR\n"
|
||||
" x := I;\n"
|
||||
"END_PROGRAM\n";
|
||||
CHECK(compile_src("layout", "", globals_st, main_st, &img, &err));
|
||||
|
||||
const compiler::StbView v = compiler::StbView::from(img);
|
||||
CHECK(v.ok());
|
||||
CHECK(v.n_globals() == 3);
|
||||
CHECK(v.data_len() == 24); // 3 槽 × 8 字节定宽
|
||||
CHECK(v.data_bytes()[0] == 0); // B 无初值
|
||||
CHECK(v.data_bytes()[8] == 0x2C && v.data_bytes()[9] == 0x01); // I = 300(槽 1)
|
||||
CHECK(v.data_bytes()[16] == 10); // T = 10ms(槽 2)
|
||||
|
||||
char buf[64];
|
||||
isa::disasm(reinterpret_cast<const uint32_t*>(v.code_bytes())[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 1") == 0); // x := I(槽 1)
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 6. 写 io.input → codegen error ----
|
||||
|
||||
static bool test_write_input() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
const char* io_extra =
|
||||
"[[io.input]]\nvar = \"I0_0\"\nchannel = 0\nbit = 0\n";
|
||||
const char* globals_st =
|
||||
"VAR_GLOBAL\n I0_0 : BOOL;\nEND_VAR\n";
|
||||
const char* main_st =
|
||||
"PROGRAM MAIN\n I0_0 := TRUE;\nEND_PROGRAM\n";
|
||||
if (compile_src("winput", io_extra, globals_st, main_st, &img, &err)) {
|
||||
std::printf("FAIL write_input: codegen passed\n");
|
||||
return false;
|
||||
}
|
||||
CHECK(err.find("codegen error") == 0);
|
||||
CHECK(err.find("cannot write to input") != std::string::npos);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 7. 用例 03:短路 AND / OR 必须编跳转 ----
|
||||
|
||||
static bool test_case03() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
CHECK(compile_case("03_short_circuit", &img, &err));
|
||||
|
||||
const compiler::StbView v = compiler::StbView::from(img);
|
||||
CHECK(v.ok());
|
||||
const compiler::StbView::FuncRow r = v.func_row(0);
|
||||
CHECK(r.nregs == 12); // r8 起:a b x + 1 临时
|
||||
CHECK(r.code_len == 9);
|
||||
|
||||
char buf[64];
|
||||
const uint8_t* base = v.code_bytes();
|
||||
const uint32_t* ins = reinterpret_cast<const uint32_t*>(base);
|
||||
// x := a AND b:MOVE r2,r0 / JF r2,+2 / MOVE r3,r1 / MOVE r2,r3
|
||||
isa::disasm(ins[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "MOVE r10, r8") == 0);
|
||||
isa::disasm(ins[1], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "JF r10, +2") == 0);
|
||||
isa::disasm(ins[2], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "MOVE r11, r9") == 0);
|
||||
isa::disasm(ins[3], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "MOVE r10, r11") == 0);
|
||||
// x := a OR b:MOVE r2,r0 / JT r2,+2 / MOVE r3,r1 / MOVE r2,r3
|
||||
isa::disasm(ins[4], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "MOVE r10, r8") == 0);
|
||||
isa::disasm(ins[5], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "JT r10, +2") == 0);
|
||||
isa::disasm(ins[6], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "MOVE r11, r9") == 0);
|
||||
isa::disasm(ins[7], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "MOVE r10, r11") == 0);
|
||||
isa::disasm(ins[8], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "RET") == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 8. NOT ----
|
||||
|
||||
static bool test_not() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
const char* main_st =
|
||||
"PROGRAM MAIN\nVAR\n a, x : BOOL;\nEND_VAR\n"
|
||||
" x := NOT a;\n"
|
||||
"END_PROGRAM\n";
|
||||
CHECK(compile_src("not", "", "", main_st, &img, &err));
|
||||
|
||||
const compiler::StbView v = compiler::StbView::from(img);
|
||||
CHECK(v.ok());
|
||||
const compiler::StbView::FuncRow r = v.func_row(0);
|
||||
CHECK(r.nregs == 11); // r8 起:a x + 1 临时
|
||||
CHECK(r.code_len == 3);
|
||||
|
||||
char buf[64];
|
||||
const uint8_t* base = v.code_bytes();
|
||||
const uint32_t* ins = reinterpret_cast<const uint32_t*>(base);
|
||||
isa::disasm(ins[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "MOVE r10, r8") == 0); // 临时 ← a
|
||||
isa::disasm(ins[1], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "NOT r9, r10") == 0); // x := NOT 临时
|
||||
isa::disasm(ins[2], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "RET") == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 9. 用例 04:IF / ELSIF / ELSE(CMP + JF + JMP 回填)----
|
||||
|
||||
static bool test_case04() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
CHECK(compile_case("04_if_elsif_else", &img, &err));
|
||||
|
||||
const compiler::StbView v = compiler::StbView::from(img);
|
||||
CHECK(v.ok());
|
||||
const compiler::StbView::FuncRow r = v.func_row(0);
|
||||
CHECK(r.nregs == 13); // r8 起:sel out + 3 临时
|
||||
CHECK(r.code_len == 14);
|
||||
|
||||
char buf[64];
|
||||
const uint8_t* base = v.code_bytes();
|
||||
const uint32_t* ins = reinterpret_cast<const uint32_t*>(base);
|
||||
// IF sel = 0 THEN:sel→r3、0→r4、CMP_EQ r2(常量序:0、10、1、20、30)
|
||||
isa::disasm(ins[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "MOVE r11, r8") == 0);
|
||||
isa::disasm(ins[1], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r12, 0") == 0);
|
||||
isa::disasm(ins[2], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "CMP_EQ r10, r11, r12") == 0);
|
||||
isa::disasm(ins[3], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "JF r10, +2") == 0); // 假 → elsif([6])
|
||||
isa::disasm(ins[4], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r9, 1") == 0); // out := 10(常量 1)
|
||||
isa::disasm(ins[5], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "JMP +7") == 0); // → end([13])
|
||||
// ELSIF sel = 1
|
||||
isa::disasm(ins[6], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "MOVE r11, r8") == 0);
|
||||
isa::disasm(ins[7], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r12, 2") == 0);
|
||||
isa::disasm(ins[8], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "CMP_EQ r10, r11, r12") == 0);
|
||||
isa::disasm(ins[9], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "JF r10, +2") == 0); // 假 → else([12])
|
||||
isa::disasm(ins[10], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r9, 3") == 0);
|
||||
isa::disasm(ins[11], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "JMP +1") == 0); // → end([13])
|
||||
// ELSE
|
||||
isa::disasm(ins[12], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r9, 4") == 0);
|
||||
isa::disasm(ins[13], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "RET") == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 10. 用例 05:WHILE 回环(JF 出口 + JMP 回填)----
|
||||
|
||||
static bool test_case05() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
CHECK(compile_case("05_while_normal", &img, &err));
|
||||
|
||||
const compiler::StbView v = compiler::StbView::from(img);
|
||||
CHECK(v.ok());
|
||||
const compiler::StbView::FuncRow r = v.func_row(0);
|
||||
CHECK(r.nregs == 12); // r8 起:n + 3 临时(条件结果 r9 + 两操作数)
|
||||
CHECK(r.code_len == 10);
|
||||
|
||||
char buf[64];
|
||||
const uint8_t* base = v.code_bytes();
|
||||
const uint32_t* ins = reinterpret_cast<const uint32_t*>(base);
|
||||
isa::disasm(ins[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r8, 0") == 0); // n := 0
|
||||
// L_loop [1]:n < 10 → r9
|
||||
isa::disasm(ins[1], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "MOVE r10, r8") == 0);
|
||||
isa::disasm(ins[2], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r11, 1") == 0); // 常量 1 = 10
|
||||
isa::disasm(ins[3], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "CMP_LT r9, r10, r11") == 0);
|
||||
isa::disasm(ins[4], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "JF r9, +4") == 0); // 假 → RET([9])
|
||||
// n := n + 1(体语句临时复用 r9/r10)
|
||||
isa::disasm(ins[5], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "MOVE r9, r8") == 0);
|
||||
isa::disasm(ins[6], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r10, 2") == 0); // 常量 2 = 1
|
||||
isa::disasm(ins[7], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "ADD r8, r9, r10") == 0);
|
||||
isa::disasm(ins[8], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "JMP -8") == 0); // 回 L_loop([1])
|
||||
isa::disasm(ins[9], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "RET") == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 11. 用例 07:四则 + 比较 ----
|
||||
|
||||
static bool test_case07() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
CHECK(compile_case("07_int_arith", &img, &err));
|
||||
|
||||
const compiler::StbView v = compiler::StbView::from(img);
|
||||
CHECK(v.ok());
|
||||
const compiler::StbView::FuncRow r = v.func_row(0);
|
||||
CHECK(r.nregs == 14); // r8 起:a b c eq + 2 临时
|
||||
|
||||
char buf[64];
|
||||
const uint8_t* base = v.code_bytes();
|
||||
const uint32_t* ins = reinterpret_cast<const uint32_t*>(base);
|
||||
bool saw_mul = false, saw_div = false, saw_sub = false, saw_gt = false;
|
||||
for (uint32_t i = 0; i < r.code_len; ++i) {
|
||||
isa::disasm(ins[i], buf, sizeof buf);
|
||||
if (std::strstr(buf, "MUL r8, ")) saw_mul = true;
|
||||
if (std::strstr(buf, "DIV r9, ")) saw_div = true;
|
||||
if (std::strstr(buf, "SUB r10, ")) saw_sub = true;
|
||||
if (std::strstr(buf, "CMP_GT r11, ")) saw_gt = true;
|
||||
}
|
||||
CHECK(saw_mul && saw_div && saw_sub && saw_gt);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 12. 用例 08:TIME 字面量进常量表 ----
|
||||
|
||||
static bool test_case08() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
CHECK(compile_case("08_time_literal", &img, &err));
|
||||
|
||||
const compiler::StbView v = compiler::StbView::from(img);
|
||||
CHECK(v.ok());
|
||||
CHECK(v.n_consts() == 2);
|
||||
const compiler::ConstEntry c0 = v.const_entry(0);
|
||||
CHECK(c0.tag == 2 && c0.value == 10); // T#10ms
|
||||
const compiler::ConstEntry c1 = v.const_entry(1);
|
||||
CHECK(c1.tag == 2 && c1.value == 1250); // T#1s250ms
|
||||
|
||||
const compiler::StbView::FuncRow r = v.func_row(0);
|
||||
CHECK(r.nregs == 10); // r8 起
|
||||
char buf[64];
|
||||
const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes());
|
||||
isa::disasm(ins[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r8, 0") == 0);
|
||||
isa::disasm(ins[1], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r9, 1") == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 13. 用例 13:FUNCTION + CALL(调用约定 r0 结果 / r1.. 参数)----
|
||||
|
||||
static bool test_case13() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
CHECK(compile_case("13_function_call", &img, &err));
|
||||
|
||||
const compiler::StbView v = compiler::StbView::from(img);
|
||||
CHECK(v.ok());
|
||||
CHECK(v.n_funcs() == 2);
|
||||
CHECK(v.entry_fn_id() == 1); // MAIN 是第二个 POU
|
||||
|
||||
char buf[64];
|
||||
const uint8_t* base = v.code_bytes();
|
||||
// Add(fn_id 0):结果 r0、输入 r1(a) r2(b)、临时 r8/r9
|
||||
const compiler::StbView::FuncRow fa = v.func_row(0);
|
||||
CHECK(fa.nregs == 10);
|
||||
CHECK(fa.code_len == 4);
|
||||
const uint32_t* ia = reinterpret_cast<const uint32_t*>(base);
|
||||
isa::disasm(ia[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "MOVE r8, r1") == 0); // t ← a
|
||||
isa::disasm(ia[1], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "MOVE r9, r2") == 0); // t ← b
|
||||
isa::disasm(ia[2], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "ADD r0, r8, r9") == 0); // 结果 r0
|
||||
isa::disasm(ia[3], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "RET") == 0);
|
||||
|
||||
// MAIN(fn_id 1):x 在 r8,实参临时 r9/r10
|
||||
const compiler::StbView::FuncRow fm = v.func_row(1);
|
||||
CHECK(fm.nregs == 11);
|
||||
CHECK(fm.code_len == 7);
|
||||
const uint32_t* im = reinterpret_cast<const uint32_t*>(base) + fm.code_offset / 4;
|
||||
isa::disasm(im[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r9, 0") == 0); // 3
|
||||
isa::disasm(im[1], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r10, 1") == 0); // 4
|
||||
isa::disasm(im[2], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "MOVE r1, r9") == 0); // 参数 1 ← 3
|
||||
isa::disasm(im[3], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "MOVE r2, r10") == 0); // 参数 2 ← 4
|
||||
isa::disasm(im[4], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "CALL 0") == 0);
|
||||
isa::disasm(im[5], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "MOVE r8, r0") == 0); // x ← 结果
|
||||
isa::disasm(im[6], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "RET") == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 14. 用例 15:用户 FB 内联展开 ----
|
||||
|
||||
static bool test_case15() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
CHECK(compile_case("15_fb_instance", &img, &err));
|
||||
|
||||
const compiler::StbView v = compiler::StbView::from(img);
|
||||
CHECK(v.ok());
|
||||
CHECK(v.n_funcs() == 2); // FB 占位 + MAIN
|
||||
CHECK(v.entry_fn_id() == 1);
|
||||
CHECK(v.data_len() == 24); // 实例 3 槽 × 8 字节定宽
|
||||
|
||||
const compiler::StbView::FuncRow fm = v.func_row(1);
|
||||
CHECK(fm.nregs == 12);
|
||||
CHECK(fm.code_len == 12);
|
||||
const uint32_t* im = reinterpret_cast<const uint32_t*>(v.code_bytes()) + fm.code_offset / 4;
|
||||
char buf[64];
|
||||
// 实参写字段:start := TRUE / stop := FALSE
|
||||
isa::disasm(im[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r9, 0") == 0);
|
||||
isa::disasm(im[1], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 0") == 0);
|
||||
isa::disasm(im[2], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r10, 1") == 0);
|
||||
isa::disasm(im[3], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_GLOBAL r10, 1") == 0);
|
||||
// 内联体:Q := start AND NOT stop(短路跳转)
|
||||
isa::disasm(im[4], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 0") == 0);
|
||||
isa::disasm(im[5], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "JF r9, +3") == 0);
|
||||
isa::disasm(im[9], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 2") == 0); // Q 字段
|
||||
// q := starter.Q
|
||||
isa::disasm(im[10], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 2") == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 15. 用例 17:TON(CAL_TON + 字段偏移)----
|
||||
|
||||
static bool test_case17() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
CHECK(compile_case("17_ton", &img, &err));
|
||||
|
||||
const compiler::StbView v = compiler::StbView::from(img);
|
||||
CHECK(v.ok());
|
||||
CHECK(v.data_len() == 32); // in@0 pt@8 q@16 et@24
|
||||
const compiler::StbView::FuncRow r = v.func_row(0);
|
||||
CHECK(r.nregs == 11);
|
||||
const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes());
|
||||
char buf[64];
|
||||
isa::disasm(ins[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r9, 0") == 0);
|
||||
isa::disasm(ins[1], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 0") == 0); // in
|
||||
isa::disasm(ins[2], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOADK r10, 1") == 0);
|
||||
isa::disasm(ins[3], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_GLOBAL r10, 1") == 0); // pt(槽 1)
|
||||
isa::disasm(ins[4], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "CAL_TON 0") == 0);
|
||||
isa::disasm(ins[5], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 2") == 0); // t.Q(槽 2)
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 16. 用例 18:TOF / CTU ----
|
||||
|
||||
static bool test_case18() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
CHECK(compile_case("18_tof_ctu", &img, &err));
|
||||
|
||||
const compiler::StbView v = compiler::StbView::from(img);
|
||||
CHECK(v.ok());
|
||||
CHECK(v.data_len() == 72); // tf 4 槽 + c 5 槽 = 9 槽 × 8
|
||||
const compiler::StbView::FuncRow r = v.func_row(0);
|
||||
CHECK(r.nregs == 14);
|
||||
const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes());
|
||||
char buf[64];
|
||||
isa::disasm(ins[4], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "CAL_TOF 0") == 0);
|
||||
isa::disasm(ins[12], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "CAL_CTU 4") == 0);
|
||||
isa::disasm(ins[13], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 7") == 0); // c.Q(槽 7)
|
||||
isa::disasm(ins[14], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r10, 8") == 0); // c.CV(槽 8)
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 17. line1 全链路(MAIN + Motor FB + I/O + 全局)----
|
||||
|
||||
static bool test_line1() {
|
||||
std::vector<uint8_t> img;
|
||||
std::string err;
|
||||
using namespace compiler;
|
||||
const std::string toml = std::string(REPO_ROOT) + "/examples/line1/project.toml";
|
||||
Project p;
|
||||
CHECK(parse_project(toml, &p, &err));
|
||||
std::vector<SourceUnit> units;
|
||||
for (const std::string& f : compile_files(p)) {
|
||||
SourceUnit u;
|
||||
CHECK(load_unit(p.base_dir + "/" + f, &u, &err));
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
LinkResult link;
|
||||
CHECK(link_project(p, units, &link, &err));
|
||||
CHECK(check_project(p, units, link, &err));
|
||||
compiler::MachineConfig cfg;
|
||||
CHECK(load_cfg(&cfg));
|
||||
CHECK(codegen_project(p, units, link, cfg, &img, &err));
|
||||
|
||||
const compiler::StbView v = compiler::StbView::from(img);
|
||||
CHECK(v.ok());
|
||||
CHECK(v.n_globals() == 4);
|
||||
CHECK(v.n_funcs() == 2);
|
||||
CHECK(v.entry_fn_id() == 1);
|
||||
CHECK(v.data_len() == 56); // 7 槽 × 8 字节定宽
|
||||
|
||||
const compiler::StbView::FuncRow fm = v.func_row(1);
|
||||
CHECK(fm.nregs == 13);
|
||||
CHECK(fm.code_len == 17);
|
||||
const uint32_t* im = reinterpret_cast<const uint32_t*>(v.code_bytes()) + fm.code_offset / 4;
|
||||
char buf[64];
|
||||
isa::disasm(im[0], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_I r8, 1") == 0); // I0_0(io.input)
|
||||
isa::disasm(im[1], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_GLOBAL r8, 4") == 0); // starter.start(槽 4)
|
||||
isa::disasm(im[2], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 2") == 0); // I0_1
|
||||
isa::disasm(im[3], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 5") == 0); // starter.stop(槽 5)
|
||||
isa::disasm(im[13], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_GLOBAL r8, 6") == 0); // starter.Q(槽 6)
|
||||
isa::disasm(im[14], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 6") == 0); // starter.Q 读回
|
||||
isa::disasm(im[15], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "STORE_Q r8, 3") == 0); // Q0_0(io.output)
|
||||
isa::disasm(im[16], buf, sizeof buf);
|
||||
CHECK(std::strcmp(buf, "RET") == 0);
|
||||
|
||||
// 12.13 写侧自检:型号标识 + SHA-256(compiler::StbView)
|
||||
const compiler::StbView self = compiler::StbView::from(img);
|
||||
CHECK(self.ok());
|
||||
CHECK(self.model_matches("STATOR", 1));
|
||||
CHECK(self.sha_ok());
|
||||
std::vector<uint8_t> tampered = img;
|
||||
tampered[100] ^= 0x01; // 篡改代码段一字节
|
||||
const compiler::StbView bad = compiler::StbView::from(tampered);
|
||||
CHECK(bad.ok() && !bad.sha_ok()); // 结构可解析但 SHA 校验失败
|
||||
return true;
|
||||
}
|
||||
|
||||
int main() {
|
||||
if (!test_case01()) return 1;
|
||||
if (!test_case02()) return 1;
|
||||
if (!test_case09()) return 1;
|
||||
if (!test_io_ops()) return 1;
|
||||
if (!test_layout()) return 1;
|
||||
if (!test_write_input()) return 1;
|
||||
if (!test_case03()) return 1;
|
||||
if (!test_not()) return 1;
|
||||
if (!test_case04()) return 1;
|
||||
if (!test_case05()) return 1;
|
||||
if (!test_case07()) return 1;
|
||||
if (!test_case08()) return 1;
|
||||
if (!test_case13()) return 1;
|
||||
if (!test_case15()) return 1;
|
||||
if (!test_case17()) return 1;
|
||||
if (!test_case18()) return 1;
|
||||
if (!test_line1()) return 1;
|
||||
std::printf("codegen_test: %d checks passed\n", g_checks);
|
||||
return 0;
|
||||
}
|
||||
+7
-107
@@ -12,7 +12,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "isa/Encode.h"
|
||||
#include "isa/Image.h"
|
||||
#include "isa/Instr.h"
|
||||
#include "isa/Op.h"
|
||||
#include "isa/Types.h"
|
||||
@@ -54,9 +53,13 @@ static bool test_sat() {
|
||||
static bool test_op() {
|
||||
using namespace isa;
|
||||
CHECK(static_cast<int>(Op::MOVE) == 0);
|
||||
CHECK(static_cast<int>(Op::RET) == 28);
|
||||
CHECK(kOpCount == 29);
|
||||
CHECK(std::strcmp(mnemonic(Op::CAL_CTU), "CAL_CTU") == 0);
|
||||
CHECK(static_cast<int>(Op::CAL_TON) == 24);
|
||||
CHECK(static_cast<int>(Op::CAL_F_TRIG) == 31); // 8 个 CAL_* 连续 24..31
|
||||
CHECK(static_cast<int>(Op::CALL) == 32);
|
||||
CHECK(static_cast<int>(Op::RET) == 33);
|
||||
CHECK(kOpCount == 34);
|
||||
CHECK(std::strcmp(mnemonic(Op::CAL_CTUD), "CAL_CTUD") == 0);
|
||||
CHECK(std::strcmp(mnemonic(Op::CAL_F_TRIG), "CAL_F_TRIG") == 0);
|
||||
CHECK(std::strcmp(mnemonic(static_cast<Op>(255)), "???") == 0);
|
||||
return true;
|
||||
}
|
||||
@@ -123,114 +126,11 @@ static bool test_encode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 5. FNV-1a 64 ----
|
||||
|
||||
static bool test_fnv() {
|
||||
using namespace isa;
|
||||
CHECK(fnv1a64(0, 0) == kFnvBasis);
|
||||
const uint8_t a1[] = {'a'};
|
||||
CHECK(fnv1a64(a1, 1) == 0xaf63dc4c8601ec8cull);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 6. 映像:write_ret_main → ImageView ----
|
||||
|
||||
static bool test_image() {
|
||||
using namespace isa;
|
||||
const uint64_t hash = 0x123456789abcdef0ull;
|
||||
std::vector<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() {
|
||||
if (!test_sat()) return 1;
|
||||
if (!test_op()) return 1;
|
||||
if (!test_instr()) return 1;
|
||||
if (!test_encode()) return 1;
|
||||
if (!test_fnv()) return 1;
|
||||
if (!test_image()) return 1;
|
||||
if (!test_files()) return 1;
|
||||
std::printf("isa_test: %d checks passed\n", g_checks);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
// BOOL:base=uint8 → 1 字节、无符号、非浮点、tag 0、range [0,1]
|
||||
const compiler::TypeMeta b = compiler::type_meta(cfg, "BOOL");
|
||||
CHECK(b && b.width() == 1 && !b.is_signed() && !b.is_float() && b.tag() == 0);
|
||||
CHECK(b.has_range() && b.value_in_range(0) && b.value_in_range(1));
|
||||
CHECK(!b.value_in_range(2) && !b.value_in_range(-1));
|
||||
// 大小写不敏感(Linker type_name 是小写)
|
||||
const compiler::TypeMeta b2 = compiler::type_meta(cfg, "bool");
|
||||
CHECK(b2 && b2.tag() == 0 && b2.width() == 1);
|
||||
|
||||
// INT:int16 → 2 字节、有符号、tag 1、无 range
|
||||
const compiler::TypeMeta i = compiler::type_meta(cfg, "int");
|
||||
CHECK(i && i.width() == 2 && i.is_signed() && !i.is_float() && i.tag() == 1);
|
||||
CHECK(!i.has_range() || i.value_in_range(30000));
|
||||
|
||||
// TIME:int64 → 8 字节、有符号、tag 2
|
||||
const compiler::TypeMeta t = compiler::type_meta(cfg, "TIME");
|
||||
CHECK(t && t.width() == 8 && t.is_signed() && !t.is_float() && t.tag() == 2);
|
||||
|
||||
// 未定义类型 → 空
|
||||
CHECK(!compiler::type_meta(cfg, "REAL"));
|
||||
CHECK(!compiler::type_meta(cfg, ""));
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 5. SHA-256 已知向量 + 型号标识 ----
|
||||
|
||||
static bool test_sha256() {
|
||||
// 标准测试向量
|
||||
{
|
||||
uint8_t out[compiler::kSha256Size];
|
||||
compiler::sha256(nullptr, 0, out);
|
||||
const uint8_t want[32] = {0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14,
|
||||
0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
|
||||
0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c,
|
||||
0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55};
|
||||
CHECK(std::memcmp(out, want, 32) == 0);
|
||||
}
|
||||
{
|
||||
const uint8_t abc[] = {'a', 'b', 'c'};
|
||||
uint8_t out[compiler::kSha256Size];
|
||||
compiler::sha256(abc, 3, out);
|
||||
const uint8_t want[32] = {0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea,
|
||||
0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
|
||||
0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c,
|
||||
0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad};
|
||||
CHECK(std::memcmp(out, want, 32) == 0);
|
||||
}
|
||||
// 型号标识:STATOR + 1 → "STATOR1" 补 '\0'
|
||||
char mid[compiler::kModelIdSize];
|
||||
compiler::fill_model_id("STATOR", 1, mid);
|
||||
CHECK(std::strncmp(mid, "STATOR1", 7) == 0);
|
||||
CHECK(mid[7] == '\0');
|
||||
CHECK(mid[31] == '\0');
|
||||
return true;
|
||||
}
|
||||
|
||||
int main() {
|
||||
if (!test_positive()) return 1;
|
||||
if (!test_negative()) return 1;
|
||||
if (!test_positive_min()) return 1;
|
||||
if (!test_type_meta()) return 1;
|
||||
if (!test_sha256()) return 1;
|
||||
std::printf("machine_test: %d checks passed\n", g_checks);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* @file typecheck_test.cpp
|
||||
* @brief 类型检查测试:用例 14 + 负例 + 正例
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "compiler/Linker.h"
|
||||
#include "compiler/Project.h"
|
||||
#include "compiler/Typecheck.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)
|
||||
|
||||
// 从用例目录加载、链接、类型检查
|
||||
static bool check_case(const char* dir, compiler::LinkResult* r, std::string* err) {
|
||||
using namespace compiler;
|
||||
const std::string toml = std::string(REPO_ROOT) + "/tests/cases/" + dir + "/project.toml";
|
||||
Project p;
|
||||
if (!parse_project(toml, &p, err)) {
|
||||
return false;
|
||||
}
|
||||
std::vector<SourceUnit> units;
|
||||
for (const std::string& f : compile_files(p)) {
|
||||
SourceUnit u;
|
||||
if (!load_unit(p.base_dir + "/" + f, &u, err)) {
|
||||
return false;
|
||||
}
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
if (!link_project(p, units, r, err)) {
|
||||
return false;
|
||||
}
|
||||
return check_project(p, units, *r, err);
|
||||
}
|
||||
|
||||
// 从内联源码构造工程(写临时目录),加载、链接、类型检查
|
||||
static bool check_src(const char* src_name, const char* st_content,
|
||||
std::string* err) {
|
||||
using namespace compiler;
|
||||
const std::string dir = std::string(REPO_ROOT) + "/build/tc_tmp_" + src_name;
|
||||
std::filesystem::create_directories(dir);
|
||||
const std::string toml_path = dir + "/project.toml";
|
||||
const std::string st_path = dir + "/main.st";
|
||||
std::FILE* f = std::fopen(toml_path.c_str(), "w");
|
||||
std::fprintf(f, "[project]\nname = \"tc\"\nentry = \"program MAIN\"\n"
|
||||
"cycle_limit = 1000\ndt_ms = 10\n"
|
||||
"[files]\nst = [\"main.st\"]\n");
|
||||
std::fclose(f);
|
||||
f = std::fopen(st_path.c_str(), "w");
|
||||
std::fputs(st_content, f);
|
||||
std::fclose(f);
|
||||
|
||||
Project p;
|
||||
if (!parse_project(toml_path, &p, err)) {
|
||||
return false;
|
||||
}
|
||||
std::vector<SourceUnit> units;
|
||||
for (const std::string& file : compile_files(p)) {
|
||||
SourceUnit u;
|
||||
if (!load_unit(p.base_dir + "/" + file, &u, err)) {
|
||||
return false;
|
||||
}
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
LinkResult r;
|
||||
if (!link_project(p, units, &r, err)) {
|
||||
return false;
|
||||
}
|
||||
return check_project(p, units, r, err);
|
||||
}
|
||||
|
||||
static bool expect_type_err(const char* src_name, const char* st_content,
|
||||
const char* keyword) {
|
||||
std::string err;
|
||||
if (check_src(src_name, st_content, &err)) {
|
||||
std::printf("FAIL %s: typecheck passed\n", src_name);
|
||||
return false;
|
||||
}
|
||||
if (err.find("type error") != 0) {
|
||||
std::printf("FAIL %s: want 'type error', got '%s'\n", src_name, err.c_str());
|
||||
return false;
|
||||
}
|
||||
if (err.find(keyword) == std::string::npos) {
|
||||
std::printf("FAIL %s: want '%s', got '%s'\n", src_name, keyword, err.c_str());
|
||||
return false;
|
||||
}
|
||||
++g_checks;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 1. 用例 14:FUNCTION 写全局(经 VAR_EXTERNAL)----
|
||||
|
||||
static bool test_case14() {
|
||||
std::string err;
|
||||
compiler::LinkResult r;
|
||||
if (check_case("14_function_write_global", &r, &err)) {
|
||||
std::printf("FAIL case14: typecheck passed\n");
|
||||
return false;
|
||||
}
|
||||
CHECK(err.find("type error") == 0);
|
||||
CHECK(err.find("function cannot write global") != std::string::npos);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 2. 负例 ----
|
||||
|
||||
static bool test_negative() {
|
||||
// AND 吃 INT
|
||||
if (!expect_type_err("and_int",
|
||||
"PROGRAM MAIN\nVAR\n a, b : INT;\n x : BOOL;\nEND_VAR\n"
|
||||
" x := a AND b;\nEND_PROGRAM\n",
|
||||
"AND operands must be BOOL")) {
|
||||
return false;
|
||||
}
|
||||
// 赋值类型不匹配(INT 赋给 BOOL)
|
||||
if (!expect_type_err("assign_mismatch",
|
||||
"PROGRAM MAIN\nVAR\n b : BOOL;\nEND_VAR\n"
|
||||
" b := 5;\nEND_PROGRAM\n",
|
||||
"type mismatch in assignment to 'b'")) {
|
||||
return false;
|
||||
}
|
||||
// INT 与 TIME 混用算术
|
||||
if (!expect_type_err("int_time",
|
||||
"PROGRAM MAIN\nVAR\n x : INT;\n t : TIME;\n y : INT;\nEND_VAR\n"
|
||||
" y := x + t;\nEND_PROGRAM\n",
|
||||
"TIME has no arithmetic")) {
|
||||
return false;
|
||||
}
|
||||
// NOT 吃 INT
|
||||
if (!expect_type_err("not_int",
|
||||
"PROGRAM MAIN\nVAR\n a : INT;\n b : BOOL;\nEND_VAR\n"
|
||||
" b := NOT a;\nEND_PROGRAM\n",
|
||||
"NOT operand must be BOOL")) {
|
||||
return false;
|
||||
}
|
||||
// 比较异型(INT = TIME)
|
||||
if (!expect_type_err("cmp_mismatch",
|
||||
"PROGRAM MAIN\nVAR\n x : INT;\n t : TIME;\n b : BOOL;\nEND_VAR\n"
|
||||
" b := x = t;\nEND_PROGRAM\n",
|
||||
"comparison of mismatched types")) {
|
||||
return false;
|
||||
}
|
||||
// FB 输入类型不匹配(TON pt 期望 TIME,给 TRUE)
|
||||
if (!expect_type_err("fb_arg",
|
||||
"PROGRAM MAIN\nVAR\n t : TON;\n q : BOOL;\nEND_VAR\n"
|
||||
" t(in := TRUE, pt := TRUE);\n"
|
||||
" q := t.Q;\nEND_PROGRAM\n",
|
||||
"FB input 'pt' expects TIME")) {
|
||||
return false;
|
||||
}
|
||||
// IF 条件非 BOOL
|
||||
if (!expect_type_err("if_cond",
|
||||
"PROGRAM MAIN\nVAR\n n : INT;\n x : INT;\nEND_VAR\n"
|
||||
" IF n THEN\n x := 1;\n END_IF\nEND_PROGRAM\n",
|
||||
"condition must be BOOL")) {
|
||||
return false;
|
||||
}
|
||||
// FB 实例当值用
|
||||
if (!expect_type_err("fb_value",
|
||||
"PROGRAM MAIN\nVAR\n t : TON;\n b : BOOL;\nEND_VAR\n"
|
||||
" b := t;\nEND_PROGRAM\n",
|
||||
"cannot be used as a value")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 3. 正例 ----
|
||||
|
||||
static bool test_positive() {
|
||||
std::string err;
|
||||
compiler::LinkResult r;
|
||||
CHECK(check_case("01_empty_main", &r, &err));
|
||||
CHECK(check_case("02_bool_assign", &r, &err));
|
||||
CHECK(check_case("03_short_circuit", &r, &err));
|
||||
CHECK(check_case("04_if_elsif_else", &r, &err));
|
||||
CHECK(check_case("05_while_normal", &r, &err));
|
||||
CHECK(check_case("07_int_arith", &r, &err));
|
||||
CHECK(check_case("08_time_literal", &r, &err));
|
||||
CHECK(check_case("09_gvl_external", &r, &err));
|
||||
CHECK(check_case("13_function_call", &r, &err));
|
||||
CHECK(check_case("15_fb_instance", &r, &err));
|
||||
CHECK(check_case("17_ton", &r, &err));
|
||||
CHECK(check_case("18_tof_ctu", &r, &err));
|
||||
CHECK(check_case("20_line1", &r, &err));
|
||||
return true;
|
||||
}
|
||||
|
||||
int main() {
|
||||
if (!test_case14()) return 1;
|
||||
if (!test_negative()) return 1;
|
||||
if (!test_positive()) return 1;
|
||||
std::printf("typecheck_test: %d checks passed\n", g_checks);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,513 @@
|
||||
/**
|
||||
* @file vm_test.cpp
|
||||
* @brief VM 测试(12.9):手工映像 + 编译器产物 + 定时器 + 确定性
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "compiler/Codegen.h"
|
||||
#include "compiler/Linker.h"
|
||||
#include "compiler/Project.h"
|
||||
#include "compiler/Stb.h"
|
||||
#include "compiler/Typecheck.h"
|
||||
#include "isa/Encode.h"
|
||||
#include "isa/Instr.h"
|
||||
#include "isa/Op.h"
|
||||
#include "vm/Machine.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)
|
||||
|
||||
// ---- 数据区槽访问 ----
|
||||
|
||||
static int64_t slot(const vm::Machine& m, int s) {
|
||||
int64_t v = 0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
v |= static_cast<int64_t>(m.data()[s * 8 + i]) << (8 * i);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static void wslot(vm::Machine& m, int s, int64_t v) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
m.data()[s * 8 + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 编译器产物全链路(编译 → 建机 → 跑周期)----
|
||||
|
||||
static bool make_machine(const char* dir, vm::Machine* m, std::string* err) {
|
||||
using namespace compiler;
|
||||
MachineConfig cfg;
|
||||
if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", err)) {
|
||||
return false;
|
||||
}
|
||||
const std::string toml = std::string(REPO_ROOT) + "/" + dir + "/project.toml";
|
||||
Project p;
|
||||
if (!parse_project(toml, &p, err)) {
|
||||
return false;
|
||||
}
|
||||
std::vector<SourceUnit> units;
|
||||
for (const std::string& f : compile_files(p)) {
|
||||
SourceUnit u;
|
||||
if (!load_unit(p.base_dir + "/" + f, &u, err)) {
|
||||
return false;
|
||||
}
|
||||
units.push_back(std::move(u));
|
||||
}
|
||||
LinkResult link;
|
||||
if (!link_project(p, units, &link, err)) {
|
||||
return false;
|
||||
}
|
||||
if (!check_project(p, units, link, err)) {
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> img;
|
||||
if (!codegen_project(p, units, link, cfg, &img, err)) {
|
||||
return false;
|
||||
}
|
||||
return vm::Machine::create(img, m, err);
|
||||
}
|
||||
|
||||
// ---- 手工拼映像 ----
|
||||
|
||||
struct HFunc {
|
||||
std::vector<isa::Instr> code;
|
||||
uint32_t nregs = 8;
|
||||
};
|
||||
|
||||
static std::vector<uint8_t> hand_image(const std::vector<vm::ConstEntry>& consts,
|
||||
const std::vector<HFunc>& funcs,
|
||||
uint32_t entry = 0,
|
||||
uint32_t cycle_limit = 100000,
|
||||
const std::vector<uint8_t>& data = {}) {
|
||||
size_t code_total = 0;
|
||||
for (const HFunc& f : funcs) {
|
||||
code_total += f.code.size() * 4;
|
||||
}
|
||||
const uint32_t off_const = 104;
|
||||
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_data = off_code + static_cast<uint32_t>(code_total);
|
||||
const uint32_t off_end = off_data + static_cast<uint32_t>(data.size()) + 32; // SHA 尾占位
|
||||
|
||||
std::vector<uint8_t> b(off_end, 0);
|
||||
auto put32 = [&](size_t o, uint32_t v) {
|
||||
b[o + 0] = static_cast<uint8_t>(v & 0xFFu);
|
||||
b[o + 1] = static_cast<uint8_t>((v >> 8) & 0xFFu);
|
||||
b[o + 2] = static_cast<uint8_t>((v >> 16) & 0xFFu);
|
||||
b[o + 3] = static_cast<uint8_t>((v >> 24) & 0xFFu);
|
||||
};
|
||||
auto put64 = [&](size_t o, uint64_t v) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
b[o + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFFu);
|
||||
}
|
||||
};
|
||||
put32(0, 0x43545353u);
|
||||
put32(4, 1u);
|
||||
put32(8, cycle_limit);
|
||||
put32(12, 10); // dt_ms
|
||||
put32(24, entry);
|
||||
put32(44, static_cast<uint32_t>(consts.size()));
|
||||
put32(48, static_cast<uint32_t>(funcs.size()));
|
||||
put32(52, off_const);
|
||||
put32(56, off_funcs);
|
||||
put32(60, off_code);
|
||||
put32(64, off_data);
|
||||
put32(68, off_data);
|
||||
|
||||
for (size_t i = 0; i < consts.size(); ++i) {
|
||||
const size_t o = off_const + i * 12;
|
||||
put32(o, static_cast<uint32_t>(consts[i].tag));
|
||||
put64(o + 4, consts[i].value);
|
||||
}
|
||||
size_t acc = 0;
|
||||
for (size_t i = 0; i < funcs.size(); ++i) {
|
||||
const size_t o = off_funcs + i * 12;
|
||||
put32(o, funcs[i].nregs);
|
||||
put32(o + 4, static_cast<uint32_t>(acc));
|
||||
put32(o + 8, static_cast<uint32_t>(funcs[i].code.size()));
|
||||
acc += funcs[i].code.size() * 4;
|
||||
}
|
||||
size_t c = off_code;
|
||||
for (const HFunc& f : funcs) {
|
||||
for (const isa::Instr in : f.code) {
|
||||
put32(c, in);
|
||||
c += 4;
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < data.size(); ++i) {
|
||||
b[off_data + i] = data[i];
|
||||
}
|
||||
// 型号标识(STATOR1)+ 真实 SHA-256 尾(用 compiler 实现填)
|
||||
const char* mid = "STATOR1";
|
||||
for (size_t i = 0; i < 32; ++i) {
|
||||
b[72 + i] = i < 7 ? static_cast<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;
|
||||
}
|
||||
|
||||
// ---- 1. 手工映像:标量 + 跳转 + 数据区 ----
|
||||
|
||||
static bool test_hand_scalar() {
|
||||
// r8 := 5;r9 := r8 + 3;槽 0 ← r9;槽 1 → r10;JT 跳过一条
|
||||
const std::vector<vm::ConstEntry> consts = {
|
||||
{1, 5},
|
||||
{1, 3},
|
||||
{1, 1},
|
||||
};
|
||||
const std::vector<HFunc> funcs = {{
|
||||
{
|
||||
isa::enc_imm(isa::Op::LOADK, 8, 0),
|
||||
isa::enc_rr(isa::Op::MOVE, 9, 8),
|
||||
isa::enc_imm(isa::Op::LOADK, 11, 1),
|
||||
isa::enc_rrr(isa::Op::ADD, 9, 9, 11),
|
||||
isa::enc_slot(isa::Op::STORE_GLOBAL, 9, 0),
|
||||
isa::enc_slot(isa::Op::LOAD_GLOBAL, 10, 0),
|
||||
isa::enc_jc(isa::Op::JT, 9, 1), // r9≠0 → 跳过下一条
|
||||
isa::enc_imm(isa::Op::LOADK, 10, 2),
|
||||
isa::enc_ret(),
|
||||
},
|
||||
12,
|
||||
}};
|
||||
const std::vector<uint8_t> img = hand_image(consts, funcs, 0, 100000,
|
||||
std::vector<uint8_t>(16, 0)); // 2 槽
|
||||
vm::Machine m;
|
||||
std::string err;
|
||||
CHECK(vm::Machine::create(img, &m, &err));
|
||||
CHECK(m.run_cycle() == vm::Fault::None);
|
||||
CHECK(m.reg(8) == 5);
|
||||
CHECK(m.reg(9) == 8); // 5 + 3
|
||||
CHECK(slot(m, 0) == 8); // 槽 0 收到 8
|
||||
CHECK(m.reg(10) == 8); // 槽 1 读回(JT 跳过覆盖)
|
||||
CHECK(m.cycle_count() == 8);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 2. 手工映像:CALL/RET 帧复制 ----
|
||||
|
||||
static bool test_hand_call() {
|
||||
// fn0(入口):r8=5、r2=7、实参 r1←r8;CALL 1;结果 r0 → r8
|
||||
// fn1:r0 = r1 + r2(r1/r2 来自调用约定区复制)
|
||||
const std::vector<vm::ConstEntry> consts = {
|
||||
{1, 5},
|
||||
{1, 7},
|
||||
};
|
||||
const std::vector<HFunc> funcs = {
|
||||
{{
|
||||
isa::enc_imm(isa::Op::LOADK, 8, 0),
|
||||
isa::enc_imm(isa::Op::LOADK, 2, 1),
|
||||
isa::enc_rr(isa::Op::MOVE, 1, 8),
|
||||
isa::enc_call(1),
|
||||
isa::enc_rr(isa::Op::MOVE, 8, 0),
|
||||
isa::enc_ret(),
|
||||
},
|
||||
12},
|
||||
{{
|
||||
isa::enc_rrr(isa::Op::ADD, 0, 1, 2),
|
||||
isa::enc_ret(),
|
||||
},
|
||||
10},
|
||||
};
|
||||
const std::vector<uint8_t> img = hand_image(consts, funcs);
|
||||
vm::Machine m;
|
||||
std::string err;
|
||||
CHECK(vm::Machine::create(img, &m, &err));
|
||||
CHECK(m.run_cycle() == vm::Fault::None);
|
||||
CHECK(m.reg(8) == 12); // 5 + 7
|
||||
CHECK(m.call_depth() == 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 3. 手工映像:自调用 → StackOverflow ----
|
||||
|
||||
static bool test_stack_overflow() {
|
||||
const std::vector<HFunc> funcs = {{
|
||||
{
|
||||
isa::enc_call(0),
|
||||
isa::enc_ret(),
|
||||
},
|
||||
8,
|
||||
}};
|
||||
const std::vector<uint8_t> img = hand_image({}, funcs);
|
||||
vm::Machine m;
|
||||
std::string err;
|
||||
CHECK(vm::Machine::create(img, &m, &err));
|
||||
CHECK(m.run_cycle() == vm::Fault::StackOverflow);
|
||||
CHECK(m.call_depth() == 1); // 周期结束已清栈(留 MAIN)
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 4. 手工映像:越界槽 → BadSlot ----
|
||||
|
||||
static bool test_bad_slot() {
|
||||
const std::vector<HFunc> funcs = {{
|
||||
{
|
||||
isa::enc_slot(isa::Op::STORE_GLOBAL, 8, 60000), // 数据区 0 字节
|
||||
isa::enc_ret(),
|
||||
},
|
||||
12,
|
||||
}};
|
||||
const std::vector<uint8_t> img = hand_image({}, funcs);
|
||||
vm::Machine m;
|
||||
std::string err;
|
||||
CHECK(vm::Machine::create(img, &m, &err));
|
||||
CHECK(m.run_cycle() == vm::Fault::BadSlot);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 5. 编译器产物正例 ----
|
||||
|
||||
static bool test_cases_positive() {
|
||||
std::string err;
|
||||
vm::Machine m;
|
||||
|
||||
// 01 空 MAIN
|
||||
CHECK(make_machine("tests/cases/01_empty_main", &m, &err));
|
||||
CHECK(m.run_cycle() == vm::Fault::None);
|
||||
|
||||
// 02 BOOL 赋值:a=TRUE(r8) b=FALSE(r9)
|
||||
CHECK(make_machine("tests/cases/02_bool_assign", &m, &err));
|
||||
CHECK(m.run_cycle() == vm::Fault::None);
|
||||
CHECK(m.reg(8) == 1 && m.reg(9) == 0);
|
||||
|
||||
// 03 短路:a=0 b=0 → x=0
|
||||
CHECK(make_machine("tests/cases/03_short_circuit", &m, &err));
|
||||
CHECK(m.run_cycle() == vm::Fault::None);
|
||||
CHECK(m.reg(10) == 0);
|
||||
|
||||
// 04 IF:sel=0 → out=10
|
||||
CHECK(make_machine("tests/cases/04_if_elsif_else", &m, &err));
|
||||
CHECK(m.run_cycle() == vm::Fault::None);
|
||||
CHECK(m.reg(9) == 10);
|
||||
|
||||
// 05 WHILE:n=10
|
||||
CHECK(make_machine("tests/cases/05_while_normal", &m, &err));
|
||||
CHECK(m.run_cycle() == vm::Fault::None);
|
||||
CHECK(m.reg(8) == 10);
|
||||
|
||||
// 07 算术:a=42 b=10 c=32 eq=1
|
||||
CHECK(make_machine("tests/cases/07_int_arith", &m, &err));
|
||||
CHECK(m.run_cycle() == vm::Fault::None);
|
||||
CHECK(m.reg(8) == 42 && m.reg(9) == 10 && m.reg(10) == 32 && m.reg(11) == 1);
|
||||
|
||||
// 08 TIME 字面量:t1=10 t2=1250
|
||||
CHECK(make_machine("tests/cases/08_time_literal", &m, &err));
|
||||
CHECK(m.run_cycle() == vm::Fault::None);
|
||||
CHECK(m.reg(8) == 10 && m.reg(9) == 1250);
|
||||
|
||||
// 09 GVL + EXTERNAL:x = G1 初值 5(槽 0)
|
||||
CHECK(make_machine("tests/cases/09_gvl_external", &m, &err));
|
||||
CHECK(m.run_cycle() == vm::Fault::None);
|
||||
CHECK(m.reg(8) == 5);
|
||||
|
||||
// 13 FUNCTION:x = Add(3,4) = 7
|
||||
CHECK(make_machine("tests/cases/13_function_call", &m, &err));
|
||||
CHECK(m.run_cycle() == vm::Fault::None);
|
||||
CHECK(m.reg(8) == 7);
|
||||
|
||||
// 15 FB 内联:starter(start=TRUE, stop=FALSE) → q=TRUE
|
||||
CHECK(make_machine("tests/cases/15_fb_instance", &m, &err));
|
||||
CHECK(m.run_cycle() == vm::Fault::None);
|
||||
CHECK(m.reg(8) == 1);
|
||||
|
||||
// 17 TON:dt=10、pt=30,in 恒真 → 第 3 周期 q=1(槽 2)
|
||||
CHECK(make_machine("tests/cases/17_ton", &m, &err));
|
||||
m.run_cycle();
|
||||
m.run_cycle();
|
||||
CHECK(m.run_cycle() == vm::Fault::None);
|
||||
CHECK(slot(m, 2) == 1); // t.Q
|
||||
CHECK(slot(m, 3) == 30); // t.ET
|
||||
|
||||
// 18 TOF/CTU:跑一个周期无故障
|
||||
CHECK(make_machine("tests/cases/18_tof_ctu", &m, &err));
|
||||
CHECK(m.run_cycle() == vm::Fault::None);
|
||||
|
||||
// 20 line1:I 组合 → Q0_0 真值表(槽 0=急停 1=I0_0 2=I0_1 3=Q0_0)
|
||||
CHECK(make_machine("tests/cases/20_line1", &m, &err));
|
||||
struct {
|
||||
int start, stop, es, expect;
|
||||
} table[] = {
|
||||
{1, 0, 0, 1},
|
||||
{1, 1, 0, 0},
|
||||
{0, 0, 0, 0},
|
||||
{1, 0, 1, 0},
|
||||
};
|
||||
for (const auto& t : table) {
|
||||
wslot(m, 0, t.es);
|
||||
wslot(m, 1, t.start);
|
||||
wslot(m, 2, t.stop);
|
||||
CHECK(m.run_cycle() == vm::Fault::None);
|
||||
CHECK(slot(m, 3) == t.expect);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 6. 用例 06:cycle_limit 打满 ----
|
||||
|
||||
static bool test_cycle_limit() {
|
||||
std::string err;
|
||||
vm::Machine m;
|
||||
CHECK(make_machine("tests/cases/06_while_cycle_limit", &m, &err));
|
||||
CHECK(m.run_cycle() == vm::Fault::CycleLimit);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 7. 确定性:同一 I 序列跑两遍,数据区一致 ----
|
||||
|
||||
static bool test_determinism() {
|
||||
std::string err;
|
||||
vm::Machine a, b;
|
||||
CHECK(make_machine("tests/cases/20_line1", &a, &err));
|
||||
CHECK(make_machine("tests/cases/20_line1", &b, &err));
|
||||
const int seq[4][3] = {{1, 0, 0}, {1, 1, 0}, {0, 0, 0}, {1, 0, 1}};
|
||||
for (int rep = 0; rep < 2; ++rep) {
|
||||
vm::Machine& m = rep ? b : a;
|
||||
for (const auto& s : seq) {
|
||||
wslot(m, 0, s[2]);
|
||||
wslot(m, 1, s[0]);
|
||||
wslot(m, 2, s[1]);
|
||||
CHECK(m.run_cycle() == vm::Fault::None);
|
||||
}
|
||||
}
|
||||
// 两遍结束:数据区逐字节一致(含 starter 实例状态)
|
||||
CHECK(a.data_len() == b.data_len());
|
||||
for (size_t i = 0; i < a.data_len(); ++i) {
|
||||
if (a.data()[i] != b.data()[i]) {
|
||||
std::printf("FAIL determinism byte %zu: %d vs %d\n", i, a.data()[i], b.data()[i]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
++g_checks;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 8. step 单步:停在指令边界 ----
|
||||
|
||||
static bool test_step() {
|
||||
std::string err;
|
||||
vm::Machine m;
|
||||
CHECK(make_machine("tests/cases/02_bool_assign", &m, &err));
|
||||
// 3 条指令:LOADK ×2 + RET
|
||||
CHECK(m.step());
|
||||
CHECK(m.cycle_count() == 1 && m.reg(8) == 1);
|
||||
CHECK(m.step());
|
||||
CHECK(m.cycle_count() == 2 && m.reg(9) == 0);
|
||||
CHECK(!m.step()); // RET → 周期结束
|
||||
CHECK(m.cycle_count() == 3);
|
||||
CHECK(!m.step()); // 结束后不再执行
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 9. 手工映像:坏常量 id → BadConst ----
|
||||
|
||||
static bool test_bad_const() {
|
||||
const std::vector<HFunc> funcs = {{
|
||||
{
|
||||
isa::enc_imm(isa::Op::LOADK, 8, 99), // 常量表为空
|
||||
isa::enc_ret(),
|
||||
},
|
||||
12,
|
||||
}};
|
||||
const std::vector<uint8_t> img = hand_image({}, funcs);
|
||||
vm::Machine m;
|
||||
std::string err;
|
||||
CHECK(vm::Machine::create(img, &m, &err));
|
||||
CHECK(m.run_cycle() == vm::Fault::BadConst);
|
||||
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() {
|
||||
if (!test_hand_scalar()) return 1;
|
||||
if (!test_hand_call()) return 1;
|
||||
if (!test_stack_overflow()) return 1;
|
||||
if (!test_bad_slot()) return 1;
|
||||
if (!test_cases_positive()) return 1;
|
||||
if (!test_cycle_limit()) return 1;
|
||||
if (!test_determinism()) return 1;
|
||||
if (!test_step()) return 1;
|
||||
if (!test_bad_const()) return 1;
|
||||
if (!test_verify()) return 1;
|
||||
std::printf("vm_test: %d checks passed\n", g_checks);
|
||||
return 0;
|
||||
}
|
||||
+2
-1
@@ -7,7 +7,8 @@ project(VM
|
||||
DESCRIPTION "寄存器虚拟机")
|
||||
|
||||
add_library(vm STATIC
|
||||
./src/Machine.cpp)
|
||||
./src/Machine.cpp
|
||||
./src/Image.cpp)
|
||||
|
||||
target_include_directories(vm PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_link_libraries(vm PUBLIC isa)
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# vm
|
||||
|
||||
寄存器虚拟机。CMake 目标:`vm`(`STATIC`),只依赖 `isa`。
|
||||
寄存器虚拟机。CMake 目标:`vm`(`STATIC`),只依赖 `isa`(只管指令定义);自带 `.stb` 读实现 + 型号/SHA-256 校验。
|
||||
|
||||
扫描周期与边界见 [`doc/vm/扫描周期.md`](../doc/vm/扫描周期.md)。
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* @file Image.h
|
||||
* @brief vm 自带的 .stb 只读视图(执行器侧;compiler 各有实现)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 格式契约见 Doc/isa/指令与映像.md(12.13 修订:头 104 = 72 + 型号标识[32],
|
||||
* 文件尾 SHA-256[32])。解析头与段、补型号匹配与 SHA-256 校验都在本模块完成。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace vm {
|
||||
|
||||
/// 执行器内建支持型号(与 machine.toml [meta] 对齐;.stb 型号不匹配直接拒绝)。
|
||||
static const char* const kModelName = "STATOR";
|
||||
/// 执行器内建支持型号版本(与 machine.toml [meta] 对齐)。
|
||||
static const uint32_t kModelVersion = 1;
|
||||
|
||||
/**
|
||||
* @brief 映像头字段(104 字节;字段顺序与 Doc/isa/指令与映像.md 一致)。
|
||||
* @details 全部为小端值;型号标识 32 字节 @72 不在此结构内(见 model_id())。
|
||||
*/
|
||||
struct ImageHeader {
|
||||
uint32_t cycle_limit = 0; ///< 每周期指令数上限(超出 → Fault::CycleLimit)
|
||||
uint32_t dt_ms = 0; ///< 周期时长(毫秒,定时器/计数器推进步长)
|
||||
uint64_t project_hash = 0; ///< 工程哈希(FNV-1a 64,只读展示)
|
||||
uint32_t entry_fn_id = 0; ///< 入口函数(PROGRAM MAIN)在函数表的下标
|
||||
uint32_t n_globals = 0; ///< 全局变量槽数(数据段最前段)
|
||||
uint32_t n_i = 0; ///< 输入变量(I)槽数
|
||||
uint32_t n_q = 0; ///< 输出变量(Q)槽数
|
||||
uint32_t n_m = 0; ///< 中间变量(M)槽数
|
||||
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; ///< FB 表段起点
|
||||
uint32_t offset_data = 0; ///< 数据段起点(之后为 SHA-256 文件尾)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 函数表一行(12 字节)。
|
||||
*/
|
||||
struct FuncRow {
|
||||
uint32_t nregs = 0; ///< 本函数寄存器数(函数头 nregs)
|
||||
uint32_t code_offset = 0; ///< 相对字节码段起点(字节)
|
||||
uint32_t code_len = 0; ///< 指令条数
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 常量表一项(12 字节)。
|
||||
*/
|
||||
struct ConstEntry {
|
||||
uint32_t tag = 0; ///< 类型标记(isa::types::TypeTag:0=BOOL、1=INT、2=TIME)
|
||||
uint64_t value = 0; ///< 值(LOADK 时按 tag 解释)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 只读视图:指向外部缓冲的校验过的 .stb 映像。
|
||||
* @details from() 完成魔数/版本/头/各段/尺寸一致性校验;型号匹配与 SHA-256
|
||||
* 由 model_matches() / sha_ok() 另行查询(Machine::create 里一起做)。
|
||||
* 所有方法在校验失败(ok() == false)时返回安全默认值。
|
||||
*/
|
||||
class Image {
|
||||
public:
|
||||
/**
|
||||
* @brief 从原始字节构造只读视图(不拷贝,调用方保证生命周期)。
|
||||
* @param buf 映像缓冲;可为 nullptr
|
||||
* @param len 缓冲字节数
|
||||
* @return 校验结果;ok() 判成功,error() 取失败原因
|
||||
* @details 校验项:非空、长度 ≥ 头、魔数 STSC、版本 1、五个段偏移单调且在
|
||||
* SHA-256 尾之前、常量表/函数表尺寸一致、代码段 4 字节对齐、
|
||||
* 入口 fn_id 在函数表内。
|
||||
*/
|
||||
static Image from(const uint8_t* buf, size_t len);
|
||||
|
||||
/**
|
||||
* @brief 从 vector 构造只读视图。
|
||||
* @param buf 映像缓冲(引用 data(),调用方保证生命周期)
|
||||
* @return 同 from(const uint8_t*, size_t)
|
||||
*/
|
||||
static Image from(const std::vector<uint8_t>& buf);
|
||||
|
||||
/// @return 校验是否通过。
|
||||
bool ok() const { return ok_; }
|
||||
|
||||
/// @return 校验失败原因;成功时为空串。
|
||||
const std::string& error() const { return err_; }
|
||||
|
||||
/// @return 映像头字段。
|
||||
const ImageHeader& header() const { return hdr_; }
|
||||
|
||||
/**
|
||||
* @brief 读函数表一行。
|
||||
* @param i 函数下标
|
||||
* @return 该行字段;越界(或校验失败)时全 0
|
||||
*/
|
||||
FuncRow func_row(size_t i) const;
|
||||
|
||||
/**
|
||||
* @brief 读常量表一项。
|
||||
* @param i 常量下标
|
||||
* @return 该条目;越界(或校验失败)时全 0
|
||||
*/
|
||||
ConstEntry const_entry(size_t i) const;
|
||||
|
||||
/// @return 字节码段起点;校验失败返回 nullptr。
|
||||
const uint8_t* code_bytes() const;
|
||||
|
||||
/// @return 数据段起点;校验失败返回 nullptr。
|
||||
const uint8_t* data_bytes() const;
|
||||
|
||||
/// @return 数据段字节数(不含 SHA-256 尾);校验失败返回 0。
|
||||
size_t data_len() const;
|
||||
|
||||
/// @return 型号标识字符串(头 @72 的 32 字节,去 '\0' 截断)。
|
||||
std::string model_id() const;
|
||||
|
||||
/**
|
||||
* @brief 型号标识是否匹配。
|
||||
* @param name 期望型号名(如 "STATOR")
|
||||
* @param version 期望版本(如 1)
|
||||
* @return 头内 32 字节与 "name + version" 补零后逐字节相等
|
||||
*/
|
||||
bool model_matches(const std::string& name, uint32_t version) const;
|
||||
|
||||
/// @return 文件尾 32 字节是否为内容(去尾后)的 SHA-256。
|
||||
bool sha_ok() const;
|
||||
|
||||
public:
|
||||
/// 默认构造为无效态(ok() == false)。
|
||||
Image() : buf_(nullptr), len_(0) {}
|
||||
|
||||
private:
|
||||
const uint8_t* buf_; ///< 外部缓冲指针(不持有)
|
||||
size_t len_; ///< 缓冲长度
|
||||
bool ok_ = false; ///< 校验是否通过
|
||||
std::string err_; ///< 失败原因
|
||||
ImageHeader hdr_; ///< 解析出的头字段
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* @file Machine.h
|
||||
* @brief 寄存器虚拟机(12.9)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 设计说明(详见 Doc/vm/扫描周期.md 与 Doc/vm/指令执行.md):
|
||||
* - 只认 .stb 映像(vm 自带读实现;Machine::create 做型号/SHA-256 校验);
|
||||
* 无 GC、无堆、无线程
|
||||
* - 扫描周期:MAIN pc=0 → RET;MAIN 帧跨周期保留,调用栈深度上限 64
|
||||
* - 帧:{ fn_id, regs[nregs], ret_pc, ret_fn_id };CALL 压帧复制 r0..r7,
|
||||
* RET 复制回调用方(r0=结果)
|
||||
* - 数据区:8 字节定宽槽(方案 a),指令 slot = 槽号,偏移 = slot × 8
|
||||
* - 可观察性(v1 预留):step / pc / fn_id / cycle_count / call_depth / reg
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "vm/Image.h"
|
||||
#include "isa/Instr.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
/**
|
||||
* @brief 周期/指令故障码。
|
||||
*/
|
||||
enum class Fault {
|
||||
None, ///< 正常
|
||||
CycleLimit, ///< 本周期指令数超过映像头 cycle_limit(用例 6)
|
||||
StackOverflow, ///< 调用栈深度超过 64
|
||||
BadOp, ///< 非法操作码 / 寄存器越界 / CALL 目标越出函数表
|
||||
BadSlot, ///< slot × 8 + 8 越出数据区
|
||||
BadConst, ///< const_id 越出常量表
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 寄存器虚拟机:按扫描周期执行 .stb 映像。
|
||||
* @details 一个实例 = 一台机器:持有映像只读视图 + 数据区工作副本 + 调用栈。
|
||||
* MAIN 帧跨周期保留(PROGRAM 变量状态持久);每周期从 pc=0 执行到
|
||||
* MAIN 的 RET,期间调用栈可进可出(上限 64 帧)。
|
||||
* I 采样写入 / Q 读回由外部(executor)经 data() 完成,VM 只管数据区。
|
||||
*/
|
||||
class Machine {
|
||||
public:
|
||||
/**
|
||||
* @brief 从映像字节构建机器(校验 + 拷贝数据段 + 建 MAIN 帧)。
|
||||
* @param image .stb 映像字节
|
||||
* @param out 输出 Machine(未初始化)
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false(映像非法,err 已写)
|
||||
* @details 校验顺序:解析 .stb(Image::from)→ SHA-256 → 型号标识匹配
|
||||
* (内建 kModelName + kModelVersion),任一不符直接拒绝。
|
||||
*/
|
||||
static bool create(const std::vector<uint8_t>& image, Machine* out,
|
||||
std::string* err);
|
||||
|
||||
/**
|
||||
* @brief 跑一个扫描周期:MAIN pc=0 执行到 RET。
|
||||
* @return 故障(None = 正常完成);调用栈清空但 MAIN 帧保留
|
||||
*/
|
||||
Fault run_cycle();
|
||||
|
||||
// ---- 可观察性(v1 预留,12.11 供单步/回放/TUI)----
|
||||
|
||||
/**
|
||||
* @brief 执行一条指令,停在指令边界。
|
||||
* @return true 继续;false 周期结束(MAIN 的 RET)或发生故障
|
||||
*/
|
||||
bool step();
|
||||
|
||||
/// @brief 最近一次故障(step 返回 false 后查询)。
|
||||
Fault fault() const;
|
||||
|
||||
/// @brief 周期是否已结束(MAIN 的 RET 后为 true)。
|
||||
bool ended() const;
|
||||
|
||||
/// @brief 映像头(dt_ms / cycle_limit 等)。
|
||||
const vm::ImageHeader& header() const;
|
||||
|
||||
/// @brief 当前指令下标(相对当前函数字节码段)。
|
||||
uint32_t pc() const;
|
||||
|
||||
/// @brief 当前函数 fn_id。
|
||||
uint32_t fn_id() const;
|
||||
|
||||
/// @brief 本周期已执行指令数。
|
||||
uint32_t cycle_count() const;
|
||||
|
||||
/// @brief 调用栈深度(MAIN 帧 = 1)。
|
||||
uint32_t call_depth() const;
|
||||
|
||||
/// @brief 数据区(I 采样写入 / Q 读回由外部做;8 字节定宽槽)。
|
||||
uint8_t* data();
|
||||
const uint8_t* data() const;
|
||||
|
||||
/// @brief 数据区字节数。
|
||||
size_t data_len() const;
|
||||
|
||||
/// @brief 当前帧寄存器值(i < nregs)。
|
||||
int64_t reg(uint8_t i) const;
|
||||
|
||||
/// @brief 当前帧寄存器数(函数头 nregs)。
|
||||
uint32_t nregs() const;
|
||||
|
||||
/// @brief 当前指令(pc_ 处;单步/观察用;pc 越界时返回 RET 哨兵)。
|
||||
isa::Instr cur_instr() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 调用帧。
|
||||
*/
|
||||
struct Frame {
|
||||
uint32_t fn_id = 0; ///< 本帧函数
|
||||
std::vector<int64_t> regs; ///< 寄存器文件(大小 = 函数头 nregs)
|
||||
uint32_t ret_pc = 0; ///< CALL 的下一条(非 MAIN 帧有意义)
|
||||
uint32_t ret_fn_id = 0; ///< 返回目标函数
|
||||
};
|
||||
|
||||
vm::Image image_; ///< 映像只读视图(vm 自实现,指向外部缓冲)
|
||||
std::vector<uint8_t> data_; ///< 数据区工作副本(8 字节定宽槽)
|
||||
std::vector<Frame> frames_; ///< 调用栈([0] = MAIN,跨周期保留)
|
||||
/**
|
||||
* @brief 边沿检测上次输入(每槽 2 字节:[slot*2] 第一边沿、[slot*2+1] 第二边沿;
|
||||
* CTU/CTD/R_TRIG/F_TRIG 用第一、CTUD 用两个),跨周期保留。
|
||||
*/
|
||||
std::vector<uint8_t> edge_prev_;
|
||||
uint32_t pc_ = 0; ///< 当前 pc(相对本帧函数字节码段)
|
||||
uint32_t cycle_count_ = 0; ///< 本周期指令数
|
||||
Fault fault_ = Fault::None;///< 最近一次故障
|
||||
bool ended_ = false; ///< step 后周期是否已结束
|
||||
|
||||
/// @brief 当前(栈顶)帧引用。
|
||||
Frame& cur_frame();
|
||||
/// @brief 当前(栈顶)帧常量引用。
|
||||
const Frame& cur_frame() const;
|
||||
|
||||
/// @brief 译码执行一条;false = 故障或周期结束。
|
||||
bool exec_one();
|
||||
|
||||
/// @brief 压帧(复制 r0..r7);fn_id 越界以 BadOp 表示。
|
||||
Fault do_call(uint32_t fn_id);
|
||||
|
||||
/// @brief 弹帧 / 周期结束(复制 r0..r7 回调用方)。
|
||||
Fault do_ret();
|
||||
|
||||
/// @brief 执行内置 FB(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG)。
|
||||
Fault do_cal(uint8_t op, uint16_t slot);
|
||||
|
||||
/// @brief 读数据区槽(8 字节定宽;越界返回 false)。
|
||||
bool slot_get(uint16_t slot, int64_t* out) const;
|
||||
|
||||
/// @brief 写数据区槽(8 字节定宽;越界返回 false)。
|
||||
bool slot_set(uint16_t slot, int64_t v);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
/**
|
||||
* @file Image.cpp
|
||||
* @brief vm 自带的 .stb 只读视图实现(执行器侧;compiler 各有实现)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*/
|
||||
|
||||
#include "vm/Image.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
namespace vm {
|
||||
|
||||
namespace {
|
||||
|
||||
/// 映像魔数:"STSC" 小端。
|
||||
const uint32_t kMagic = 0x43545353u;
|
||||
/// 映像格式版本。
|
||||
const uint32_t kVersion = 1;
|
||||
/// 映像头字节数(72 原字段 + 型号标识[32] @72,12.13 修订)。
|
||||
const size_t kHeaderSize = 104;
|
||||
/// 常量表一行字节数(tag:4 + value:8)。
|
||||
const size_t kConstEntrySize = 12;
|
||||
/// 函数表一行字节数(nregs/code_offset/code_len 各 4)。
|
||||
const size_t kFuncRowSize = 12;
|
||||
/// SHA-256 摘要长度(文件尾)。
|
||||
const size_t kSha256Size = 32;
|
||||
/// 型号标识长度(头内 @72,不足补 '\0')。
|
||||
const size_t kModelIdSize = 32;
|
||||
|
||||
/// 小端读 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);
|
||||
}
|
||||
|
||||
/// 小端读 64 位。
|
||||
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 各一份)----
|
||||
|
||||
/// SHA-256 轮常量 K[0..63]。
|
||||
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)); }
|
||||
|
||||
/**
|
||||
* @brief SHA-256 增量状态机。
|
||||
* @details 标准 FIPS 180-4 实现:update() 吸收任意长度字节流,final() 输出
|
||||
* 32 字节大端摘要。按 64 字节块 process()。
|
||||
*/
|
||||
struct Sha256 {
|
||||
uint32_t h[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; ///< 初始哈希值
|
||||
uint64_t total = 0; ///< 已吸收字节数(final 时编码进长度域)
|
||||
uint8_t block[64]; ///< 当前块缓冲
|
||||
size_t block_len = 0; ///< 块缓冲已用字节数
|
||||
|
||||
/**
|
||||
* @brief 吸收数据。
|
||||
* @param data 输入字节
|
||||
* @param len 字节数
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 压缩一个满块(64 字节,w[0..63] 展开 + 64 轮)。
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 结束并输出摘要。
|
||||
* @param out 32 字节输出缓冲(大端)
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// 大端读 32 位。
|
||||
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]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 一次性 SHA-256。
|
||||
* @param data 输入字节
|
||||
* @param len 字节数
|
||||
* @param out 32 字节摘要输出
|
||||
*/
|
||||
void sha256(const uint8_t* data, size_t len, uint8_t out[32]) {
|
||||
Sha256 s;
|
||||
s.update(data, len);
|
||||
s.final(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按型号标识约定填充 32 字节:name + version(如 "STATOR1"),余下补 '\0'。
|
||||
* @param name 型号名
|
||||
* @param version 版本号
|
||||
* @param out 32 字节输出缓冲
|
||||
*/
|
||||
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
|
||||
|
||||
/**
|
||||
* @brief 从原始字节构造只读视图(不拷贝,调用方保证生命周期)。
|
||||
* @param buf 映像缓冲;可为 nullptr
|
||||
* @param len 缓冲字节数
|
||||
* @return 校验结果;ok() 判成功,error() 取失败原因
|
||||
*/
|
||||
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 文件尾;这里仅保证有尾,sha_ok() 做完整性校验)
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从 vector 构造只读视图。
|
||||
* @param buf 映像缓冲(引用 data(),调用方保证生命周期)
|
||||
* @return 同 from(const uint8_t*, size_t)
|
||||
*/
|
||||
Image Image::from(const std::vector<uint8_t>& buf) {
|
||||
return from(buf.data(), buf.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读函数表一行。
|
||||
* @param i 函数下标
|
||||
* @return 该行字段;越界(或校验失败)时全 0
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读常量表一项。
|
||||
* @param i 常量下标
|
||||
* @return 该条目;越界(或校验失败)时全 0
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 字节码段起点。
|
||||
* @return 段指针;校验失败返回 nullptr
|
||||
*/
|
||||
const uint8_t* Image::code_bytes() const {
|
||||
return ok_ ? buf_ + hdr_.offset_code : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 数据段起点。
|
||||
* @return 段指针;校验失败返回 nullptr
|
||||
*/
|
||||
const uint8_t* Image::data_bytes() const {
|
||||
return ok_ ? buf_ + hdr_.offset_data : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 数据段字节数。
|
||||
* @return 段长度(不含 SHA-256 尾);校验失败返回 0
|
||||
*/
|
||||
size_t Image::data_len() const {
|
||||
return ok_ ? (len_ - kSha256Size) - hdr_.offset_data : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 型号标识字符串。
|
||||
* @return 头 @72 起 32 字节,去 '\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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 型号标识是否匹配。
|
||||
* @param name 期望型号名(如 "STATOR")
|
||||
* @param version 期望版本(如 1)
|
||||
* @return 头内 32 字节与 "name + version" 补零后逐字节相等
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 文件尾 SHA-256 完整性校验。
|
||||
* @return 对 len-32 字节内容算摘要,与文件尾 32 字节相等
|
||||
*/
|
||||
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
|
||||
+595
-2
@@ -1,8 +1,601 @@
|
||||
/**
|
||||
* @file Machine.cpp
|
||||
* @brief 寄存器虚拟机(12.9 实现)
|
||||
* @date 2026-08-19
|
||||
* @brief 寄存器虚拟机实现(12.9:译码 switch + CALL/RET 帧栈 + CAL_* 定时器)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 设计说明(详见 Doc/vm/指令执行.md):
|
||||
* - 取指:函数表 → code_offset(字节)→ u32;执行后 pc 先 +1,跳转再 pc += off
|
||||
* - 数据区 8 字节定宽槽:偏移 = slot × 8
|
||||
* - 寄存器越界检查仅对把字段当寄存器的指令;a|b 作偏移/槽号/常量 id 时跳过
|
||||
*/
|
||||
|
||||
#include "vm/Machine.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "isa/Encode.h"
|
||||
#include "isa/Instr.h"
|
||||
#include "isa/Op.h"
|
||||
#include "isa/Types.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
/**
|
||||
* @brief 从映像字节构建机器(校验 + 拷贝数据段 + 建 MAIN 帧)。
|
||||
* @param image .stb 映像字节
|
||||
* @param out 输出 Machine(未初始化)
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false(映像非法,err 已写)
|
||||
* @details 校验顺序:解析 .stb(Image::from)→ SHA-256 → 型号标识匹配;
|
||||
* 通过后拷贝数据段为工作副本,初始化边沿检测缓冲(每槽 2 字节),
|
||||
* 建 MAIN 帧(跨周期保留)并把执行状态复位。
|
||||
*/
|
||||
bool Machine::create(const std::vector<uint8_t>& image, Machine* out,
|
||||
std::string* err) {
|
||||
out->image_ = vm::Image::from(image);
|
||||
if (!out->image_.ok()) {
|
||||
if (err) {
|
||||
*err = out->image_.error();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// 12.13:型号匹配与 SHA-256 校验(不匹配/篡改直接拒绝)
|
||||
if (!out->image_.sha_ok()) {
|
||||
if (err) {
|
||||
*err = "image sha256 mismatch";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!out->image_.model_matches(kModelName, kModelVersion)) {
|
||||
if (err) {
|
||||
*err = "image model mismatch: '" + out->image_.model_id() +
|
||||
"' (expect " + kModelName + std::to_string(kModelVersion) + ")";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// 数据区工作副本(8 字节定宽槽)
|
||||
out->data_.assign(out->image_.data_bytes(),
|
||||
out->image_.data_bytes() + out->image_.data_len());
|
||||
out->edge_prev_.assign(out->data_len() / 8 * 2, 0);
|
||||
|
||||
// MAIN 帧(跨周期保留)
|
||||
const uint32_t entry = out->image_.header().entry_fn_id;
|
||||
const vm::FuncRow main = out->image_.func_row(entry);
|
||||
out->frames_.clear();
|
||||
Frame f;
|
||||
f.fn_id = entry;
|
||||
f.regs.assign(main.nregs, 0);
|
||||
out->frames_.push_back(std::move(f));
|
||||
|
||||
out->pc_ = 0;
|
||||
out->cycle_count_ = 0;
|
||||
out->fault_ = Fault::None;
|
||||
out->ended_ = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 跑一个扫描周期:MAIN pc=0 执行到 RET。
|
||||
* @return 故障(None = 正常完成)
|
||||
* @details 循环执行 exec_one() 直到周期结束或故障;结束/故障后清空调用栈
|
||||
* (保留 MAIN 帧,PROGRAM 变量状态跨周期持久)。
|
||||
*/
|
||||
Fault Machine::run_cycle() {
|
||||
cycle_count_ = 0;
|
||||
ended_ = false;
|
||||
fault_ = Fault::None;
|
||||
pc_ = 0;
|
||||
while (!ended_ && fault_ == Fault::None) {
|
||||
if (!exec_one()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 周期结束:清调用栈(保留 MAIN 帧,PROGRAM 变量状态跨周期)
|
||||
while (frames_.size() > 1) {
|
||||
frames_.pop_back();
|
||||
}
|
||||
return fault_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 执行一条指令,停在指令边界。
|
||||
* @return true 继续;false 周期结束(MAIN 的 RET)或发生故障
|
||||
*/
|
||||
bool Machine::step() {
|
||||
if (ended_ || fault_ != Fault::None) {
|
||||
return false;
|
||||
}
|
||||
return exec_one();
|
||||
}
|
||||
|
||||
Fault Machine::fault() const { return fault_; }
|
||||
bool Machine::ended() const { return ended_; }
|
||||
const vm::ImageHeader& Machine::header() const { return image_.header(); }
|
||||
uint32_t Machine::pc() const { return pc_; }
|
||||
uint32_t Machine::fn_id() const { return cur_frame().fn_id; }
|
||||
uint32_t Machine::cycle_count() const { return cycle_count_; }
|
||||
uint32_t Machine::call_depth() const { return static_cast<uint32_t>(frames_.size()); }
|
||||
uint8_t* Machine::data() { return data_.data(); }
|
||||
const uint8_t* Machine::data() const { return data_.data(); }
|
||||
size_t Machine::data_len() const { return data_.size(); }
|
||||
int64_t Machine::reg(uint8_t i) const { return cur_frame().regs[i]; }
|
||||
|
||||
/**
|
||||
* @brief 当前帧寄存器数。
|
||||
* @return 栈顶帧 regs 大小(函数头 nregs)
|
||||
*/
|
||||
uint32_t Machine::nregs() const {
|
||||
return static_cast<uint32_t>(cur_frame().regs.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 当前指令(pc_ 处)。
|
||||
* @return 指令字;pc_ 越出本函数字节码段时返回 RET 哨兵
|
||||
* @details 供单步/观察用;不修改任何执行状态。
|
||||
*/
|
||||
isa::Instr Machine::cur_instr() const {
|
||||
const vm::FuncRow row = image_.func_row(cur_frame().fn_id);
|
||||
const uint8_t* base = image_.code_bytes() + row.code_offset;
|
||||
if (pc_ >= row.code_len) {
|
||||
return isa::pack(isa::Op::RET, 0, 0, 0);
|
||||
}
|
||||
return reinterpret_cast<const uint32_t*>(base)[pc_];
|
||||
}
|
||||
|
||||
/// @brief 当前(栈顶)帧引用。
|
||||
Machine::Frame& Machine::cur_frame() { return frames_.back(); }
|
||||
|
||||
/// @brief 当前(栈顶)帧常量引用。
|
||||
const Machine::Frame& Machine::cur_frame() const { return frames_.back(); }
|
||||
|
||||
/**
|
||||
* @brief 译码执行一条指令。
|
||||
* @return true 继续;false 故障(fault_ 已置)或周期结束
|
||||
* @details 流程:取指(越界 → BadOp)→ 周期计数(超 cycle_limit → CycleLimit)→
|
||||
* pc 先 +1(跳转按"相对下一条"叠加)→ 寄存器越界检查 → 按操作码分发。
|
||||
* 标量指令用 isa::types 饱和算术;LOAD/STORE 走 slot_get/slot_set;
|
||||
* CALL/RET 走 do_call/do_ret;CAL_* 走 do_cal。
|
||||
*/
|
||||
bool Machine::exec_one() {
|
||||
// ---- 取指 ----
|
||||
const vm::FuncRow row = image_.func_row(cur_frame().fn_id);
|
||||
if (pc_ >= row.code_len) {
|
||||
fault_ = Fault::BadOp;
|
||||
return false;
|
||||
}
|
||||
const uint8_t* base = image_.code_bytes() + row.code_offset;
|
||||
const isa::Instr w = reinterpret_cast<const uint32_t*>(base)[pc_];
|
||||
|
||||
// ---- 周期指令计数 ----
|
||||
++cycle_count_;
|
||||
if (cycle_count_ > image_.header().cycle_limit) {
|
||||
fault_ = Fault::CycleLimit;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 先指向下一条(跳转指令再按"相对下一条"语义叠加偏移)
|
||||
++pc_;
|
||||
|
||||
const isa::Op op = isa::op(w);
|
||||
const uint8_t rd = isa::rd(w);
|
||||
const uint8_t ra = isa::a(w);
|
||||
const uint8_t rb = isa::b(w);
|
||||
std::vector<int64_t>& regs = cur_frame().regs;
|
||||
|
||||
// 寄存器越界检查(仅对把字段当寄存器的指令;a|b 作偏移/槽号/常量 id 时跳过)
|
||||
const bool uses_rd = op == isa::Op::MOVE || op == isa::Op::NOT || op == isa::Op::AND ||
|
||||
op == isa::Op::OR || op == isa::Op::ADD || op == isa::Op::SUB ||
|
||||
op == isa::Op::MUL || op == isa::Op::DIV || op == isa::Op::CMP_EQ ||
|
||||
op == isa::Op::CMP_NE || op == isa::Op::CMP_LT ||
|
||||
op == isa::Op::CMP_LE || op == isa::Op::CMP_GT ||
|
||||
op == isa::Op::CMP_GE || op == isa::Op::JT || op == isa::Op::JF ||
|
||||
op == isa::Op::LOADK || op == isa::Op::LOAD_I ||
|
||||
op == isa::Op::LOAD_M || op == isa::Op::LOAD_GLOBAL ||
|
||||
op == isa::Op::STORE_Q || op == isa::Op::STORE_M ||
|
||||
op == isa::Op::STORE_GLOBAL;
|
||||
const bool uses_ra = op == isa::Op::MOVE || op == isa::Op::NOT || op == isa::Op::AND ||
|
||||
op == isa::Op::OR || op == isa::Op::ADD || op == isa::Op::SUB ||
|
||||
op == isa::Op::MUL || op == isa::Op::DIV || op == isa::Op::CMP_EQ ||
|
||||
op == isa::Op::CMP_NE || op == isa::Op::CMP_LT ||
|
||||
op == isa::Op::CMP_LE || op == isa::Op::CMP_GT ||
|
||||
op == isa::Op::CMP_GE;
|
||||
const bool uses_rb = uses_ra;
|
||||
if ((uses_rd && rd >= regs.size()) || (uses_ra && ra >= regs.size()) ||
|
||||
(uses_rb && rb >= regs.size())) {
|
||||
fault_ = Fault::BadOp;
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (op) {
|
||||
case isa::Op::MOVE:
|
||||
regs[rd] = regs[ra];
|
||||
return true;
|
||||
case isa::Op::LOADK: {
|
||||
const uint16_t cid = isa::imm16(w);
|
||||
if (cid >= image_.header().n_consts) {
|
||||
fault_ = Fault::BadConst;
|
||||
return false;
|
||||
}
|
||||
const vm::ConstEntry c = image_.const_entry(cid);
|
||||
// 按类型标记解释常量值:BOOL 归一化 0/1,INT 符号扩展,TIME 全 64 位
|
||||
if (c.tag == isa::types::Bool) {
|
||||
regs[rd] = c.value ? 1 : 0;
|
||||
} else if (c.tag == isa::types::Int) {
|
||||
regs[rd] = static_cast<int16_t>(c.value);
|
||||
} else {
|
||||
regs[rd] = static_cast<int64_t>(c.value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case isa::Op::NOT:
|
||||
regs[rd] = (regs[ra] == 0) ? 1 : 0;
|
||||
return true;
|
||||
case isa::Op::AND:
|
||||
regs[rd] = (regs[ra] != 0 && regs[rb] != 0) ? 1 : 0;
|
||||
return true;
|
||||
case isa::Op::OR:
|
||||
regs[rd] = (regs[ra] != 0 || regs[rb] != 0) ? 1 : 0;
|
||||
return true;
|
||||
case isa::Op::ADD:
|
||||
regs[rd] = isa::types::sat_add(static_cast<int16_t>(regs[ra]),
|
||||
static_cast<int16_t>(regs[rb]));
|
||||
return true;
|
||||
case isa::Op::SUB:
|
||||
regs[rd] = isa::types::sat_sub(static_cast<int16_t>(regs[ra]),
|
||||
static_cast<int16_t>(regs[rb]));
|
||||
return true;
|
||||
case isa::Op::MUL:
|
||||
regs[rd] = isa::types::sat_mul(static_cast<int16_t>(regs[ra]),
|
||||
static_cast<int16_t>(regs[rb]));
|
||||
return true;
|
||||
case isa::Op::DIV:
|
||||
regs[rd] = isa::types::sat_div(static_cast<int16_t>(regs[ra]),
|
||||
static_cast<int16_t>(regs[rb]));
|
||||
return true;
|
||||
case isa::Op::CMP_EQ:
|
||||
case isa::Op::CMP_NE:
|
||||
case isa::Op::CMP_LT:
|
||||
case isa::Op::CMP_LE:
|
||||
case isa::Op::CMP_GT:
|
||||
case isa::Op::CMP_GE: {
|
||||
const int64_t l = regs[ra];
|
||||
const int64_t r = regs[rb];
|
||||
bool res = false;
|
||||
switch (op) {
|
||||
case isa::Op::CMP_EQ: res = (l == r); break;
|
||||
case isa::Op::CMP_NE: res = (l != r); break;
|
||||
case isa::Op::CMP_LT: res = (l < r); break;
|
||||
case isa::Op::CMP_LE: res = (l <= r); break;
|
||||
case isa::Op::CMP_GT: res = (l > r); break;
|
||||
case isa::Op::CMP_GE: res = (l >= r); break;
|
||||
default: break;
|
||||
}
|
||||
regs[rd] = res ? 1 : 0;
|
||||
return true;
|
||||
}
|
||||
case isa::Op::JMP:
|
||||
pc_ += isa::off16(w);
|
||||
return true;
|
||||
case isa::Op::JT:
|
||||
if (regs[rd] != 0) {
|
||||
pc_ += isa::off16(w);
|
||||
}
|
||||
return true;
|
||||
case isa::Op::JF:
|
||||
if (regs[rd] == 0) {
|
||||
pc_ += isa::off16(w);
|
||||
}
|
||||
return true;
|
||||
case isa::Op::LOAD_I:
|
||||
case isa::Op::LOAD_M:
|
||||
case isa::Op::LOAD_GLOBAL: {
|
||||
const uint16_t slot = isa::imm16(w);
|
||||
if (!slot_get(slot, ®s[rd])) {
|
||||
fault_ = Fault::BadSlot;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case isa::Op::STORE_Q:
|
||||
case isa::Op::STORE_M:
|
||||
case isa::Op::STORE_GLOBAL: {
|
||||
const uint16_t slot = isa::imm16(w);
|
||||
if (!slot_set(slot, regs[rd])) {
|
||||
fault_ = Fault::BadSlot;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case isa::Op::CALL:
|
||||
fault_ = do_call(isa::imm16(w));
|
||||
return fault_ == Fault::None;
|
||||
case isa::Op::RET:
|
||||
fault_ = do_ret();
|
||||
if (fault_ != Fault::None) {
|
||||
return false;
|
||||
}
|
||||
return !ended_; // MAIN 的 RET → 周期结束(返回 false)
|
||||
case isa::Op::CAL_TON:
|
||||
case isa::Op::CAL_TOF:
|
||||
case isa::Op::CAL_TP:
|
||||
case isa::Op::CAL_CTU:
|
||||
case isa::Op::CAL_CTD:
|
||||
case isa::Op::CAL_CTUD:
|
||||
case isa::Op::CAL_R_TRIG:
|
||||
case isa::Op::CAL_F_TRIG:
|
||||
fault_ = do_cal(static_cast<uint8_t>(op), isa::imm16(w));
|
||||
return fault_ == Fault::None;
|
||||
default:
|
||||
fault_ = Fault::BadOp;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读数据区槽(8 字节定宽,小端)。
|
||||
* @param slot 槽号
|
||||
* @param out 输出值
|
||||
* @return false = slot × 8 + 8 越出数据区
|
||||
*/
|
||||
bool Machine::slot_get(uint16_t slot, int64_t* out) const {
|
||||
const size_t off = static_cast<size_t>(slot) * 8;
|
||||
if (off + 8 > data_.size()) {
|
||||
return false;
|
||||
}
|
||||
int64_t v = 0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
v |= static_cast<int64_t>(data_[off + i]) << (8 * i);
|
||||
}
|
||||
*out = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 写数据区槽(8 字节定宽,小端)。
|
||||
* @param slot 槽号
|
||||
* @param v 要写入的值
|
||||
* @return false = slot × 8 + 8 越出数据区
|
||||
*/
|
||||
bool Machine::slot_set(uint16_t slot, int64_t v) {
|
||||
const size_t off = static_cast<size_t>(slot) * 8;
|
||||
if (off + 8 > data_.size()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
data_[off + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFFu);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 调用:压新帧。
|
||||
* @param fn_id 目标函数(函数表下标)
|
||||
* @return Fault(fn_id 越界 → BadOp;栈深 ≥ 64 → StackOverflow;成功 → None)
|
||||
* @details 新帧寄存器清零,ret_pc = 当前 pc(已指向 CALL 下一条);
|
||||
* 按调用约定把调用方 r0..r7 复制进新帧(实参已由调用点 MOVE 进 r1..r7);
|
||||
* pc 复位到新函数起点。
|
||||
*/
|
||||
Fault Machine::do_call(uint32_t fn_id) {
|
||||
if (fn_id >= image_.header().n_funcs) {
|
||||
return Fault::BadOp;
|
||||
}
|
||||
if (frames_.size() >= 64) {
|
||||
return Fault::StackOverflow;
|
||||
}
|
||||
const vm::FuncRow row = image_.func_row(fn_id);
|
||||
Frame f;
|
||||
f.fn_id = fn_id;
|
||||
f.regs.assign(row.nregs, 0);
|
||||
f.ret_pc = pc_; // 已指向 CALL 的下一条
|
||||
f.ret_fn_id = cur_frame().fn_id;
|
||||
// 调用约定:复制当前帧 r0..r7 → 新帧(实参已由调用点 MOVE 进 r1..r7)
|
||||
const size_t n = std::min<size_t>(8, std::min(cur_frame().regs.size(), f.regs.size()));
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
f.regs[i] = cur_frame().regs[i];
|
||||
}
|
||||
frames_.push_back(std::move(f));
|
||||
pc_ = 0;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 返回:弹帧 / 周期结束。
|
||||
* @return Fault(成功 → None)
|
||||
* @details MAIN 帧的 RET:ended_ = true(周期结束,不弹帧)。
|
||||
* 非 MAIN 帧:按调用约定把 r0..r7 复制回调用方(r0 = 结果,r1..r7 原样),
|
||||
* pc 恢复为 ret_pc,弹帧。
|
||||
*/
|
||||
Fault Machine::do_ret() {
|
||||
if (frames_.size() <= 1) {
|
||||
ended_ = true; // MAIN 的 RET:周期结束
|
||||
return Fault::None;
|
||||
}
|
||||
Frame& cur = cur_frame();
|
||||
Frame& caller = frames_[frames_.size() - 2];
|
||||
// 调用约定:复制当前帧 r0..r7 → 调用方(r0 = 结果,r1..r7 原样返回)
|
||||
const size_t n = std::min<size_t>(8, std::min(cur.regs.size(), caller.regs.size()));
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
caller.regs[i] = cur.regs[i];
|
||||
}
|
||||
pc_ = cur.ret_pc;
|
||||
frames_.pop_back();
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 执行内置 FB(CAL_*)。
|
||||
* @param op 操作码(CAL_TON..CAL_F_TRIG,8 个)
|
||||
* @param slot 实例起始槽号
|
||||
* @return Fault(槽越界 → BadSlot;非 CAL_* → BadOp;成功 → None)
|
||||
* @details 内建 FB 布局冻结(Doc/compiler/符号表与链接.md):
|
||||
* - TON/TOF/TP:in/pt/q/et(字段 0/1/2/3)
|
||||
* - CTU:cu/r/pv/q/cv(0/1/2/3/4);CTD:cd/ld/pv/q/cv(同)
|
||||
* - CTUD:cu/cd/r/lu/pv/qu/qd/cv(0..7)
|
||||
* - R_TRIG / F_TRIG:clk/q(0/1)
|
||||
* 字段偏移 = 字段序号 × 8(8 字节定宽槽);边沿存 edge_prev_(每槽 2 字节)。
|
||||
*/
|
||||
Fault Machine::do_cal(uint8_t op, uint16_t slot) {
|
||||
// 内建 FB 布局冻结(Doc/compiler/符号表与链接.md):
|
||||
// TON/TOF/TP:in/pt/q/et(字段 0/1/2/3)
|
||||
// CTU:cu/r/pv/q/cv(0/1/2/3/4);CTD:cd/ld/pv/q/cv(同)
|
||||
// CTUD:cu/cd/r/lu/pv/qu/qd/cv(0..7)
|
||||
// R_TRIG / F_TRIG:clk/q(0/1)
|
||||
// 字段偏移 = 字段序号 × 8(8 字节定宽槽);边沿存 edge_prev_
|
||||
const int64_t dt = image_.header().dt_ms;
|
||||
int64_t v = 0;
|
||||
|
||||
const bool is_ton = op == static_cast<uint8_t>(isa::Op::CAL_TON);
|
||||
const bool is_tof = op == static_cast<uint8_t>(isa::Op::CAL_TOF);
|
||||
if (is_ton || is_tof) {
|
||||
// in=0 pt=1 q=2 et=3
|
||||
if (!slot_get(static_cast<uint16_t>(slot + 0), &v)) return Fault::BadSlot;
|
||||
const bool in = v != 0;
|
||||
if (!slot_get(static_cast<uint16_t>(slot + 1), &v)) return Fault::BadSlot;
|
||||
const int64_t pt = v;
|
||||
int64_t et = 0;
|
||||
if (!slot_get(static_cast<uint16_t>(slot + 3), &et)) return Fault::BadSlot;
|
||||
int64_t q = 0;
|
||||
if (is_ton) {
|
||||
// TON:in 真 → et += dt(到 pt 停)、q = et ≥ pt;in 假 → et = 0、q = 0
|
||||
if (in) {
|
||||
et += dt;
|
||||
if (pt > 0 && et >= pt) {
|
||||
et = pt;
|
||||
}
|
||||
q = (et >= pt) ? 1 : 0;
|
||||
} else {
|
||||
et = 0;
|
||||
q = 0;
|
||||
}
|
||||
} else {
|
||||
// TOF:in 真 → q = 1、et = 0;掉电 → et += dt、et ≥ pt → q = 0
|
||||
if (in) {
|
||||
et = 0;
|
||||
q = 1;
|
||||
} else {
|
||||
et += dt;
|
||||
q = (pt > 0 && et >= pt) ? 0 : 1;
|
||||
}
|
||||
}
|
||||
if (!slot_set(static_cast<uint16_t>(slot + 3), et)) return Fault::BadSlot;
|
||||
if (!slot_set(static_cast<uint16_t>(slot + 2), q)) return Fault::BadSlot;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
if (op == static_cast<uint8_t>(isa::Op::CAL_TP)) {
|
||||
// in=0 pt=1 q=2 et=3 + 上次 in(edge_prev_[slot*2])
|
||||
if (static_cast<size_t>(slot) * 2 + 1 >= edge_prev_.size()) return Fault::BadSlot;
|
||||
if (!slot_get(static_cast<uint16_t>(slot + 0), &v)) return Fault::BadSlot;
|
||||
const bool in = v != 0;
|
||||
if (!slot_get(static_cast<uint16_t>(slot + 1), &v)) return Fault::BadSlot;
|
||||
const int64_t pt = v;
|
||||
int64_t et = 0;
|
||||
if (!slot_get(static_cast<uint16_t>(slot + 3), &et)) return Fault::BadSlot;
|
||||
// TP(脉冲):in 上升沿启动 PT 时长的脉冲,期间 in 变化不影响
|
||||
const bool prev = edge_prev_[slot * 2] != 0;
|
||||
const bool rising = in && !prev;
|
||||
int64_t q = 0;
|
||||
if (rising) {
|
||||
et = 0;
|
||||
}
|
||||
const bool timing = rising || (et > 0);
|
||||
if (timing) {
|
||||
et += dt;
|
||||
if (pt > 0 && et >= pt) {
|
||||
et = 0; // 脉冲结束:et 归零,下周期不再计时
|
||||
q = 0;
|
||||
} else {
|
||||
q = 1;
|
||||
}
|
||||
} else {
|
||||
et = 0;
|
||||
q = 0;
|
||||
}
|
||||
edge_prev_[slot * 2] = in ? 1 : 0;
|
||||
if (!slot_set(static_cast<uint16_t>(slot + 3), et)) return Fault::BadSlot;
|
||||
if (!slot_set(static_cast<uint16_t>(slot + 2), q)) return Fault::BadSlot;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
if (op == static_cast<uint8_t>(isa::Op::CAL_CTU) ||
|
||||
op == static_cast<uint8_t>(isa::Op::CAL_CTD)) {
|
||||
// CTU:cu/r/pv/q/cv;CTD:cd/ld/pv/q/cv
|
||||
if (static_cast<size_t>(slot) * 2 >= edge_prev_.size()) return Fault::BadSlot;
|
||||
if (!slot_get(static_cast<uint16_t>(slot + 0), &v)) return Fault::BadSlot;
|
||||
const bool edge_in = v != 0; // cu(CTU)/ cd(CTD)
|
||||
if (!slot_get(static_cast<uint16_t>(slot + 1), &v)) return Fault::BadSlot;
|
||||
const bool ld_in = v != 0; // r(CTU)/ ld(CTD)
|
||||
if (!slot_get(static_cast<uint16_t>(slot + 2), &v)) return Fault::BadSlot;
|
||||
const int64_t pv = v;
|
||||
int64_t cv = 0;
|
||||
if (!slot_get(static_cast<uint16_t>(slot + 4), &cv)) return Fault::BadSlot;
|
||||
const bool prev = edge_prev_[slot * 2] != 0;
|
||||
if (ld_in) {
|
||||
cv = pv;
|
||||
} else if (edge_in && !prev) {
|
||||
cv += (op == static_cast<uint8_t>(isa::Op::CAL_CTU)) ? 1 : -1;
|
||||
}
|
||||
edge_prev_[slot * 2] = edge_in ? 1 : 0;
|
||||
const int64_t q = (op == static_cast<uint8_t>(isa::Op::CAL_CTU))
|
||||
? ((cv >= pv) ? 1 : 0)
|
||||
: ((cv <= 0) ? 1 : 0);
|
||||
if (!slot_set(static_cast<uint16_t>(slot + 4), cv)) return Fault::BadSlot;
|
||||
if (!slot_set(static_cast<uint16_t>(slot + 3), q)) return Fault::BadSlot;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
if (op == static_cast<uint8_t>(isa::Op::CAL_CTUD)) {
|
||||
// cu=0 cd=1 r=2 lu=3 pv=4 qu=5 qd=6 cv=7
|
||||
if (static_cast<size_t>(slot) * 2 + 1 >= edge_prev_.size()) return Fault::BadSlot;
|
||||
if (!slot_get(static_cast<uint16_t>(slot + 0), &v)) return Fault::BadSlot;
|
||||
const bool cu = v != 0;
|
||||
if (!slot_get(static_cast<uint16_t>(slot + 1), &v)) return Fault::BadSlot;
|
||||
const bool cd = v != 0;
|
||||
if (!slot_get(static_cast<uint16_t>(slot + 2), &v)) return Fault::BadSlot;
|
||||
const bool r = v != 0;
|
||||
if (!slot_get(static_cast<uint16_t>(slot + 3), &v)) return Fault::BadSlot;
|
||||
const bool lu = v != 0;
|
||||
if (!slot_get(static_cast<uint16_t>(slot + 4), &v)) return Fault::BadSlot;
|
||||
const int64_t pv = v;
|
||||
int64_t cv = 0;
|
||||
if (!slot_get(static_cast<uint16_t>(slot + 7), &cv)) return Fault::BadSlot;
|
||||
const bool prev_cu = edge_prev_[slot * 2] != 0;
|
||||
const bool prev_cd = edge_prev_[slot * 2 + 1] != 0;
|
||||
if (r) {
|
||||
cv = 0;
|
||||
} else if (lu) {
|
||||
cv = pv;
|
||||
} else {
|
||||
if (cu && !prev_cu) cv += 1;
|
||||
if (cd && !prev_cd) cv -= 1;
|
||||
}
|
||||
edge_prev_[slot * 2] = cu ? 1 : 0;
|
||||
edge_prev_[slot * 2 + 1] = cd ? 1 : 0;
|
||||
const int64_t qu = (cv >= pv) ? 1 : 0;
|
||||
const int64_t qd = (cv <= 0) ? 1 : 0;
|
||||
if (!slot_set(static_cast<uint16_t>(slot + 7), cv)) return Fault::BadSlot;
|
||||
if (!slot_set(static_cast<uint16_t>(slot + 5), qu)) return Fault::BadSlot;
|
||||
if (!slot_set(static_cast<uint16_t>(slot + 6), qd)) return Fault::BadSlot;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
if (op == static_cast<uint8_t>(isa::Op::CAL_R_TRIG) ||
|
||||
op == static_cast<uint8_t>(isa::Op::CAL_F_TRIG)) {
|
||||
// clk=0 q=1
|
||||
if (static_cast<size_t>(slot) * 2 >= edge_prev_.size()) return Fault::BadSlot;
|
||||
if (!slot_get(static_cast<uint16_t>(slot + 0), &v)) return Fault::BadSlot;
|
||||
const bool clk = v != 0;
|
||||
const bool prev = edge_prev_[slot * 2] != 0;
|
||||
const int64_t q = (op == static_cast<uint8_t>(isa::Op::CAL_R_TRIG))
|
||||
? ((clk && !prev) ? 1 : 0)
|
||||
: ((!clk && prev) ? 1 : 0);
|
||||
edge_prev_[slot * 2] = clk ? 1 : 0;
|
||||
if (!slot_set(static_cast<uint16_t>(slot + 1), q)) return Fault::BadSlot;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
return Fault::BadOp;
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
|
||||
Reference in New Issue
Block a user