Compare commits
146
Commits
9a35a881d1
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af81109e8a | ||
|
|
c53e84f180 | ||
|
|
766e3a39b2 | ||
|
|
c19e306512 | ||
|
|
f32a8c6aa6 | ||
|
|
68261cd78b | ||
|
|
92e91350cc | ||
|
|
536ad1a96b | ||
|
|
94634dc0b0 | ||
|
|
aa0c66780c | ||
|
|
6fa4eda020 | ||
|
|
38202fd21e | ||
|
|
7e95e8200e | ||
|
|
84de006665 | ||
|
|
98d2e86dca | ||
|
|
bc10307e0a | ||
|
|
5eda33fed6 | ||
|
|
06ff7d8359 | ||
|
|
dbedc17032 | ||
|
|
4f3da55ed7 | ||
|
|
384e681ef3 | ||
|
|
7b4961f7ed | ||
|
|
b19a61aabf | ||
|
|
2c5852797a | ||
|
|
316b252afc | ||
|
|
8ded7090c8 | ||
|
|
6be2873c88 | ||
|
|
6ab2195a6b | ||
|
|
250a0c9fac | ||
|
|
1335283279 | ||
|
|
a7f89dc180 | ||
|
|
65ccd6c2b7 | ||
|
|
f7de78e528 | ||
|
|
4785cf3e3b | ||
|
|
924c0c1923 | ||
|
|
246fe09646 | ||
|
|
a4335693d4 | ||
|
|
fc76fea47b | ||
|
|
dd5e69d2f7 | ||
|
|
7745dadd2d | ||
|
|
7caa407485 | ||
|
|
ec1df225e2 | ||
|
|
59d7ff3159 | ||
|
|
53df69f4be | ||
|
|
3431f83e7c | ||
|
|
638e0ef058 | ||
|
|
146abd2929 | ||
|
|
7769b515b0 | ||
|
|
2cc09903c9 | ||
|
|
721dfabf87 | ||
|
|
d38d78c942 | ||
|
|
6869624f09 | ||
|
|
44370c448e | ||
|
|
c9d7c16d6d | ||
|
|
c193c760ef | ||
|
|
c9024a060b | ||
|
|
af7054480f | ||
|
|
a6b6990834 | ||
|
|
ee1e06c628 | ||
|
|
1c0c4d02f2 | ||
|
|
134f1748ec | ||
|
|
fda4f7a3ec | ||
|
|
ed78f06e29 | ||
|
|
5b10c2c826 | ||
|
|
21c4b953f9 | ||
|
|
770e92d109 | ||
|
|
e98054706c | ||
|
|
a91528a228 | ||
|
|
8f7140be64 | ||
|
|
5e6f2b45fc | ||
|
|
9e2a6e95f1 | ||
|
|
91fc00ed6d | ||
|
|
6e8b9896f2 | ||
|
|
109f9a7619 | ||
|
|
3ec803c5a8 | ||
|
|
b70bea6b54 | ||
|
|
06a497636e | ||
|
|
d109a3ca77 | ||
|
|
7aa2bbefcf | ||
|
|
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 | ||
|
|
93a4662d6a | ||
|
|
36f2f5637c | ||
|
|
6f02c18bbb | ||
|
|
93d2c96f20 | ||
|
|
4ba44459e2 | ||
|
|
d4e2c1ae7c | ||
|
|
9a07f938f9 | ||
|
|
c076c25368 | ||
|
|
6df3465b37 | ||
|
|
223dc3eb45 | ||
|
|
f1d53de2d1 | ||
|
|
a82be632f6 | ||
|
|
ef94ab1987 | ||
|
|
dd7f7bcba7 | ||
|
|
c378411637 | ||
|
|
eba48f852e | ||
|
|
7c855b1618 | ||
|
|
94accd52c3 |
@@ -0,0 +1,6 @@
|
||||
[submodule "third_party/tomlplusplus"]
|
||||
path = third_party/tomlplusplus
|
||||
url = https://github.com/marzer/tomlplusplus.git
|
||||
[submodule "third_party/FTXUI"]
|
||||
path = third_party/FTXUI
|
||||
url = https://github.com/ArthurSonzogni/FTXUI.git
|
||||
+1
-1
@@ -12,7 +12,7 @@ include("${CMAKE_SOURCE_DIR}/cmake/Standards.cmake")
|
||||
add_subdirectory(isa)
|
||||
add_subdirectory(compiler)
|
||||
add_subdirectory(vm)
|
||||
add_subdirectory(host)
|
||||
add_subdirectory(executor)
|
||||
|
||||
# 自动化用例
|
||||
enable_testing()
|
||||
|
||||
+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,168 @@
|
||||
# STCompiler 使用说明
|
||||
|
||||
把 ST 工程(`project.toml` + 若干 `.st`)编译成单一映像 `.stb`。执行由 `BytecodeExecutor` 负责。
|
||||
|
||||
## 构建
|
||||
|
||||
```bash
|
||||
cmake --preset gcc-debug # 配置
|
||||
cmake --build --preset gcc-debug # 编译
|
||||
ctest --test-dir build/gcc-debug # 测试(14/14 全绿)
|
||||
```
|
||||
|
||||
可执行文件:`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 (399 bytes)
|
||||
header:
|
||||
magic = 0x43545353 # 魔数 "STSC"
|
||||
version = 2 # 格式版本(V2)
|
||||
model = STATOR2 # 型号标识
|
||||
...
|
||||
n_slots = 5 # 槽表条目数
|
||||
values_size = 7 # 值段字节数
|
||||
max_stack = 116 # 栈区字节数(编译期算)
|
||||
sha = ok # 文件尾 SHA-256 完整性
|
||||
functions:
|
||||
fn 0: nregs=8, offset=0, len=4
|
||||
0x0098 RET
|
||||
fn 1: nregs=13, offset=4, len=164
|
||||
0x009c LOAD.U8 r8, s1
|
||||
0x00a4 STORE_OFF.U8 r8, s4, 0
|
||||
...
|
||||
slots:
|
||||
[0] addr=0
|
||||
data (7 bytes):
|
||||
0x0000: ...
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- 指令偏移是**文件绝对字节偏移**,可直接对照 `xxd line1.stb`(V2 指令变长 4/8/16/32B)
|
||||
- 映像里**没有符号名**(函数表只有 fn_id,槽表/值段无名字映射),因此按 `fn 0` / `fn 1`、槽号显示;`--dump-map` 可打印变量/槽映射
|
||||
- `model=` 显示型号标识(`machine.toml [meta]` 生成);`sha=ok|BAD` 显示文件尾 SHA-256 校验结果
|
||||
- 文件损坏或打不开 → `error: ...` 退出码 1
|
||||
|
||||
## 编译产物(.stb 加固)
|
||||
|
||||
- **型号标识[32]**:`[meta] name + version` 拼成(如 `"STATOR2"`),写进头(V2 头 128B,`kOffModelId`=32)
|
||||
- **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 (399 bytes, 2 functions, 4 globals)
|
||||
```
|
||||
|
||||
产物:`line1.stb`(399 字节:头 128B 含型号标识 + 各段 + 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` / `VAR_TEMP`(每次调用重新初始化,不跨周期持久;FB 内不占实例字段)。
|
||||
|
||||
**类型**:19 种(`BOOL`/`SINT`/`INT`/`DINT`/`LINT`/`BYTE`/`WORD`/`DWORD`/`LWORD`/`USINT`/`UINT`/`UDINT`/`ULINT`/`REAL`/`LREAL`/`TIME`/`DATE`/`TOD`/`DT`);内建 FB 15 条(`TON`/`TOF`/`TP`/`CTU`/`DCTU`/`CTD`/`DCTD`/`CTUD`/`DCTUD`/`R_TRIG`/`F_TRIG`/`SR`/`RS`/`PWM`/`RTC`,关键字,不可作变量名;FB 名三层判定:toml fb 表 → 用户 FB → 类型名)。
|
||||
|
||||
**语句**:赋值 `:=`、`IF / ELSIF / ELSE / END_IF`、`WHILE / END_WHILE`、FB 调用 `fb(in := ..., ...);`、字段读 `fb.Q`。
|
||||
|
||||
**表达式**:字面量(`TRUE`/`FALSE`、整数、`T#10ms` 式 TIME、`R#`/`D#`/`TOD#`/`DT#` 浮点与日期)、`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 字段赋值、字符串、用户类型套娃、**隐式类型转换**(类型检查层拒绝,字面量按目标类型适配)、函数循环调用(链接层拒绝)、`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 14 项(`compiler_toml` / `lexer_tokens` / `parser_syntax` / `linker_links` / `typecheck_types` / `codegen_slice1` / `isa_roundtrip` / `machine_config` / `vm_test` / `vm_cycles` / `cases_all` / `cli_*`)
|
||||
|
||||
## 当前状态
|
||||
|
||||
- ✅ 可编译:14 个正例用例全部产出 `.stb`(V2 头 128B + 型号标识 + SHA-256);6 个负例按期望拒绝
|
||||
- ✅ 执行:`BytecodeExecutor` 加载 `.stb` 跑扫描周期(型号/SHA 校验、回放、单步),V2 槽表两级采样
|
||||
- ✅ 配置驱动:指令 opcode / 类型元数据 / FB 布局来自 `compiler/machine.toml`(`--machine`)
|
||||
@@ -0,0 +1,132 @@
|
||||
# 寄存器码(V2)
|
||||
|
||||
compiler 模块的代码生成。输入:`Project` + `Unit`(AST)+ `LinkResult`;输出:`.stb` 映像字节(`std::vector<uint8_t>`)。全工程定案见 [`初步计划.md`](../初步计划.md) 12.8,指令/映像布局见 [`指令与映像.md`](../isa/指令与映像.md),槽表/值段格式见 [`stb文件格式.md`](../isa/stb文件格式.md)。
|
||||
|
||||
## 做 / 不做
|
||||
|
||||
**做**
|
||||
|
||||
- 寄存器分配:局部/变量固定寄存器,表达式临时值往后编号,`nregs` 写进函数头
|
||||
- 跳转先留洞、后回填(字节单位,相对下一条);`AND` / `OR` **必须编成跳转**(短路),禁止两边都算
|
||||
- 常量表(`LOADK`,8B 原始值无 tag,按 func 解释)、全局/实例槽(`LOAD` / `STORE` / `LOAD_OFF` / `STORE_OFF`)、FB 共享体基址访问(`LOAD_PTR` / `STORE_PTR`)
|
||||
- `CALL fn_id` 有界帧;用户 FB **共享体函数化**(r1 = 实例指针);内建 `CAL fb_id, 实例槽`
|
||||
- 拼装映像:V2 头 128B / 函数表 / 常量表 / 字节码 / 槽表 / 值段 + SHA-256 尾部
|
||||
- `max_stack`:MAIN + FUNCTION 调用图 DAG 最坏路径 Σ(12 + nregs×8);递归 = 编译期报错
|
||||
|
||||
**不做(第一版)**
|
||||
|
||||
- 图染色、SSA、生命周期分析、优化、异常表、闭包
|
||||
- 函数指针、间接跳转、动态实例
|
||||
|
||||
## 存储布局(V2 定案)
|
||||
|
||||
| 数据 | 放哪 | 说明 |
|
||||
|---|---|---|
|
||||
| PROGRAM / FUNCTION 标量变量与临时 | **寄存器**(函数帧) | 每周期帧保留由 VM 负责(12.9) |
|
||||
| 全局(含 I/Q/M)与 FB 实例 | **值区**(紧凑变宽,1/2/4/8 对齐) | 槽表条目(8B:addr:u32 + 预留)指到值区偏移;状态跨周期持久 |
|
||||
|
||||
- **槽表两级**:指令操作数 = **槽号**(u32)→ 槽表条目 → 值区字节偏移;`slot_addr(slot)` 由 VM/Image 提供
|
||||
- 全局按 GVL 声明序编槽;**FB 实例 = 1 个槽表条目**,字段在实例基址上按偏移访问(字段偏移编译期按 1/2/4/8 对齐计算,字段类型 = machine.toml `[[fb]]` 表)
|
||||
- 字段访问 `fb.Q`:`LOAD_OFF rd, <实例槽>, <偏移>` / `STORE_OFF <实例槽>, <偏移>, rs`(16B SLOT+off 指令,调用点/输出读用);FB 共享体函数体内:`LOAD_PTR` / `STORE_PTR rd, r1, <偏移>`(16B RS+off,基址寄存器 r1 = 实例指针);I/Q/M 同普通槽(`LOAD` / `STORE`)
|
||||
- 函数帧寄存器:`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 (func = 语言类型;REAL/LREAL 位模式按目标类型装载)
|
||||
变量读 LOAD rd, slot (帧内直接 MOVE)
|
||||
字段读 LOAD_OFF 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> NEG rd, t
|
||||
|
||||
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_OFF <实例槽>, <字段偏移>, 临时
|
||||
LOADK r1, <实例值区地址> CALL <fb_fn_id>
|
||||
(FB 函数体内:字段访问 = LOAD_PTR/STORE_PTR rd, r1, <字段偏移>)
|
||||
内建 实参同左 → CAL <fb_id>, <实例槽>
|
||||
```
|
||||
|
||||
- 常量统一进常量表(8B 原始值,位模式去重;BOOL / 整数 / 时间 / 浮点 / 日期),`const_id` = 首次出现序;`LOADK` 按 func 解释位模式
|
||||
- 指令 func 位段 = 语言类型对应(`func_of_name`:宽度 × 符号 + F32/F64)
|
||||
|
||||
## 用户 FB 共享体(函数化,2026-08-26 实施)
|
||||
|
||||
FB 体共享会与「绝对字段地址」冲突(不同实例基址不同),V1 内联;V2 改为**共享体函数化**:
|
||||
|
||||
- FB 编译为**独立共享函数**(fn_id 收集序):`r1` = 实例指针(值区地址,隐藏参数/this),字段访问 `LOAD_PTR/STORE_PTR rd, r1, 字段偏移`
|
||||
- 调用点:实参 `STORE_OFF`(绝对槽)→ `LOADK r1, c(实例addr)` → `CALL fb_fn_id`(2 条指令替代整个展开体)
|
||||
- 嵌套(FB 函数内调 FUNCTION):实参区 r1..r7 会覆盖实例指针 → 调用点自动 `MOVE t, r1` / 调用后 `MOVE r1, t` 保护
|
||||
- `VAR_TEMP` 落 FB 帧寄存器,帧每周期重建 → 天然每次调用重置(内联期的寄存器隔离 hack 退役)
|
||||
- 多实例共享同一函数体,状态经 r1 指针隔离(`count` 等 `VAR` 仍在实例字段)
|
||||
- FB 无递归、无动态实例(链接层保证),函数化安全
|
||||
|
||||
## 映像拼装(V2)
|
||||
|
||||
```text
|
||||
头 128B:magic / version=2 / model=STATOR2 / cycle_limit / dt_ms / 工程哈希 /
|
||||
entry_fn_id / n_globals / n_consts / n_funcs / 各段偏移 /
|
||||
n_slots / values_size / meta / max_stack
|
||||
函数表:收集序 fn_id → { nregs, code_offset(相对字节码段), code_len }
|
||||
常量表:const_id → { value:u64 }(无 tag)
|
||||
字节码:各函数指令连续
|
||||
槽表:每条目 8B(addr:u32 + 预留:u32)
|
||||
值段:全局 + FB 实例紧凑变宽
|
||||
尾部:SHA-256(覆盖头部到值段末尾)
|
||||
```
|
||||
|
||||
- 用 `isa` 的 `enc_*` 编码指令(变长 4/8/16/32B,前缀分区)
|
||||
- 工程哈希用 12.3 的 `compute_project_hash`(FNV-1a 64)
|
||||
- `--dump-map` 可打印变量/槽映射(对照源码 ↔ 反汇编)
|
||||
|
||||
## 错误
|
||||
|
||||
稳定前缀 `codegen error`:
|
||||
|
||||
```text
|
||||
codegen error: register overflow (>256) in function 'f'
|
||||
codegen error: constant table overflow
|
||||
codegen error: no storage for 'x'
|
||||
codegen error: no instance 't' in 'MAIN'
|
||||
codegen error: recursive CALL/FB nesting too deep
|
||||
```
|
||||
|
||||
## 完成标准
|
||||
|
||||
1. `line1` 能写出映像(函数字节码 + 槽表/值段 + 工程哈希 + SHA-256),`StbView` 校验通过
|
||||
2. 跳转回填无悬空(`--disasm` 抽查)
|
||||
3. 用例 1~9、13、15、17、18、20 编译出映像;负例(10/11/12/14/16/19)仍在前层被拒
|
||||
4. 全部构建 + `ctest` 无回归(14/14)
|
||||
@@ -0,0 +1,129 @@
|
||||
# 用例反汇编对照(源码 ↔ 指令验证)
|
||||
|
||||
> 状态:**13 个正例全部对照通过(2026-08-26)**。方法:`STCompiler` 编译 → `--dump-map`
|
||||
> 取变量/槽映射 → `--disasm` 取指令序列 → 逐条对照源码语义。
|
||||
> 每用例记录「源码语句 ↔ 指令序列 ↔ 结论」;完整输出见 build/audit/(临时)。
|
||||
|
||||
## 01_empty_main:最小映像
|
||||
|
||||
```text
|
||||
PROGRAM MAIN → fn0: RET(nregs=8, 4 字节)✓
|
||||
```
|
||||
|
||||
## 02_bool_assign:常量表 + BOOL 字面量
|
||||
|
||||
| 源码 | 指令 | 结论 |
|
||||
|---|---|---|
|
||||
| a := TRUE | LOADK.U8 r8, 0(常量[0]=1) | ✓ |
|
||||
| b := FALSE | LOADK.U8 r9, 1(常量[1]=0) | ✓ |
|
||||
|
||||
常量表 8B 无 tag、BOOL func=U8 ✓
|
||||
|
||||
## 03_short_circuit:短路 AND/OR
|
||||
|
||||
| 源码 | 指令 | 结论 |
|
||||
|---|---|---|
|
||||
| x := a AND b | MOVE r10,r8 / JF r10,+8 / MOVE r11,r9 / MOVE r10,r11 | ✓ 短路跳过 rhs |
|
||||
| x := a OR b | MOVE r10,r8 / JT r10,+8 / ... | ✓ |
|
||||
|
||||
## 04_if_elsif_else:分支跳转
|
||||
|
||||
| 源码 | 指令 | 结论 |
|
||||
|---|---|---|
|
||||
| IF sel=0 THEN out:=10 | MOVE/LOADK(0)/CMP_EQ.I16/JF +16/LOADK(10)/JMP +52 | ✓ |
|
||||
| ELSIF sel=1 THEN out:=20 | MOVE/LOADK(1)/CMP_EQ/JF/LOADK(20)/JMP +8 | ✓ |
|
||||
| ELSE out:=30 | LOADK(30) | ✓ |
|
||||
|
||||
跳转回填、常量正确 ✓
|
||||
|
||||
## 05_while_normal:WHILE 回填
|
||||
|
||||
| 源码 | 指令 | 结论 |
|
||||
|---|---|---|
|
||||
| n := 0 | LOADK.I16 r8, 0 | ✓ |
|
||||
| WHILE n<10 | MOVE/LOADK(10)/CMP_LT.I16/JF +28 | ✓ |
|
||||
| n := n+1 | MOVE/LOADK(1)/ADD.I16 r8 | ✓ |
|
||||
| 回跳 | JMP -56 | ✓ |
|
||||
|
||||
## 07_int_arith:算术 func
|
||||
|
||||
| 源码 | 指令 | 结论 |
|
||||
|---|---|---|
|
||||
| a := 7*6 | LOADK(7)/LOADK(6)/MUL.I16 r8 | ✓ |
|
||||
| b := a/4 | MOVE/LOADK(4)/DIV.I16 r9 | ✓ |
|
||||
| c := a-b | MOVE/MOVE/SUB.I16 r10 | ✓ |
|
||||
| eq := c>30 | MOVE/LOADK(30)/CMP_GT.I16 | ✓ |
|
||||
|
||||
## 08_time_literal:TIME 字面量
|
||||
|
||||
| 源码 | 指令 | 结论 |
|
||||
|---|---|---|
|
||||
| t1 := T#10ms | LOADK.I64 r8, 0(常量 10) | ✓ |
|
||||
| t2 := T#1s250ms | LOADK.I64 r9, 1(常量 1250) | ✓ |
|
||||
|
||||
## 09_gvl_external:全局 + 初值
|
||||
|
||||
| 项 | 值 | 结论 |
|
||||
|---|---|---|
|
||||
| G1 槽 | slot=0 off=0 type=int func=3 | ✓ |
|
||||
| G1 初值 | 值段 `05 00`(5 小端) | ✓ |
|
||||
| x := G1 | LOAD.I16 r8, s0 | ✓ |
|
||||
|
||||
## 13_function_call:调用约定 + max_stack
|
||||
|
||||
| 源码 | 指令 | 结论 |
|
||||
|---|---|---|
|
||||
| Add: a+b | MOVE r8,r1 / MOVE r9,r2 / ADD.I16 r0 | ✓ r0 结果 |
|
||||
| x := Add(3,4) | LOADK(3)/LOADK(4)/MOVE r1/MOVE r2/CALL 0/MOVE r8,r0 | ✓ 实参 r1..r7 |
|
||||
| max_stack | 192 = (12+11×8) + (12+10×8) | ✓ 编译期 DAG |
|
||||
| 映射 | add: a=r1 b=r2 result=r0;main: x=r8 | ✓ |
|
||||
|
||||
## 15_fb_instance:用户 FB 共享体(函数化)
|
||||
|
||||
| 源码 | 指令 | 结论 |
|
||||
|---|---|---|
|
||||
| starter(start:=TRUE,...) | LOADK(TRUE)/STORE_OFF r9,s0,0;LOADK(FALSE)/STORE_OFF r10,s0,1 → LOADK.U32 r1,1 → CALL 0 | ✓ 实参绝对槽 + r1 实例指针 |
|
||||
| Q := start AND NOT stop | fn0(共享体):LOAD_PTR.U8 r8,r1,0 / JF / LOAD_PTR.U8 r10,r1,1 / NOT / MOVE / STORE_PTR.U8 r8,r1,2 | ✓ 基址寻址+短路 |
|
||||
| q := starter.Q | LOAD_OFF r8, s0, 2 | ✓ 输出读绝对槽 |
|
||||
|
||||
值段 3B、映射 start@0 stop@1 q@2 ✓;fn0 单函数体服务所有实例 ✓
|
||||
|
||||
## 17_ton:内建 TON
|
||||
|
||||
| 源码 | 指令 | 结论 |
|
||||
|---|---|---|
|
||||
| t(in:=TRUE, pt:=T#30ms) | STORE_OFF.U8 s0,0;STORE_OFF.I64 s0,8 | ✓ pt=TIME→I64 |
|
||||
| 调用 | CAL.TON s0 | ✓ fb 名显示 |
|
||||
| q := t.Q | LOAD_OFF.U8 s0,16 | ✓ 偏移 16 |
|
||||
|
||||
值段 32B(in@0/pt@8/q@16/et@24)✓
|
||||
|
||||
## 18_tof_ctu:双实例 + 字段偏移
|
||||
|
||||
| 源码 | 指令 | 结论 |
|
||||
|---|---|---|
|
||||
| tf(in:=FALSE, pt:=T#20ms) | STORE_OFF s0,0/8;CAL.TOF s0;LOAD_OFF s0,16 | ✓ |
|
||||
| c(cu:=TRUE,r:=FALSE,pv:=3) | STORE_OFF s1,0/1;STORE_OFF.I16 s1,2;CAL.CTU s1 | ✓ pv=INT 2B |
|
||||
| q2 := c.Q / cnt := c.CV | LOAD_OFF.U8 s1,4;LOAD_OFF.I16 s1,6 | ✓ q@4 cv@6 |
|
||||
|
||||
槽表 2 条(addr 0/32)、值段 40B ✓
|
||||
|
||||
## 20_line1:全链路
|
||||
|
||||
| 源码 | 指令 | 结论 |
|
||||
|---|---|---|
|
||||
| starter(start:=I0_0, stop:=I0_1) | LOAD.U8 r8,s1 → STORE_OFF s4,0;LOAD.U8 r9,s2 → STORE_OFF s4,1 → LOADK.U32 r1 → CALL 0 | ✓ I 读 + 共享体调用 |
|
||||
| Q := start AND NOT stop AND NOT EmergencyStop | fn0:LOAD_PTR r1,0/JF/LOAD_PTR r1,1/NOT/MOVE/JF/LOAD s0(急停)/NOT/MOVE/STORE_PTR r1,2 | ✓ 基址+双短路+全局 |
|
||||
| Q0_0 := starter.Q | LOAD_OFF s4,2 / STORE.U8 r8,s3 | ✓ Q 写 |
|
||||
|
||||
槽表 5 条(addr 0..4)、值段 7B ✓
|
||||
|
||||
## 结构自检(stbcheck,13 用例)
|
||||
|
||||
寄存器越界 / func 非法 / 跳转越界 / 槽号越界 / 指令长度越界:**全部通过**。
|
||||
|
||||
## 结论
|
||||
|
||||
- 编译器生成正确性:**13 正例全部对照通过**
|
||||
- 结构自检:**全部通过**
|
||||
- 已知缺陷(已修,阶段5-1):LREAL 字面量按目标 func 装载(LREAL → LOADK.F64,REAL → LOADK.F32);实测位模式 0x3FC00000(float 1.5)/ 0x4004000000000000(double 2.5)精确
|
||||
@@ -0,0 +1,124 @@
|
||||
# 符号表与链接(12.6)
|
||||
|
||||
compiler 模块的符号收集与链接。输入:`Project`(toml)+ 各 `.st` 文件的 `Unit`(AST,见 [`语法.md`](语法.md));输出:链接结果(全局槽表、POU 符号表、函数表、FB 实例布局)。全工程定案见 [`初步计划.md`](../初步计划.md) 12.6。
|
||||
|
||||
## 做 / 不做
|
||||
|
||||
**做**
|
||||
|
||||
- 先扫完全部文件再解析引用(禁止边解析边执行)
|
||||
- 全局收集:仅 `gvl.file` 顶层 `VAR_GLOBAL`,声明顺序 = 全局槽顺序
|
||||
- `VAR_EXTERNAL` 接已有槽,种类/类型一致,不是新变量
|
||||
- `FUNCTION` 标无状态;`FUNCTION_BLOCK` 必须先有实例才能调用
|
||||
- `CALL` 目标解析成 `fn_id`(立即数),无函数指针
|
||||
- 校验:同名全局、未声明 `io.var`、重复 POU 名、未声明 FB 实例、函数循环调用 → 失败
|
||||
- FB 实例布局:按类型算字段清单(输入/输出/内部;内建 FB 15 条字段来自 machine.toml `[[fb]]` 表,编译期按 1/2/4/8 对齐计算偏移)
|
||||
|
||||
**不做(第一版)**
|
||||
|
||||
- 寄存器分配(12.8)、类型检查(12.7)
|
||||
- 多 GVL 文件、I/Q/M 与全局分槽(v1 **单一数据区**,见下)
|
||||
|
||||
## 数据区定址(V2 定案)
|
||||
|
||||
- **槽表两级**:全部全局(含 I/Q/M)与 FB 实例统一编槽号(0..n-1);槽表条目(8B:addr:u32 + 预留)指向**值区**字节偏移;值区按变量类型紧凑变宽(1/2/4/8 对齐)
|
||||
- 全局按 `globals.st` 声明顺序编槽;**FB 实例 = 1 个槽表条目**,字段走 `LOAD_OFF` / `STORE_OFF` 实例槽 + 编译期算死的字段偏移
|
||||
- 映像头 `n_globals` = 全局声明总数;`n_i` / `n_q` / `n_m` = 0(第一版不拆,见 [`初步计划.md`](../初步计划.md) 5.1)
|
||||
- 访问统一 `LOAD` / `STORE`(+ `LOAD_OFF` / `STORE_OFF`),无 I/Q/M 专用操作码
|
||||
- io 绑定(toml)只负责 channel/bit → 槽号映射(sidecar),不创造变量
|
||||
|
||||
## 符号表一行
|
||||
|
||||
```cpp
|
||||
enum class SymbolKind { Global, External, Local, Input, Output, Pou, FbInstance, Const };
|
||||
|
||||
struct Symbol {
|
||||
SymbolKind kind;
|
||||
std::string name; // 小写
|
||||
std::string type_name; // bool / int / time / fb 类型名(小写)
|
||||
uint32_t address; // 全局槽号(Global/External);POU 局部暂 0(12.8 分配)
|
||||
std::string source_file;
|
||||
uint32_t line, col;
|
||||
};
|
||||
```
|
||||
|
||||
`kind` 与 12.1 冻结表一致:`global` / `external` / `local` / `input` / `output` / `pou` / `fb_instance` / `const`(`const` 本期未用,保留)。
|
||||
|
||||
## 链接流程
|
||||
|
||||
```text
|
||||
1. 收集导出
|
||||
遍历全部文件:
|
||||
- 顶层 globals:仅 gvl.file 允许;同名 → duplicate global;声明顺序编槽号
|
||||
- POU 导出:program / function / fb 名登记;重复 → duplicate POU
|
||||
2. 逐 POU 建局部符号
|
||||
VAR / VAR_INPUT / VAR_OUTPUT → local / input / output
|
||||
VAR_EXTERNAL → 查全局表,必须存在、类型一致 → External(同一槽)
|
||||
POU 内 VAR_GLOBAL → error(仅 gvl 顶层允许)
|
||||
3. 语句引用解析
|
||||
- FB 调用 instance(...) → 局部表查实例,类型必须是 FB → 否则 undeclared FB instance
|
||||
- 字段读 fb.field → 实例类型有该字段(用户 FB 的 input/output;内建固定字段)
|
||||
- VarRef / 赋值左值 → 局部表或全局表查得到 → 否则 undeclared identifier
|
||||
- 函数调用 fn(...) → POU 表查得到且是 FUNCTION → 记依赖边;FUNCTION 不得调用 PROGRAM/FB
|
||||
4. 循环调用检测
|
||||
函数依赖图 DFS 找环 → recursive call(用例 19)
|
||||
5. io 校验
|
||||
io.var 必须在全局表 → 否则 io.var not in GVL(用例 12)
|
||||
6. 输出
|
||||
全局表 / POU 符号表 / 函数表(fn_id 按收集序)/ FB 实例布局
|
||||
```
|
||||
|
||||
## FB 实例布局
|
||||
|
||||
- 用户 FB:字段 = 全部 `VAR_INPUT` + `VAR_OUTPUT` + `VAR`(内部),按声明段序排
|
||||
- 内建 FB 15 条(`compiler/machine.toml` `[[fb]]` 表驱动,fb_id 0..14):
|
||||
|
||||
| fb_id | FB | 字段(名 → 类型) |
|
||||
|---|---|---|
|
||||
| 0 | TON | in BOOL, pt TIME, q BOOL, et TIME |
|
||||
| 1 | TOF | 同 TON |
|
||||
| 2 | TP | 同 TON |
|
||||
| 3 | CTU | cu BOOL, r BOOL, pv INT, q BOOL, cv INT |
|
||||
| 4 | DCTU | cu BOOL, r BOOL, pv DINT, q BOOL, cv DINT |
|
||||
| 5 | CTD | cd BOOL, ld BOOL, pv INT, q BOOL, cv INT |
|
||||
| 6 | DCTD | cd BOOL, ld BOOL, pv DINT, q BOOL, cv DINT |
|
||||
| 7 | CTUD | cu BOOL, cd BOOL, r BOOL, lu BOOL, pv INT, qu BOOL, qd BOOL, cv INT |
|
||||
| 8 | DCTUD | cu BOOL, cd BOOL, r BOOL, lu BOOL, pv DINT, qu BOOL, qd BOOL, cv DINT |
|
||||
| 9 | R_TRIG | clk BOOL, q BOOL |
|
||||
| 10 | F_TRIG | clk BOOL, q BOOL |
|
||||
| 11 | SR | S1 BOOL, R BOOL, Q1 BOOL |
|
||||
| 12 | RS | SET BOOL, RESET1 BOOL, Q1 BOOL |
|
||||
| 13 | PWM | in BOOL, pt TIME, duty REAL, q BOOL, et TIME |
|
||||
| 14 | RTC | enable BOOL, date DATE, tod TOD |
|
||||
|
||||
- 实例布局 = 类型字段表的一份拷贝(`VAR_TEMP` 不是字段,不持久,编译期映射为帧寄存器 + 每次调用初值装载);字段偏移编译期按 1/2/4/8 对齐计算(字段类型决定宽度),实例 = 槽表 1 条
|
||||
|
||||
## 错误
|
||||
|
||||
稳定前缀 `link error`:
|
||||
|
||||
```text
|
||||
link error: duplicate global 'g1' (globals.st, line 4, col 1)
|
||||
link error: VAR_GLOBAL only allowed in gvl file (other.st, line 2, col 1)
|
||||
link error: io.var 'Nope' not in GVL (project.toml)
|
||||
link error: undeclared FB instance 'starter' (main.st, line 4, col 5)
|
||||
link error: recursive call involving 'a' (main.st, line 2, col 4)
|
||||
link error: undeclared identifier 'x' (main.st, line 3, col 3)
|
||||
```
|
||||
|
||||
## 完成标准
|
||||
|
||||
1. 用例 9(同一槽)、10(非 GVL 写全局)、11(同名全局)、12(io.var 未声明)、16(未声明实例)、19(循环调用)按表变绿
|
||||
2. 符号表能打印 `name kind type address file`
|
||||
3. 全部构建 + `ctest` 无回归
|
||||
|
||||
---
|
||||
|
||||
## 执行计划(单步确认)
|
||||
|
||||
1. **`Linker.h`(新建 `compiler/include/compiler/Linker.h`)**:`Symbol`/`SymbolKind` + 链接结果结构 + `link_project` 声明
|
||||
2. **`Linker.cpp` 前半**:收集导出(gvl 校验 / 全局定槽 / POU 登记)+ 逐 POU 局部符号
|
||||
3. **`Linker.cpp` 后半**:引用解析(FB 实例 / 字段 / 变量 / 函数)+ 循环检测 + io 校验
|
||||
4. **`linker_test`(`tests/src/linker_test.cpp` + CMake)**:用例 09/10/11/12/16/19 + 符号表打印断言
|
||||
5. **验证**:`cmake --build` + `ctest`(新增 `linker_links` 后 7/7 全绿)
|
||||
6. **提交**:`Linker.h/cpp` + `linker_test` + 文档
|
||||
@@ -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` + 文档
|
||||
@@ -0,0 +1,107 @@
|
||||
# 编译器优化(V2 草案)
|
||||
|
||||
> 状态:**设计中**。本文聚焦**编译器(codegen/优化策略)侧**的现状与 V2 变更;
|
||||
> 指令集/映像层(指令格式、槽表/值区、类型位段)见 [`isa/指令集优化.md`](../isa/指令集优化.md)。
|
||||
> 全部内容为草案,未进入实现。
|
||||
|
||||
## 1. 定位
|
||||
|
||||
| 文档 | 覆盖层 | 内容 |
|
||||
|---|---|---|
|
||||
| [`isa/指令集优化.md`](../isa/指令集优化.md) | ISA/映像层 | 变长指令、槽表/值区、type 位段、映像格式 |
|
||||
| **本文**(compiler) | 编译策略层 | 代码生成策略、POU 调用方式、寄存器分配、优化方向 |
|
||||
|
||||
## 2. V1 现状(已实现的编译策略)
|
||||
|
||||
### 2.1 POU 调用方式(三种)
|
||||
|
||||
| 对象 | V1 处理 | 理由 | 代价 |
|
||||
|---|---|---|---|
|
||||
| 用户 FB | **调用点内联展开** | FB 体访问绝对字段地址(`LOAD_GLOBAL rd, 基槽+字段序`),不同实例基址不同,共享体无法复用;无递归、无动态实例 → 内联安全(12.8 定案) | 字节码随调用点数膨胀 |
|
||||
| FUNCTION | 独立函数 + `CALL fn_id` 帧栈 | 无状态、寄存器约定(r1..r7 实参 / r0 结果)无地址冲突 | 帧压栈 |
|
||||
| 内建 FB | `CAL_*` 指令(VM 原语) | 定时器/计数器在 VM 侧 | 无 |
|
||||
|
||||
### 2.2 其他编译策略(V1 冻结)
|
||||
|
||||
- **短路求值必须编跳转**:`AND`/`OR` 禁止两边都算(`JF(AND)/JT(OR) rd, L_end`)
|
||||
- **寄存器分配**:变量固定前段(r0..k-1)、表达式临时从 k 起、语句结束复用基址(不分析生命周期);`nregs` = 峰值 + 1;上限 256(`register overflow (>256)`)
|
||||
- **调用约定**:r0 结果、r1..r7 参数(≤7)、r8+ 变量/临时;调用点实参求值到 r8+ → `MOVE r(1+i), t` → `CALL` → `MOVE rd, r0`
|
||||
- **跳转留洞回填**:`patch_jump`(相对下一条:目标 = 当前 + 1 + off)
|
||||
- **常量统一进常量表**:`const_id` = 首次出现序,去重
|
||||
- **空条件寄存器**:r0 恒 0(`enc_jmp` 用);一元负号 = `SUB rd, r0, t`
|
||||
- **IF/WHILE 模式**:`JF` 跳 else、`JMP` 连 end、`WHILE` 回跳循环头
|
||||
|
||||
## 3. V2 编译器侧变更(对应指令集优化)
|
||||
|
||||
### 3.1 FB 共享体(基址寻址)——消除内联膨胀
|
||||
|
||||
- **现状问题**:FB 每调用一次复制一份体,字节码膨胀
|
||||
- **V2 定案(已实施,2026-08-26 阶段5)**:FB 编译为**独立共享函数**,调用点 `LOADK r1, c(实例地址)` + `CALL`;函数体内字段访问用 `LOAD_PTR/STORE_PTR rd, r1, off`(16B RS+off 形态,寄存器基址 + 偏移)——同一实例可被多个调用点复用;字段偏移编译期算死
|
||||
- 收益:字节码不再随调用点膨胀(多实例共享一份函数体);VAR_TEMP 落 FB 帧寄存器天然每调用重置;代价:每次字段访问多一个偏移操作数
|
||||
|
||||
### 3.2 类型驱动语义(type 位段红利)
|
||||
|
||||
- CMP/算术按 `type` 位段自动选择语义:无符号**无符号比较**、整数**饱和到类型宽度**(模板化)、浮点 **IEEE 754**(无饱和,溢出→±inf)
|
||||
- 读取按宽度 + 符号/零扩展(`slot_read(addr, width, signed)`)
|
||||
- 常量表 tag = type 位段值(0..15),REAL=14 / LREAL=15 存位模式
|
||||
|
||||
### 3.3 槽表/值区布局(codegen 职责)
|
||||
|
||||
- **槽表段**:每槽 {addr:u32},编译器为每个变量分配槽 + 值区地址(编译期算死)
|
||||
- **值段**:紧凑变宽布局,对齐规则 1/2/8 字节(BOOL 1 / INT 2 / TIME·LREAL·LWORD·ULINT·LINT 8 / REAL·DWORD·UDINT·DINT 4),**对齐由编译器保证**,VM 从 type 位段已知宽度
|
||||
- 初值写值段(编译期 layout);结构体 = 值区连续字段块(元数据段字段表,v1 预留)
|
||||
|
||||
### 3.3b 调用栈与递归(U10 方案 C)
|
||||
|
||||
- **递归 = 编译期错误**:链接层对 FUNCTION 调用图做环检测(自调用/循环调用)→ `link error: recursive call`(V1 用例 19 已是负例,V2 明确为编译期错误)
|
||||
- **max_stack 计算**(链接层/Codegen 职责):无环调用图 → 最坏调用路径 Σ(帧头 12B + 各函数 nregs×8),写入映像头 @116
|
||||
- VM 按 max_stack 预分配栈区(运行期零分配);溢出双检查(字节 + 深度 64)
|
||||
|
||||
### 3.4 变长指令对 codegen 的影响
|
||||
|
||||
- **跳转回填改字节单位**:`patch_jump` 按字节(off32,相对下一条),pc/code_offset/code_len 全字节化
|
||||
- 发射器(E_*)按前缀分区决定长度组(4/8/16B),指令缓冲按字节追加
|
||||
- 取指/对齐:code 段 16 字节对齐
|
||||
|
||||
### 3.5 类型系统扩展(ST 层)
|
||||
|
||||
- **19 个 IEC 基础类型**(BOOL + 位串 4 + 有符号 4 + 无符号 4 + TIME + REAL/LREAL + DATE/TOD/DT)全量进关键字(声明即可用)
|
||||
- **进制前缀**:`2#`/`8#`/`16#` 字面量 + 浮点十进制字面量(`1.5`、`-2.5e3`)
|
||||
- 类型检查:**禁止隐式变量转换**(赋值/比较/算术类型严格一致,编译期报错);**字面量按上下文适配**(`5` 可赋 INT/REAL/DINT,编译期解释);显式转换函数 v1 不做
|
||||
- 进制字面量默认类型归属与适配细则 → 指令集优化.md §4 项 A9
|
||||
|
||||
## 4. 已定案(编译器相关)
|
||||
|
||||
| 项 | 定案 |
|
||||
|---|---|
|
||||
| I/Q/M | 六条合并为 LOAD/STORE 两条(单一值区,语义标签交给 sidecar) |
|
||||
| 结构体 | v1 只留编码(LOAD/STORE 16B 变体 + 元数据段),语法不实现 |
|
||||
| ST 层类型 | 19 类型名全量进关键字 |
|
||||
| 类型转换 | 禁止隐式变量转换;字面量按上下文适配 |
|
||||
| 字面量 | 进制前缀 2#/8#/16# + 浮点十进制 |
|
||||
| FB 共享体 | 实施(16B 变体 = 基址寻址) |
|
||||
|
||||
仍待闭合的细节见指令集优化.md §4(A1..A10,编译器相关:A5 算术语义 / A6 sidecar / A8 machine.toml schema / A9 字面量类型推断)。
|
||||
|
||||
## 5. 影响面(编译器侧)
|
||||
|
||||
| 子模块 | 改动 |
|
||||
|---|---|
|
||||
| Lexer | 类型关键字扩展(19)、进制前缀 + 浮点字面量 |
|
||||
| Parser | 类型引用、字面量 AST |
|
||||
| Typecheck | 禁隐式转换(严格一致)+ 字面量上下文适配、19 类型元数据 |
|
||||
| Linker | 槽表条目分配(addr:u32)、FB 实例字段偏移、**调用图环检测(递归报错)+ max_stack 计算** |
|
||||
| Codegen | E_* 变长发射、字节化回填、槽表+值区 layout、FB 共享体(16B 变体) |
|
||||
| MachineConfig / machine.toml | type 表 19 条(base/width/signed/tag)、前缀分区/func 位段校验 |
|
||||
|
||||
## 6. 实施阶段(合并进指令集优化.md §6)
|
||||
|
||||
- 阶段 2(isa)后接**阶段 3(compiler 侧)**:machine.toml V2 → MachineConfig → Codec → Codegen(槽表/值区/变长发射)→ Stb → 对应测试
|
||||
- FB 共享体(§3.1 已定案)并入阶段 3 的 Codegen 子步骤
|
||||
|
||||
## 7. 决策历史
|
||||
|
||||
- V1 定案(12.8):用户 FB 内联 / FUNCTION CALL / 内建 FB CAL_*;短路必须跳转;固定寄存器分配
|
||||
- V2 讨论:用户确认 V1 内联现状;提出 V2 用基址寻址消除内联膨胀(已定案)
|
||||
- V2 实施(2026-08-26):FB 共享体落地——LOAD_PTR/STORE_PTR(RS+off)+ FB 函数化(r1 实例指针),内联机制退役
|
||||
- 类型扩展:19 IEC 基础类型(type:5);禁隐式转换 + 字面量适配;进制前缀定案
|
||||
@@ -1,26 +1,28 @@
|
||||
# 编译管线
|
||||
|
||||
compiler 模块:ST / toml 编译器。CMake 目标:`compiler`(`STATIC`),依赖 [`指令与映像.md`](../isa/指令与映像.md)。
|
||||
compiler 模块:ST / toml 编译器。CMake 目标:`compiler`(`STATIC`),**不依赖 isa**(指令 opcode / 类型元数据 / FB 布局来自 `compiler/machine.toml`,见 [`指令配置.md`](../isa/指令配置.md))。可执行入口 `STCompiler`(本模块 `src/main.cpp`,链 `compiler`)。
|
||||
|
||||
读齐工程后走直通 pass,产出单一映像,不上 LLVM。词法、语法、符号表、检查、codegen、链接都留在本库,不再拆 CMake 子库。
|
||||
读齐工程后走直通 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(头 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`
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
# 词法(12.4)
|
||||
|
||||
compiler 模块的词法分析。输入:单个 `.st` 源文件内容;输出:稳定 token 流(带 `source_file` + 行列)。语法与字段表见 [`初步计划.md`](../初步计划.md) 12.1、12.4。
|
||||
|
||||
## 做 / 不做
|
||||
|
||||
**做**
|
||||
|
||||
- `(* *)` 注释、标识符、关键字、整数、`TIME` 字面量、`:=`、比较符、括号
|
||||
- 关键字与标识符统一折成**小写**内部形(与 IEC 一致,大小写不敏感)
|
||||
- 每个 token 带 `source_file` + 行列,供以后报错
|
||||
- 非法字符 → `lex error` 硬错误(带行列)
|
||||
|
||||
**不做(第一版)**
|
||||
|
||||
- 字符串字面量、`REAL` 字面量、十六进制/科学计数
|
||||
- 注释嵌套(`(*` 到第一个 `*)` 结束,未闭合报错)
|
||||
- 浮点数、`_` 数字分隔符
|
||||
|
||||
## 关键字表(12.1 冻结,只认这些)
|
||||
|
||||
```text
|
||||
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
|
||||
AND OR NOT
|
||||
TRUE FALSE
|
||||
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)出明确错误。
|
||||
|
||||
## Token
|
||||
|
||||
```cpp
|
||||
enum class Tok {
|
||||
// 关键字(25 个,逐一对应上表)
|
||||
...
|
||||
// 字面量
|
||||
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, // 文件结束
|
||||
};
|
||||
|
||||
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 起
|
||||
};
|
||||
```
|
||||
|
||||
## 规则
|
||||
|
||||
### 大小写
|
||||
|
||||
- 关键字匹配**大小写不敏感**(`program` / `PROGRAM` / `Program` 同义)
|
||||
- 标识符统一折小写存入 `text`(内部形);原始大小写不保留(v1 无字符串字面量,报错靠行列定位)
|
||||
|
||||
### 字面量
|
||||
|
||||
- 整数:`[0-9]+`,`int64_t` 存储;超 `INT64_MAX` → `lex error`
|
||||
- `TIME`:`T#`(不敏感)+ 一个或多个 `<整数><单位>` 段,单位不敏感:`ms s m h d`,累加为毫秒:
|
||||
- `T#10ms` → 10
|
||||
- `T#1s250ms` → 1250
|
||||
- `T#2h` → 7200000
|
||||
- 缺单位 / 未知单位 / 空段 → `lex error`
|
||||
- 负数不是字面量:`-5` 是 `MINUS` + 整数,由语法层处理
|
||||
|
||||
### 符号
|
||||
|
||||
- `:=` 优先于 `:`;`<>` 优先于 `<`/`>`;`<=`/`>=` 单独匹配
|
||||
|
||||
### 注释
|
||||
|
||||
- `(* ... *)`,**不嵌套**;未闭合 → `lex error`
|
||||
- 注释内不产 token
|
||||
|
||||
## 错误
|
||||
|
||||
稳定前缀 `lex error`,带文件与行列:
|
||||
|
||||
```text
|
||||
lex error: illegal character '@' (motor.st, line 3, col 7)
|
||||
lex error: unterminated comment (main.st, line 1, col 1)
|
||||
lex error: bad time literal 'T#3q' (main.st, line 4, col 10)
|
||||
```
|
||||
|
||||
## 完成标准
|
||||
|
||||
1. 第 11 节三个文件(`examples/line1/*.st`)lex 成稳定 token 流;`lexer_test` 断言数量、类型、小写化、行列、`END` 收尾
|
||||
2. 非法字符 / 未闭合注释 / 坏 TIME 字面量 → `lex error` 负例过
|
||||
3. 全部构建 + `ctest` 无回归
|
||||
|
||||
---
|
||||
|
||||
## 执行计划(单步确认)
|
||||
|
||||
1. **`Lexer.h`(新建 `compiler/include/compiler/Lexer.h`)**
|
||||
- `Tok` 枚举:25 关键字 + `IDENT`/`INT_LIT`/`TIME_LIT` + 符号(`:= = <> < <= > >= + - * / ( ) , ; : .`)+ `END`
|
||||
- `Token { type, text, int_value, source_file, line, col }`(行列从 1 起)
|
||||
- `bool lex_file(path, out, err)` / `bool lex(source_file, content, out, err)`
|
||||
|
||||
2. **`Lexer.cpp`(重写占位)**
|
||||
- 25 关键字小写映射表,大小写不敏感,标识符统一折小写
|
||||
- `(* *)` 注释不嵌套、未闭合报错;非法字符报错
|
||||
- 整数 `[0-9]+`(超 `INT64_MAX` 报错);`T#<int><单位>` 段式解析累加毫秒(`ms s m h d`)
|
||||
- `:=` / `<>` / `<=` / `>=` 最长匹配优先
|
||||
- 错误前缀 `lex error`,带 file/line/col
|
||||
|
||||
3. **`lexer_test`(`tests/src/lexer_test.cpp` + CMake,链 `compiler` 库)**
|
||||
- line1 三文件:token 数量 / 类型序列 / 小写化 / 行列 / `END` 收尾断言
|
||||
- 负例:`@` 非法字符、未闭合注释、`T#3q` 坏字面量、超大整数 → `lex error` 类别
|
||||
|
||||
4. **验证**:`cmake --build` + `ctest`(新增 `lexer_test` 后 5/5 全绿)
|
||||
|
||||
5. **提交**:`Lexer.h/cpp` + `lexer_test` + 文档
|
||||
@@ -0,0 +1,183 @@
|
||||
# 语法(12.5 递归下降)
|
||||
|
||||
compiler 模块的语法分析。输入:单个 `.st` 文件的 token 流(见 [`词法.md`](词法.md));输出:**AST**(不分配寄存器、不写映像)。全工程定案见 [`初步计划.md`](../初步计划.md) 12.1、12.5。
|
||||
|
||||
## 做 / 不做
|
||||
|
||||
**做**
|
||||
|
||||
- POU 外壳:`PROGRAM` / `FUNCTION` / `FUNCTION_BLOCK` 与对应 `END_*`
|
||||
- 变量段:`VAR` / `VAR_INPUT` / `VAR_OUTPUT` / `VAR_GLOBAL` / `VAR_EXTERNAL`
|
||||
- 类型名只接受 `BOOL` `INT` `TIME`;内置 FB 名 `TON` `TOF` `CTU`;标识符(用户 FB 类型)
|
||||
- 语句:赋值、`IF` / `ELSIF` / `ELSE`、`WHILE`、FB 调用 `fb(in := …)`、字段 `fb.Q`
|
||||
- 表达式:`NOT`、`AND` / `OR`(AST 保留短路语义,12.8 编跳转)、比较、加减乘除、一元负号、函数调用 `Add(3, 4)`
|
||||
- 明确拒绝(`syntax error`,不静默):`VAR_IN_OUT`、`VAR_TEMP`、`REF`、`CLASS`、`ANY` 等保留名
|
||||
|
||||
**不做(第一版)**
|
||||
|
||||
- 寄存器分配、跳转回填、映像(12.8)
|
||||
- 链式比较 `a < b < c`、字符串、`REAL`、指针、`VAR_IN_OUT`、接口、`TYPE` 定义
|
||||
- 语义检查(未声明变量等归 12.6 / 12.7)
|
||||
|
||||
## AST 结构
|
||||
|
||||
```cpp
|
||||
// 类型引用:类型位置出现的名字
|
||||
enum class TypeKind { Bool, Int, Time, FbBuiltin, FbUser };
|
||||
struct TypeRef {
|
||||
TypeKind kind;
|
||||
std::string name; // FbUser 时为用户 FB 类型名(小写)
|
||||
};
|
||||
|
||||
struct VarDecl {
|
||||
std::string name;
|
||||
TypeRef type;
|
||||
bool has_init; // 可选初值(v1 仅 BOOL/INT/TIME 字面量)
|
||||
int64_t init_value; // BOOL 0/1、INT 值、TIME 毫秒
|
||||
uint32_t line, col;
|
||||
};
|
||||
|
||||
enum class VarSection { Local, Input, Output, Global, External };
|
||||
struct VarBlock {
|
||||
VarSection section;
|
||||
std::vector<VarDecl> vars;
|
||||
};
|
||||
|
||||
// 表达式
|
||||
enum class ExprKind {
|
||||
LitBool, LitInt, LitTime,
|
||||
VarRef, // 标识符(局部/全局/外部/实例名)
|
||||
Field, // 对象.字段:fb.Q / fb.ET
|
||||
Not, And, Or,
|
||||
Cmp, Add, Sub, Mul, Div, // 二目;比较不连锁
|
||||
Neg, // 一元负号
|
||||
Call, // 函数调用(仅 FUNCTION,有返回值)
|
||||
};
|
||||
enum class BinOp { Eq, Ne, Lt, Le, Gt, Ge, Add, Sub, Mul, Div };
|
||||
|
||||
struct Expr {
|
||||
ExprKind kind;
|
||||
BinOp op; // 二目运算
|
||||
std::string name; // VarRef / Field 对象名 / Call 函数名
|
||||
std::string field; // Field 字段名
|
||||
int64_t int_value; // 字面量值
|
||||
std::unique_ptr<Expr> lhs, rhs, operand; // 二目 / NOT / Neg
|
||||
std::vector<std::unique_ptr<Expr>> args; // Call 实参
|
||||
};
|
||||
|
||||
// 语句
|
||||
enum class StmtKind { Assign, If, While, FbCall };
|
||||
struct FbArg { std::string name; std::unique_ptr<Expr> value; };
|
||||
struct IfBranch { std::unique_ptr<Expr> cond; std::vector<Stmt> body; };
|
||||
|
||||
struct Stmt {
|
||||
StmtKind kind;
|
||||
// Assign
|
||||
std::string target; // 左值标识符
|
||||
bool target_is_field; // 左值是 fb.field(v1:只允许读取,禁止赋值)
|
||||
std::string field;
|
||||
std::unique_ptr<Expr> value;
|
||||
// If / While
|
||||
std::unique_ptr<Expr> cond;
|
||||
std::vector<Stmt> body; // IF/WHILE 体
|
||||
std::vector<IfBranch> elsifs;
|
||||
std::vector<Stmt> else_body;
|
||||
// FbCall
|
||||
std::string instance;
|
||||
std::vector<FbArg> args;
|
||||
};
|
||||
|
||||
enum class PouKind { Program, Function, FunctionBlock };
|
||||
struct POU {
|
||||
PouKind kind;
|
||||
std::string name;
|
||||
TypeRef result_type; // FUNCTION 才有
|
||||
std::vector<VarBlock> blocks;
|
||||
std::vector<Stmt> body;
|
||||
std::string source_file;
|
||||
};
|
||||
```
|
||||
|
||||
## 文法(递归下降)
|
||||
|
||||
```text
|
||||
file := (VAR_GLOBAL vars END_VAR | POU)*
|
||||
POU := PROGRAM ident var_block* body END_PROGRAM
|
||||
| FUNCTION ident ':' type var_block* body END_FUNCTION
|
||||
| FUNCTION_BLOCK ident var_block* body END_FUNCTION_BLOCK
|
||||
var_block := VAR vars END_VAR
|
||||
| VAR_INPUT vars END_VAR | VAR_OUTPUT vars END_VAR
|
||||
| VAR_GLOBAL vars END_VAR | VAR_EXTERNAL vars END_VAR
|
||||
vars := (ident (',' ident)* ':' type (':=' literal)? ';')*
|
||||
type := BOOL | INT | TIME | TON | TOF | CTU | ident
|
||||
body := stmt*
|
||||
stmt := ident ('.' ident)? ':=' expr ';' (* 赋值 *)
|
||||
| ident '(' arg (',' arg)* ')' ';' (* FB 调用 *)
|
||||
| IF expr THEN body (ELSIF expr THEN body)* (ELSE body)? END_IF
|
||||
| WHILE expr DO body END_WHILE
|
||||
arg := ident ':=' expr
|
||||
expr := or_expr
|
||||
or_expr := and_expr (OR and_expr)*
|
||||
and_expr := not_expr (AND not_expr)*
|
||||
not_expr := NOT not_expr | cmp_expr
|
||||
cmp_expr := add_expr (('='|'<>'|'<'|'<='|'>'|'>=') add_expr)?
|
||||
add_expr := mul_expr (('+'|'-') mul_expr)*
|
||||
mul_expr := unary (('*'|'/') unary)*
|
||||
unary := '-' unary | primary
|
||||
primary := INT_LIT | TIME_LIT | TRUE | FALSE | ident
|
||||
| ident '.' ident (* fb.field *)
|
||||
| ident '(' expr (',' expr)* ')' (* 函数调用 *)
|
||||
| '(' expr ')'
|
||||
```
|
||||
|
||||
要点:
|
||||
|
||||
- `file` 顶层允许无 POU 的 `VAR_GLOBAL` 段(**GVL 文件形态**,如 `globals.st`);
|
||||
其他变量段出现在顶层 → syntax error。解析结果:
|
||||
```cpp
|
||||
struct Unit {
|
||||
std::vector<VarBlock> globals; // 顶层 VAR_GLOBAL 段
|
||||
std::vector<POU> pous;
|
||||
};
|
||||
```
|
||||
- `THEN` / `DO` 是关键字(12.5 修订补入词法表,见 [`词法.md`](词法.md))
|
||||
- 赋值左侧 `ident.ident` 是字段引用,v1 只能读(右侧),左侧字段赋值报错
|
||||
- `ident(...)` 是**语句**即 FB 调用(无返回值);在**表达式**里即函数调用(有返回值)——靠上下文区分,与 12.1 语义一致
|
||||
- 比较不连锁(`a < b < c` → syntax error)
|
||||
- `AND` / `OR` 短路语义保留在 AST(`And`/`Or` 节点),12.8 编成跳转
|
||||
|
||||
## 明确拒绝清单
|
||||
|
||||
`IDENT` 文本(小写后)命中下列保留名 → `syntax error: 'X' is not supported`:
|
||||
|
||||
```text
|
||||
var_in_out var_temp ref class any pointer interface method
|
||||
```
|
||||
|
||||
## 错误
|
||||
|
||||
稳定前缀 `syntax error`,带文件与行列:
|
||||
|
||||
```text
|
||||
syntax error: expected 'END_PROGRAM' (main.st, line 5, col 3)
|
||||
syntax error: 'VAR_IN_OUT' is not supported (motor.st, line 2, col 5)
|
||||
syntax error: chained comparison not supported (main.st, line 7, col 15)
|
||||
```
|
||||
|
||||
## 完成标准
|
||||
|
||||
1. `examples/line1/*.st` 三文件 parse 成功,AST 结构符合文法
|
||||
2. `parser_test`:AST 断言(POU 种类/变量段/语句/表达式优先级)+ 负例(缺 `END_*`、`VAR_IN_OUT`、链式比较、字段赋值、坏语句)→ `syntax error`
|
||||
3. 全部构建 + `ctest` 无回归
|
||||
4. 用例 10 / 14 / 19 属 12.6 / 12.7 层(非语法层),不在本阶段点亮
|
||||
|
||||
---
|
||||
|
||||
## 执行计划(单步确认)
|
||||
|
||||
1. **`Parser.h`(新建 `compiler/include/compiler/Parser.h`)**:上述 AST 结构 + `bool parse_pous(source_file, content, out, err)`
|
||||
2. **`Parser.cpp` 前半**:POU 外壳 + 变量段 + 类型(文法 program / POU / var_block / vars / type)
|
||||
3. **`Parser.cpp` 后半**:语句 + 表达式(优先级爬升)+ 拒绝清单
|
||||
4. **`parser_test`(`tests/src/parser_test.cpp` + CMake)**:line1 三文件 AST 断言 + 负例
|
||||
5. **验证**:`cmake --build` + `ctest`(新增 `parser_syntax` 后 6/6 全绿)
|
||||
6. **提交**:`Parser.h/cpp` + `parser_test` + 文档
|
||||
@@ -0,0 +1,45 @@
|
||||
# 阶段 3:编译器语言层 V2(计划与待定项)
|
||||
|
||||
> 状态:**计划中**。阶段 3 = 编译器语言侧 V2 化(Lexer/Parser/Typecheck/Linker/codegen_test)。
|
||||
> 生成侧(MachineConfig/Codec/Stb/Codegen)已在阶段 2 完成。本文记录待定决策点,
|
||||
> **逐个解决**,解决后更新状态并落到代码/规格文档。
|
||||
|
||||
## 1. 目标
|
||||
|
||||
| 子模块 | V1 现状 | V2 目标 |
|
||||
|---|---|---|
|
||||
| Lexer | 25 关键字 + 8 FB 名 Tok;INT/TIME 字面量 | 19 类型关键字、15 FB 名、浮点字面量、进制前缀 |
|
||||
| Parser | TypeKind{Bool,Int,Time,FbBuiltin,FbUser} | 类型引用覆盖 19 类型;LitReal |
|
||||
| Typecheck | TType 3 种硬编码 | 禁隐式转换、BOOL 0/1 强制、字面量适配、FB 校验查 toml |
|
||||
| Linker | FbField.type 用 TypeKind | 字段类型字符串化(与类型系统一致) |
|
||||
| codegen_test | V1,剥离中 | V2 重写(去 isa 依赖) |
|
||||
|
||||
## 2. 待定决策点(逐个解决)
|
||||
|
||||
| # | 问题 | 草案/结论 | 状态 |
|
||||
|---|---|---|---|
|
||||
| L1 | 内建 FB 类型名校验来源 | 查 machine.toml `[[fb]]` 表(15 条,替换 V1 硬编码 8 Tok);**用户 FB 类型来自用户源码**(FUNCTION_BLOCK 声明,不查 toml) | ✅ 已澄清 |
|
||||
| ~~L2~~ | ~~禁隐式转换范围~~ | **已定案(用户)**:**变量间禁止任何隐式类型转换**(赋值/算术/比较严格同型 → type error);**整数字面量 / 浮点字面量按上下文适配**,上下文冲突直接报错(例:a 是 UINT,`a := -1` → 越域报错);**TIME/DATE 等字面量有格式要求**(见 L9) | ✅ |
|
||||
| ~~L9~~ | ~~字面量类型化语法~~ | **已定案**:不强制类型前缀——整数字面量(含进制 `16#FF`)/ 浮点字面量(`1.5`)**无类型、按上下文适配 + 值域检查**(冲突即报错,不转换);TIME 字面量沿用 V1 格式(`5s`,单位后缀)并可类型化;DATE/TOD/DT 字面量 v1 不支持(A4) | ✅ |
|
||||
| ~~L3~~ | ~~字面量适配规则~~ | **已定案(随 L2/L9)**:整数/浮点按上下文适配 + 值域检查(冲突报错);进制字面量同规则;无默认类型兜底 | ✅ |
|
||||
| ~~L4~~ | ~~浮点/日期字面量 AST~~ | **已定案**:ExprKind 新增 **LitReal**(double_value)/ **LitDate**(天数)/ **LitTod**(当日 ms)/ **LitDt**(date<<32\|tod);Expr 加 double_value 字段;词法 FLOAT_LIT/DATE_LIT/TOD_LIT/DT_LIT | ✅ |
|
||||
| ~~L5~~ | ~~--disasm 输出与断言范围~~ | **已定案(用户)**:`--disasm` 输出**头 22 字段一行一个 + 中文注释**(magic/version/model/cycle_limit/dt_ms/hash/entry_fn_id/n_globals/n_i/n_q/n_m/n_consts/n_funcs/五个 offset/n_slots/values_size/offset_meta/max_stack/sha)+ 常量表 + 函数表逐指令反汇编 + **slots 节**(槽表逐条 addr)+ 值段 hex dump;codegen_test 按此断言 | ✅ |
|
||||
| ~~L6~~ | ~~FB 类型名表示~~ | **已定案**:废除 8 个 FB 关键字 Tok;FB 名一律 IDENT;类型解析统一判定:① toml `[[fb]]` 表(内建,布局查 toml)→ ② 用户 FB 符号表(源码 FUNCTION_BLOCK)→ ③ toml `[[type]]` 表(标量)→ ④ 未命中报错 | ✅ |
|
||||
| ~~L7~~ | ~~TypeKind 表示~~ | **已定案**:TypeKind 收为 {Scalar, FbBuiltin, FbUser};TypeRef.name 统一小写类型名(标量 = type 表键 / FB = fb 表名或用户声明名);删 TypeKind::Bool/Int/Time(Parser/Typecheck/Linker/Codegen 连锁适配) | ✅ |
|
||||
| ~~L8~~ | ~~Linker 字段类型字符串化~~ | **已定案**:FbField.type → **FbField.type_name**(小写字符串);`link_project` 加 `const MachineConfig& cfg` 参数(main 传参);删除 8 个硬编码布局;`builtin_layout(name)` 查 cfg `[[fb]]` 表构建 FbLayout(带缓存) | ✅ |
|
||||
| ~~L10~~ | ~~日期字面量(A4 修订)~~ | **已定案(用户)**:V2 支持 `D#2026-8-24`/`TOD#12:30:00`/`DT#2026-8-24-12:30:00`(IEC 格式,强类型字面量);纪元 1970-01-01=0 天(与 RTC 一致);TOD 不支持小数秒;年范围 1..9999;换算:历法→天数(闰年)、TOD=时×3600000+分×60000+秒×1000 | ✅ |
|
||||
|
||||
## 3. 执行步骤(决策点解决后按序)
|
||||
|
||||
1. **Lexer V2**:19 类型关键字 + 15 FB 名(IDENT 化)+ FLOAT_LIT + 进制前缀(2#/8#/16# → 十进制换算)+ 日期字面量(D#/TOD#/DT#,最长匹配,历法换算与值域检查)
|
||||
2. **Parser V2**:TypeRef 字符串化 + LitReal/LitDate/LitTod/LitDt + 类型解析(L6/L7 落地)
|
||||
3. **Typecheck V2**:禁隐式转换 / BOOL 0/1 强制 / 字面量上下文适配 / FB 校验三层判定(L1/L2/L3)
|
||||
4. **Linker 适配**:FbField.type 字符串化(L8)
|
||||
5. **Codegen 适配**:浮点字面量(LOADK func 8/9 位模式)、REAL 变量/运算路径
|
||||
6. **codegen_test V2 重写**(L5)
|
||||
7. **验证**:13 正例 + 新类型语法用例编译;ctest(codegen_slice1 恢复)
|
||||
|
||||
## 4. 验证标准
|
||||
|
||||
- 13 个正例 + 新语法用例(DINT/REAL 声明、进制/浮点字面量)编译通过
|
||||
- codegen_slice1 恢复绿;ctest 12/13(vm_cycles/cases_all 待阶段 4)
|
||||
@@ -0,0 +1,46 @@
|
||||
# 执行器入口
|
||||
|
||||
BytecodeExecutor 模块:可执行入口。CMake 目标:`BytecodeExecutor`(`EXECUTABLE`),链接 `vm` 和 `isa`,**不链 `compiler`**。源码目录 `executor/`。
|
||||
|
||||
第一版两个可执行:`STCompiler` 编译出 `<name>.stb` 与 `<name>.runtime.toml`;`BytecodeExecutor` 加载映像按扫描周期跑。总顺序见 [`初步计划.md`](../初步计划.md) 第 12.10 节。
|
||||
|
||||
## 用法
|
||||
|
||||
```text
|
||||
BytecodeExecutor <name>.stb [--cycles N] [--replay <file>] [--step] [--sidecar <path>]
|
||||
```
|
||||
|
||||
- `--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
|
||||
|
||||
### sidecar 定位(当前 + 计划)
|
||||
|
||||
- **当前(已实现)**:sidecar 路径按「同名替换扩展名」推导——`<name>.stb` → `<name>.runtime.toml`。依据:sidecar 是编译产物的配对文件,同名约束由 STCompiler 写侧强制,约定即可。
|
||||
- **已实现**:`--sidecar <path>` 显式指定 I/O 绑定文件。适用场景:同一程序多套接线配置并存(厂内/现场不同 sidecar)、接线文件与 `.stb` 不同目录。缺省行为**不变**(未指定时回退到同名推导),向后兼容。
|
||||
|
||||
## 职责
|
||||
|
||||
- 加载 `.stb` 映像与 `<name>.runtime.toml` sidecar(I/O 绑定 var → 槽号 → channel/bit;V2 槽号为槽表条目下标)
|
||||
- 读取时**校验型号标识与 SHA-256**:型号不匹配 / 文件被篡改 → 直接报错拒绝(执行器只接受与自身内建型号一致的映像)
|
||||
- 按 sidecar 采样 I、写回 Q(不创造变量;V2 槽表两级:绑定 var → 槽号 → `slot_addr` → 值区)
|
||||
- `dt_ms` / `cycle_limit` 从映像头取,推进内置 FB(15 条:TON/TOF/TP/CTU/DCTU/CTD/DCTD/CTUD/DCTUD/R_TRIG/F_TRIG/SR/RS/PWM/RTC)
|
||||
- 单步、看寄存器和映像、按录制的 `I` 回放
|
||||
|
||||
## 边界
|
||||
|
||||
- 指令与映像编码见 [`指令与映像.md`](../isa/指令与映像.md)
|
||||
- ST 解析与链接见 [`编译管线.md`](../compiler/编译管线.md),本模块不碰 `.st`
|
||||
- 寄存器执行循环见 [`扫描周期.md`](../vm/扫描周期.md)
|
||||
|
||||
---
|
||||
|
||||
## TUI 观察界面(最后做,v1 只预留)
|
||||
|
||||
TUI 属于本模块(`BytecodeExecutor` 的界面),用途是**观察 VM 运行状态**(寄存器、I/Q/M、全局、FB 字段、pc、cycle 计数)。v1 不做 TUI,只预留两点:
|
||||
|
||||
1. **可替换输出层**:所有人类可读输出走可注入 sink(默认文本模式)。将来 TUI 独占终端时,文本输出换成 TUI 渲染器,互不污染。
|
||||
2. **展示层与 vm 观察 API 分离**:TUI 只轮询 vm 的只读快照(见 [`扫描周期.md`](../vm/扫描周期.md) 可观察性接口),不写 VM 状态。
|
||||
|
||||
候选库:`third_party/FTXUI/`(占位,vendor 在 TUI 阶段进行,见 [`third_party/README.md`](../third_party/README.md))。
|
||||
@@ -1,18 +0,0 @@
|
||||
# 宿主入口
|
||||
|
||||
host 模块:可执行入口。CMake 目标:`host`(`EXECUTABLE`),链接 `compiler` 和 `vm`。
|
||||
|
||||
第一版一条命令:读工程 → 编译映像 → 按扫描周期跑。不拆独立的编译器可执行文件。总顺序见 [`初步计划.md`](../初步计划.md) 第 12.10 节。
|
||||
|
||||
## 职责
|
||||
|
||||
- 读 `project.toml` 和其中的 `.st`,交给 `compiler`
|
||||
- 按 `[[io.input]]` / `[[io.output]]` 采样 I、写回 Q(不创造变量)
|
||||
- 把 `dt_ms` 交给 VM,推进 `TON` / `TOF` / `CTU`
|
||||
- 单步、看寄存器和映像、按录制的 `I` 回放
|
||||
|
||||
## 边界
|
||||
|
||||
- 指令编码见 [`指令与映像.md`](../isa/指令与映像.md)
|
||||
- ST 解析与链接见 [`编译管线.md`](../compiler/编译管线.md)
|
||||
- 寄存器执行循环见 [`扫描周期.md`](../vm/扫描周期.md)
|
||||
@@ -0,0 +1,134 @@
|
||||
# .stb 映像文件结构(V2,STATOR2)
|
||||
|
||||
编译产物 `.stb` 的**面向使用者**的结构说明。规范以 [`指令与映像.md`](指令与映像.md) 为准,本文给结构与示例。
|
||||
真实 `line1.stb` 字节拆解在**阶段 5**(`line1` 推演重算,C5)补充;本文示例为概念性布局。
|
||||
|
||||
## 概览
|
||||
|
||||
- 魔数 `STSC`,**版本 `2`**,型号标识 `STATOR2`,全部整数小端
|
||||
- 文件 = 头 + 七个段,段序:**常量表 → 函数表 → 字节码 → 槽表段 → 值段 → 元数据段 → SHA-256 尾**
|
||||
- 段偏移记在头里,单调:`offset_const ≤ offset_funcs ≤ offset_code ≤ offset_slots ≤ offset_values ≤ 文件长度`
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ 头(128 字节,偏移 0,含型号标识[32]) │
|
||||
├──────────────────────────────────────────────────┤
|
||||
│ 常量表段 offset_const .. offset_funcs │
|
||||
├──────────────────────────────────────────────────┤
|
||||
│ 函数表段 offset_funcs .. offset_code │
|
||||
├──────────────────────────────────────────────────┤
|
||||
│ 字节码段 offset_code .. offset_slots(16 对齐)│
|
||||
├──────────────────────────────────────────────────┤
|
||||
│ 槽表段 offset_slots .. offset_values │
|
||||
├──────────────────────────────────────────────────┤
|
||||
│ 值段 offset_values .. offset_meta(或文件尾)│
|
||||
├──────────────────────────────────────────────────┤
|
||||
│ 元数据段 offset_meta (v1 = 0,空段) │
|
||||
├──────────────────────────────────────────────────┤
|
||||
│ SHA-256 文件尾(32 字节,对文件尾之前全部内容) │
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 头(128 字节,16 对齐)
|
||||
|
||||
| 偏移 | 宽 | 字段 | 说明 |
|
||||
|---|---|---|---|
|
||||
| 0 | 4 | 魔数 `STSC` | 不变(V1 沿用) |
|
||||
| 4 | 4 | 版本 `2` | V1 = 1 |
|
||||
| 8 | 4 | `cycle_limit` | 每周期指令数上限 |
|
||||
| 12 | 4 | `dt_ms` | 周期时长(毫秒,定时器推进步长) |
|
||||
| 16 | 8 | 工程哈希(FNV-1a 64) | 源文件路径排序后对内容计算 |
|
||||
| 24 | 4 | 入口 `entry_fn_id` | MAIN 函数表行下标 |
|
||||
| 28 | 4 | 全局槽数 `n_globals` | 槽表内全局块条目数 |
|
||||
| 32/36/40 | 4×3 | `n_i` / `n_q` / `n_m` | **保留恒 0**(V1 遗产,兼容 + 将来语义) |
|
||||
| 44 | 4 | 常量数 `n_consts` | 常量表条目数 |
|
||||
| 48 | 4 | 函数数 `n_funcs` | 函数表行数 |
|
||||
| 52/56/60 | 4×3 | `offset_const` / `offset_funcs` / `offset_code` | 段偏移 |
|
||||
| 64 | 4 | `offset_slots` | 槽表段偏移 |
|
||||
| 68 | 4 | `offset_values` | 值段偏移(**16 对齐**) |
|
||||
| 72 | 32 | **型号标识**(定长 ASCII 含版本,补 `'\0'`) | `"STATOR2"`(name + version=2) |
|
||||
| 104 | 4 | `n_slots` | 槽表条目数(= n_globals + FB 实例/结构体字段槽数) |
|
||||
| 108 | 4 | `values_size` | 值段字节数 |
|
||||
| 112 | 4 | `offset_meta` | 元数据段偏移(**v1 = 0** = 空段) |
|
||||
| 116 | 4 | `max_stack` | 栈区字节数(编译期调用图最坏路径) |
|
||||
| 120 | 8 | 预留 | 未来扩展 |
|
||||
|
||||
## 常量表段(`offset_const` 起)
|
||||
|
||||
一行 **8 字节**:`[value:u64]`(原始位模式,**无 tag**——类型由 LOADK 指令的 func 位段解释)。
|
||||
|
||||
- 按位模式去重(同值多类型复用:`5` 可被 func=INT 与 func=REAL 的 LOADK 引用)
|
||||
- `const_id` = 行下标(u32,IMM 形态 imm32 字段)
|
||||
|
||||
## 函数表段(`offset_funcs` 起)
|
||||
|
||||
一行 12 字节:`[nregs:u32][code_offset:u32][code_len:u32]`
|
||||
|
||||
- **行下标即 `fn_id`**;`code_offset` 相对字节码段起点(**字节**);`code_len` 为**字节数**(变长指令,字节单位)
|
||||
- 入口 = 头 `entry_fn_id` 指向的行
|
||||
- `nregs` = 本函数帧寄存器数(帧分配 = nregs×8 + 帧头 12B;max_stack 由编译期按调用图算)
|
||||
|
||||
## 字节码段(`offset_code` 起)
|
||||
|
||||
**变长指令 4/8/16/32B**,段起点 16 对齐。指令字:
|
||||
|
||||
```text
|
||||
字节 0 字节 1
|
||||
[长度前缀+形态分区 | op] [func:4 | 空:4]
|
||||
```
|
||||
|
||||
- 形态(前缀直推):`11`=RR、`10`=NONE、`011`=RRR、`010 000..101`=IMM/SLOT/JMP/JC/CALL/CAL、`0010`=SLOT+off、`0011`=IMM64、`0001`=预留
|
||||
- 编码明细(位号/操作数区/uses_*)见 [`指令编码.md`](指令编码.md)、清单见 [`指令集清单.md`](指令集清单.md)
|
||||
- 跳转偏移 off32 **字节单位,相对下一条**(目标 = 当前指令地址 + 指令长 + off)
|
||||
- 调用约定:结果 r0、参数 r1..r7、变量与临时 r8 起;CALL/RET 复制 r0..r7 全宽
|
||||
|
||||
## 槽表段(`offset_slots` 起)
|
||||
|
||||
一行 **8 字节**:`[addr:u32][预留:u32]`(addr = 值段内字节偏移,指向变量/FB 实例起始)。
|
||||
|
||||
- **行下标即槽号**(slot u32);`n_slots` 行
|
||||
- 槽序:全局块(声明序,`n_globals` 个)→ FB 实例块(POU 收集序、实例声明序)
|
||||
|
||||
## 值段(`offset_values` 起)
|
||||
|
||||
**变宽紧凑布局**(对齐 1/2/4/8B,编译器保证;段起点 16 对齐):
|
||||
|
||||
- 变量按类型占宽:BOOL 1B、INT 2B、DINT/REAL 4B、TIME/LREAL 8B…(func 宽度表)
|
||||
- 变量初值、FB 实例字段初值(0)写于此;运行时拷入工作副本,文件只读
|
||||
- 字段访问:`LOAD/STORE rd, slot, off32`——addr = 槽表[slot].addr + off(结构体/FB 共享体统一)
|
||||
|
||||
FB 实例布局(字段偏移/大小,对齐规则)见 [`指令集清单.md`](指令集清单.md) §8.1,例:
|
||||
|
||||
| FB | 布局(偏移) | 大小 |
|
||||
|---|---|---|
|
||||
| TON | in=0、pt=8、q=16、et=24 | 32B |
|
||||
| CTU | cu=0、r=1、pv=2、q=4、cv=6 | 8B |
|
||||
| DCTU | cu=0、r=1、pad 2..3、pv=4、q=8、pad 9..11、cv=12 | 16B |
|
||||
|
||||
## 元数据段(`offset_meta` 起)
|
||||
|
||||
v1 为空段(`offset_meta = 0`)。将来承载结构体/字符串/数组的类型描述表(字段表),schema 延后(U12)。
|
||||
|
||||
## SHA-256 文件尾(32 字节)
|
||||
|
||||
文件尾追加 32 字节 SHA-256:**对文件尾之前全部内容计算**。compiler 写侧与 vm 读侧**各自实现**一份。执行器校验不通过 → 直接拒绝。
|
||||
|
||||
## 读取校验链(vm 读侧)
|
||||
|
||||
1. **结构校验**:魔数/版本/头大小/段偏移单调且在 SHA 前/常量表·函数表尺寸一致/代码段 16 对齐/入口 fn_id 范围
|
||||
2. **SHA-256**:完整性(防篡改)
|
||||
3. **型号匹配**:头内 32 字节与 `"STATOR2"` 补零逐字节比对
|
||||
|
||||
任一不符 → `Machine::create` 拒绝,不创建机器。
|
||||
|
||||
## 与 sidecar / 执行器的关系
|
||||
|
||||
- `<name>.stb` + `<name>.runtime.toml`(sidecar:`[[io.input]]`/`[[io.output]]`,var → 槽号 → channel/bit)
|
||||
- I/O 绑定**不进映像**;绑定粒度 `slot = 槽号`(指向槽表条目),channel/bit 语义不变
|
||||
- executor 按 sidecar 采样 I / 写回 Q;`dt_ms`/`cycle_limit` 从映像头取;`max_stack` 供 VM 预分配栈区
|
||||
|
||||
## 用工具验证
|
||||
|
||||
- `ctest`:`isa_roundtrip`(编解码)、`vm_cycles`(型号/SHA 拒绝)、`machine_config`(V2 schema 强校验)
|
||||
- `STCompiler x.stb --disasm --machine machine.toml`:显示 `model=STATOR2` 与 `sha=ok|BAD`
|
||||
- 阶段 5 后:`xxd line1.stb` 对照本文逐段核对(C5 line1 推演重算)
|
||||
+288
-92
@@ -1,28 +1,32 @@
|
||||
# 指令与映像
|
||||
# 指令与映像(V2 规格,STATOR2)
|
||||
|
||||
isa 模块规范:指令集 + 映像 + 定宽类型。不是编译器,不把 `.st` 编成映像。
|
||||
> 状态:**V2 规格(阶段 1 重写)**。本文件为指令编码、类型、数据模型、映像格式的
|
||||
> **规范**,头文件与代码实现本文,不另当规范。
|
||||
> 定案依据:[`阶段1规格总览.md`](阶段1规格总览.md)(主参考)、[`指令编码.md`](指令编码.md)、
|
||||
> [`指令集清单.md`](指令集清单.md)、[`指令集优化.md`](指令集优化.md)。
|
||||
> 与 V1 关系:V1(32bit 指令、8 字节定宽槽、STATOR1)已实现并冻结;本文为**全新 V2**,
|
||||
> 头 `version = 2`、型号 `STATOR2`,老映像被版本/型号校验拒绝。
|
||||
|
||||
`compiler` 按本文件写出字节,`vm` 只认这些字节。两边不互链。
|
||||
|
||||
全工程定案见 [`初步计划.md`](../初步计划.md)。**类型、指令编码、映像布局以本文为准**,不要另写一份互相打架的表。头文件实现本文,不另当规范。
|
||||
`compiler` 写 `.stb`、`vm` 读 `.stb`,两边**各自实现**读写;`isa` 归执行器侧只管指令定义。
|
||||
指令 opcode / 类型 / FB 布局的登记见 [`指令配置.md`](指令配置.md)(`compiler/machine.toml`,与代码强校验)。
|
||||
|
||||
```text
|
||||
host → compiler → isa
|
||||
host → 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/`。
|
||||
|
||||
---
|
||||
|
||||
## 做 / 不做
|
||||
## 1. 做 / 不做
|
||||
|
||||
**做**
|
||||
|
||||
- 操作码与 32 位指令布局
|
||||
- 映像头与段(函数字节码、全局 / FB 槽、工程哈希)
|
||||
- `BOOL` / `INT` / `TIME` 宽度与溢出规则
|
||||
- 编解码、工程哈希、一条指令的文本转储(给 host 打印)
|
||||
- 操作码(前缀分区)与**变长指令**布局(4/8/16/32B)
|
||||
- 映像头 128B 与段(常量表 / 函数表 / 字节码 / 槽表段 / 值段 / 元数据段)
|
||||
- 19 种 IEC 类型 → 10 种 func 的宽度/符号表、饱和与除法边界
|
||||
- 编解码、工程哈希、一条指令的文本转储(反汇编,给 STCompiler `--disasm` 与测试打印)
|
||||
|
||||
**不做**
|
||||
|
||||
@@ -34,127 +38,319 @@ CMake 目标:`isa`(`STATIC`),无依赖。公开头:`isa/include/isa/`
|
||||
|
||||
---
|
||||
|
||||
## 特性
|
||||
## 2. 特性总表(V2)
|
||||
|
||||
| 项 | 第一版 |
|
||||
| 项 | V2 定案 |
|
||||
|---|---|
|
||||
| `BOOL` | `uint8_t`,只允许 0/1 |
|
||||
| `INT` | `int16_t`(IEC `INT`) |
|
||||
| `TIME` | `int32_t` 毫秒;与 `INT` 不混用 |
|
||||
| 整数溢出 | **饱和**(夹到 `INT` 最小/最大) |
|
||||
| 指令宽度 | **变长 4/8/16/32B**,长度与形态由**前缀分区**表达 |
|
||||
| 前缀 | `11`=RR、`10`=NONE、`011`=RRR、`010 000..101`=IMM/SLOT/JMP/JC/CALL/CAL、`0010`=SLOT+off / RS+off、`0011`=IMM64、`0001`=预留(32B) |
|
||||
| 头部 | 字节 0 = `[前缀+分区\|op]`;字节 1 = `[func:4\|空:4]`(空位延后定) |
|
||||
| func | 4 位:0..7 = 宽度×符号(1/2/4/8B×有/无)、8=F32、9=F64;无类型指令 = 0 |
|
||||
| 寄存器 | 编号 8 位(**256 = 寻址上限**),值 int64;**每帧按函数头 nregs 动态分配** |
|
||||
| 调用约定 | r0 结果、r1..r7 参数(≤7)、r8+ 变量/临时;CALL/RET 复制 r0..r7 全宽(64B) |
|
||||
| 调用栈 | 预分配栈区(max_stack 编译期算)、帧头 12B、溢出双检查(字节+深度 64);**递归 = 编译期报错** |
|
||||
| 跳转 | **字节单位**,相对下一条(目标 = 当前 + off32) |
|
||||
| 类型 | 19 IEC 类型 → 10 种 func 编译期映射;禁隐式变量转换 |
|
||||
| 整数溢出 | **饱和**(各宽度模板);除零返回被除数;MIN/-1 饱和;截断除法;MOD 符号随被除数 |
|
||||
| 浮点 | IEEE 754(溢出/零除 → ±inf、NaN 传播) |
|
||||
| 变量访问 | **槽表(引用)→ 值区(变宽紧凑)**两级间接;全 32 位寻址 |
|
||||
| 字节序 | 小端 |
|
||||
| 指令宽度 | 32 bit,一种主布局 |
|
||||
| 寄存器 | 下标 0..255;每函数实际个数写在函数头 `nregs` |
|
||||
| 跳转 | 相对 offset,单位是**指令条数**,不是字节 |
|
||||
| `CALL` | 立即数 `fn_id`(u16),无函数指针 |
|
||||
| 映像魔数 | `STSC`,版本 `1` |
|
||||
| 映像 | 魔数 `STSC`、**version = 2**、型号 `STATOR2` |
|
||||
| 工程哈希 | 源文件路径排序后对内容做 FNV-1a 64(非密码学) |
|
||||
| I/O 绑定 | **不进映像**;编译期把 `var` 收成槽号,channel/bit 留在 host 读 toml |
|
||||
| 不做 | `REAL`、隐式宽化、独立 `stc.exe` |
|
||||
| 完整性 | 文件尾 SHA-256[32];型号标识[32]@72 |
|
||||
| I/O 绑定 | **不进映像**;sidecar(`.runtime.toml`)绑定 var→槽号→channel/bit,粒度不变 |
|
||||
| 内建 FB | 15 条(CAL + fb_id 16 位);字段布局见 [`指令集清单.md`](指令集清单.md) §8.1 |
|
||||
|
||||
---
|
||||
|
||||
## 类型
|
||||
|
||||
C++11 定宽:
|
||||
|
||||
- `BOOL`:`uint8_t`,0/1
|
||||
- `INT`:`int16_t`;加减乘除溢出饱和
|
||||
- `TIME`:`int32_t` 毫秒
|
||||
|
||||
`compiler` 与 `vm` 都按同一规则,不再另选溢出策略。
|
||||
|
||||
---
|
||||
|
||||
## 指令
|
||||
|
||||
定宽 32 bit:
|
||||
## 3. 指令编码(详见指令编码.md)
|
||||
|
||||
```text
|
||||
[ op:8 | rd:8 | a:8 | b:8 ]
|
||||
指令 = 字节0 + 字节1 + 操作数区(按形态)
|
||||
|
||||
字节 0 字节 1
|
||||
[长度前缀+形态分区 | op] [func:4 | 空:4]
|
||||
```
|
||||
|
||||
- 三寄存器:`a`/`b` 为 `ra`/`rb`(`MOVE`/`NOT` 只用 `rd`+`a`)
|
||||
- `LOADK`:`a|b` 为 `const_id`(u16)
|
||||
- `JMP` / `JT` / `JF`:`a|b` 为有符号相对 offset(指令条数)
|
||||
- `CALL`:`a|b` 为 `fn_id`(u16)
|
||||
### 3.1 前缀分区与 op 空间
|
||||
|
||||
操作码一次列全(`CMP_xx` 冻下面六种):
|
||||
| 前缀 | 长度 | 形态 | op 位宽 | op 空间 | 识别 |
|
||||
|---|---|---|---|---|---|
|
||||
| `11` | 4B | RR | 6 位 | 64 | 前缀直推 |
|
||||
| `10` | 4B | NONE | 6 位 | 64 | 前缀直推 |
|
||||
| `011` | 8B | RRR | 5 位 | 32 | 前缀直推 |
|
||||
| `010 000` | 8B | IMM | 2 位 | 4 | 前缀直推 |
|
||||
| `010 001` | 8B | SLOT | 2 位 | 4 | 前缀直推 |
|
||||
| `010 010` | 8B | JMP | 2 位 | 4 | 前缀直推 |
|
||||
| `010 011` | 8B | JC | 2 位 | 4 | 前缀直推 |
|
||||
| `010 100` | 8B | CALL | 2 位 | 4 | 前缀直推 |
|
||||
| `010 101` | 8B | CAL | 2 位 | 4 | 前缀直推 |
|
||||
| `010 110/111` | 8B | 预留 | 2 位 | 8 | — |
|
||||
| `0010` | 16B | SLOT+off / RS+off | 4 位 | 16 | 前缀直推 |
|
||||
| `0011` | 16B | IMM64 | 4 位 | 16 | 前缀直推 |
|
||||
| `0001` | 32B | 预留(N2) | 4 位 | 16 | — |
|
||||
|
||||
- op 按分区独立编号(0 起);取指:读字节 0 → 前缀定长 → 搬整条
|
||||
- **uses_rd/uses_ra/uses_rb 全部前缀直推**(零查表):RRR 三寄存器全真;RR 的 rd/rs;IMM/SLOT/JC/SLOT+off/RS+off/IMM64 的 rd;RS+off 另含 rs(基址寄存器);JMP/CALL/CAL/NONE 无
|
||||
|
||||
### 3.2 func 位段(字节 1 高 4 位)
|
||||
|
||||
| func | 语义 | 覆盖类型 |
|
||||
|---|---|---|
|
||||
| 0 | 1B 无符号 | BOOL、BYTE、USINT |
|
||||
| 1 | 1B 有符号 | SINT |
|
||||
| 2 | 2B 无符号 | WORD、UINT |
|
||||
| 3 | 2B 有符号 | INT |
|
||||
| 4 | 4B 无符号 | DWORD、UDINT、DATE |
|
||||
| 5 | 4B 有符号 | DINT |
|
||||
| 6 | 8B 无符号 | LWORD、ULINT、DT、TOD |
|
||||
| 7 | 8B 有符号 | LINT、TIME |
|
||||
| 8 | F32 | REAL |
|
||||
| 9 | F64 | LREAL |
|
||||
| 10..15 | 预留 | |
|
||||
|
||||
- **func 语义随 op**:MOVE=宽度;LOAD/STORE=宽度×符号;ADD..=宽度×符号+浮点;逻辑=宽度;无类型指令=0
|
||||
- 字节 1 低 4 位暂空(N1,延后定)
|
||||
|
||||
### 3.3 操作数区位号(bit16 起;pad = 对齐填充,编码固定 0)
|
||||
|
||||
| 形态 | 操作数区 |
|
||||
|---|---|
|
||||
| RR | rd=23..16、rs=31..24 |
|
||||
| NONE | pad=31..16 |
|
||||
| RRR | rd=23..16、rs1=31..24、rs2=39..32、pad=63..40 |
|
||||
| IMM | rd=23..16、imm32=55..24、pad=63..56 |
|
||||
| SLOT | rd=23..16、slot32=55..24、pad=63..56 |
|
||||
| JMP | off32=47..16、pad=63..48 |
|
||||
| JC | rd=23..16、off32=55..24、pad=63..56 |
|
||||
| CALL | fn_id32=47..16、pad=63..48(**rd 恒 0**) |
|
||||
| CAL | fb_id16=31..16、slot32=63..32(**rd 恒 0**) |
|
||||
| SLOT+off | rd=23..16、slot32=55..24、off32=87..56、pad=127..88 |
|
||||
| RS+off | rd=23..16、rs=31..24(基址寄存器)、off32=87..56、pad=127..88 |
|
||||
| IMM64 | rd=23..16、imm64=87..24、pad=127..88 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 寄存器与调用栈
|
||||
|
||||
- **寄存器编号 8 位**:256 为寻址上限(不是每帧分配数);**每帧按函数头 nregs 动态分配**(nregs×8B,百字节级)
|
||||
- 寄存器值 int64;codegen `register overflow (>256)` 检查
|
||||
- **调用约定**:r0 结果、r1..r7 参数(≤7)、r8+ 变量/临时;CALL/RET 复制 r0..r7 全宽(64B);函数输入只读(compiler 拒绝写入)
|
||||
|
||||
### 调用栈(方案 C)
|
||||
|
||||
- **递归禁止**:链接层调用图环检测(自调用/循环调用)→ 编译期报错 `link error: recursive call`
|
||||
- **max_stack**:MAIN 帧 + DAG 最坏调用路径 Σ(帧需求),帧需求 = 帧头 12B + nregs×8;写入映像头 @116
|
||||
- **预分配栈区**:Machine 构造时一次性分配 max_stack 连续内存,运行期零分配
|
||||
- 帧结构:帧头 `{ fn_id:u32, ret_pc:u32, ret_fn_id:u32 }`(12B)+ 寄存器区 nregs×8
|
||||
- 压帧:栈顶取帧需求,sp += 需求;弹帧:sp -= 需求
|
||||
- **溢出双检查**:剩余栈 < 帧需求 → `StackOverflow`(字节主检查)+ 深度 ≤64(双保险)
|
||||
- MAIN 帧常驻栈底(跨周期保留),周期结束回退 sp 到 MAIN 帧顶
|
||||
|
||||
---
|
||||
|
||||
## 5. 类型系统
|
||||
|
||||
### 5.1 19 种 IEC 基础类型(编译期概念 → func 映射)
|
||||
|
||||
| 类型 | 宽度 | 符号 | func | 类型 | 宽度 | 符号 | func |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| BOOL | 1B | — | 0 | LINT | 8B | 有 | 7 |
|
||||
| BYTE | 1B | 无 | 0 | USINT | 1B | 无 | 0 |
|
||||
| WORD | 2B | 无 | 2 | UINT | 2B | 无 | 2 |
|
||||
| DWORD | 4B | 无 | 4 | UDINT | 4B | 无 | 4 |
|
||||
| LWORD | 8B | 无 | 6 | ULINT | 8B | 无 | 6 |
|
||||
| SINT | 1B | 有 | 1 | TIME | 8B | 有(ms) | 7 |
|
||||
| INT | 2B | 有 | 3 | REAL | 4B | IEEE754 | 8 |
|
||||
| DINT | 4B | 有 | 5 | LREAL | 8B | IEEE754 | 9 |
|
||||
| | | | | DATE | 4B | 无(天) | 4 |
|
||||
| | | | | TOD | 4B | 无(ms) | 4 |
|
||||
| | | | | DT | 8B | 无 | 6 |
|
||||
|
||||
- 编译器类型检查**严格比对**(不匹配编译期报错);**BOOL 编译器强制 0/1**(越界报错)
|
||||
- **禁隐式变量转换**;**字面量按上下文适配**(`5` 可赋 INT/REAL/DINT…,编译期解释),默认 DINT 兜底
|
||||
- 进制前缀 `2#`/`8#`/`16#` 字面量(浮点无前缀)
|
||||
- **DATE/TOD/DT**:v1 不运算(仅存取/比较);DT = u64 `{date:32 | tod:32}` 拼接(高 32 位日期)
|
||||
|
||||
### 5.2 值表示(寄存器 / 槽统一约定)
|
||||
|
||||
| 类型 | 寄存器(int64 位模式) |
|
||||
|---|---|
|
||||
| BOOL | 低 1 字节(0/1) |
|
||||
| 有符号整数 | 按 func 宽度符号扩展 |
|
||||
| 无符号/位串 | 按 func 宽度零扩展 |
|
||||
| TIME/LREAL/DT | 全 64 位 |
|
||||
| REAL | 低 4 字节(float 位模式) |
|
||||
|
||||
全宽 int64 比较等价于各类型自身比较(编译层保证类型语义)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 指令语义(清单见指令集清单.md)
|
||||
|
||||
### 6.1 数据搬运
|
||||
|
||||
- `MOVE rd, rs`(RR,func=宽度 0..3):寄存器间按宽度搬运(高位清 0),不管符号/浮点
|
||||
- `LOAD rd, slot` / `STORE slot, rs`(SLOT,func 全矩阵):槽表[slot].addr → 值区按 func 读/写宽度
|
||||
- `LOADK rd, const_id`(IMM,func 全矩阵):常量表条目 8B 原始值,按 func 解释
|
||||
- `LOAD_OFF rd, slot, off` / `STORE_OFF slot, off, rs`(SLOT+off):字段访问(绝对实例槽,调用点/输出读用)
|
||||
- `LOAD_PTR rd, rs, off` / `STORE_PTR rd, rs, off`(RS+off):**基址寻址**——rs 寄存器 = 值区地址(实例指针),addr+off 直读值区(FB 共享体函数体内字段访问)
|
||||
|
||||
### 6.2 逻辑与位运算
|
||||
|
||||
- `NOT`(RR)、`AND/OR/XOR`(RRR):按 func 宽度逐位(BOOL 由编译器保证 0/1)
|
||||
|
||||
### 6.3 算术
|
||||
|
||||
- `ADD/SUB/MUL/DIV`(RRR,func 全矩阵):整数饱和到 func 宽度 / 浮点 IEEE
|
||||
- **整数除法边界(A 定案)**:除零 → 返回被除数;MIN/-1 → 饱和夹界;截断除法(向零)
|
||||
- `MOD`:符号随被除数(-7 MOD 3 = -1);除零同 DIV
|
||||
- `NEG/ABS/INC/DEC`(RR);浮点单目/舍入(RR,func 8/9);`CONV`(RR,func = 目标类型)
|
||||
|
||||
### 6.4 比较
|
||||
|
||||
- `CMP_EQ..GE`(RRR,func 全矩阵):按 func 有/无符号、浮点 IEEE(NaN 传播)
|
||||
|
||||
### 6.5 控制流
|
||||
|
||||
- `JMP off32`:pc += off(字节,相对下一条);`JT/JF rd, off`:条件跳转
|
||||
- `CALL fn_id`(rd 恒 0):压帧(复制 r0..r7 全宽);`RET`:弹帧 / MAIN 周期结束
|
||||
- `CAL fb_id, slot`(rd 恒 0):内建 FB 调用,fb_id 查 [[fb]] 表
|
||||
- 周期指令计数:每条 +1,超 `cycle_limit` → `CycleLimit` 故障
|
||||
|
||||
### 6.6 内建 FB(15 条)
|
||||
|
||||
| fb_id | FB | fb_id | FB |
|
||||
|---|---|---|---|
|
||||
| 0 | TON | 7 | CTUD |
|
||||
| 1 | TOF | 8 | DCTUD |
|
||||
| 2 | TP | 9 | R_TRIG |
|
||||
| 3 | CTU | 10 | F_TRIG |
|
||||
| 4 | DCTU | 11 | SR |
|
||||
| 5 | CTD | 12 | RS |
|
||||
| 6 | DCTD | 13 | PWM |
|
||||
| | | 14 | RTC |
|
||||
|
||||
字段布局(类型化 + 对齐 1/2/4/8 + 偏移)见 [`指令集清单.md`](指令集清单.md) §8.1。
|
||||
RTC 时钟源 = **虚拟时钟**(周期计数 × dt_ms,确定性,不读系统时钟)。
|
||||
|
||||
---
|
||||
|
||||
## 7. 数据模型:槽表 → 值区
|
||||
|
||||
```text
|
||||
MOVE rd, rs
|
||||
LOADK rd, const_id
|
||||
NOT rd, rs
|
||||
AND/OR rd, ra, rb (* 已算好的值;短路用跳转 *)
|
||||
ADD/SUB/MUL/DIV rd, ra, rb
|
||||
CMP_EQ/NE/LT/LE/GT/GE rd, ra, rb
|
||||
JMP offset
|
||||
JT/JF r, offset
|
||||
LOAD_I / STORE_Q / LOAD_M / STORE_M
|
||||
LOAD_GLOBAL / STORE_GLOBAL
|
||||
CAL_TON / CAL_TOF / CAL_CTU
|
||||
CALL fn_id
|
||||
RET
|
||||
槽表段(静态引用表,8B/条) 值段(初值,紧凑变宽)
|
||||
{ addr:u32, 预留:u32 } ──地址──▶ BOOL 1B / INT 2B / DINT·REAL 4B / TIME·LREAL 8B
|
||||
槽 0..n_slots-1 结构体 = 连续字段块(v1 预留)
|
||||
```
|
||||
|
||||
局部 / `VAR` = 固定寄存器;表达式临时值往后编号;`nregs` 写进函数头。
|
||||
`I`/`Q`/`M` 走映像指令,不是通用寄存器。
|
||||
FB 实例固定布局,字段偏移编译期算死。
|
||||
- **访问**:`LOAD rd, slot` → `addr = 槽表[slot].addr` → 值区按 func 读宽度;字段访问 `LOAD rd, slot, off` → `addr + off`
|
||||
- **两级间接**:每次访问多一次内存读(PLC 规模无感)
|
||||
- **对齐**:1/2/4/8B,编译器布局保证;值段起点 16 对齐
|
||||
- **初值**:编译期写值段(变量初值、FB 实例字段 0);运行时拷贝工作副本,文件只读
|
||||
- **寻址**:槽号 u32(2^32 引用)、值区地址 u32(4GB)
|
||||
- 槽表分配:n_slots = n_globals(全局声明序)+ FB 实例/字段槽数
|
||||
|
||||
---
|
||||
|
||||
## 映像
|
||||
## 8. 映像格式
|
||||
|
||||
魔数 `STSC`,版本 `1`。
|
||||
### 8.1 头(128 字节,16 对齐)
|
||||
|
||||
头:
|
||||
| 偏移 | 字段 | 说明 |
|
||||
|---|---|---|
|
||||
| 0 | magic | `STSC`(不变) |
|
||||
| 4 | version | **= 2**(V1 = 1) |
|
||||
| 8 | cycle_limit | 每周期指令数上限 |
|
||||
| 12 | dt_ms | 周期时长(毫秒) |
|
||||
| 16 | project_hash | FNV-1a 64 |
|
||||
| 24 | entry_fn_id | 入口函数(MAIN) |
|
||||
| 28 | n_globals | 全局槽数 |
|
||||
| 32/36/40 | n_i / n_q / n_m | **保留恒 0**(V1 遗产,兼容/将来语义) |
|
||||
| 44 | n_consts | 常量表条目数 |
|
||||
| 48 | n_funcs | 函数表行数 |
|
||||
| 52/56/60 | offset_const / funcs / code | 段偏移 |
|
||||
| 64 | offset_slots | 槽表段偏移 |
|
||||
| 68 | offset_values | 值段偏移(16 对齐) |
|
||||
| 72..103 | 型号标识[32] | `STATOR2`(name+version 补零) |
|
||||
| 104 | n_slots | 槽表条目数(= n_globals + 实例/字段槽数) |
|
||||
| 108 | values_size | 值段字节数 |
|
||||
| 112 | offset_meta | 元数据段偏移(v1 = 0 空段) |
|
||||
| 116 | max_stack | 栈区字节数(编译期算) |
|
||||
| 120..127 | 预留 | 8 字节 |
|
||||
|
||||
- `cycle_limit`、`dt_ms`
|
||||
- 工程哈希
|
||||
- 全局槽数、I/Q/M 槽数
|
||||
- 常量表 / 函数表 / 字节码 / FB 布局的偏移
|
||||
- 入口 `MAIN` 的 `fn_id`
|
||||
### 8.2 段序
|
||||
|
||||
函数表一行:`fn_id`、`nregs`、字节码偏移与长度。
|
||||
```text
|
||||
头(128) → 常量表(8B/条) → 函数表(12B/行) → 字节码(16 对齐) →
|
||||
槽表段(8B/条) → 值段 → 元数据段(v1 空) → SHA-256[32]
|
||||
```
|
||||
|
||||
工程哈希:收录源文件路径排序后对内容做 **FNV-1a 64**,只保证回放槽位一致,不是密码学哈希。
|
||||
### 8.3 段细节
|
||||
|
||||
`[[io.input]]` / `[[io.output]]` 不进映像。
|
||||
- **常量表行(8B)**:原始 64 位值(无 tag),LOADK 按 func 解释,按位模式去重
|
||||
- **函数表行(12B)**:`{ nregs:u32, code_offset:u32(字节), code_len:u32(字节) }`
|
||||
- **槽表行(8B)**:`{ addr:u32, 预留:u32 }`
|
||||
- **值段**:全局块 + FB 实例块(1/2/4/8 对齐,编译器布局)
|
||||
- **元数据段**:v1 0 长度(offset_meta = 0)
|
||||
- **文件尾**:SHA-256[32](对去掉尾的内容计算)
|
||||
|
||||
### 8.4 校验(vm 读侧)
|
||||
|
||||
1. 结构校验:魔数/版本/段偏移单调且在 SHA 前/常量表·函数表尺寸一致/代码段对齐/入口 fn_id 范围
|
||||
2. **SHA-256**:完整性(防篡改)
|
||||
3. **型号匹配**:`STATOR2`(name + version=2 补零 32 字节逐字节比对)
|
||||
|
||||
---
|
||||
|
||||
## 目录与 CMake
|
||||
## 9. `.stb` 与 sidecar
|
||||
|
||||
- `<name>.stb`(映像)+ `<name>.runtime.toml`(sidecar:`[[io.input]]`/`[[io.output]]`,var→slot→channel/bit)
|
||||
- I/O 绑定**不进映像**;绑定粒度 `slot = 槽号`(指向槽表条目),channel/bit 语义不变
|
||||
- executor 按 sidecar 采样 I / 写回 Q;`dt_ms`/`cycle_limit` 从映像头取
|
||||
|
||||
---
|
||||
|
||||
## 10. 反汇编格式(A7 定案)
|
||||
|
||||
按 func 显示**类型后缀**:
|
||||
|
||||
```text
|
||||
LOAD.I16 r5, s3 STORE.U32 s1, r7 ADD.F64 r1, r2, r3
|
||||
MOVE r1, r2 LOAD_OFF.I8 r4, s2, 8
|
||||
JMP +4 JT r0, -1 CALL 1 CAL.TON s5
|
||||
```
|
||||
|
||||
前缀分区决定打印模板;010 区/16B 区按形态直推。
|
||||
|
||||
---
|
||||
|
||||
## 11. 目录与 CMake
|
||||
|
||||
```text
|
||||
isa/
|
||||
CMakeLists.txt
|
||||
README.md # 代码目录入口,指向本文
|
||||
README.md
|
||||
include/isa/
|
||||
Types.hpp # 宽度、饱和、槽值表示
|
||||
Op.hpp # 操作码枚举 + 助记符
|
||||
Instr.hpp # 32-bit 打包/拆字段(inline)
|
||||
Image.hpp # 头、段、函数表/常量表/FB 布局
|
||||
Encode.hpp # 编解码、哈希、反汇编一行
|
||||
Types.h # 宽度/符号/饱和、除零边界
|
||||
Op.h # 前缀分区 + 操作码 + 形态
|
||||
Instr.h # 变长 pack/拆字段(inline)
|
||||
Encode.h # encode / decode / disasm
|
||||
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`。
|
||||
|
||||
---
|
||||
|
||||
## 实现顺序(对齐初步计划 12.0~12.1)
|
||||
## 12. 实现约定
|
||||
|
||||
1. `Types.hpp`:三类型 + `sat_add` 等饱和函数(inline 即可)
|
||||
2. `Op.hpp` + `Instr.hpp`:枚举与 32-bit 编解码
|
||||
3. `Encode.cpp`:`encode` / `decode` / `disasm` 一条指令
|
||||
4. `Image.hpp` + `Image.cpp`:头布局、FNV-1a、读写缓冲(先能写空映像 + 一条 `RET`)
|
||||
5. 测试随后在 `tests/` 加编解码往返;本阶段以头文件和库能链为准
|
||||
|
||||
不在本模块做:VM `switch`、toml、关键字、符号表。
|
||||
- machine.toml(V2 schema)与代码**强校验**:前缀分区/op/func 互锁(见指令配置.md,阶段 1 重写)
|
||||
- 操作码有变:先改 machine.toml + 本目录 + 测试,再动两端
|
||||
- 错误类别:`isa` 层无错误前缀(编解码纯函数);vm/compiler 各自带前缀
|
||||
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
# 指令编码(V2:RISC-V 式 op + func + 前缀分区形态)
|
||||
|
||||
> 状态:**定案存档**。RISC-V 式指令编码:opcode 定功能、func 定细节、
|
||||
> **操作数形态由长度组前缀分区表达**(fmt 位段已取消)。
|
||||
> 与 [`指令集优化.md`](指令集优化.md)(格式/数据模型/待确认)配套。修订:2026-08-24。
|
||||
|
||||
## 1. 设计原则(RISC-V 式)
|
||||
|
||||
```text
|
||||
opcode 决定指令的"功能"(MOVE/LOAD/ADD…)
|
||||
func 决定"功能的细节"(宽度/符号/浮点)
|
||||
操作数形态(RR/RRR/IMM/SLOT/…)由长度组前缀分区 + 小表表达
|
||||
```
|
||||
|
||||
## 2. 指令字结构(头两字节 + 操作数区)
|
||||
|
||||
```text
|
||||
指令 = 字节0 + 字节1 + 操作数区(rd/rs/slot/off…,按形态)
|
||||
|
||||
字节 0 字节 1
|
||||
[长度前缀+分区 | op] [func:4 | 空:4]
|
||||
↑ ↑
|
||||
长度+形态分区+操作码 功能细节(空 4 位待全部编码后定)
|
||||
```
|
||||
|
||||
## 3. 前缀编码与形态分区
|
||||
|
||||
前缀从最高位起比对,长度与形态分区一体:
|
||||
|
||||
| 前缀 | 指令长度 | 分区 | 形态 | op 位宽 | op 空间 | 指令 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `11` | 4B | 11 | **RR** | 6 位 | 64 | MOVE/NOT/NEG/ABS/INC/DEC/浮点单目/舍入/CONV(21) |
|
||||
| `10` | 4B | 10 | **NONE** | 6 位 | 64 | NOP/RET(2) |
|
||||
| `011` | 8B | 011 | **RRR** | 5 位 | 32 | 算术/逻辑/移位/位操作/比较(26) |
|
||||
| `010 000` | 8B | 010 000 | **IMM** | 2 位 | 4 | LOADK(1) |
|
||||
| `010 001` | 8B | 010 001 | **SLOT** | 2 位 | 4 | LOAD/STORE(2) |
|
||||
| `010 010` | 8B | 010 010 | **JMP** | 2 位 | 4 | JMP(1) |
|
||||
| `010 011` | 8B | 010 011 | **JC** | 2 位 | 4 | JT/JF(2) |
|
||||
| `010 100` | 8B | 010 100 | **CALL** | 2 位 | 4 | CALL(1) |
|
||||
| `010 101` | 8B | 010 101 | **CAL** | 2 位 | 4 | CAL(1) |
|
||||
| `010 110/111` | 8B | 010 11x | 预留 | 2 位 | 4+4 | — |
|
||||
| `0010` | 16B | 0010 | **SLOT+off** | 4 位 | 16 | LOAD_OFF/STORE_OFF/字符串(8) |
|
||||
| `0011` | 16B | 0011 | **IMM64** | 4 位 | 16 | LOADK64(1) |
|
||||
| `0001` | 32B | 0001 | 预留 | 4 位 | 16 | JTBL/复杂字符串/数组(6,形态待定) |
|
||||
|
||||
- **长度自描述**:`1` 开头 → 4B、`01` → 8B、`001` → 16B、`0001` → 32B(前缀分区不改变长度判定)
|
||||
- **op 按组内独立编号**(各区从 0 起)
|
||||
- 取指:读字节 0 → 前缀定长 → 搬整条;**形态全部由前缀分区直推**(含 010 区内部分区)
|
||||
|
||||
## 4. func 位段(字节 1 高 4 位,16 种)
|
||||
|
||||
| func | 语义 | 覆盖类型(编译期映射) |
|
||||
|---|---|---|
|
||||
| 0 | 1 字节、无符号 | BOOL、BYTE、USINT |
|
||||
| 1 | 1 字节、有符号 | SINT |
|
||||
| 2 | 2 字节、无符号 | WORD、UINT |
|
||||
| 3 | 2 字节、有符号 | INT |
|
||||
| 4 | 4 字节、无符号 | DWORD、UDINT、DATE、TOD |
|
||||
| 5 | 4 字节、有符号 | DINT |
|
||||
| 6 | 8 字节、无符号 | LWORD、ULINT、DT |
|
||||
| 7 | 8 字节、有符号 | LINT、TIME |
|
||||
| 8 | F32 | REAL |
|
||||
| 9 | F64 | LREAL |
|
||||
| 10..15 | 预留 | |
|
||||
|
||||
- **func 语义随 op**:MOVE=宽度;LOAD/STORE=宽度×符号;ADD..=宽度×符号+浮点;逻辑=宽度;无类型指令 func=0(不使用)
|
||||
- **字节 1 低 4 位暂空**:待全部指令编码完后决定用途(func 扩位/扩展字段)
|
||||
|
||||
## 5. 形态识别(全部前缀直推,零查表)
|
||||
|
||||
| 前缀 | 形态 | 识别方式 |
|
||||
|---|---|---|
|
||||
| `11` | RR | 前缀直推 |
|
||||
| `10` | NONE | 前缀直推 |
|
||||
| `011` | RRR | 前缀直推 |
|
||||
| `010 000` | IMM | 前缀直推 |
|
||||
| `010 001` | SLOT | 前缀直推 |
|
||||
| `010 010` | JMP | 前缀直推 |
|
||||
| `010 011` | JC | 前缀直推 |
|
||||
| `010 100` | CALL | 前缀直推 |
|
||||
| `010 101` | CAL | 前缀直推 |
|
||||
| `0010` | SLOT+off | 前缀直推 |
|
||||
| `0011` | IMM64 | 前缀直推 |
|
||||
| `0001` | 预留 | 待定 |
|
||||
|
||||
**010 区内部分区(8 条,形态位 3 位 + 子 op 2 位)**:
|
||||
|
||||
| 完整前缀 | 指令 | 子 op | 形态 | 操作数区 |
|
||||
|---|---|---|---|---|
|
||||
| `010 000` | LOADK | 00 | IMM | rd=23..16、imm32=55..24、pad=63..56 |
|
||||
| `010 001` | LOAD | 00 | SLOT | rd=23..16、slot32=55..24、pad=63..56 |
|
||||
| `010 001` | STORE | 01 | SLOT | 同上 |
|
||||
| `010 010` | JMP | 00 | JMP | off32=47..16、pad=63..48 |
|
||||
| `010 011` | JT | 00 | JC | rd=23..16、off32=55..24、pad=63..56 |
|
||||
| `010 011` | JF | 01 | JC | 同上 |
|
||||
| `010 100` | CALL | 00 | CALL | fn_id32=47..16、pad=63..48(rd 恒 0) |
|
||||
| `010 101` | CAL | 00 | CAL | fb_id16=31..16、slot32=63..32(rd 恒 0) |
|
||||
|
||||
> **rd 恒 0 约定(U14 定案)**:CALL / CAL 的 rd 字节(23..16)未使用,编码恒 0;指令身份由前缀分区 + op 决定,rd 不参与语义。
|
||||
|
||||
## 6. uses_rd / uses_ra / uses_rb(寄存器越界检查推导)
|
||||
|
||||
"哪些字段是寄存器"由形态决定,**全部前缀直推(零查表)**:
|
||||
|
||||
| 前缀 | 形态 | uses_rd | uses_ra | uses_rb |
|
||||
|---|---|---|---|---|
|
||||
| `11` | RR | ✓ | ✓(rs) | — |
|
||||
| `10` | NONE | — | — | — |
|
||||
| `011` | RRR | ✓ | ✓ | ✓ |
|
||||
| `010 000` | IMM | ✓ | — | — |
|
||||
| `010 001` | SLOT | ✓ | — | — |
|
||||
| `010 010` | JMP | — | — | — |
|
||||
| `010 011` | JC | ✓ | — | — |
|
||||
| `010 100` | CALL | — | — | — |
|
||||
| `010 101` | CAL | — | — | — |
|
||||
| `0010` | SLOT+off | ✓ | — | — |
|
||||
| `0011` | IMM64 | ✓ | — | — |
|
||||
|
||||
执行:`if (uses_rd && rd >= nregs) → BadOp`——所有指令零查表,形态由前缀直推。
|
||||
|
||||
## 7. 长度组 × 操作数区
|
||||
|
||||
| 前缀 | 长度 | 操作数区(bit16 起) |
|
||||
|---|---|---|
|
||||
| 11/10 | 4B | RR: rd=23..16、rs=31..24;NONE: pad=31..16 |
|
||||
| 011/010 | 8B | RRR: rd+rs1+rs2+pad;010 区六形态见 §5 |
|
||||
| 0010/0011 | 16B | SLOT+off: rd+slot32+off32+pad;IMM64: rd+imm64+pad |
|
||||
| 0001 | 32B | 预留(操作数区 30B,格式待定) |
|
||||
|
||||
## 8. 类型系统(编译期)→ func 映射
|
||||
|
||||
- **编译期**:19 种 IEC 基础类型,类型检查严格比对——不匹配编译期报错
|
||||
- **BOOL 约束**:编译器限制 0/1,越界报错;VM 无 BOOL 特例
|
||||
- **运行时**:VM 只认 func 的 10 种"宽度+符号+浮点"组合
|
||||
- 常量表:条目 8B 原始值(无 tag),LOADK 按 func 解释
|
||||
|
||||
## 9. 决策历史
|
||||
|
||||
- 用户提出 RISC-V 式:opcode 定功能、func 定细节
|
||||
- 前缀分区:用户提出 `011`=RRR、`010`=其他;后定 010 区内部分区(形态位 3 位+子 op 2 位),**全部形态前缀直推、零查表**;`11`=RR、`10`=NONE(4B);`0010`=SLOT+off、`0011`=IMM64(16B)
|
||||
- **fmt 位段取消**:形态全部由前缀分区表达(010 区内部再分区,零查表);字节 1 = [func:4 | 空:4],空位待全部编码后定
|
||||
- func 4 位(10 组合);MOVE 只管宽度;19 类型 → 10 func 编译期映射;BOOL 编译器强制 0/1
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
# 机器定义文件(machine.toml V2)
|
||||
|
||||
指令集与类型的**唯一事实来源**,**只给编译器**(STCompiler/compiler)使用。执行器(vm)不读本文件——它是下位机程序,内建全部指令。
|
||||
V2 规格(STATOR2,阶段 1 定稿;N5 闭合)。schema 变更:opcode → 前缀分区 + op;class/format → fmt;CAL_* 8 op → 1 CAL + fb 表 15 条。
|
||||
|
||||
## 架构
|
||||
|
||||
```text
|
||||
machine.toml(只给编译器:meta + type 表 + op 表 + fb 表)
|
||||
│ 运行时加载(toml++)+ 强校验(前缀/op/func/fmt 互锁)
|
||||
▼
|
||||
STCompiler → compiler(编码 opcode / 类型元数据 / FB 布局 全部来自配置)
|
||||
│ 写 .stb(compiler 自带写实现:型号标识[32] + SHA-256 文件尾)
|
||||
▼
|
||||
.stb ──→ BytecodeExecutor → vm(执行 switch 在 vm;读 .stb 自实现)
|
||||
│ + 型号匹配校验(STATOR2)+ SHA-256 校验
|
||||
▼
|
||||
isa(归执行器侧,只管指令定义)
|
||||
```
|
||||
|
||||
| 模块 | 职责 |
|
||||
|---|---|
|
||||
| `compiler` | 配置驱动编译;自带 `.stb` 写实现;`--disasm` 用配置参数名/形态自格式化 |
|
||||
| `vm` | 内建全指令集(执行 switch 在 vm);自带 `.stb` 读实现 + 型号/SHA-256 校验;不读配置 |
|
||||
| `isa` | 归执行器侧(vm 依赖),只管指令定义:前缀分区/Op/Instr/Encode/形态/类型/饱和/disasm |
|
||||
| `machine.toml` | 编译器唯一知识来源(V2 schema,本文) |
|
||||
|
||||
## 决策清单(V2 定案)
|
||||
|
||||
1. **指令编码**:变长 4/8/16/32B;**前缀分区**表达长度与形态(`11`/`10`/`011`/`010 xxx`/`0010`/`0011`/`0001`)
|
||||
2. **op 表**:`prefix + op`(分区内编号)替代 V1 的单一 opcode;`fmt` 与前缀互锁;`func` 类别表达数据语义
|
||||
3. **类型**:19 种 IEC 类型(编译器内建基元别名 + kind 语义 + func 显式映射)
|
||||
4. **FB**:15 条内建(fb_id 0..14),`[[fb]]` 表承载字段类型;CAL 指令 + fb_id16(V1 的 CAL_* 8 op 作废)
|
||||
5. 新增指令/类型/FB:**只改配置 + 对应实现**,指令格式零变化(N2/N3 预留位已留)
|
||||
|
||||
## 配置文件格式(TOML V2)
|
||||
|
||||
**全属性必填,不可省略**:
|
||||
|
||||
```toml
|
||||
[meta]
|
||||
name = "STATOR" # 型号名(参与 .stb 型号标识)
|
||||
version = 2 # 格式版本(型号标识 = "STATOR2";V1 = 1)
|
||||
|
||||
# ---- 类型:19 种 IEC 类型(V2)
|
||||
# base = 编译器内建基元(bit/int8/int16/int32/int64/uint8/uint16/uint32/uint64/float32/float64)
|
||||
# kind = 语言语义(bool/bit/int/uint/float/time/date)——同 func 不同 kind 区分(BOOL/BYTE/USINT)
|
||||
# func = type 位段值(0..9),与 base 宽度/符号互锁强校验
|
||||
[[type]]
|
||||
name = "BOOL"
|
||||
base = "uint8"
|
||||
kind = "bool"
|
||||
func = 0
|
||||
|
||||
[[type]]
|
||||
name = "BYTE"
|
||||
base = "uint8"
|
||||
kind = "bit"
|
||||
func = 0
|
||||
|
||||
[[type]]
|
||||
name = "USINT"
|
||||
base = "uint8"
|
||||
kind = "uint"
|
||||
func = 0
|
||||
|
||||
# ...(其余 16 条见 §类型系统,格式同)
|
||||
|
||||
# ---- 指令(V2:prefix + op + fmt + func 类别)
|
||||
# prefix:形态前缀(11/10/011/010000..010101/0010/0011/0001)
|
||||
# op:分区内编号(0 起)
|
||||
# fmt:操作数形态(RR/NONE/RRR/IMM/SLOT/JMP/JC/CALL/CAL/SLOT+off/IMM64/预留)
|
||||
# func:数据语义类别(none=无类型 / width=宽度 0..3 / full=全矩阵 0..9 /
|
||||
# float=浮点 8/9 / dst=目标类型 0..9)
|
||||
# params:参数名(与 fmt 互锁,见强校验)
|
||||
[[op]]
|
||||
name = "MOVE"
|
||||
prefix = "11"
|
||||
op = 0
|
||||
fmt = "RR"
|
||||
func = "width"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "LOAD"
|
||||
prefix = "010001"
|
||||
op = 0
|
||||
fmt = "SLOT"
|
||||
func = "full"
|
||||
params = ["rd", "slot"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CAL"
|
||||
prefix = "010101"
|
||||
op = 0
|
||||
fmt = "CAL"
|
||||
func = "none"
|
||||
params = ["fb_id", "slot"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "RET"
|
||||
prefix = "10"
|
||||
op = 1
|
||||
fmt = "NONE"
|
||||
func = "none"
|
||||
params = []
|
||||
enabled = true
|
||||
|
||||
# ---- 内建 FB(15 条;fb_id 唯一 0..14;字段类型命中 type 表;布局偏移编译期按 1/2/4/8 对齐计算)
|
||||
[[fb]]
|
||||
name = "TON"
|
||||
fb_id = 0
|
||||
fields = [["in", "BOOL"], ["pt", "TIME"], ["q", "BOOL"], ["et", "TIME"]]
|
||||
|
||||
[[fb]]
|
||||
name = "CTU"
|
||||
fb_id = 3
|
||||
fields = [["cu", "BOOL"], ["r", "BOOL"], ["pv", "INT"], ["q", "BOOL"], ["cv", "INT"]]
|
||||
|
||||
[[fb]]
|
||||
name = "DCTU"
|
||||
fb_id = 4
|
||||
fields = [["cu", "BOOL"], ["r", "BOOL"], ["pv", "DINT"], ["q", "BOOL"], ["cv", "DINT"]]
|
||||
|
||||
[[fb]]
|
||||
name = "RTC"
|
||||
fb_id = 14
|
||||
fields = [["enable", "BOOL"], ["date", "DATE"], ["tod", "TOD"]]
|
||||
```
|
||||
|
||||
## 类型系统(19 条,func 互锁)
|
||||
|
||||
| 类型 | base 基元 | kind | func | 宽度/符号 |
|
||||
|---|---|---|---|---|
|
||||
| BOOL | uint8 | bool | 0 | 1B — |
|
||||
| BYTE | uint8 | bit | 0 | 1B 无 |
|
||||
| WORD | uint16 | bit | 2 | 2B 无 |
|
||||
| DWORD | uint32 | bit | 4 | 4B 无 |
|
||||
| LWORD | uint64 | bit | 6 | 8B 无 |
|
||||
| SINT | int8 | int | 1 | 1B 有 |
|
||||
| INT | int16 | int | 3 | 2B 有 |
|
||||
| DINT | int32 | int | 5 | 4B 有 |
|
||||
| LINT | int64 | int | 7 | 8B 有 |
|
||||
| USINT | uint8 | uint | 0 | 1B 无 |
|
||||
| UINT | uint16 | uint | 2 | 2B 无 |
|
||||
| UDINT | uint32 | uint | 4 | 4B 无 |
|
||||
| ULINT | uint64 | uint | 6 | 8B 无 |
|
||||
| TIME | int64 | time | 7 | 8B 有(ms) |
|
||||
| REAL | float32 | float | 8 | 4B IEEE754 |
|
||||
| LREAL | float64 | float | 9 | 8B IEEE754 |
|
||||
| DATE | uint32 | date | 4 | 4B 无(天) |
|
||||
| TOD | uint32 | date | 4 | 4B 无(当日 ms) |
|
||||
| DT | uint64 | date | 6 | 8B 无 |
|
||||
|
||||
- **func 互锁规则**:`(width, signed)` 组合必须与 func 匹配(1B无=0、1B有=1、2B无=2、2B有=3、4B无=4、4B有=5、8B无=6、8B有=7、F32=8、F64=9)
|
||||
- **kind 语义**:bool(编译器强制 0/1)、bit(位串,逻辑/移位运算)、uint(无符号整数算术)、int(有符号整数算术)、float(浮点)、time(TIME 毫秒)、date(DATE/TOD/DT,v1 不运算)
|
||||
- **语言层 19 类型名全量进关键字**;禁隐式变量转换;字面量按上下文适配;进制前缀 2#/8#/16#
|
||||
|
||||
## 强校验清单(加载时,任一不符 → 报错退出)
|
||||
|
||||
1. **meta**:`name` 非空、`version` = 2
|
||||
2. **type**:19 条必填全集(BOOL..DT);`base` 命中内建基元;`kind` 合法;`func` ∈ 0..9 且与 base 的 (width, signed) 互锁
|
||||
3. **op**:`prefix` 合法(11/10/011/010000..010101/0010/0011/0001);`prefix` 与 `fmt` 互锁(11↔RR、10↔NONE、011↔RRR、010000↔IMM、010001↔SLOT、010010↔JMP、010011↔JC、010100↔CALL、010101↔CAL、0010↔SLOT+off、0011↔IMM64);`op` 分区内唯一;`func` 类别合法(none/width/full/float/dst);`params` 数量与 fmt 互锁(RR=2、NONE=0、RRR=3、IMM=2、SLOT=2、JMP=1、JC=2、CALL=1、CAL=2、SLOT+off=3、IMM64=2);`enabled` 布尔
|
||||
4. **fb**:15 条必填全集(fb_id 0..14);`fb_id` 唯一;字段名唯一;字段类型命中 `[[type]]` 表
|
||||
5. **op-fb 互锁**:`CAL`(010101)的 fb_id 由 `[[fb]]` 表提供——CAL 指令存在当且仅当 fb 表非空
|
||||
|
||||
## 加东西的流程
|
||||
|
||||
| 加什么 | 改配置 | 改代码 |
|
||||
|---|---|---|
|
||||
| 新指令(RR/RRR 等已有形态) | op 表加一行(分区内空闲 op) | 编译器发射 + vm 执行 switch |
|
||||
| 新形态(fmt 11..15) | — | 指令编码 + 两端实现(预留位) |
|
||||
| 新类型(标量) | type 表加行 + func 空位 | 编译器类型层 + vm func 表 |
|
||||
| 新内建 FB | fb 表加行 | vm do_cal 分派 + 编译器实例布局 |
|
||||
| 字符串/数组/跳转表 | — | 32B 组(0001)实现(N2/N4,延后) |
|
||||
|
||||
## 与 V1 schema 的差异
|
||||
|
||||
| 项 | V1 | V2 |
|
||||
|---|---|---|
|
||||
| meta.version | 1 | **2** |
|
||||
| op 标识 | `opcode`(0..33 全局) | **`prefix` + `op`**(分区内) |
|
||||
| 分类 | class(plain/instance)+ format | **fmt**(与 prefix 互锁) |
|
||||
| 数据语义 | tag(0/1/2) | **func** 类别(none/width/full/float/dst) |
|
||||
| 类型 | 3 条(BOOL/INT/TIME) | **19 条**(base/kind/func) |
|
||||
| FB | 8 条(opcode 对应 CAL_*) | **15 条**(fb_id 0..14) |
|
||||
@@ -0,0 +1,356 @@
|
||||
# 指令集优化(V2 规格,型号 STATOR2)
|
||||
|
||||
> 状态:**规格冻结中**。核心决策已全部定案(§2),本文为 V2 规格草案;
|
||||
> §4 列出**尚未确定的细节项**,全部闭合后进入实现阶段。
|
||||
> 与现状(V1)的关系:V1 编码冻结不可改(opcode 0..33、32bit 指令、8 字节定宽槽),
|
||||
> 本优化为**全新 V2 架构**,.stb 格式 / 指令字 / 数据模型全部重设计,型号 bump 为 STATOR2。
|
||||
|
||||
## 1. 背景与动机
|
||||
|
||||
| # | 现状问题 | 优化方向 |
|
||||
|---|---|---|
|
||||
| 1 | opcode 定义草率:34 条按语义乱序编号,无位域结构 | format/宽度**编码进 opcode**,位掩码机械推导 |
|
||||
| 2 | 8 字节定宽槽浪费:BOOL 只用 1/8 空间 | **变宽紧凑值区**(1/2/4/8 字节按类型) |
|
||||
| 3 | 变量区利用率低 | 值区按类型紧凑布局 + 地址 u32(4GB) |
|
||||
| 4 | 类型只有 BOOL/INT/TIME | 扩展 IEC 全基础类型(19 种) |
|
||||
| 5 | 结构体无法支持 | 槽表引用 + LOAD 16B 变体(基槽+偏移,预留) |
|
||||
| 6 | 指令 32bit 固定 | 变长指令(长度编码在 opcode);寄存器编号维持 8 位 |
|
||||
| 7 | 槽(变量)与初值(数据)耦合在同一格 | **槽表(引用)与值区(数据)解耦** |
|
||||
|
||||
## 2. 已定案设计
|
||||
|
||||
### 2.1 变长指令,前缀编码 + 形态分区
|
||||
|
||||
- 指令长度**不固定**:4 / 8 / 16 字节(32 字节预留);**长度与形态由前缀分区一体表达**(见 [`指令编码.md`](指令编码.md))
|
||||
- **前缀分区**:`11`=RR、`10`=NONE(4B);`011`=RRR、`010 000/001/010/011/100/101`=IMM/SLOT/JMP/JC/CALL/CAL(8B);`0010`=SLOT+off、`0011`=IMM64(16B);`0001`=预留(32B)
|
||||
- **fmt 位段已取消**(用户定案):形态全部由前缀分区表达(含 010 区内部分区,零查表)
|
||||
|
||||
```text
|
||||
字节 0 字节 1 字节 2..n(按长度组)
|
||||
[前缀+形态分区 | op] [func:4 | 空:4] 操作数区
|
||||
```
|
||||
|
||||
| 位段 | 位号 | 内容 |
|
||||
|---|---|---|
|
||||
| 前缀+分区 | 4B: bit7..6 / 8B: bit7..5 / 16B: bit7..4 / 32B: bit7..4 | 长度与形态分区 |
|
||||
| op | 4B: bit5..0 / 8B: bit4..0 / 16B: bit3..0 / 32B: bit3..0 | 分区内独立编号 |
|
||||
| func | bit15..12 | 数据语义(宽度×符号/浮点),语义随 op |
|
||||
| 空 | bit11..8 | 待全部编码完后定用途 |
|
||||
|
||||
| 长度组 | 指令长度 | 形态分区 | 操作数区 |
|
||||
|---|---|---|---|
|
||||
| `11`/`10` | 4 字节 | RR / NONE | rd + rs1(各 1B)/ 无 |
|
||||
| `011`/`010` | 8 字节 | RRR / 010 区六形态(分区直推) | rd+rs1+rs2 / 见指令编码.md §5 |
|
||||
| `0010`/`0011` | 16 字节 | SLOT+off / IMM64 | rd + slot32 + off32 / rd + imm64 |
|
||||
| `0001` | 32 字节 | 预留 | 超大立即数/未来扩展 |
|
||||
|
||||
- 取指:读字节 0 → 前缀定总长 → 搬 4/8/16 字节
|
||||
- **推导**(编码在头两字节):
|
||||
- 指令长度 + 形态 ← 前缀分区(010 区查表)
|
||||
- uses_rd/uses_ra/uses_rb ← 形态:全部前缀直推(指令编码.md §6)
|
||||
- 数据宽度/符号 ← `func` 位段(查 constexpr 宽度/符号表)
|
||||
- **字段访问统一为 LOAD/STORE 16B 变体**:`LOAD rd, slot, off32`(基槽 + 偏移,编译期算死)——结构体字段与 FB 共享体字段**同一指令**,无单独 LOADF
|
||||
- **跳转偏移字节化**:off32(字节,相对下一条);pc/code_offset/code_len 全字节单位;code 段 16 字节对齐
|
||||
|
||||
### 2.2 寄存器模型与调用栈(U10 定案)
|
||||
|
||||
- **寄存器编号 = 1 字节(8 位)→ 256 个为寻址上限**(8 位字段能编的最大寄存器号),**不是每帧分配数**
|
||||
- **每帧按函数头 nregs 动态分配**(编译器按该函数变量+临时峰值算):帧大小 = nregs × 8B(百字节级),栈深 64 上限
|
||||
- 编号容量恒定关系:`8bit×256 == 16bit×128 == 32bit×64 == 64bit×32`(指编号位宽 × 数量反比,编号空间,非帧大小)
|
||||
- 寄存器值仍 8 字节(int64);codegen `register overflow (>256)` 检查保留
|
||||
- 调用约定不变:r0 结果、r1..r7 参数(≤7)、r8+ 变量/临时;CALL/RET 帧复制 = **r0..r7 全宽(64B)**,类型解释是下游指令按 func 的事
|
||||
|
||||
#### 调用栈(方案 C:预分配栈区,U10 定案)
|
||||
|
||||
- **无递归**:链接层检测调用图环(FUNCTION 自调用/循环调用)→ **编译期报错**(`link error: recursive call`);无环 → 最坏调用路径编译期可算
|
||||
- **帧结构(每帧 12B 帧头 + nregs×8B 寄存器区)**:帧头 = { fn_id:u32, ret_pc:u32, ret_fn_id:u32 };寄存器区 = 本函数 nregs 个 int64
|
||||
- **max_stack 编译期计算(写入映像头 @116)**:`max_stack = MAIN 帧需求 + 最坏 FUNCTION 调用路径 Σ(帧需求)`,帧需求 = 12 + nregs×8;调用图为 DAG(无递归),最坏路径 = 按字节权重的最长路径;MAIN 帧计入(常驻栈底)
|
||||
- **预分配栈区**:Machine 构造时一次性分配 max_stack 字节连续内存,运行期零分配
|
||||
- **压帧**:从栈顶取 帧需求(12 + nregs×8),sp += 帧需求;**弹帧**:sp -= 帧需求
|
||||
- **溢出检查双口径**:剩余栈 < 帧需求 → `StackOverflow`(字节主检查)+ 深度 ≤64(双保险)
|
||||
- **MAIN 帧常驻栈底**(跨周期保留),周期结束回退 sp 到 MAIN 帧顶
|
||||
|
||||
### 2.3 数据模型:槽表(引用)→ 值区(数据)
|
||||
|
||||
```text
|
||||
槽表段(静态引用表,8B/条) 值段(初值,紧凑变宽)
|
||||
┌─────────────────────────┐ ┌──────────────────────────┐
|
||||
│ { addr:u32, 预留:u32 } │ ──地址──▶ │ BOOL 1B / INT 2B(对齐) │
|
||||
│ 槽 0..n_slots-1 │ │ DINT·REAL 4B / TIME 8B │
|
||||
└─────────────────────────┘ │ 结构体 = 连续字段块(预留) │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
- 访问:`LOAD rd, slot` → `addr = 槽表[slot].addr` → 值区按 `type` 位段读 1/2/4/8 字节;字段访问 `LOAD rd, slot, off32` → `addr + off`
|
||||
- **两级间接**:每次访问多一次内存读(PLC 规模无感)
|
||||
- **宽度编码进 opcode**:`type` 位段携带宽度 + 符号,VM 无歧义读写(V1 因"变宽布局无法从槽号推断宽度"而改定宽,V2 用 type 位段化解)
|
||||
- 对齐规则:1/2/4/8 字节,编译器布局保证
|
||||
- 初值**整合在 .stb 内分段**(ELF 式):槽表段 / 值段 / 元数据段并列,单文件 SHA-256 校验
|
||||
- 寻址规模:**全 32 位**——槽号 u32(2^32 引用)、值区地址 u32(4GB)
|
||||
|
||||
### 2.4 类型系统:IEC 全基础类型(19 种,type:5)
|
||||
|
||||
`type` 位段 **type:5(32 种)**:19 个 IEC 基础类型 + 13 预留;结构体/字符串/数组不占位段(组合类型走元数据段)。
|
||||
|
||||
| type | 类型 | 宽度 | 符号 | C 对应 | type | 类型 | 宽度 | 符号 | C 对应 |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| 0 | BOOL | 1B | — | bool | 10 | UINT | 2B | 无 | uint16_t |
|
||||
| 1 | BYTE | 1B | 无 | uint8_t | 11 | UDINT | 4B | 无 | uint32_t |
|
||||
| 2 | WORD | 2B | 无 | uint16_t | 12 | ULINT | 8B | 无 | uint64_t |
|
||||
| 3 | DWORD | 4B | 无 | uint32_t | 13 | TIME | 8B | 有(ms) | int64_t |
|
||||
| 4 | LWORD | 8B | 无 | uint64_t | 14 | **REAL** | 4B | IEEE754 | **float** |
|
||||
| 5 | SINT | 1B | 有 | int8_t | 15 | **LREAL** | 8B | IEEE754 | **double** |
|
||||
| 6 | INT | 2B | 有 | int16_t | 16 | **DATE** | 4B | 无 | uint32_t(天) |
|
||||
| 7 | DINT | 4B | 有 | int32_t | 17 | **TOD** | 4B | 无 | uint32_t(当日 ms) |
|
||||
| 8 | LINT | 8B | 有 | int64_t | 18 | **DT** | 8B | 无 | uint64_t |
|
||||
| 9 | USINT | 1B | 无 | uint8_t | 19..31 | 预留 | | | |
|
||||
|
||||
- **type 位段红利**:CMP/算术按类型自动语义——无符号类型无符号比较、整数饱和到类型宽度(模板化)、浮点走 IEEE 754(无饱和,溢出→±inf)、读取按宽度+符号/零扩展
|
||||
- **整数除法语义(A 定案)**:除零 → **返回被除数**(结果保持,Fail-safe);MIN/-1 溢出 → **饱和夹界**(各宽度模板化);截断除法(向零,C 风格);**MOD 符号随被除数**(-7 MOD 3 = -1,与除法一致),除零同 DIV
|
||||
- **DATE/TOD/DT(U13 定案)**:DATE=uint32(天数)、TOD=uint32(当日 ms)、DT=uint64 **{date:32 \| tod:32} 拼接**(高 32 位日期);DT→DATE 取高 32 位、DT→TOD 取低 32 位(CONV/移位展开);v1 无日期运算仅存取/比较
|
||||
- **浮点表示**:REAL 4B / LREAL 8B,值区 4/8 字节对齐;寄存器 int64 位模式搬运(REAL 低 4 字节),运算时按 type 重解释
|
||||
- **常量表契约(A2 定案)**:条目 = **8 字节原始值(无 tag)**,按位模式去重;
|
||||
`LOADK rd, const_id`(8B IMM 形态)**用指令 type 位段**解释(读 width 字节、按符号扩展),
|
||||
与 LOAD 语义统一;同一常量可被不同 type 的 LOADK 复用(字面量按上下文适配的落地)。
|
||||
V1 的 {tag, value} 契约废弃。IMM64 形态(16B 内嵌 64 位值)**保留编码、v1 不发射**。
|
||||
- **ST 层**:19 类型名全量进关键字;**进制前缀** `2#`/`8#`/`16#` 支持(浮点无前缀);浮点十进制字面量(`1.5`、`-2.5e3`)
|
||||
- **类型转换**:**禁止隐式变量转换**(赋值/比较/算术要求类型严格一致,Typecheck 阶段报错);**字面量按上下文适配**(`5` 可赋 INT/REAL/DINT 等,编译期解释,非运行时转换);显式转换函数(`INT_TO_REAL()`)v1 不做
|
||||
|
||||
### 2.5 映像格式(头 128 字节,16 对齐)——U7 已定案
|
||||
|
||||
```text
|
||||
0..3 magic(STSC,不变)
|
||||
4..7 version = 2(V2 格式;V1 = 1)
|
||||
8..71 原 15 字段(cycle_limit/dt_ms/project_hash/entry_fn_id/
|
||||
n_globals/n_i/n_q/n_m/n_consts/n_funcs + offset_const/funcs/code/fb/data)
|
||||
64 offset_slots ← 槽表段(原 offset_fb 改义,FB 并入值区)
|
||||
68 offset_values ← 值段(原 offset_data 改义,起点 16 对齐)
|
||||
72..103 型号标识(STATOR2)
|
||||
104 n_slots ← 槽表条目数(= n_globals + FB 实例/结构体字段槽数)
|
||||
108 values_size ← 值段字节数(原 n_values,命名修正)
|
||||
112 offset_meta ← 元数据段偏移(v1 恒 0 = 空段;结构体/字符串类型表预留)
|
||||
116 max_stack ← 栈区字节数(编译期按调用图算,U10 方案 C)
|
||||
120..127 预留 8 字节
|
||||
```
|
||||
|
||||
段序:头 → 常量表 → 函数表 → 字节码 → 槽表段 → 值段 → 元数据段(v1 空) → SHA-256 尾
|
||||
|
||||
**U7 修正注记(2026-08-24)**:
|
||||
- 「原 15 字段」修正为**原 17 字段(72 字节)**
|
||||
- `n_values` 改名 **`values_size`**(值段字节数,避免与"数量"混淆)
|
||||
- 预留区固定 **`offset_meta` @112**(v1 填 0 = 元数据段不存在,将来启用不改头)
|
||||
- **n_i/n_q/n_m 保留恒 0**(V1 遗产字段:兼容 + sidecar 校验/将来 I/Q/M 分区语义预留,不删)
|
||||
- **n_slots = n_globals + FB 实例/结构体字段槽数**(明确关系)
|
||||
- **offset_values 16 对齐**(TIME/LREAL 8B 对齐的上界;编译器保证)
|
||||
|
||||
### 2.6 其他定案
|
||||
|
||||
- **型号 bump**:STATOR1 → **STATOR2**
|
||||
- **I/Q/M 六条 LOAD/STORE 合并为 LOAD/STORE 两条**(单一值区,语义标签交给 sidecar)
|
||||
- **LOADF 取消**:结构体/FB 字段访问统一用 LOAD/STORE 16B 变体(基槽 + 偏移)
|
||||
- **FB 共享体实施**:16B 变体即基址寻址指令,FB 体编译一次消除内联膨胀
|
||||
- 结构体语法 v1 **不实现**,只留编码路径(16B 变体 + 元数据段)
|
||||
- **FB 编码定案(B1/B2)**:**1 条 CAL**(SLOT 形态,8B:头 2 + fb_id:16 + slot32)+ **fb_id 16 位**(查 machine.toml `[[fb]]` 表);新增内建 FB 只改配置 + VM 实现,指令格式零变化。V1 的 CAL_TON..CAL_F_TRIG 8 个 opcode 作废
|
||||
- **领域指令不进指令集(B4 定案)**:运动控制 / 通信等**全部走 FB 库**(ST 层 FB + CALL + 共享体),V2 **不设 EXT 服务指令**——op 位段只承载基础指令
|
||||
|
||||
### 2.7 调用与内联策略(V1 现状 + V2 方向)
|
||||
|
||||
**V1 现状**:
|
||||
|
||||
| 对象 | V1 处理 | 原因 |
|
||||
|---|---|---|
|
||||
| 用户 FB(FUNCTION_BLOCK) | **调用点内联展开**(字节码随调用点数膨胀) | 12.8 定案:FB 体访问**绝对字段地址**,不同实例基址不同,共享体无法复用 |
|
||||
| FUNCTION(FC) | 独立函数 + `CALL fn_id` 帧栈 | FC 无状态,寄存器约定(r1..r7 / r0),无地址冲突;递归在前层被拒 |
|
||||
| 内建 FB(TON/CTU/…) | `CAL_*` 指令(VM 运行时原语 `do_cal`) | 定时器/计数器是 VM 侧状态机,天然共享 |
|
||||
|
||||
**V2 定案**:16B 变体 `LOAD/STORE rd, slot, off` = 基址寻址——FB 体编译一次,运行时按实例槽定位字段,**消除内联膨胀**。FUNCTION 仍走 CALL 帧栈;内建 FB 仍走 CAL(SLOT 形态,实例号即槽号)。
|
||||
|
||||
## 3. 与 V1 的差异对照
|
||||
|
||||
| 项 | V1(现状) | V2(定案) |
|
||||
|---|---|---|
|
||||
| 指令长度 | 固定 32bit | **变长 4/8/16B**,长度在 opcode |
|
||||
| opcode | 0..33 乱序,无位域 | `[前缀+形态分区\|op]` + `[func:4\|空:4]` |
|
||||
| 格式 | 9 种(RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE) | **形态由前缀分区表达**(11/10/011/010查表/0010/0011),fmt 位段取消 |
|
||||
| 寄存器编号 | 8 位(256/帧) | 8 位(256/帧,不变) |
|
||||
| 变量访问 | 立即数槽号直接寻址 | **槽表引用 → 值区**(两级间接) |
|
||||
| 槽 | 8B 定宽 | 8B 引用表({addr:u32}) |
|
||||
| 值区 | 定宽 8B/变量 | **变宽紧凑**(1/2/4/8B) |
|
||||
| 宽度信息 | 无(定宽) | **type 位段**(RISC-V 式) |
|
||||
| 类型 | BOOL/INT/TIME | **19 IEC 基础类型 + 13 预留** |
|
||||
| 字段访问 | 无(FB 内联) | **LOAD/STORE rd, slot, off**(结构体/FB 共用) |
|
||||
| FB 调用 | 用户 FB **内联展开** | **基址寻址共享体**(消除膨胀) |
|
||||
| 寻址空间 | 槽 65535(512KB) | 槽 2^32、值区 4GB |
|
||||
| 初值 | 槽内 | 值段(逻辑分段,同文件) |
|
||||
| 跳转/pc | 条数单位 | **字节单位** |
|
||||
| 型号 | STATOR1 | STATOR2 |
|
||||
|
||||
## 4. 尚未确定项(细节,待逐项闭合)
|
||||
|
||||
核心决策已全部定案,以下为进入实现前需闭合的设计细节:
|
||||
|
||||
| # | 项 | 状态 |
|
||||
|---|---|---|
|
||||
| ~~A1~~ | ~~元数据段格式~~ | **已定案(U12)**:v1 0 长度占位(offset_meta=0),schema 延后 |
|
||||
| ~~A2~~ | ~~常量内嵌 vs 常量表~~ | **已定案**:常量表(8B LOADK 查表);LOADK 用 type 位段解释;条目 8B 无 tag;IMM64 预留不发射 |
|
||||
| A3 | 内建 FB 字段类型化 | **已定案**:字段类型化(TON: in/pt/q/et = BOOL/TIME/BOOL/TIME;R_TRIG: clk/q = BOOL/BOOL);计数器单/双字(INT/DINT)**编码形式 = fb 表拆条**(CTU/DCTU/CTD/DCTD/CTUD/DCTUD 6 条,名字即宽度)——U5 已定案 |
|
||||
| A4 | DATE/TOD/DT 字面量 | **已定案(V2 修订)**:**V2 支持** IEC 格式 `D#2026-8-24`/`TOD#12:30:00`/`DT#2026-8-24-12:30:00`;纪元 1970-01-01=0 天;TOD 无小数秒;年 1..9999(详见阶段3-语言层V2.md L10) |
|
||||
| A5 | 算术语义细化 | **已定案**:饱和模板化到各宽度(SINT/DINT/LINT);TIME+TIME / TIME-TIME / TIME 比较;浮点 IEEE 常规(溢出→±inf、0 除→±inf、NaN 传播,无特判);DATE/TOD/DT **不做运算**(仅存取/比较) |
|
||||
| A6 | sidecar/I/O 绑定 | **已定案**:与现状一致——绑定粒度仍 `slot = 槽号`(指向槽表条目),channel/bit 语义不变 |
|
||||
| A7 | disasm 文本格式 | **已定案**:**类型后缀**形式:`LOAD.I16 r5, s3`、`STORE.U32 s1, r7`、`ADD.F64 r1, r2, r3` |
|
||||
| A8 | machine.toml V2 schema | **已定案**:type 表 = **编译器内建基元别名**(bit/int8/int16/int32/int64/uint8/uint16/uint32/uint64/float32/float64):REAL→base float32(=C float)、LREAL→base float64(=C double),其余按位宽/符号映射;op 表 {name, op(0..63), fmt} + fmt/type 位段互锁校验 |
|
||||
| ~~A9~~ | ~~字面量类型推断~~ | **已定案**:按上下文适配;默认 DINT 仅作兜底语义(上下文恒存在,正常程序不触发) |
|
||||
| ~~A10~~ | ~~FB 编码~~ | **已定案(B1/B2)**:1 CAL + fb_id **16 位**(查 [[fb]] 表);新增 FB 只改配置 + VM 实现 |
|
||||
|
||||
另:CALL 的 V2 确认(随 A10)——ret_pc 字节化(随跳转已定);CALL(IMM 形态)rd 恒 0;帧/寄存器约定不变(r0..r7 复制)。
|
||||
|
||||
### 未定 / 待细化(C1..C6)
|
||||
|
||||
| # | 项 | 说明 |
|
||||
|---|---|---|
|
||||
| ~~C1~~ | ~~元数据段格式(原 A1)~~ | **已定案(U12)**:v1 0 长度占位(offset_meta=0),schema 延后 |
|
||||
| ~~C2~~ | ~~头 128B 布局终稿~~ | **已定案(U7 修正版)**:见 §2.5(offset_meta/max_stack/values_size 等) |
|
||||
| C3 | machine.toml `[[fb]]` 表 schema | **字段/布局已定案**(指令集清单 §8.1);`[[fb]]` **配置格式**并入 N5(machine.toml V2 schema,阶段 1) |
|
||||
| ~~C4~~ | ~~CALL/CAL 的 rd 恒 0 成文~~ | **已定案(U14)**:已写入指令编码.md §5 |
|
||||
| C5 | line1 推演重算 | 实施阶段(阶段 5)才需要 |
|
||||
| ~~C6~~ | ~~计数器字段表终稿~~ | **已定案**:指令集清单 §8.1(CTU 8B / DCTU 16B / CTUD 10B / DCTUD 16B) |
|
||||
|
||||
## 8. 待讨论(用户反问)
|
||||
|
||||
### A9:什么情况下会没有上下文?(已定案)
|
||||
|
||||
ST 中字面量只能出现在表达式/赋值里(`x := 5`、`y + 5`、`IF 16#FF > t THEN`),
|
||||
目标类型或运算操作数类型恒在,v1 无"裸字面量"位置——**上下文恒存在**。
|
||||
因此"默认 DINT"只是防御性兜底(编译器内部缺类型时的报错路径),
|
||||
正常程序不触发。结论:按上下文适配即可,默认 DINT 保留为兜底语义。**已确认(2026-08-24)**。
|
||||
|
||||
### A10:FB 为什么会有 8 种?(已定案)
|
||||
|
||||
V1 的 CAL_TON..CAL_F_TRIG 是 8 个 opcode(24..31),因为 V1 opcode 充足、无位段压力。
|
||||
V2 的 op 位段仅 6 位(64 个),FB 占 8 个不划算,且 machine.toml 已有 `[[fb]]` 表
|
||||
(8 条,含 opcode 字段)——**可收敛为 1 个 CAL(SLOT 形态)+ FB 类型 id(立即数/查 fb 表)**:
|
||||
- 收益:op 位段省 7 个;新增 FB(如自定义内建)不加 opcode、只加配置
|
||||
- 代价:VM 按 id 分派(一次查表,可接受);disasm 需查 fb 表打印名称
|
||||
- 关联:A3 的单字/双字计数变体编码也随此定(fb 表加 width/type 列即可)
|
||||
**已定案(B1/B2)**:1 CAL + fb_id 16 位。
|
||||
|
||||
后续扩展讨论(B4/B5):领域指令(运动控制/通信)**不进指令集**——PLCopen 运动控制本
|
||||
是 FB 库;V2 不设 EXT 服务指令,一切领域功能走 FB 库(CALL + 共享体)。op 位段预算见 B5。
|
||||
|
||||
## 5. 影响面清单
|
||||
|
||||
| 模块 | 改动 |
|
||||
|---|---|
|
||||
| isa | Op.h 重排(位域)、Instr.h 变长 pack/拆、Encode disasm 表驱动、Types 宽度/符号表 + 饱和模板化 |
|
||||
| compiler | machine.toml V2(type 表 19 条 + fmt/type 位段互锁)、MachineConfig 校验、Codec 变长、Codegen(槽表+值区布局、初值写值段、E_* 变长发射、字节化回填)、Stb 新头/段 |
|
||||
| vm | Image 新头/段校验、Machine 取指变长 + 两级访问(槽表→值区)+ 按 func 读写 + **预分配栈区**(max_stack,替代动态 vector frames) |
|
||||
| executor | slot_bool 按 BOOL 读 1 字节(小改) |
|
||||
| tests | isa/vm/machine/codegen/compiler 全部重写;cases 20 个重新生成产物 |
|
||||
| docs | 指令与映像、指令配置、stb文件格式、寄存器码、指令执行(line1 推演重算)、扫描周期、初步计划、README |
|
||||
|
||||
## 6. 实施阶段(每阶段独立可验证 + 提交)
|
||||
|
||||
1. **规格冻结**:本文 §4 全部闭合后,重写指令与映像.md / 指令配置.md / stb文件格式.md
|
||||
2. **isa 重构**:变长 Instr + 位域 Op + Encode + isa_test
|
||||
3. **compiler 侧**:machine.toml V2 + MachineConfig + Codec + Codegen + Stb + 对应测试
|
||||
4. **vm 侧**:Image + Machine(两级访问)+ vm_test
|
||||
5. **executor + 全链路**:cases 20 个 + cli 测试 + line1 产物重生成
|
||||
6. **文档收尾**:指令执行推演、扫描周期、初步计划、README
|
||||
|
||||
## 7. 决策历史(讨论纪要)
|
||||
|
||||
- 动机源起:用户质疑 opcode 定义草率;探索 format 位前缀编码(方案 A)与 uses_rd 位掩码推导
|
||||
- 数据模型演进:8B 定宽槽 →(用户提出槽表引用 + 初值分离)→ 槽表/值区两级模型
|
||||
- 宽度信息:用户选「宽度编码进 opcode」(RISC-V 式,对应「槽表条目带类型」被否)
|
||||
- 初值组织:用户选「整合 .stb 内分段」(ELF 式)
|
||||
- 寻址规模:用户选「全 32 位」(槽号 u32 + 地址 u32)
|
||||
- 指令长度:用户定「变长指令,长度编码在 opcode」;此前 128bit 固定长度方案被否
|
||||
- 寄存器:用户定「编号 8 位(256/帧)维持现状」,容量恒定式 8bit×256=16bit×128=32bit×64=64bit×32
|
||||
- 类型扩展:IEC 整数族 12 种 + REAL/LREAL(= C float/double)+ DATE/TOD/DT → **19 种,type:5(32 种)**
|
||||
- 格式合并:用户确认原子类型固定后选位段方案;数位得出 type:5;CALL→IMM、CAL→SLOT 形态合并,fmt 3 位
|
||||
- 调用策略:V1 用户 FB 内联 / FC CALL / 内建 CAL_*;V2 用 16B 变体(基槽+偏移)实现 FB 共享体
|
||||
- LOADF 取消:用户指出 LOADF 与 LOAD 无本质区别(仅数据搬运)→ 字段访问统一 LOAD/STORE 16B 变体
|
||||
- 类型转换:用户定「禁止隐式变换,编译阶段检查」+ 字面量按上下文适配
|
||||
- 字面量:用户定支持 IEC 进制前缀(2#/8#/16#)
|
||||
- A2 定案:LOADK 用 type 位段解释常量值(与 LOAD 语义统一);常量表条目 8B 无 tag;IMM64 预留不发射
|
||||
- A3..A8 定案(2026-08-24):计数器单/双字两种;DATE 字面量不支持;TIME/浮点运算按推荐;sidecar 一致;disasm 类型后缀;type 表=基元别名(REAL→float32、LREAL→float64)
|
||||
- A9 定案:按上下文适配、默认 DINT 兜底(用户确认)
|
||||
- U5 定案:内建 FB 15 条全部 v1 实现(6 计数拆条 + TON/TOF/TP/R/F_TRIG + SR/RS、PWM、RTC)
|
||||
- U6 定案:op 预算按指令集清单/指令编码文档方案(前缀分区)
|
||||
- U7 定案:头 128B 修正版(offset_meta 占位、values_size 命名、n_i/n_q/n_m 保留恒 0、n_slots 关系、16 对齐)
|
||||
- U8 定案:槽表条目 {addr:u32, 预留:u32} 8B
|
||||
- U9 定案:值段 1/2/4/8B 对齐编译器保证
|
||||
- U10 定案:寄存器/调用约定维持现状、帧按 nregs 动态分配;栈方案 C(预分配栈区 + max_stack@116 编译期算 + 字节/深度双检查);递归 = 链接层环检测编译期报错
|
||||
- U12 定案:元数据段 v1 0 长度占位(offset_meta=0),schema 延后
|
||||
- U13 定案:DATE/TOD/DT 表示(DT={date:32|tod:32} 拼接,转换取高/低 32 位)
|
||||
- U14 定案:CALL/CAL rd 字段恒 0(按指令编码/指令集清单文档)——**全部 U 项闭合,V2 规格冻结**
|
||||
- 版本线确认:当前设计 = **V2**(V1 为已实现简化版);头 version = 2,型号 STATOR2
|
||||
- 帧头 12B 成文(fn_id/ret_pc/ret_fn_id)+ max_stack 计算细则(MAIN 计入、DAG 最长路径)
|
||||
- 整数除法语义 A 定案:除零返回被除数、MIN/-1 饱和、截断除法、MOD 符号随被除数(延续 V1 饱和算术家族)
|
||||
- A10 定案(B1/B2):1 CAL + fb_id 16 位(查 [[fb]] 表);V1 的 8 个 CAL_* opcode 作废
|
||||
- B4 定案:领域指令(运动控制/通信)不进指令集,一律 FB 库;无 EXT 服务指令
|
||||
- B3/B5 说明中待确认:单/双字计数编码、op 位段预算(均已随 U5/U6 定案)
|
||||
- 未定项记录:C1..C6(均已闭合:C1=U12、C2=U7、C3=字段表已定+配置格式入 N5、C4=U14、C5=实施阶段、C6=§8.1 字段表终稿)
|
||||
|
||||
## 9. 确认核验清单(2026-08-24 核对)
|
||||
|
||||
> 原则:**未明确回答 ≠ 同意**。本清单逐项记录「用户明确回答」与「待确认」,
|
||||
> 待确认项由用户逐项表态后闭合。
|
||||
|
||||
### 9.1 已确认(用户明确回答)
|
||||
|
||||
| # | 项 | 用户回答 |
|
||||
|---|---|---|
|
||||
| K1 | 变长指令(长度编码在 opcode) | 「变长指令,指令长度编码再opcode里面」 |
|
||||
| K2 | 寄存器 8 位编号 256/帧(容量恒定式) | 「寄存器大小不变,8bit*256 == 16bit*128 == ...」 |
|
||||
| K3 | 宽度编码进 opcode(非槽表带类型) | question 选「宽度编码进 opcode」 |
|
||||
| K4 | 初值整合 .stb 内分段(ELF 式) | question 选「整合在 .stb 内分段」 |
|
||||
| K5 | 全 32 位寻址(槽号 u32 + 地址 u32) | question 选「全 32 位寻址」 |
|
||||
| K6 | type:5(32 种,含 DATE/TOD/DT 19 基础类型) | 「B」 |
|
||||
| K7 | 跳转偏移字节化 | 「接受」 |
|
||||
| K8 | I/Q/M 六条合并为 LOAD/STORE 两条 | 「合并」 |
|
||||
| K9 | LOADF 取消(LOAD/STORE 16B 变体=字段访问) | 「LOADF和普通的LOAD应该没有区别吧」 |
|
||||
| K10 | ST 层类型全量(19 类型名进关键字) | 「全量」 |
|
||||
| K11 | 禁止隐式变量转换(编译期检查) | 「禁止隐式变换,只需要编译阶段检查」 |
|
||||
| K12 | 进制前缀(2#/8#/16#) | 「进制前缀」 |
|
||||
| K13 | A2 常量表(LOADK type 解释、条目 8B 无 tag、IMM64 预留) | 「使用常量表吧」+ 讨论 |
|
||||
| K14 | A3 计数单字/双字两种都要 | 「需要两种,单字的和双字的计数指令都要有」 |
|
||||
| K15 | A4 DATE/TOD/DT 字面量 v1 不支持 | 「先不支持」 |
|
||||
| K16 | A5 算术语义(饱和模板/TIME±/浮点 IEEE/DATE 不运算) | 「先做吧」 |
|
||||
| K17 | A6 sidecar 绑定粒度一致(slot=槽号) | 「应该可以保持一致」 |
|
||||
| K18 | A7 disasm 类型后缀(LOAD.I16 r5, s3) | 「类型后缀」 |
|
||||
| K19 | A8 type 表=基元别名(REAL→float32、LREAL→float64) | 「类型:编译器内建基元...float32-float,float64-double」 |
|
||||
| K20 | A9 字面量按上下文适配、默认 DINT 兜底 | 「就这么做」 |
|
||||
| K21 | B1 fb_id 16 位 | 「16位」 |
|
||||
| K22 | B2 1 CAL + fb_id(查 [[fb]] 表) | 「cal + fb_id」 |
|
||||
| K23 | B4 领域指令不进指令集(运动/通信走 FB 库) | 「不进指令集」 |
|
||||
| K24 | 前缀分区形态:`011`=RRR、`010`=其他 | 「那就011前缀的是RRR类型指令,010前缀的是其他指令」 |
|
||||
| K25 | fmt 位段取消,形态全前缀分区(零查表) | 「010区也换成前缀编码,这样所有指令都统一了」 |
|
||||
| K26 | MOVE 只搬宽度,不管符号/浮点;类型不匹配编译期报错 | 「move指令只管宽度和搬运...类型不匹配,编译时就报错」 |
|
||||
|
||||
### 9.2 待确认(用户未明确回答)——已全部闭合(2026-08-24)
|
||||
|
||||
| # | 事项 | 当前文档写法 | 待表态 |
|
||||
|---|---|---|---|
|
||||
| ~~U1~~ | ~~格式合并 9→7~~ | **作废**:fmt 位段已取消,形态由前缀分区表达,无合并问题 |
|
||||
| ~~U2~~ | ~~长度组映射~~ | **已被前缀编码取代**(11/10/011/010xxx/0010/0011/0001,见指令编码.md §3) |
|
||||
| ~~U3~~ | ~~字节布局~~ | **已被取代**:`[前缀+形态分区\|op] + [func:4\|空:4]`(指令编码.md §2) |
|
||||
| ~~U4~~ | ~~结构体 v1 只留编码不实现语法~~ | **已定案(预留)**:只留编码路径(LOAD/STORE 16B 变体 + 元数据段),语法 v1 不实现 |
|
||||
| ~~U5~~ | ~~B3 计数拆条~~ | **已定案**:fb 表拆条 11 条(CTU/DCTU/CTD/DCTD/CTUD/DCTUD 6 计数 + TON/TOF/TP/R_TRIG/F_TRIG),名字即宽度;**清单内建 FB 全部 15 条(含 SR/RS、PWM、RTC)v1 实现** |
|
||||
| ~~U6~~ | ~~op 预算~~ | **已定案**:按指令集清单.md §12 / 指令编码.md §3 方案(前缀分区预算:RR 21/64、NONE 2/64、RRR 26/32、010 区 8/24、SLOT+off 8/16、IMM64 1/16、0001 预留 6/16) |
|
||||
| ~~U7~~ | ~~头 128 字节布局~~ | **已定案(修正版)**:17 字段表述、values_size 命名、offset_meta @112(v1=0)、n_i/n_q/n_m 保留恒 0、n_slots 关系定义、16 对齐(见 §2.5 注记) |
|
||||
| ~~U8~~ | ~~槽表条目格式~~ | **已定案**:{addr:u32, 预留:u32} 8B/条(引用表,地址指向值段) |
|
||||
| ~~U9~~ | ~~值段对齐~~ | **已定案**:1/2/4/8B 对齐由编译器布局保证(值段起点 16 对齐,见 U7) |
|
||||
| ~~U10~~ | ~~寄存器与调用栈~~ | **已定案**:寄存器 int64 不变;调用约定不变;CALL/RET 全宽复制;帧按 nregs 动态分配(§2.2 表述修正);栈方案 C(预分配 + max_stack@116 + 字节/深度双检查 + 递归编译器报错) |
|
||||
| ~~U11~~ | ~~CAL 编码布局~~ | **已定案**:010 101 分区,操作数区 fb_id16=31..16 + slot32=63..32 |
|
||||
| ~~U12~~ | ~~元数据段占位~~ | **已定案**:v1 定 0 长度占位(offset_meta=0 = 空段,接口预留),字段表 schema 延后(有使用方时再定) |
|
||||
| ~~U13~~ | ~~DATE/TOD/DT 表示~~ | **已定案**:DATE=uint32(天数)、TOD=uint32(当日 ms)、DT=uint64 **{date:32 \| tod:32} 拼接**(高 32 位日期,取高/低即转换,比较天然有序);v1 无日期运算(A5);将来转换编译展开 |
|
||||
| ~~U14~~ | ~~CALL/CAL 的 rd 恒 0~~ | **已定案**:CALL(010 100 区)/ CAL(010 101 区)的 rd 字节恒 0(未使用),按指令编码/指令集清单文档编码 |
|
||||
| ~~U15~~ | ~~无类型指令 type=NONE~~ | **已被 func=0 取代**(func 位段,无类型指令编码 0 不使用) |
|
||||
| ~~U16~~ | ~~off32 相对下一条公式~~ | **已确认**(随 K7 跳转字节化接受) |
|
||||
|
||||
|
||||
## 10. 全功能指令集清单
|
||||
|
||||
全功能目标下的完整指令清单(10 类 ~55 条,含 op 预算核对)已拆分为独立文档:
|
||||
[`指令集清单.md`](指令集清单.md)。状态列:`核心` = v1 实现;`扩展` = v1 后实现;`预留` = 仅留编码/未来。
|
||||
@@ -0,0 +1,280 @@
|
||||
# 指令集清单(V2 编码定案)
|
||||
|
||||
> 状态:**已编码**。全功能指令清单,全部指令已按 [`指令编码.md`](指令编码.md) 的
|
||||
> RISC-V 式格式(前缀长度组 + op + func + fmt)分配编码。
|
||||
> 状态列:`核心` = v1 实现(含内建 FB 全部 15 条);`扩展` = v1 后实现;`预留` = 仅留编码/未来。
|
||||
|
||||
## 编码规则摘要(详见指令编码.md)
|
||||
|
||||
- **字节 0** = 长度前缀 + 形态分区 + 主操作码:`11`/`10`+op:6(4B)、`011`/`010 xxx`+op:2(8B)、`0010`/`0011`+op:4(16B)、`0001`+op:4(32B)
|
||||
- **字节 1** = `[func:4 | 空:4]`(空位待全部编码后定)
|
||||
- **形态由前缀分区表达**(fmt 位段已取消,零查表):`11`=RR、`10`=NONE、`011`=RRR、`010 000/001/010/011/100/101`=IMM/SLOT/JMP/JC/CALL/CAL、`0010`=SLOT+off、`0011`=IMM64
|
||||
- **func**(语义随 op):0..7 = 宽度×符号(1B无/1B有/2B无/2B有/4B无/4B有/8B无/8B有)、8=F32、9=F64、`—`=无类型(编码 0 保留)
|
||||
- disasm 按 func 显示类型后缀(A7 定案):func=3 → `LOAD.I16 r5, s3`
|
||||
- 无类型指令(跳转/调用/RET/NOP/CAL)func 编码为 0(不使用)
|
||||
- 010 区 8 条(LOADK/LOAD/STORE/JMP/JT/JF/CALL/CAL)形态由前缀分区直推:见指令编码.md §5
|
||||
|
||||
|
||||
|
||||
|
||||
## 1. 前缀分区与字段位号
|
||||
|
||||
### 1.1 通用头部(字节 0 + 字节 1,所有指令相同)
|
||||
|
||||
| 字段 | 位号 | 说明 |
|
||||
|---|---|---|
|
||||
| 长度前缀+形态分区 | 4B: bit7..6(`11`=RR / `10`=NONE);8B: bit7..5(`011`=RRR / `010`=其他 6 形态分区:bit4..2 形态位 + bit1..0 子 op);16B: bit7..4(`0010`=SLOT+off / `0011`=IMM64);32B: bit7..4(`0001`=预留) | 长度与形态分区一体 |
|
||||
| opcode | 4B: bit5..0 / 8B: bit4..0 / 16B: bit3..0 / 32B: bit3..0 | 分区内独立编号(0 起) |
|
||||
| func | bit15..12 | 功能细节(宽度×符号/浮点),语义随 op |
|
||||
| (空) | bit11..8 | 待全部指令编码完后决定用途 |
|
||||
|
||||
### 1.2 形态分区总表(操作数区 = bit16 起;pad = 对齐填充,编码固定 0)
|
||||
|
||||
| 前缀 | 形态 | 长度组 | 操作数区字段位号 | 识别方式 |
|
||||
|---|---|---|---|---|
|
||||
| `11` | RR | 4B | rd=23..16、rs=31..24 | 前缀直推 |
|
||||
| `10` | NONE | 4B | pad=31..16 | 前缀直推 |
|
||||
| `011` | RRR | 8B | rd=23..16、rs1=31..24、rs2=39..32、pad=63..40 | 前缀直推 |
|
||||
| `010 000` | IMM | 8B | rd=23..16、imm32=55..24、pad=63..56 | 前缀直推 |
|
||||
| `010 001` | SLOT | 8B | rd=23..16、slot32=55..24、pad=63..56 | 前缀直推 |
|
||||
| `010 010` | JMP | 8B | off32=47..16、pad=63..48 | 前缀直推 |
|
||||
| `010 011` | JC | 8B | rd=23..16、off32=55..24、pad=63..56 | 前缀直推 |
|
||||
| `010 100` | CALL | 8B | fn_id32=47..16、pad=63..48 | 前缀直推 |
|
||||
| `010 101` | CAL | 8B | fb_id16=31..16、slot32=63..32 | 前缀直推 |
|
||||
| `0010` | SLOT+off / RS+off | 16B | SLOT+off: rd=23..16、slot32=55..24、off32=87..56;RS+off: rd=23..16、rs=31..24、off32=87..56、pad=127..88 | 前缀直推 |
|
||||
| `0011` | IMM64 | 16B | rd=23..16、imm64=87..24、pad=127..88 | 前缀直推 |
|
||||
| `0001` | 预留 | 32B | 操作数区 30B(bit 240..16),格式待定 | — |
|
||||
|
||||
### 1.3 各位图(bit 从左(高)到右(低);小端字节序:字节 0 = bit0..7 在右端)
|
||||
|
||||
**RR(前缀 `11`,4B = bit31..0)**
|
||||
```text
|
||||
bit31 24 bit23 16 bit15 12 bit11 8 bit7 0
|
||||
┌────────────────┬────────────────┬───────────┬─────────┬───────────┐
|
||||
│ rs:8 (31..24) │ rd:8 (23..16) │ func:4 │ 空:4 │ 前缀11+op:6│
|
||||
└────────────────┴────────────────┴───────────┴─────────┴───────────┘
|
||||
```
|
||||
|
||||
**NONE(前缀 `10`,4B = bit31..0)**
|
||||
```text
|
||||
bit31 16 bit15 12 bit11 8 bit7 0
|
||||
┌──────────────────┬─────────┬───────┬───────────┐
|
||||
│ pad:16 (31..16) │ func:4 │ 空:4 │ 前缀10+op:6│
|
||||
└──────────────────┴─────────┴───────┴───────────┘
|
||||
```
|
||||
|
||||
**RRR(前缀 `011`,8B = bit63..0)**
|
||||
```text
|
||||
bit63 40 bit39 32 bit31 24 bit23 16 bit15 12 bit11 8 bit7 0
|
||||
┌─────────────────┬──────────┬──────────┬──────────┬─────────┬───────┬───────────┐
|
||||
│ pad:24 (63..40) │ rs2:8 │ rs1:8 │ rd:8 │ func:4 │ 空:4 │ 前缀011+op5│
|
||||
│ │ (39..32) │ (31..24) │ (23..16) │ (15..12)│(11..8)│ (7..0) │
|
||||
└─────────────────┴──────────┴──────────┴──────────┴─────────┴───────┴───────────┘
|
||||
```
|
||||
|
||||
**010 区(8B = bit63..0)**:前缀 `010` + 3 位形态位(bit4..2)+ 2 位子 op(bit1..0)——形态全部直推:
|
||||
```text
|
||||
IMM(010000): rd=23..16、imm32=55..24、pad=63..56
|
||||
SLOT(010001): rd=23..16、slot32=55..24、pad=63..56
|
||||
JMP(010010): off32=47..16、pad=63..48
|
||||
JC(010011): rd=23..16、off32=55..24、pad=63..56
|
||||
CALL(010100): fn_id32=47..16、pad=63..48
|
||||
CAL(010101): fb_id16=31..16、slot32=63..32
|
||||
```
|
||||
|
||||
**SLOT+off(前缀 `0010`,16B = bit127..0)**
|
||||
```text
|
||||
bit127 88 bit87 56 bit55 24 bit23 16 bit15 12 bit11 8 bit7 0
|
||||
┌─────────────────┬──────────────┬──────────────┬──────────────┬─────────┬───────┬──────────┐
|
||||
│ pad:40 │ off32 │ slot32 │ rd:8 │ func:4 │ 空:4 │ 前缀0010+op4│
|
||||
│ (127..88) │ (87..56) │ (55..24) │ (23..16) │ (15..12)│(11..8)│ (7..0) │
|
||||
└─────────────────┴──────────────┴──────────────┴──────────────┴─────────┴───────┴──────────┘
|
||||
```
|
||||
|
||||
**IMM64(前缀 `0011`,16B = bit127..0)**
|
||||
```text
|
||||
bit127 88 bit87 24 bit23 16 bit15 12 bit11 8 bit7 0
|
||||
┌─────────────────┬────────────┬──────────────┬─────────┬───────┬──────────┐
|
||||
│ pad:40 │ imm64 │ rd:8 │ func:4 │ 空:4 │ 前缀0011+op4│
|
||||
│ (127..88) │ (87..24) │ (23..16) │ (15..12)│(11..8)│ (7..0) │
|
||||
└─────────────────┴────────────┴──────────────┴─────────┴───────┴──────────┘
|
||||
```
|
||||
|
||||
**32B 预留(前缀 `0001`)**:操作数区 30B(bit 240..16),格式待实现时定稿。
|
||||
|
||||
## 2. 数据搬运
|
||||
|
||||
| 指令 | 组 | 字节0 编码 | fmt | func | 操作数 | 语义 | 状态 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| MOVE | 4B | `11000000` | RR | 宽度:0=1B/1=2B/2=4B/3=8B | rd, rs | 寄存器搬运(只搬宽度,不管符号/浮点) | 核心 |
|
||||
| LOAD | 8B | `01000100` | SLOT | 全矩阵 0..9 | rd, slot | 槽→寄存器(按 func 读宽+扩展) | 核心 |
|
||||
| STORE | 8B | `01000101` | SLOT | 全矩阵 0..9 | slot, rs | 寄存器→槽(按 func 写宽) | 核心 |
|
||||
| LOADK | 8B | `01000000` | IMM | 全矩阵 0..9 | rd, const_id | 常量表→寄存器(按 func 解释) | 核心 |
|
||||
| LOAD_OFF | 16B | `00100000` | SLOT+off | 全矩阵 0..9 | rd, slot, off | 字段读(结构体/FB 共享体) | 核心 |
|
||||
| STORE_OFF | 16B | `00100001` | SLOT+off | 全矩阵 0..9 | slot, off, rs | 字段写 | 核心 |
|
||||
| LOAD_PTR | 16B | `00101000` | RS+off | 全矩阵 0..9 | rd, rs, off | 基址读(rs 寄存器 = 值区地址 + 偏移;FB 共享体) | 核心 |
|
||||
| STORE_PTR | 16B | `00101001` | RS+off | 全矩阵 0..9 | rd, rs, off | 基址写 | 核心 |
|
||||
| LOADK64 | 16B | `00110000` | IMM64 | 全矩阵 0..9 | rd, imm64 | 64 位立即数内嵌(免查表) | 预留 |
|
||||
|
||||
## 3. 逻辑与位运算
|
||||
|
||||
| 指令 | 组 | 字节0 编码 | fmt | func | 操作数 | 语义 | 状态 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| NOT | 4B | `11000001` | RR | 无符号宽度 0/2/4/6 | rd, rs | BOOL 逻辑非 / 位串按位取反 | 核心 |
|
||||
| AND | 8B | `01100110` | RRR | 无符号宽度 0/2/4/6 | rd, ra, rb | 按宽度逐位与(BOOL 由编译器保证 0/1) | 核心 |
|
||||
| OR | 8B | `01100111` | RRR | 无符号宽度 0/2/4/6 | rd, ra, rb | 逐位或 | 核心 |
|
||||
| XOR | 8B | `01101000` | RRR | 无符号宽度 0/2/4/6 | rd, ra, rb | 逐位异或 | 核心 |
|
||||
| NAND | 8B | `01101001` | RRR | 无符号宽度 0/2/4/6 | rd, ra, rb | 逐位与非 | 扩展 |
|
||||
| NOR | 8B | `01101010` | RRR | 无符号宽度 0/2/4/6 | rd, ra, rb | 逐位或非 | 扩展 |
|
||||
| XNOR | 8B | `01101011` | RRR | 无符号宽度 0/2/4/6 | rd, ra, rb | 逐位同或 | 扩展 |
|
||||
| SHL | 8B | `01101100` | RRR | 无符号宽度 0/2/4/6 | rd, rs, n | 左移(n 在 rb) | 扩展 |
|
||||
| SHR | 8B | `01101101` | RRR | 0..7(宽度×符号,算术/逻辑右移) | rd, rs, n | 右移 | 扩展 |
|
||||
| ROL | 8B | `01101110` | RRR | 无符号宽度 0/2/4/6 | rd, rs, n | 循环左移(位串) | 扩展 |
|
||||
| ROR | 8B | `01101111` | RRR | 无符号宽度 0/2/4/6 | rd, rs, n | 循环右移 | 扩展 |
|
||||
| GETBIT | 8B | `01110110` | RRR | — | rd, rs, bit | 取位(bit 在 rb) | 扩展 |
|
||||
| SETBIT | 8B | `01110111` | RRR | — | rd, rs, bit | 置位 | 扩展 |
|
||||
| CLRBIT | 8B | `01111000` | RRR | — | rd, rs, bit | 清位 | 扩展 |
|
||||
| TOGBIT | 8B | `01111001` | RRR | — | rd, rs, bit | 翻转位 | 扩展 |
|
||||
|
||||
> 注:SETBIT/CLRBIT/TOGBIT 的"目标可槽"形式(`slot, bit`)后续可在 16B 组扩展(op 预留)。
|
||||
|
||||
## 4. 算术
|
||||
|
||||
| 指令 | 组 | 字节0 编码 | fmt | func | 操作数 | 语义 | 状态 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| ADD | 8B | `01100000` | RRR | 全矩阵 0..9 | rd, ra, rb | 整数饱和 / 浮点 IEEE / 无符号 | 核心 |
|
||||
| SUB | 8B | `01100001` | RRR | 全矩阵 0..9 | rd, ra, rb | 减 | 核心 |
|
||||
| MUL | 8B | `01100010` | RRR | 全矩阵 0..9 | rd, ra, rb | 乘 | 核心 |
|
||||
| DIV | 8B | `01100011` | RRR | 全矩阵 0..9 | rd, ra, rb | 除(整数零除/浮点 ±inf) | 核心 |
|
||||
| MOD | 8B | `01100100` | RRR | 0..7(整数) | rd, ra, rb | 取模 | 扩展 |
|
||||
| EXPT | 8B | `01100101` | RRR | 0..9 | rd, ra, rb | 幂 | 扩展 |
|
||||
| NEG | 4B | `11000010` | RR | 0..9 | rd, rs | 取负 | 扩展 |
|
||||
| ABS | 4B | `11000011` | RR | 0..9 | rd, rs | 绝对值 | 扩展 |
|
||||
| INC | 4B | `11000100` | RR | 0..9 | rd, rs | 自增 | 扩展 |
|
||||
| DEC | 4B | `11000101` | RR | 0..9 | rd, rs | 自减 | 扩展 |
|
||||
| SQRT | 4B | `11000110` | RR | 浮点 8/9 | rd, rs | 平方根 | 扩展 |
|
||||
| SIN | 4B | `11000111` | RR | 浮点 8/9 | rd, rs | 正弦 | 扩展 |
|
||||
| COS | 4B | `11001000` | RR | 浮点 8/9 | rd, rs | 余弦 | 扩展 |
|
||||
| TAN | 4B | `11001001` | RR | 浮点 8/9 | rd, rs | 正切 | 扩展 |
|
||||
| ASIN | 4B | `11001010` | RR | 浮点 8/9 | rd, rs | 反正弦 | 扩展 |
|
||||
| ACOS | 4B | `11001011` | RR | 浮点 8/9 | rd, rs | 反余弦 | 扩展 |
|
||||
| ATAN | 4B | `11001100` | RR | 浮点 8/9 | rd, rs | 反正切 | 扩展 |
|
||||
| LN | 4B | `11001101` | RR | 浮点 8/9 | rd, rs | 自然对数 | 扩展 |
|
||||
| LOG | 4B | `11001110` | RR | 浮点 8/9 | rd, rs | 常用对数 | 扩展 |
|
||||
| EXP | 4B | `11001111` | RR | 浮点 8/9 | rd, rs | 指数 | 扩展 |
|
||||
| ROUND | 4B | `11010000` | RR | 浮点 8/9 | rd, rs | 四舍五入(浮点→整数) | 扩展 |
|
||||
| TRUNC | 4B | `11010001` | RR | 浮点 8/9 | rd, rs | 截断 | 扩展 |
|
||||
| FLOOR | 4B | `11010010` | RR | 浮点 8/9 | rd, rs | 向下取整 | 扩展 |
|
||||
| CEIL | 4B | `11010011` | RR | 浮点 8/9 | rd, rs | 向上取整 | 扩展 |
|
||||
|
||||
## 5. 比较
|
||||
|
||||
| 指令 | 组 | 字节0 编码 | fmt | func | 操作数 | 语义 | 状态 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| CMP_EQ | 8B | `01110000` | RRR | 全矩阵 0..9 | rd, ra, rb | 相等 | 核心 |
|
||||
| CMP_NE | 8B | `01110001` | RRR | 全矩阵 0..9 | rd, ra, rb | 不等 | 核心 |
|
||||
| CMP_LT | 8B | `01110010` | RRR | 全矩阵 0..9 | rd, ra, rb | 小于(无符号/浮点按 func) | 核心 |
|
||||
| CMP_LE | 8B | `01110011` | RRR | 全矩阵 0..9 | rd, ra, rb | 小于等于 | 核心 |
|
||||
| CMP_GT | 8B | `01110100` | RRR | 全矩阵 0..9 | rd, ra, rb | 大于 | 核心 |
|
||||
| CMP_GE | 8B | `01110101` | RRR | 全矩阵 0..9 | rd, ra, rb | 大于等于 | 核心 |
|
||||
|
||||
## 6. 转换
|
||||
|
||||
| 指令 | 组 | 字节0 编码 | fmt | func | 操作数 | 语义 | 状态 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| CONV | 4B | `11010100` | RR | **目标类型** 0..9 | rd, rs | 类型转换(src 值已在寄存器,按目标 func 截断/扩展/舍入;舍入模式随目标为浮点→整数时取 TRUNC 语义) | 扩展 |
|
||||
|
||||
## 7. 控制流
|
||||
|
||||
| 指令 | 组 | 字节0 编码 | fmt | func | 操作数 | 语义 | 状态 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| JMP | 8B | `01001000` | JMP | — | off32 | 无条件跳转(相对下一条,字节) | 核心 |
|
||||
| JT | 8B | `01001100` | JC | — | rd, off | rd≠0 跳转 | 核心 |
|
||||
| JF | 8B | `01001101` | JC | — | rd, off | rd==0 跳转 | 核心 |
|
||||
| CALL | 8B | `01010000` | CALL | — | fn_id | 压帧调用(r0..r7 复制) | 核心 |
|
||||
| RET | 4B | `10000001` | NONE | — | — | 弹帧 / MAIN 周期结束 | 核心 |
|
||||
| CAL | 8B | `01010100` | CAL | — | fb_id, slot | 内建 FB 调用(fb_id 查 [[fb]] 表) | 核心 |
|
||||
| JTBL | 32B | `00010000` | 预留 | — | rd, slot, n | CASE 跳转表 | 预留 |
|
||||
|
||||
## 8. 内建 FB(CAL + fb_id,[[fb]] 表)
|
||||
|
||||
| fb_id | FB | 字段(类型化) | 状态 |
|
||||
|---|---|---|---|
|
||||
| 0 | TON | in/pt/q/et = BOOL/TIME/BOOL/TIME | 核心 |
|
||||
| 1 | TOF | 同上 | 核心 |
|
||||
| 2 | TP | 同上 | 核心 |
|
||||
| 3 | CTU | cu/r/pv(INT)/q/cv(INT) 单字 | 核心 |
|
||||
| 4 | DCTU | cu/r/pv(DINT)/q/cv(DINT) 双字 | 核心 |
|
||||
| 5 | CTD | cd/ld/pv(INT)/q/cv(INT) | 核心 |
|
||||
| 6 | DCTD | cd/ld/pv(DINT)/q/cv(DINT) | 核心 |
|
||||
| 7 | CTUD | cu/cd/r/lu/pv(INT)/qu/qd/cv(INT) | 核心 |
|
||||
| 8 | DCTUD | cu/cd/r/lu/pv(DINT)/qu/qd/cv(DINT) | 核心 |
|
||||
| 9 | R_TRIG | clk/q = BOOL/BOOL | 核心 |
|
||||
| 10 | F_TRIG | clk/q = BOOL/BOOL | 核心 |
|
||||
| 11 | SR | S1/R/Q1 = BOOL/BOOL/BOOL(置位优先) | 核心 |
|
||||
| 12 | RS | SET/RESET1/Q1 = BOOL/BOOL/BOOL(复位优先) | 核心 |
|
||||
| 13 | PWM | in/pt/duty/q/et = BOOL/TIME/REAL/BOOL/TIME | 核心 |
|
||||
| 14 | RTC | enable/date/tod = BOOL/DATE/TOD(虚拟时钟) | 核心 |
|
||||
|
||||
### 8.1 字段布局终稿(对齐规则 1/2/4/8B,字段按声明序;偏移 = 实例基地址 + 字段偏移)
|
||||
|
||||
| FB | 字段偏移(字节) | 实例大小 |
|
||||
|---|---|---|
|
||||
| TON/TOF/TP | in=0、pt=8、q=16、et=24 | 32B |
|
||||
| CTU/CTD | cu/cd=0、r/ld=1、pv=2、q=4、cv=6 | 8B |
|
||||
| DCTU/DCTD | cu/cd=0、r/ld=1、pad 2..3、pv=4、q=8、pad 9..11、cv=12 | 16B |
|
||||
| CTUD | cu=0、cd=1、r=2、lu=3、pv=4、qu=6、qd=7、cv=8 | 10B |
|
||||
| DCTUD | cu=0、cd=1、r=2、lu=3、pv=4、qu=8、qd=9、pad 10..11、cv=12 | 16B |
|
||||
| R_TRIG/F_TRIG | clk=0、q=1 | 2B |
|
||||
| SR | S1=0、R=1、Q1=2 | 3B |
|
||||
| RS | SET=0、RESET1=1、Q1=2 | 3B |
|
||||
| PWM | in=0、pt=8(pad 1..7)、duty=16、q=20、et=24(pad 21..23) | 32B |
|
||||
| RTC | enable=0、date=4(pad 1..3)、tod=8 | 12B |
|
||||
|
||||
> **RTC 时钟源(确定性注记)**:v1 无墙钟(确定性原则)——RTC 用**虚拟时钟**:映像加载起
|
||||
> 周期计数 × dt_ms 累计推导日期时间;不读系统时钟,保证确定性(A5 定案延伸)。
|
||||
> **PWM 语义**:in 使能时按 pt 周期输出 q(duty×pt 高电平),et 为当前周期已运行时间。
|
||||
> **SR/RS 语义**:SR 置位优先(S1=1 → Q1=1,R 仅在 S1=0 时复位);RS 复位优先。
|
||||
|
||||
## 9. 字符串(未来,元数据段 + 16/32B)
|
||||
|
||||
| 指令 | 组 | 字节0 编码 | fmt | 语义 | 状态 |
|
||||
|---|---|---|---|---|---|
|
||||
| STR_LEN | 16B | `00100010` | SLOT+off | 字符串长度 | 预留 |
|
||||
| STR_CMP | 16B | `00100011` | SLOT+off | 比较 | 预留 |
|
||||
| STR_EQ | 16B | `00100100` | SLOT+off | 相等 | 预留 |
|
||||
| STR_MID | 16B | `00100101` | SLOT+off | 取子串 | 预留 |
|
||||
| STR_LEFT | 16B | `00100110` | SLOT+off | 左侧子串 | 预留 |
|
||||
| STR_RIGHT | 16B | `00100111` | SLOT+off | 右侧子串 | 预留 |
|
||||
| STR_CONCAT | 32B | `00010001` | 预留 | 拼接 | 预留 |
|
||||
| STR_FIND | 32B | `00010010` | 预留 | 查找 | 预留 |
|
||||
| STR_REPLACE | 32B | `00010011` | 预留 | 替换 | 预留 |
|
||||
|
||||
## 10. 数组(未来)
|
||||
|
||||
| 指令 | 组 | 字节0 编码 | fmt | 语义 | 状态 |
|
||||
|---|---|---|---|---|---|
|
||||
| ARR_LEN | 16B | `00101001` | SLOT+off | 数组长度 | 预留 |
|
||||
| ARR_LOAD | 32B | `00010100` | 预留 | 索引读(基槽+index) | 预留 |
|
||||
| ARR_STORE | 32B | `00010101` | 预留 | 索引写 | 预留 |
|
||||
|
||||
## 11. 系统
|
||||
|
||||
| 指令 | 组 | 字节0 编码 | fmt | func | 操作数 | 语义 | 状态 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| NOP | 4B | `10000000` | NONE | — | — | 空操作(占位/对齐) | 核心 |
|
||||
|
||||
## 12. op 预算核对(前缀分区按组分治)
|
||||
|
||||
| 前缀 | 分区 | op 空间 | 已分配 | 指令 | 余量 |
|
||||
|---|---|---|---|---|---|
|
||||
| `11` | RR | 64 | 21 | MOVE/NOT/NEG/ABS/INC/DEC/浮点单目 10/舍入 4/CONV | 43 |
|
||||
| `10` | NONE | 64 | 2 | NOP/RET | 62 |
|
||||
| `011` | RRR | 32 | 26 | 算术 6 + 逻辑 6 + 移位 4 + 位操作 4 + CMP 6 | 6 |
|
||||
| `010 000`..`010 101` | IMM/SLOT/JMP/JC/CALL/CAL | 6×4 | 8 | LOADK(1)/LOAD·STORE(2)/JMP(1)/JT·JF(2)/CALL(1)/CAL(1) | 16(含 2 个预留形态区) |
|
||||
| `0010` | SLOT+off / RS+off | 16 | 10 | LOAD_OFF/STORE_OFF + LOAD_PTR/STORE_PTR + 字符串 6 | 6 |
|
||||
| `0011` | IMM64 | 16 | 1 | LOADK64 | 15 |
|
||||
| `0001` | 预留 | 16 | 6 | JTBL + STR_CONCAT/FIND/REPLACE + ARR_LOAD/STORE | 10 |
|
||||
|
||||
> 编码约定:op 按分区独立编号;无类型指令 func=0;010 区形态由前缀分区直推(指令编码.md §5);
|
||||
> 字符串/数组的具体操作数形态在实现时定稿(`0001` 区)。
|
||||
@@ -0,0 +1,99 @@
|
||||
# V2 规格总览(阶段 1 主参考)
|
||||
|
||||
> 状态:**规格冻结,阶段 1 开工前的主参考文档**。
|
||||
> 本文汇总 V2 全部已定案设计,作为重写三份规范文档(指令与映像.md / 指令配置.md /
|
||||
> stb文件格式.md)的依据。细节见各参考文档,本文只给"下一阶段要看什么、写什么"。
|
||||
|
||||
## 1. 阶段 1 任务
|
||||
|
||||
| 产出物 | 内容 | 依据 |
|
||||
|---|---|---|
|
||||
| `Doc/isa/指令与映像.md`(重写为 V2) | 指令编码、数据模型、类型/func、调用栈、映像格式 | 本文 + 指令编码.md + 指令集清单.md |
|
||||
| `Doc/isa/指令配置.md`(重写为 V2) | **machine.toml V2 schema**(本文唯一开放项 N5) | 本文 §6 |
|
||||
| `Doc/isa/stb文件格式.md`(重写为 V2) | 新 .stb 结构拆解(头 128B/段序/line1 字节) | 本文 §5 + 阶段 5 再补 line1 推演 |
|
||||
|
||||
阶段 1 完成后:V2 规格文档与代码蓝图自洽,进入阶段 2(isa 重构)。
|
||||
|
||||
## 2. 指令编码(定案,详见指令编码.md)
|
||||
|
||||
```text
|
||||
字节 0 = [前缀+形态分区 | op] 字节 1 = [func:4 | 空:4](空位 = N1,延后)
|
||||
```
|
||||
|
||||
| 前缀 | 长度 | 形态 | op 空间 |
|
||||
|---|---|---|---|
|
||||
| `11` | 4B | RR | 6 位(用 21) |
|
||||
| `10` | 4B | NONE | 6 位(用 2) |
|
||||
| `011` | 8B | RRR | 5 位(用 26) |
|
||||
| `010 000..101` | 8B | IMM/SLOT/JMP/JC/CALL/CAL | 2 位(用 8,全直推零查表) |
|
||||
| `0010` | 16B | SLOT+off | 4 位(用 8) |
|
||||
| `0011` | 16B | IMM64 | 4 位(用 1) |
|
||||
| `0001` | 32B | 预留(N2) | 4 位(用 6) |
|
||||
|
||||
- func 位段:0..7 = 宽度×符号、8=F32、9=F64、无类型指令 = 0
|
||||
- 位掩码推导:uses_rd/uses_ra/uses_rb 全部前缀直推
|
||||
- 跳转/PC 字节单位(off32 相对下一条);code 段 16 对齐
|
||||
|
||||
## 3. 数据模型与类型(定案)
|
||||
|
||||
- **槽表(8B/条 {addr:u32, 预留:u32})→ 值区**:两级间接;全 32 位寻址(槽 2^32、值区 4GB)
|
||||
- **值区变宽紧凑**:1/2/4/8B 对齐编译器保证(值段起点 16 对齐)
|
||||
- **类型**:19 IEC 类型 → **10 种 func** 编译期映射;BOOL 编译器强制 0/1;字面量按上下文适配、默认 DINT 兜底;进制前缀 2#/8#/16#;**禁隐式变量转换**(编译期报错)
|
||||
- **DATE/TOD/DT**:DATE=u32(天)、TOD=u32(当日 ms)、DT=u64 {date:32|tod:32} 拼接;v1 不运算(仅存取/比较);**字面量 V2 支持**(D#/TOD#/DT#,纪元 1970)
|
||||
- **常量表**:条目 8B 原始值(无 tag),LOADK 按 func 解释,同值多类型复用
|
||||
|
||||
## 4. 执行语义(定案)
|
||||
|
||||
- **寄存器**:编号 8 位(256 = 寻址上限)、值 int64;帧按函数头 nregs 动态分配;调用约定 r0 结果 / r1..r7 参数 / r8+ 变量临时;CALL/RET 复制 r0..r7 全宽
|
||||
- **调用栈(方案 C)**:递归 = 链接层环检测编译期报错;max_stack = MAIN + DAG 最坏路径 Σ(帧头 12B + nregs×8) 写入映像头 @116;预分配栈区、运行期零分配;溢出双检查(字节 + 深度 64);MAIN 帧常驻
|
||||
- **整数除法(A 定案)**:除零 → 返回被除数;MIN/-1 → 饱和夹界(各宽度模板);截断除法(向零);MOD 符号随被除数
|
||||
- **浮点**:IEEE 754(溢出/零除 → ±inf、NaN 传播)
|
||||
- **TIME**:+ - 比较;**DATE/TOD/DT 不运算**(仅存取/比较)
|
||||
- **字段访问**:LOAD/STORE 16B 变体(基槽 + 偏移)= 结构体 + FB 共享体统一(LOADF 取消)
|
||||
- **I/Q/M 合并**:LOAD/STORE 两条;语义标签交 sidecar(绑定粒度 slot=槽号不变)
|
||||
- **内建 FB**:15 条(CAL + fb_id16);字段布局终稿见指令集清单 §8.1(含 SR/RS、PWM、RTC);RTC 用**虚拟时钟**(周期×dt_ms,确定性)
|
||||
|
||||
## 5. 映像格式(定案,详见指令集优化.md §2.5)
|
||||
|
||||
```text
|
||||
头 128B(16 对齐):
|
||||
0 magic "STSC"(不变)
|
||||
4 version = 2(V1=1)
|
||||
8..71 cycle_limit/dt_ms/project_hash/entry_fn_id/n_globals/n_i/n_q/n_m/
|
||||
n_consts/n_funcs + offset_const/funcs/code/fb/data(n_i/n_q/n_m 保留恒 0)
|
||||
64 offset_slots 68 offset_values(16 对齐)
|
||||
72..103 型号标识 STATOR2
|
||||
104 n_slots(= n_globals + 实例/字段槽数) 108 values_size
|
||||
112 offset_meta(v1=0 空段) 116 max_stack 120..127 预留 8B
|
||||
段序:头 → 常量表 → 函数表 → 字节码 → 槽表段 → 值段 → 元数据段(v1 空)→ SHA-256[32]
|
||||
```
|
||||
|
||||
## 6. machine.toml V2 schema(N5,阶段 1 内定)
|
||||
|
||||
阶段 1 重写指令配置.md 时定稿,方向已定:
|
||||
|
||||
```toml
|
||||
[meta] # name="STATOR"、version=2
|
||||
[[type]] # 19 条:name/width/signed/tag(func 值 0..18)/kind(int|uint|bit|float|time|date)
|
||||
[[op]] # 按前缀分区:prefix/op/func/fmt 互锁校验
|
||||
[[fb]] # 15 条:fb_id/name/字段列表(字段名+类型,布局规则 1/2/4/8 对齐)
|
||||
```
|
||||
|
||||
## 7. 延后项(阶段 1 不处理)
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| N1 | 字节 1 空 4 位用途(待全部编码完后定) |
|
||||
| N2 | 32B 组(0001)操作数形态(JTBL/STR_*/ARR_*) |
|
||||
| N3 | 010 区子 op 2 位扩展策略 |
|
||||
| N4 | 字符串/数组操作数区语义 |
|
||||
| C5 | line1 推演重算(阶段 5) |
|
||||
|
||||
## 8. 参考文档索引
|
||||
|
||||
| 文档 | 角色 |
|
||||
|---|---|
|
||||
| [`指令编码.md`](指令编码.md) | 编码定案(前缀分区/func/fmt 位号/uses_*)——阶段 1 指令部分主依据 |
|
||||
| [`指令集清单.md`](指令集清单.md) | 全指令编码表 + FB 字段布局终稿(§8.1) |
|
||||
| [`指令集优化.md`](指令集优化.md) | 全部设计定案 + 决策历史(K/U/A/B/C 全闭合) |
|
||||
| [`编译器优化.md`](../compiler/编译器优化.md) | 编译侧策略(FB 共享体/max_stack 职责/类型→func 映射) |
|
||||
@@ -0,0 +1,61 @@
|
||||
# 阶段 4/5:执行侧 V2 与全链路(待办清单)
|
||||
|
||||
> 状态:**待办**。阶段 3(编译器语言层 V2)已完成;本文记录阶段 4(isa+vm 执行侧 V2)
|
||||
> 与阶段 5(全链路收尾)的待办项,供后续执行。
|
||||
|
||||
## 阶段 4:isa 重构 + vm 同步(执行侧 V2)
|
||||
|
||||
### 0. 执行步骤总览(细化为子步骤,一次一个)
|
||||
|
||||
| 步骤 | 内容 | 验证 |
|
||||
|---|---|---|
|
||||
| 4-1a | isa/Types.h:func 宽度/符号表 + 饱和模板(除法 A)+ 删 TypeTag | 编译 |
|
||||
| 4-1b | isa/Op.h:前缀分区 + 指令表 + uses_* 直推 | 编译 |
|
||||
| 4-1c | isa/Instr.h:变长 instr_len + 位号 get/set + enc_*(**JC off@55..24**) | 编译 |
|
||||
| 4-1d | isa/Encode.h/.cpp:encode/decode/disasm(类型后缀 + CAL fb 名) | 编译 |
|
||||
| 4-1e | isa_test V2 重写(饱和/前缀/位号往返/disasm/uses_*) | isa_test 绿 |
|
||||
| 4-2a | vm/Image.h/.cpp V2(头 128B/6 段校验/槽表访问器/常量 8B) | 编译 |
|
||||
| 4-2b | vm/Machine.h/.cpp V2(变长取指/槽表两级/栈方案 C/CAL 15 条/LOAD_OFF) | 编译 |
|
||||
| 4-2c | vm_test V2 重写(手拼 + 产物执行/定时器/确定性) | vm_test 绿 |
|
||||
| 4-3 | executor 适配(Image 新接口/I-Q 采样) | executor_version 绿 |
|
||||
| 4-4 | 验证:vm_cycles 恢复 + line1 端到端执行(Q 对照真值表) | ctest 13/14 |
|
||||
|
||||
> 说明:isa 重构会破坏 vm 编译(Machine.cpp 89 处引用)——**isa 与 vm 在同一阶段内连续完成**(4-1 后立即 4-2),中间态不提交(或提交 isa 时标注 vm 暂破)。
|
||||
|
||||
### 4.1 isa 重构(4 文件 + isa_test)
|
||||
|
||||
| 文件 | 内容 |
|
||||
|---|---|
|
||||
| `isa/include/isa/Types.h` | func 宽度/符号表(`kFuncWidth[16]`/`kFuncSigned[16]`)+ 饱和模板(各宽度 + 除法 A:除零返回被除数、MIN/-1 夹界、截断)+ 浮点工具;**删 TypeTag** |
|
||||
| `isa/include/isa/Op.h` | 前缀分区(11/10/011/010xxx/0010/0011/0001)+ 指令表(prefix/op/fmt/func_mode,与 machine.toml 72 条对应)+ **uses_rd/ra/rb 前缀直推**(编译期表) |
|
||||
| `isa/include/isa/Instr.h` | 变长 `instr_len`(前缀定长)+ 位号 get/set(**注意 JC off@55..24**)+ enc_* 发射器 |
|
||||
| `isa/src/Encode.cpp` | encode/decode/disasm(类型后缀;CAL 显示 fb 名) |
|
||||
| `tests/isa_test.cpp` | V2 重写:饱和边界/前缀定长/位号往返(含 JC off)/disasm/uses_* |
|
||||
|
||||
### 4.2 vm 同步(Image + Machine + vm_test)
|
||||
|
||||
| 文件 | 内容 |
|
||||
|---|---|
|
||||
| `vm/include/vm/Image.h` + `vm/src/Image.cpp` | 头 128B 解析(version=2/型号 STATOR2/n_slots/values_size/offset_meta/max_stack)、6 段校验(含 meta=0 空段)、槽表访问器(slot_addr)、常量表 8B 无 tag |
|
||||
| `vm/include/vm/Machine.h` + `vm/src/Machine.cpp` | **变长取指**(instr_len 前缀定长搬字节);译码(前缀分区+func+uses_* 直推);**槽表两级访问**(slot→addr→值区按 func 读宽/扩展、写宽);指令语义(MOVE 按 func 宽度搬运高位清 0、LOADK 按 func、算术**各宽度饱和模板+无符号+浮点 IEEE**、CMP 按 func 符号、跳转 off 字节相对下一条、CALL/RET 帧 r0..r7 全宽+ret_pc 字节);**CAL 15 条 fb_id 分派**(含 SR/RS/PWM/RTC,RTC 虚拟时钟=周期×dt_ms);**栈方案 C**(预分配 max_stack、帧头 12B、字节+深度 64 双检查、MAIN 帧常驻);LOAD_OFF/STORE_OFF 字段访问;边沿检测 edge_prev_ |
|
||||
| `tests/vm_test.cpp` | V2 重写:手拼 V2 映像 + **compiler V2 产物执行**(13 正例 run_cycle 断言)+ TON/CTU + 确定性 |
|
||||
|
||||
### 4.3 executor 适配
|
||||
|
||||
- `executor/src/main.cpp`:Image 新接口(max_stack 显示等)、I/Q 采样按槽、链 vm+isa(isa 变 V2 后适配)
|
||||
|
||||
### 4.4 验证
|
||||
|
||||
- `vm_cycles` 恢复绿;`bytecode_executor_version` 恢复
|
||||
- 端到端:`STCompiler 20_line1 → BytecodeExecutor` 跑周期,Q 符合真值表
|
||||
|
||||
## 阶段 5:全链路 + 文档收尾
|
||||
|
||||
| # | 待办 |
|
||||
|---|---|
|
||||
| 1 | `cases_all` 恢复(20 用例 V2 产物 + V2 vm 执行)——阶段 4 完成后预期自动恢复 |
|
||||
| 2 | **C5:line1 推演重算**(V2 产物逐指令推演,补 `stb文件格式.md` 真实拆解) |
|
||||
| 3 | line1 端到端验证(编译 + 执行 + 回放,Q 对照 EXPECTED.md) |
|
||||
| 4 | **文档收尾**:指令执行.md(V2 指令表/帧栈/CAL 15 条)、扫描周期.md、初步计划.md、README、编译器优化.md 同步 V2 状态 |
|
||||
| 5 | 新类型语法用例固化(19 类型/进制/浮点/日期字面量用例进 tests/cases) |
|
||||
| 6 | 分支合并(dev_optimize → dev)——用户决定时机 |
|
||||
+56
-8
@@ -1,22 +1,70 @@
|
||||
# 扫描周期
|
||||
|
||||
vm 模块:寄存器虚拟机。CMake 目标:`vm`(`STATIC`),只依赖 [`指令与映像.md`](../isa/指令与映像.md)。
|
||||
vm 模块:寄存器虚拟机(V2:变长指令 / 槽表两级 / 栈方案 C)。CMake 目标:`vm`(`STATIC`),只依赖 [`指令与映像.md`](../isa/指令与映像.md)。
|
||||
|
||||
运行时是 **一台 VM、一个入口、一份映像**。不链接 `compiler`。扫描周期与确定性见 [`初步计划.md`](../初步计划.md) 第 2、8 节。
|
||||
|
||||
## 扫描周期
|
||||
|
||||
```text
|
||||
1. 按 toml 的 io 绑定,采样 → I / 输入全局
|
||||
1. 按 sidecar 的 io 绑定,采样 → I / 输入全局(executor 写值区槽)
|
||||
2. 从 PROGRAM MAIN 的 pc=0 执行到 RET
|
||||
3. 写回 Q / 输出全局
|
||||
4. 用本周期 Δt 推进 TON / TOF / CTU(不用 wall clock)
|
||||
3. 写回 Q / 输出全局(executor 读值区槽)
|
||||
4. 用本周期 Δt(映像头 dt_ms)推进内置 FB(不用 wall clock);RTC 用累计周期数
|
||||
```
|
||||
|
||||
## 边界
|
||||
|
||||
- 只认 `isa` 映像;类型与溢出按 [`指令与映像.md`](../isa/指令与映像.md)(`INT` 饱和)
|
||||
- 局部 / `VAR` = 固定寄存器;`nregs` 在函数头
|
||||
- `CALL` 目标是立即数 `fn_id`,有界调用栈
|
||||
- 只认 `.stb` 映像(`vm` 自带读实现);读取时**型号标识匹配(STATOR2)+ SHA-256 校验**,不符/篡改 → 拒绝
|
||||
- 类型与溢出按 [`指令与映像.md`](../isa/指令与映像.md)(整数饱和 / 除法 A / 浮点 IEEE);指令定义归 `isa`
|
||||
- 局部 / `VAR` = 固定寄存器;`nregs` 在函数头;栈区预分配(`max_stack`,编译期 DAG 最坏路径)
|
||||
- `CALL` 目标是立即数 `fn_id`,有界调用栈(深度 64 双检查)
|
||||
- 无 GC、无堆、无线程;每周期受 `cycle_limit` 限制
|
||||
- I/O 从哪来、Δt 谁给、回放怎么喂,属于 `host`
|
||||
- I/O 从哪来、Δt 谁给、回放怎么喂,属于 `BytecodeExecutor`(读 `.stb` + sidecar)
|
||||
|
||||
---
|
||||
|
||||
## 可观察性接口(v1 预留)
|
||||
|
||||
TUI 是执行器(`BytecodeExecutor`)的界面,用途是方便观察 VM 运行状态。**v1 不做 TUI**,只预留接口,避免后续改 VM 的观察面。原则:
|
||||
|
||||
- **数据与展示分离**:`vm` 只提供只读观察 API(数据);TUI 渲染与文本输出属于 `executor`(展示)。vm 不画任何界面。
|
||||
- **只读轮询**:TUI 通过快照轮询,绝不写 VM 状态;"继续 / 单步"是 executor 侧命令,不改变确定性。
|
||||
- **快照 POD 化**:快照保持简单数据(将来跨进程 TUI 可序列化)。
|
||||
|
||||
已落地的观察面(`Machine` 公共接口):
|
||||
|
||||
```cpp
|
||||
bool step(); // 精确执行一条指令,停在指令边界
|
||||
Fault fault() const; // 最近一次故障
|
||||
bool ended() const; // 周期是否已结束
|
||||
uint32_t pc() const; // 当前 pc(字节,相对本帧函数)
|
||||
uint32_t fn_id() const; // 当前函数
|
||||
uint32_t cycle_count() const; // 本周期已执行指令数
|
||||
uint32_t call_depth() const; // 调用栈深度
|
||||
uint8_t* data(); // 值区(I 采样 / Q 读回由外部做)
|
||||
uint32_t slot_addr(uint32_t slot) const; // 槽 → 值区偏移(槽表两级)
|
||||
int64_t reg(uint8_t i) const; // 当前帧寄存器
|
||||
const uint8_t* cur_instr_bytes() const; // 当前指令首字节(单步反汇编用)
|
||||
uint32_t cur_instr_len() const; // 当前指令长度(变长)
|
||||
```
|
||||
|
||||
- 寄存器 ≤256、映像很小,快照拷贝成本可忽略。
|
||||
- 单步、回放、dump 都建立在同一套观察 API 上。
|
||||
|
||||
---
|
||||
|
||||
## V2 落地状态
|
||||
|
||||
| 项 | 状态 |
|
||||
|---|---|
|
||||
| 变长取指(4/8/16/32B,前缀分区)+ 字节 pc | ✅ `Machine.cpp` exec_one |
|
||||
| 槽表两级(slot → addr → 值区变宽紧凑)+ LOAD_OFF/STORE_OFF | ✅ |
|
||||
| 栈方案 C(预分配 max_stack、帧头 12B、r0..r7 复制、深度 64 双检查) | ✅ do_call/do_ret |
|
||||
| 19 类型 func 驱动(整数饱和 / 无符号 / F32 / F64 IEEE) | ✅ exec_arith/exec_cmp |
|
||||
| CAL 15 条(TON/TOF/TP/CTU/DCTU/CTD/DCTD/CTUD/DCTUD/R_TRIG/F_TRIG/SR/RS/PWM/RTC) | ✅ do_cal |
|
||||
| 故障(None/CycleLimit/StackOverflow/BadOp/BadSlot/BadConst) | ✅ |
|
||||
| 边沿缓冲 edge_prev_(每槽 2 字节,跨周期) | ✅ |
|
||||
| 确定性:同一 I 序列跑两遍,数据区完全一致 | ✅ vm_test |
|
||||
|
||||
指令执行语义(帧/调用栈、指令表、定时器、故障、line1 推演)见 [`指令执行.md`](指令执行.md)。
|
||||
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
# 指令执行流程(V2)
|
||||
|
||||
`vm` 模块的指令执行语义:取指、译码、执行、故障。规范以 [`指令与映像.md`](../isa/指令与映像.md) 为准,本文描述**执行器怎么跑一条指令**。执行模型见 [`初步计划.md`](../初步计划.md) 第 2 节。
|
||||
|
||||
## 执行模型
|
||||
|
||||
```text
|
||||
加载(Machine::create):解析 .stb(vm 自读实现)→ 型号匹配(内建 STATOR2)→ SHA-256 校验
|
||||
任一不符 → 直接拒绝(不创建机器)
|
||||
值区(data_)取映像值段的写副本(周期内 FB 字段/全局可变)
|
||||
边沿缓冲 edge_prev_(每槽 2 字节,跨周期保留)
|
||||
栈区预分配 max_stack 字节(栈方案 C,编译期算出)
|
||||
|
||||
每个扫描周期(run_cycle):
|
||||
1. cycle_count = 0;pc = 0,从入口函数(MAIN)逐条执行
|
||||
2. 执行到 MAIN 的 RET → 周期结束
|
||||
3. 周期末清调用栈(保留栈底 MAIN 帧,sp 回退到 MAIN 帧顶)
|
||||
4. (I 采样 / Q 写回由 executor 按 sidecar 做,VM 只管值区)
|
||||
5. 定时器在 CAL 指令执行时用映像 dt_ms 推进;RTC 用累计周期数 × dt_ms
|
||||
```
|
||||
|
||||
- **pc**:当前指令在函数字节码段内的**字节**下标(从 0 起)
|
||||
- **取指**:函数表 `func_row(fn_id)` → `code_offset` + `code_len`(字节)→ pc 越界 → `BadOp`;`instr_len` 按前缀定长(4/8/16/32B)搬字节
|
||||
- **译码**:前缀分区 + `[func:4|空:4]` 字节1;`op` 按前缀分区查名;寄存器越界按 `uses_*` 直推(RRR/CC/RR 等)→ `BadOp`
|
||||
- **周期计数**:每条指令执行后 `cycle_count++`;超过映像头 `cycle_limit` → `CycleLimit`(周期中止)
|
||||
- **pc 先指向下一条(字节)**;跳转指令再 `pc += off`(off 相对下一条,与编译端 patch_jump 一致)
|
||||
|
||||
## 帧与调用栈(栈方案 C)
|
||||
|
||||
```cpp
|
||||
// 栈区(stack_,预分配 max_stack 字节):
|
||||
// [MAIN 帧][帧2][帧3]... 从低地址起连续压栈
|
||||
// 每帧 = 12B 帧头 + nregs×8 寄存器文件
|
||||
// 帧头:hdr[0]=fn_id hdr[1]=ret_pc(字节) hdr[2]=ret_fn_id
|
||||
```
|
||||
|
||||
- 调用栈深度**上限 64**(`frames_.size() >= 64` 时 CALL → `StackOverflow`);同时 `sp_ + 帧大小 > max_stack` → `StackOverflow`(双检查)
|
||||
- **MAIN 帧跨周期保留**(PROGRAM 变量状态持久);周期结束只清调用栈(保留栈底 MAIN 帧)
|
||||
- 帧寄存器初值 0
|
||||
|
||||
### CALL / RET(调用约定)
|
||||
|
||||
```text
|
||||
CALL fn_id:
|
||||
1. 新帧:regs 大小 = callee 的 nregs,全部 0
|
||||
2. 复制当前帧 r0..r7 → 新帧 r0..r7(实参已在调用点 MOVE 进 r1..r7)——全宽复制
|
||||
3. 新帧 ret_pc = 当前 pc(已指向 CALL 下一条,字节)、ret_fn_id = 当前函数
|
||||
4. 压栈,pc = 0,fn_id = callee
|
||||
|
||||
RET:
|
||||
1. 若当前是 MAIN(栈深 1)→ 周期结束(ended_ = true)
|
||||
2. 否则:复制当前帧 r0..r7 → 调用方帧 r0..r7(r0 = 结果,r1..r7 原样返回)
|
||||
3. 弹栈,pc = ret_pc,fn_id = ret_fn_id
|
||||
```
|
||||
|
||||
## 值表示(func 位段驱动)
|
||||
|
||||
| func | 语义 | 宽度 | 读值扩展 | 类型 |
|
||||
|---|---|---|---|---|
|
||||
| 0 | 无符号 | 1B | 高位清 0 | BOOL/BYTE 等 |
|
||||
| 1 | 有符号 | 1B | 符号扩展 | SINT |
|
||||
| 2 | 无符号 | 2B | 高位清 0 | WORD/UINT |
|
||||
| 3 | 有符号 | 2B | 符号扩展 | INT |
|
||||
| 4 | 无符号 | 4B | 高位清 0 | DWORD/UDINT |
|
||||
| 5 | 有符号 | 4B | 符号扩展 | DINT |
|
||||
| 6 | 无符号 | 8B | 原样 | LWORD/ULINT |
|
||||
| 7 | 有符号 | 8B | 原样 | LINT |
|
||||
| 8 | F32 | 4B | 位模式(int64 低 32 位) | REAL |
|
||||
| 9 | F64 | 8B | 位模式原样 | LREAL |
|
||||
|
||||
- 寄存器(int64)统一承载:窄类型符号扩展/清 0 后,**全宽 int64 比较等价于各类型自身比较**;类型语义由编译期类型检查保证,VM 只按 func 解释
|
||||
- TIME/DATE/TOD/DT 走 func 6/7(64 位整数值)
|
||||
|
||||
## 数据区访问(槽表两级)
|
||||
|
||||
```text
|
||||
槽号 slot(指令操作数,u32)→ 槽表条目(8B:addr:u32 + 预留:u32)→ 值区字节偏移 addr
|
||||
LOAD rd, slot → rd = 读 value[addr .. addr+宽)(按 func 宽度 + 扩展)
|
||||
STORE slot, rs → 写 value[addr .. addr+宽) = rs(按 func 宽度截断)
|
||||
LOAD_OFF rd, slot, off → 读 value[addr+off .. ](FB 字段访问,绝对槽)
|
||||
STORE_OFF slot, off, rs → 写 value[addr+off .. ](FB 字段访问,绝对槽)
|
||||
LOAD_PTR rd, rs, off → 读 value[regs[rs] + off .. ](基址寻址:rs = 实例指针,FB 共享体函数体内)
|
||||
STORE_PTR rd, rs, off → 写 value[regs[rs] + off .. ](基址寻址)
|
||||
```
|
||||
|
||||
槽表条目数 = `n_slots`;槽号越界(`slot >= n_slots`)或地址 + 宽度越出值段 → `BadSlot`。
|
||||
|
||||
## 指令执行表
|
||||
|
||||
| 指令 | 流程 |
|
||||
|---|---|
|
||||
| `MOVE rd, rs` | `reg[rd] = reg[rs]`(func 0..3 限宽 1/2/4/8B,高位清 0) |
|
||||
| `LOADK rd, cid` | 查常量表 `const_entry(cid)`(8B 原始值):按 func 读低 w 字节 + 扩展(同 read_value;F32/F64 位模式) |
|
||||
| `NOT rd, rs` | **逻辑非**:`reg[rd] = (reg[rs] == 0) ? 1 : 0`(恒产 0/1) |
|
||||
| `AND/OR/XOR/NAND/NOR/XNOR rd, ra, rb` | **位串逐位**(64 位整域);BOOL 输入 0/1 下与逻辑等价 |
|
||||
| `ADD/SUB/MUL/DIV rd, ra, rb` | 按 func:整数饱和(__int128 中间量)、无符号、F32/F64 IEEE |
|
||||
| `CMP_EQ/NE/LT/LE/GT/GE rd, ra, rb` | 按 func 符号性比较(浮点 IEEE)→ `1` / `0` |
|
||||
| `NEG rd, rs` | func 8/9 浮点取负(位模式),整数取负 |
|
||||
| `JMP off` | `pc += off` |
|
||||
| `JT rd, off` / `JF rd, off` | `reg[rd] != 0`(JT)/ `== 0`(JF)时 `pc += off` |
|
||||
| `LOAD / STORE` | 见数据区访问(槽表两级) |
|
||||
| `LOAD_OFF / STORE_OFF` | 见数据区访问(FB 字段,绝对实例槽) |
|
||||
| `LOAD_PTR / STORE_PTR` | 见数据区访问(基址寻址:rs 寄存器 = 值区地址 + 偏移;FB 共享体) |
|
||||
| `CAL fb_id, slot` | 见内置 FB(15 条) |
|
||||
| `CALL fn_id` / `RET` | 见帧与调用栈 |
|
||||
| `NOP` | 空转 |
|
||||
|
||||
### 整数算术(除法 A:饱和 + 截断)
|
||||
|
||||
```text
|
||||
整数(func 0..7):sat_add/sat_sub/sat_mul/sat_div(__int128 中间量,结果夹到类型范围)
|
||||
sat_div:除零 → 返回被除数原值(不抛错);MIN / -1 → 夹到 MAX;其余截断向零
|
||||
浮点(func 8/9):IEEE 754(除零 → inf;编译期类型检查保证使用域)
|
||||
```
|
||||
|
||||
## 内置 FB(CAL,15 条)
|
||||
|
||||
`CAL fb_id, slot`:`slot` 是实例**槽号**(槽表条目指向实例基址);字段偏移**编译期对齐计算**(1/2/4/8),字段类型按 machine.toml `[[fb]]` 表。每次执行 = 推进一次,Δt = 映像头 `dt_ms`(不读系统时钟)。边沿检测的"上次输入"存 VM 侧 `edge_prev_`(每槽 2 字节),跨周期保留:
|
||||
|
||||
| fb_id | FB | 字段布局(偏移:类型) | 语义 |
|
||||
|---|---|---|---|
|
||||
| 0 | TON | in@0:B,pt@8:TIME,q@16:B,et@24:TIME | in 真:et += dt(et ≥ pt 停在 pt);q = (et ≥ pt) |
|
||||
| 1 | TOF | 同 TON | in 真:q=1、et=0;in 掉电:et += dt,et ≥ pt → q=0 |
|
||||
| 2 | TP | 同 TON | in 上升沿启动 PT 时长脉冲;计时 et += dt,期满 q=0、et=0 |
|
||||
| 3 | CTU | cu@0:B,r@1:B,pv@2:INT,q@4:B,cv@6:INT | cu 上升沿 cv += 1;r → cv=0;q = (cv ≥ pv) |
|
||||
| 4 | DCTU | cu@0:B,r@1:B,pv@4:DINT,q@8:B,cv@12:DINT | 同 CTU(DINT) |
|
||||
| 5 | CTD | cd@0:B,ld@1:B,pv@2:INT,q@4:B,cv@6:INT | cd 上升沿 cv -= 1;ld → cv=pv;q = (cv ≤ 0) |
|
||||
| 6 | DCTD | 同 DCTU 布局 | 同 CTD(DINT) |
|
||||
| 7 | CTUD | cu@0:B,cd@1:B,r@2:B,lu@3:B,pv@4:INT,qu@6:B,qd@7:B,cv@8:INT | cu 上升沿 +1、cd 上升沿 -1;r → 0;lu → pv;qu=(cv≥pv)、qd=(cv≤0) |
|
||||
| 8 | DCTUD | cu@0,cd@1,r@2,lu@3:B,pv@4:DINT,qu@8,qd@9:B,cv@12:DINT | 同 CTUD(DINT) |
|
||||
| 9 | R_TRIG | clk@0:B,q@1:B | q = clk 且上次 clk 假(上升沿) |
|
||||
| 10 | F_TRIG | clk@0:B,q@1:B | q = !clk 且上次 clk 真(下降沿) |
|
||||
| 11 | SR | S1@0:B,R@1:B,Q1@2:B | **置位优先**:S1 → Q1=1;否则 R → Q1=0 |
|
||||
| 12 | RS | SET@0:B,RESET1@1:B,Q1@2:B | **复位优先**:RESET1 → Q1=0;否则 SET → Q1=1 |
|
||||
| 13 | PWM | in@0:B,pt@8:TIME,duty@16:REAL,q@20:B,et@24:TIME | in 真:et += dt(≥ pt 回绕);q = (et < pt × duty);in 假:et=0、q=0 |
|
||||
| 14 | RTC | enable@0:B,date@4:DATE,tod@8:TOD | 虚拟时钟:累计周期数 × dt_ms(1970 纪元);enable 真时写 date/tod |
|
||||
|
||||
## 故障
|
||||
|
||||
| 故障 | 触发 | 周期行为 |
|
||||
|---|---|---|
|
||||
| `None` | — | 正常完成 |
|
||||
| `CycleLimit` | 周期指令数 > `cycle_limit`(用例 6) | 中止,返回故障 |
|
||||
| `StackOverflow` | 调用栈深度 > 64 或 `sp_ + 帧大小 > max_stack` | 中止 |
|
||||
| `BadOp` | pc 越界 / 指令截断 / 前缀 RESERVED / 寄存器越界 / CAL fb_id 未知 | 中止 |
|
||||
| `BadSlot` | 槽号 ≥ n_slots,或地址 + 宽度越出值段 | 中止 |
|
||||
| `BadConst` | LOADK const_id ≥ n_consts | 中止 |
|
||||
|
||||
## 示例:line1 MAIN 逐指令推演(V2)
|
||||
|
||||
映像(`--disasm` 实取):`dt_ms=10`,`cycle_limit=100000`,值区 7 字节;槽 0..4 → 值区 0..4(紧凑 U8:0=EmergencyStop、1=I0_0、2=I0_1、3=Q0_0、4=starter 实例)。
|
||||
|
||||
```text
|
||||
初始:MAIN 帧 regs[13] 全 0;槽 0..4 全 0(初值 0)
|
||||
假定 executor 已采样:槽 0 = EmergencyStop = 0、槽 1 = I0_0 = 1、槽 2 = I0_1 = 0
|
||||
|
||||
pc 指令 执行后
|
||||
── ───────────────────── ──────────────────────────────
|
||||
0x9c LOAD.U8 r8, s1 r8 = value[1] = 1(I0_0)
|
||||
0xa4 STORE_OFF.U8 r8, s4, 0 starter.S1(实例基址 4 + 0)= 1
|
||||
0xb4 LOAD.U8 r9, s2 r9 = value[2] = 0(I0_1)
|
||||
0xbc STORE_OFF.U8 r9, s4, 1 starter.R(4 + 1)= 0
|
||||
0xcc LOAD_OFF.U8 r8, s4, 0 r8 = 1(S1)
|
||||
0xdc JF r8, +24 r8≠0 不跳;pc = 0xe4 + 0
|
||||
0xe4 LOAD_OFF.U8 r10, s4, 1 r10 = 0(R)
|
||||
0xf4 NOT r9, r10 r9 = 1
|
||||
0xf8 MOVE r8, r9 r8 = 1
|
||||
0xfc JF r8, +16 r8≠0 不跳;pc = 0x104
|
||||
0x104 LOAD.U8 r12, s0 r12 = value[0] = 0(EmergencyStop)
|
||||
0x10c NOT r11, r12 r11 = 1
|
||||
0x110 MOVE r8, r11 r8 = 1
|
||||
0x114 STORE_OFF.U8 r8, s4, 2 starter.Q1(4 + 2)= 1
|
||||
0x124 LOAD_OFF.U8 r8, s4, 2 r8 = 1(Q1)
|
||||
0x134 STORE.U8 r8, s3 Q0_0(槽 3)= 1
|
||||
0x13c RET 周期结束;executor 读槽 3 → 硬件
|
||||
```
|
||||
|
||||
结果:`Q0_0 = 1`(start=1、stop=0、急停=0 → 电机启动)——与 `EXPECTED.md` 真值表一致(vm_test 94 checks + executor 端到端验证)。
|
||||
@@ -0,0 +1,41 @@
|
||||
# 全量验证表(2026-08-26)
|
||||
|
||||
20 用例全量扫描:`STCompiler` 编译 → `BytecodeExecutor` 执行冒烟(4 周期)→ line1 真值表回放。方法:编译失败/成功按退出码;负例按期望错误类别匹配;执行按退出码与故障打印。内部值断言(a=42、n=10、x=7 等)由 `vm_test`(94 checks)与 `vm_cycles`/`cases_all` 覆盖。
|
||||
|
||||
## 结果
|
||||
|
||||
| case | 类别 | 阶段 | 结果 | 备注 |
|
||||
|---|---|---|---|---|
|
||||
| 01_empty_main | 正例 | 编译+执行 | ok | 4 周期无故障 |
|
||||
| 02_bool_assign | 正例 | 编译+执行 | ok | |
|
||||
| 03_short_circuit | 正例 | 编译+执行 | ok | |
|
||||
| 04_if_elsif_else | 正例 | 编译+执行 | ok | |
|
||||
| 05_while_normal | 正例 | 编译+执行 | ok | |
|
||||
| 06_while_cycle_limit | 正例 | 编译+执行 | CycleLimit | 期望故障,正确触发(FAULT(1)) |
|
||||
| 07_int_arith | 正例 | 编译+执行 | ok | |
|
||||
| 08_time_literal | 正例 | 编译+执行 | ok | |
|
||||
| 09_gvl_external | 正例 | 编译+执行 | ok | |
|
||||
| 10_gvl_wrong_file | 负例 | 编译 | REJECTED ok | link error: VAR_GLOBAL only allowed in gvl file |
|
||||
| 11_duplicate_global | 负例 | 编译 | REJECTED ok | link error: duplicate global |
|
||||
| 12_io_unknown_var | 负例 | 编译 | REJECTED ok | link error: io.var not in GVL |
|
||||
| 13_function_call | 正例 | 编译+执行 | ok | |
|
||||
| 14_function_write_global | 负例 | 编译 | REJECTED ok | type error: function cannot write global |
|
||||
| 15_fb_instance | 正例 | 编译+执行 | ok | |
|
||||
| 16_fb_undeclared | 负例 | 编译 | REJECTED ok | link error: undeclared FB instance |
|
||||
| 17_ton | 正例 | 编译+执行 | ok | |
|
||||
| 18_tof_ctu | 正例 | 编译+执行 | ok | |
|
||||
| 19_recursive_call | 负例 | 编译 | REJECTED ok | link error: recursive call |
|
||||
| 20_line1 | 正例 | 编译+执行 | ok | 真值表 4 行全对 |
|
||||
| 21_var_temp | 正例 | 编译+执行 | ok | VAR_TEMP 每周期重置(a=10107/10108/10109) |
|
||||
| 22_multi_instance | 正例 | 编译+执行 | ok | FB 共享体多实例隔离(a=102/204/306) |
|
||||
|
||||
## line1 真值表(回放:io.input 顺序 EmergencyStop, I0_0;I0_1=0 未绑定)
|
||||
|
||||
| EmergencyStop | I0_0 | Q0_0 | 结果 |
|
||||
|---|---|---|---|
|
||||
| 0 | 1 | 1 | ok |
|
||||
| 0 | 0 | 0 | ok |
|
||||
| 1 | 0 | 0 | ok(急停生效) |
|
||||
| 0 | 1 | 1 | ok |
|
||||
|
||||
**结论:22/22 用例符合期望,ctest 14/14,全部绿。**
|
||||
+33
-21
@@ -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
|
||||
```
|
||||
@@ -271,13 +271,13 @@ RET
|
||||
|
||||
## 10. 目录
|
||||
|
||||
仓库布局见根目录 `README.md`。实现时按四个 CMake 目标拆,样例不放仓库根:
|
||||
仓库布局见根目录 `README.md`。实现时按模块拆 CMake 目标(三个库 + 两个可执行),样例不放仓库根:
|
||||
|
||||
```text
|
||||
isa/ # 指令编码、映像头、定宽类型(compiler 与 vm 的合同)
|
||||
compiler/ # lexer parser symbols checker codegen linker
|
||||
vm/ # 扫描周期、寄存器、映像、FB
|
||||
host/ # 喂 I、收 Q、Δt、回放
|
||||
compiler/ # lexer parser symbols checker codegen linker;STCompiler 入口在 src/main.cpp
|
||||
vm/ # 扫描周期、寄存器、映像、FB(库)
|
||||
executor/ # BytecodeExecutor:加载 .stb + sidecar,喂 I、收 Q、Δt、回放
|
||||
examples/line1/ # 第 11 节最小样例(给人看)
|
||||
tests/ # 冻规则用的正例 / 负例
|
||||
```
|
||||
@@ -327,13 +327,13 @@ END_PROGRAM
|
||||
|
||||
原则:**先冻合同,再解析,再链接,最后写 VM**。`compiler` 与 `vm` 不互链,合同只进 `isa`。每一阶段先有可运行的测试,再进入下一阶段。一人约 4~8 周做到第 11 节 `line1`。
|
||||
|
||||
第一版不做:图染色、SSA、WHILE 有界证明、异常表、闭包、优化、`REAL`、独立 `stc.exe`。
|
||||
第一版不做:图染色、SSA、WHILE 有界证明、异常表、闭包、优化、`REAL`。
|
||||
|
||||
---
|
||||
|
||||
### 12.0 骨架(约 1 天)
|
||||
|
||||
CMake 四个目标能空编译:`isa`、`compiler`、`vm`、`host`。`host` 先打印版本即可。
|
||||
CMake 三个库能空编译:`isa`、`compiler`、`vm`;两个可执行 `STCompiler`、`BytecodeExecutor` 先打印版本即可。
|
||||
|
||||
- 落下 `examples/line1/`:第 3 节 toml + 第 11 节三个 `.st`。
|
||||
- `tests/` 接上 CTest,先放一个必过的空测试。
|
||||
@@ -354,7 +354,7 @@ CMake 四个目标能空编译:`isa`、`compiler`、`vm`、`host`。`host` 先
|
||||
| `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 |
|
||||
@@ -367,9 +367,10 @@ CMake 四个目标能空编译:`isa`、`compiler`、`vm`、`host`。`host` 先
|
||||
`VAR` `VAR_INPUT` `VAR_OUTPUT` `VAR_GLOBAL` `VAR_EXTERNAL` `END_VAR`
|
||||
`BOOL` `INT` `TIME`
|
||||
`IF` `ELSIF` `ELSE` `END_IF` `WHILE` `END_WHILE`
|
||||
`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` 等出明确错误)。
|
||||
|
||||
@@ -448,7 +449,7 @@ CMake 四个目标能空编译:`isa`、`compiler`、`vm`、`host`。`host` 先
|
||||
|
||||
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 里保留短路语义)、比较、加减乘除
|
||||
|
||||
@@ -511,7 +512,7 @@ CMake 四个目标能空编译:`isa`、`compiler`、`vm`、`host`。`host` 先
|
||||
此时才写执行循环。只认 `isa` 映像。
|
||||
|
||||
```text
|
||||
采样 I → pc=0 执行到 RET → 写回 Q → 用本周期 Δt 推进 TON/TOF/CTU
|
||||
采样 I → pc=0 执行到 RET → 写回 Q → 用本周期 Δt 推进内置 FB
|
||||
```
|
||||
|
||||
- 译码 + 大 `switch`;寄存器文件按函数头 `nregs` 分配。
|
||||
@@ -524,16 +525,26 @@ CMake 四个目标能空编译:`isa`、`compiler`、`vm`、`host`。`host` 先
|
||||
|
||||
---
|
||||
|
||||
### 12.10 host(约 2~3 天)
|
||||
### 12.10 双可执行:STCompiler 与 BytecodeExecutor(约 2~3 天)
|
||||
|
||||
一条命令:读 toml → `compiler` → `vm` 跑 N 个周期。不拆第二个可执行文件。
|
||||
编译与执行拆成两个可执行,映像 `.stb` 是中间产物:
|
||||
|
||||
- 按 `[[io.*]]` 从宿主缓冲采样 / 写回,不创造变量。
|
||||
- 把 `dt_ms` 每周期传给 VM。
|
||||
- 最小可观测:打印本周期 I/Q,或把寄存器/映像 dump 出来。
|
||||
```text
|
||||
STCompiler <project.toml> -o <name>.stb --machine <machine.toml>
|
||||
BytecodeExecutor <name>.stb [--cycles N]
|
||||
```
|
||||
|
||||
- `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 序列(文本即可),不接真实硬件。
|
||||
|
||||
完成:`host examples/line1/project.toml` 能编译并跑若干周期。
|
||||
完成:`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`。
|
||||
|
||||
---
|
||||
|
||||
@@ -543,6 +554,7 @@ CMake 四个目标能空编译:`isa`、`compiler`、`vm`、`host`。`host` 先
|
||||
- `line1` 固定输入表:急停、start/stop 组合 → 期望 `Q0_0`。
|
||||
- 回放:同一 I 序列跑两遍,槽位与哈希一致。
|
||||
- 单步:能停在指令边界,看到寄存器和 I/Q/M(可先做打印,不必做 UI)。
|
||||
- TUI 界面**最后做**,v1 只预留接口:执行器(`BytecodeExecutor`)的观察界面,数据走 vm 观察 API(见 `Doc/vm/扫描周期.md`),展示与输出层分离(见 `Doc/executor/执行器入口.md`)。
|
||||
|
||||
完成标准(第一版可交付):**`MAIN` + 一个 Motor FB + I/O + 全局**,扫描周期可重复。
|
||||
|
||||
@@ -556,7 +568,7 @@ CMake 四个目标能空编译:`isa`、`compiler`、`vm`、`host`。`host` 先
|
||||
| 2 | 12.5:AST,能 parse `line1`,拒绝多余特性 |
|
||||
| 3 | 12.6~12.7:链接、定址、类型、io 校验 |
|
||||
| 4 | 12.8 前半 + 12.9 前半:赋值/短路/IF 出映像,VM 能跑 |
|
||||
| 5 | 12.8 后半 + 12.10:CALL、FB、host 跑起来 |
|
||||
| 5 | 12.8 后半 + 12.10:CALL、FB、两个可执行跑起来 |
|
||||
| 6 | 定时器/计数器、io、Δt、`line1` 语义 |
|
||||
| 7~8 | 20 用例、回放、单步、补漏 |
|
||||
|
||||
@@ -567,8 +579,8 @@ CMake 四个目标能空编译:`isa`、`compiler`、`vm`、`host`。`host` 先
|
||||
### 12.13 依赖方向(全程遵守)
|
||||
|
||||
```text
|
||||
host → compiler → isa
|
||||
host → vm → isa
|
||||
STCompiler → compiler (编码查 machine.toml;自带 .stb 写)
|
||||
BytecodeExecutor → vm → isa (isa 只管指令;vm 自带 .stb 读)
|
||||
```
|
||||
|
||||
- 指令或映像布局有变:先改 `isa` 和编解码测试,再改两端。
|
||||
|
||||
@@ -7,17 +7,51 @@ doc/
|
||||
索引.md
|
||||
初步计划.md
|
||||
isa/指令与映像.md
|
||||
isa/stb文件格式.md
|
||||
isa/指令配置.md
|
||||
isa/指令集优化.md
|
||||
isa/指令集清单.md
|
||||
isa/指令编码.md
|
||||
isa/阶段1规格总览.md
|
||||
isa/阶段4-执行侧V2.md
|
||||
compiler/编译管线.md
|
||||
compiler/STCompiler使用说明.md
|
||||
compiler/词法.md
|
||||
compiler/语法.md
|
||||
compiler/符号表与链接.md
|
||||
compiler/类型检查.md
|
||||
compiler/寄存器码.md
|
||||
compiler/编译器优化.md
|
||||
compiler/阶段3-语言层V2.md
|
||||
compiler/用例反汇编对照.md
|
||||
vm/扫描周期.md
|
||||
host/宿主入口.md
|
||||
vm/指令执行.md
|
||||
executor/执行器入口.md
|
||||
```
|
||||
|
||||
| 路径 | 内容 |
|
||||
|---|---|
|
||||
| [`初步计划.md`](初步计划.md) | 全工程定案:子集 ST、toml、执行模型、第 12 节阶段 |
|
||||
| [`isa/指令与映像.md`](isa/指令与映像.md) | 指令、映像、定宽类型、饱和;**规范以此为准** |
|
||||
| [`isa/stb文件格式.md`](isa/stb文件格式.md) | `.stb` 结构说明(用 line1 真实字节拆解) |
|
||||
| [`isa/指令配置.md`](isa/指令配置.md) | ops.txt:指令登记唯一事实来源、OpTable、屏蔽机制 |
|
||||
| [`isa/指令集优化.md`](isa/指令集优化.md) | **V2 草案**:变长指令、槽表/值区、IEC 类型扩展、待确认问题 |
|
||||
| [`isa/指令集清单.md`](isa/指令集清单.md) | **V2 草案**:全功能指令清单(10 类 ~55 条,核心/扩展/预留) |
|
||||
| [`isa/指令编码.md`](isa/指令编码.md) | **V2 定案**:RISC-V 式 op + func 编码(前缀长度组 / func 10 组合 / 19 类型映射) |
|
||||
| [`isa/阶段1规格总览.md`](isa/阶段1规格总览.md) | **V2 阶段 1 主参考**:全部定案速览 + 三份规范文档重写任务 + N5 schema 方向 |
|
||||
| [`isa/阶段4-执行侧V2.md`](isa/阶段4-执行侧V2.md) | **阶段 4/5 待办**:isa 重构 + vm 同步 + 全链路收尾清单 |
|
||||
| [`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 片计划 |
|
||||
| [`compiler/编译器优化.md`](compiler/编译器优化.md) | **V2 草案**:编译策略现状(FB 内联/短路/寄存器分配)+ V2 方向(FB 共享体/类型驱动/变长发射) |
|
||||
| [`compiler/阶段3-语言层V2.md`](compiler/阶段3-语言层V2.md) | **阶段 3 计划**:语言层 V2(Lexer/Parser/Typecheck/Linker)+ 待定决策点 L1..L8 |
|
||||
| [`compiler/用例反汇编对照.md`](compiler/用例反汇编对照.md) | **编译器验证**:13 正例源码 ↔ 反汇编逐条对照结论 |
|
||||
| [`vm/扫描周期.md`](vm/扫描周期.md) | 扫描周期与 VM 边界 |
|
||||
| [`host/宿主入口.md`](host/宿主入口.md) | 可执行入口:编译 + 跑周期 |
|
||||
| [`vm/指令执行.md`](vm/指令执行.md) | 12.9 指令执行流程:帧/调用栈、指令表、定时器、故障 |
|
||||
| [`executor/执行器入口.md`](executor/执行器入口.md) | 可执行入口:加载 `.stb` + sidecar,跑扫描周期 |
|
||||
|
||||
代码目录里的 README 只作入口;类型、指令、映像以 `isa/指令与映像.md` 为准。
|
||||
|
||||
@@ -5,26 +5,26 @@
|
||||
方案见 [`Doc/初步计划.md`](Doc/初步计划.md),模块文档见 [`Doc/索引.md`](Doc/索引.md)。
|
||||
|
||||
```text
|
||||
多个 .st
|
||||
↑ project.toml 列出
|
||||
多个 .st + machine.toml(指令/类型/FB 布局)
|
||||
↑ project.toml 列出源文件;machine.toml 唯一事实来源
|
||||
↓
|
||||
compiler(词法 / 语法 → 符号表 → 类型检查 → 寄存器码 → 链接)
|
||||
STCompiler(compiler 库:词法 / 语法 → 符号表 → 类型检查 → 寄存器码 → 链接;编码查 machine.toml)
|
||||
↓
|
||||
单一映像(函数字节码 + 全局 / FB 数据槽 + 工程哈希)
|
||||
<name>.stb 映像(头 104 含型号标识 + 段 + SHA-256 文件尾)+ <name>.runtime.toml sidecar
|
||||
↓
|
||||
vm 按扫描周期执行;host 喂 I、收 Q、给 Δt、回放
|
||||
BytecodeExecutor(vm 库按扫描周期执行;型号/SHA 校验;喂 I、收 Q、给 Δt、回放)
|
||||
```
|
||||
|
||||
## 目录
|
||||
|
||||
```text
|
||||
.
|
||||
├── CMakeLists.txt # 顶层工程:加入四个目标,并启用测试
|
||||
├── CMakeLists.txt # 顶层工程:加入各目标,并启用测试
|
||||
├── cmake/ # CMake 辅助(警告、C++ 标准等),不是业务模块
|
||||
├── isa/ # 共享契约:指令、映像、定宽类型
|
||||
├── compiler/ # ST / toml 编译器
|
||||
├── vm/ # 寄存器虚拟机
|
||||
├── host/ # 可执行入口:编译并跑扫描周期
|
||||
├── compiler/ # ST / toml 编译器(库)+ STCompiler 可执行入口
|
||||
├── vm/ # 寄存器虚拟机(库)
|
||||
├── executor/ # BytecodeExecutor:加载 .stb + sidecar 并执行
|
||||
├── examples/ # 样例工程(给人看、跑通最小闭环)
|
||||
│ └── line1/ # MAIN + Motor FB + I/O
|
||||
│ ├── project.toml
|
||||
@@ -40,32 +40,33 @@ vm 按扫描周期执行;host 喂 I、收 Q、给 Δt、回放
|
||||
|
||||
## 模块与 CMake 目标
|
||||
|
||||
四个链接目标,与目录一一对应。`compiler` 与 `vm` 不互相链接,合同放在 `isa`。
|
||||
三个静态库与两个可执行,与目录一一对应。`compiler` 与 `vm` 不互相链接。指令/类型/FB 布局的**唯一事实来源是 `compiler/machine.toml`**(编译器运行时加载);`isa` 归执行器侧(`vm` 依赖),只管指令定义。依赖方向:`STCompiler → compiler`、`BytecodeExecutor → vm → isa`。
|
||||
|
||||
| 目录 | 目标 | 类型 | 依赖 | 职责 |
|
||||
|---|---|---|---|---|
|
||||
| `isa/` | `isa` | `STATIC` 或 `INTERFACE` | 无 | 约 20 条指令编码、映像头、工程哈希、`BOOL` / `INT` / `TIME` 宽度 |
|
||||
| `compiler/` | `compiler` | `STATIC` | `isa` | 读 toml、收齐 `.st`、解析、链接、定址、类型检查、编寄存器码、写映像 |
|
||||
| `vm/` | `vm` | `STATIC` | `isa` | 一台 VM、一个入口、一份映像;扫描周期、寄存器、FB、有界 `CALL` / `RET` |
|
||||
| `host/` | `host` | `EXECUTABLE` | `compiler`、`vm` | 读工程、编译、按 toml 的 io 采样 I、执行 `PROGRAM MAIN`、写回 Q、用 Δt 推进定时器 / 计数器、回放 |
|
||||
| `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**(不匹配/篡改 → 拒绝)。
|
||||
|
||||
### `host`
|
||||
### `executor`(BytecodeExecutor)
|
||||
|
||||
第一版做成一条命令:编译 + 跑周期。不必再拆独立的编译器可执行文件。
|
||||
`STCompiler` 编译出 `<name>.stb`(含型号标识 + SHA-256)+ `<name>.runtime.toml`(sidecar:I/O 绑定);`BytecodeExecutor` 加载后按扫描周期跑。`dt_ms` / `cycle_limit` 从映像头取。
|
||||
|
||||
### `examples/` 与 `tests/`
|
||||
|
||||
@@ -74,4 +75,4 @@ toml **不声明变量**,只列出源文件、指定唯一 GVL、可选地把
|
||||
| `examples/line1/` | 计划中的最小样例:全局、`MotorStarter` FB、`PROGRAM MAIN` |
|
||||
| `tests/` | 冻关键字、指令、符号表、链接错误(重复 `VAR_GLOBAL`、`io.var` 未声明等) |
|
||||
|
||||
`tests/` 通过 `add_test` 调用 `host` 或直接链接 `compiler` + `vm`。
|
||||
`tests/` 通过 `add_test` 调 `STCompiler` / `BytecodeExecutor`,或直接链接 `compiler` + `vm`。样例工程数据按「一份 toml + 若干 `.st`」分子目录存放,不做成 library。
|
||||
|
||||
+1
-1
@@ -4,6 +4,6 @@
|
||||
|
||||
| 文件 | 内容 |
|
||||
|---|---|
|
||||
| `Standards.cmake` | C11、C++11;`STANDARD_REQUIRED`;关闭 GNU 扩展 |
|
||||
| `Standards.cmake` | C17、C++20;`STANDARD_REQUIRED`;关闭 GNU 扩展 |
|
||||
|
||||
不要按词法、VM、host 再拆成业务 CMake module。改语言标准只改本目录,不要在各子目录再 `set(CMAKE_CXX_STANDARD …)`。
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# C / C++ 语言标准。在 project() 之后 include,对后续目标生效。
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD 17)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_C_EXTENSIONS OFF)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
+20
-3
@@ -6,8 +6,25 @@ project(Compiler
|
||||
VERSION 0.1
|
||||
DESCRIPTION "ST / toml 编译器")
|
||||
|
||||
include_directories(./include)
|
||||
|
||||
# 词法、语法、符号表、类型检查、codegen、链接都留在 compiler 一个库里
|
||||
# 指令 opcode / 类型 / FB 布局来自 machine.toml(MachineConfig),不依赖 isa
|
||||
add_library(compiler STATIC
|
||||
./src/Lexer.cpp)
|
||||
./src/Lexer.cpp
|
||||
./src/Project.cpp
|
||||
./src/Parser.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)
|
||||
|
||||
# 可执行入口:STCompiler <project.toml> -o <name>.stb
|
||||
add_executable(STCompiler
|
||||
./src/main.cpp)
|
||||
|
||||
target_link_libraries(STCompiler PRIVATE compiler)
|
||||
|
||||
+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,127 @@
|
||||
/**
|
||||
* @file Codec.h
|
||||
* @brief 指令字编解码 + 配置驱动反汇编(编译器侧,V2)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 编译器不依赖 isa:变长指令字(前缀分区编码,4/8/16/32B)与
|
||||
* 反汇编输出(按 machine.toml 的 prefix/op/func/fmt)全部由本模块 + MachineConfig
|
||||
* 提供,保证与执行器(isa V2)产生的字节一致。位号见 Doc/isa/指令编码.md。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "compiler/MachineConfig.h"
|
||||
|
||||
namespace compiler {
|
||||
|
||||
/// 指令字最大长度(字节)。
|
||||
static constexpr size_t kMaxInstrLen = 16;
|
||||
|
||||
/**
|
||||
* @brief 前缀定长:读字节 0 判指令长度。
|
||||
* @param code 指令字节(至少 1 字节)
|
||||
* @return 4/8/16/32(前缀 `1`/`01`/`001`/`0001`)
|
||||
*/
|
||||
size_t instr_len(const uint8_t* code);
|
||||
|
||||
/**
|
||||
* @brief 推导形态前缀字符串。
|
||||
* @param code 指令字节
|
||||
* @return "11"/"10"/"011"/"010000".."010101"/"0010"/"0011"/"0001"
|
||||
*/
|
||||
std::string prefix_of(const uint8_t* code);
|
||||
|
||||
/**
|
||||
* @brief 取分区内操作码。
|
||||
* @param code 指令字节
|
||||
* @return op(4B 6 位 / 8B 5 位或 2 位 / 16B·32B 4 位)
|
||||
*/
|
||||
uint8_t op_of(const uint8_t* code);
|
||||
|
||||
/**
|
||||
* @brief 取 func 位段(字节 1 高 4 位)。
|
||||
* @param code 指令字节
|
||||
* @return 0..15(0..9 有效)
|
||||
*/
|
||||
uint8_t func_of(const uint8_t* code);
|
||||
|
||||
/// @brief 取 rd(23..16)。
|
||||
uint8_t rd_of(const uint8_t* code);
|
||||
/// @brief 取 rs1(31..24)。
|
||||
uint8_t rs1_of(const uint8_t* code);
|
||||
/// @brief 取 rs2(39..32)。
|
||||
uint8_t rs2_of(const uint8_t* code);
|
||||
/// @brief 取 imm32(55..24,IMM 形态)。
|
||||
uint32_t imm32_of(const uint8_t* code);
|
||||
/// @brief 取 slot32(55..24,SLOT 形态)。
|
||||
uint32_t slot32_of(const uint8_t* code);
|
||||
/// @brief 取 off32(47..16,JMP 形态,有符号字节偏移)。
|
||||
int32_t off32_of(const uint8_t* code);
|
||||
/// @brief 取 off32(55..24,JC 形态,有符号字节偏移)。
|
||||
int32_t off32_jc_of(const uint8_t* code);
|
||||
/// @brief 取 fn_id32(47..16,CALL 形态)。
|
||||
uint32_t fn_id32_of(const uint8_t* code);
|
||||
/// @brief 取 fb_id16(31..16,CAL 形态)。
|
||||
uint16_t fb_id16_of(const uint8_t* code);
|
||||
/// @brief 取 CAL 的实例槽号(63..32)。
|
||||
uint32_t cal_slot32_of(const uint8_t* code);
|
||||
/// @brief 取 16B 形态的 off32(87..56)。
|
||||
int32_t off32_hi_of(const uint8_t* code);
|
||||
/// @brief 取 imm64(87..24,IMM64 形态)。
|
||||
uint64_t imm64_of(const uint8_t* code);
|
||||
|
||||
/**
|
||||
* @brief 写字节 0(前缀 + 操作码)。
|
||||
* @param out 指令缓冲(≥16B,写入后高位补 0 由调用方保证)
|
||||
* @param prefix 形态前缀(11/10/011/010000..010101/0010/0011/0001)
|
||||
* @param op 分区内编号
|
||||
*/
|
||||
void set_byte0(uint8_t* out, const std::string& prefix, uint32_t op);
|
||||
|
||||
/// @brief 写字节 1 的 func(高 4 位)。
|
||||
void set_func(uint8_t* out, uint8_t func);
|
||||
/// @brief 写 rd(23..16)。
|
||||
void set_rd(uint8_t* out, uint8_t rd);
|
||||
/// @brief 写 rs1(31..24)。
|
||||
void set_rs1(uint8_t* out, uint8_t rs1);
|
||||
/// @brief 写 rs2(39..32)。
|
||||
void set_rs2(uint8_t* out, uint8_t rs2);
|
||||
/// @brief 写 imm32(55..24)。
|
||||
void set_imm32(uint8_t* out, uint32_t v);
|
||||
/// @brief 写 slot32(55..24)。
|
||||
void set_slot32(uint8_t* out, uint32_t v);
|
||||
/// @brief 写 off32(47..16,JMP 形态)。
|
||||
void set_off32(uint8_t* out, int32_t v);
|
||||
/// @brief 写 off32(55..24,JC 形态)。
|
||||
void set_off32_jc(uint8_t* out, int32_t v);
|
||||
/// @brief 写 fn_id32(47..16)。
|
||||
void set_fn_id32(uint8_t* out, uint32_t v);
|
||||
/// @brief 写 fb_id16(31..16)。
|
||||
void set_fb_id16(uint8_t* out, uint16_t v);
|
||||
/// @brief 写 CAL 实例槽号(63..32)。
|
||||
void set_cal_slot32(uint8_t* out, uint32_t v);
|
||||
/// @brief 写 16B 形态的 off32(87..56)。
|
||||
void set_off32_hi(uint8_t* out, int32_t v);
|
||||
/// @brief 写 imm64(87..24)。
|
||||
void set_imm64(uint8_t* out, uint64_t v);
|
||||
|
||||
/**
|
||||
* @brief 配置驱动反汇编:按 machine.toml 的 prefix/op/func/fmt 输出一行文本。
|
||||
* @param cfg 机器配置(指令名 / fmt / func 类别 / fb 名来源)
|
||||
* @param code 指令字节
|
||||
* @param len 指令长度(instr_len 定)
|
||||
* @param out 输出缓冲
|
||||
* @param cap 缓冲容量(含 '\0');cap==0 时直接返回、不写 out
|
||||
* @details 文本格式与 Doc/isa/指令与映像.md §10 一致:
|
||||
* `MOVE r1, r2`、`LOAD.I16 r5, s3`(full/float/dst 类显示类型后缀)、
|
||||
* `JMP +4`、`JT r0, -1`、`CALL 1`、`CAL.TON s5`、`RET`;
|
||||
* 未知 (prefix, op) 输出 `??? 0x<hex dump>`。
|
||||
*/
|
||||
void disasm(const MachineConfig& cfg, const uint8_t* code, size_t len,
|
||||
char* out, size_t cap);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @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 <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "compiler/Linker.h"
|
||||
#include "compiler/MachineConfig.h"
|
||||
#include "compiler/Parser.h"
|
||||
#include "compiler/Project.h"
|
||||
|
||||
namespace compiler {
|
||||
|
||||
/**
|
||||
* @brief 变量/槽映射(--dump-map 用;验证源码 ↔ 反汇编对照)。
|
||||
*/
|
||||
struct CodegenMap {
|
||||
/// 每函数:{ 函数名, { 局部变量名 → "寄存器号" } }
|
||||
std::vector<std::pair<std::string, std::map<std::string, std::string>>> funcs;
|
||||
/// 全局:{ 变量名 → "slot=.. off=.. type=.." }
|
||||
std::map<std::string, std::string> globals;
|
||||
/// 实例:{ "POU名/实例名" → "slot=.. off=.. fb=.. size=.. 字段:名@off.." }
|
||||
std::map<std::string, std::string> instances;
|
||||
};
|
||||
|
||||
/**
|
||||
* @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,
|
||||
CodegenMap* map = nullptr);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* @file Lexer.h
|
||||
* @brief ST 子集词法分析
|
||||
* @details 本文件是词法层对外接口:定义 token 类型(Tok)、token 载体
|
||||
* (Token)与两个入口(lex / lex_file)。扫描器实现见 Lexer.cpp,
|
||||
* 关键字与 token 集见 Doc/compiler/词法.md。
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace compiler {
|
||||
|
||||
/**
|
||||
* @brief token 类型枚举。
|
||||
* @details 关键字与 token 集见 Doc/compiler/词法.md;关键字共 25 个,
|
||||
* 与 12.1 冻结表一致(12.5 修订补入 THEN/DO)。顺序冻结后
|
||||
* 不得插值改动,只能追加到末尾。
|
||||
*/
|
||||
enum class Tok {
|
||||
// 关键字(25 个,与 12.1 冻结表一致)
|
||||
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, ///< 外部变量段起始
|
||||
VAR_TEMP, ///< 临时变量段起始(VAR_TEMP)
|
||||
END_VAR, ///< 变量段结束
|
||||
BOOL, ///< 布尔类型
|
||||
BYTE, ///< 位串类型(8 位)
|
||||
WORD, ///< 位串类型(16 位)
|
||||
DWORD, ///< 位串类型(32 位)
|
||||
LWORD, ///< 位串类型(64 位)
|
||||
SINT, ///< 有符号整数(8 位)
|
||||
INT, ///< 有符号整数(16 位)
|
||||
DINT, ///< 有符号整数(32 位)
|
||||
LINT, ///< 有符号整数(64 位)
|
||||
USINT, ///< 无符号整数(8 位)
|
||||
UINT, ///< 无符号整数(16 位)
|
||||
UDINT, ///< 无符号整数(32 位)
|
||||
ULINT, ///< 无符号整数(64 位)
|
||||
TIME, ///< 时间类型(毫秒)
|
||||
REAL, ///< 浮点类型(32 位)
|
||||
LREAL, ///< 浮点类型(64 位)
|
||||
DATE, ///< 日期类型(天数)
|
||||
TOD, ///< 时刻类型(当日毫秒)
|
||||
DT, ///< 日期时刻类型(64 位)
|
||||
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, ///< 布尔假字面量
|
||||
// 字面量
|
||||
IDENT, ///< 标识符(text = 小写名;含 FB 类型名,Typecheck 查表)
|
||||
INT_LIT, ///< 整数字面量(int_value;含进制 2#/8#/16# 已换算)
|
||||
FLOAT_LIT, ///< 浮点字面量(double_value)
|
||||
TIME_LIT, ///< 时间字面量(int_value = 毫秒)
|
||||
DATE_LIT, ///< 日期字面量(int_value = 天数,1970 纪元)
|
||||
TOD_LIT, ///< 时刻字面量(int_value = 当日毫秒)
|
||||
DT_LIT, ///< 日期时刻字面量(int64 = date<<32|tod)
|
||||
// 符号
|
||||
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; ///< token 类型
|
||||
std::string text; ///< 标识符/关键字的小写文本;字面量原文
|
||||
int64_t int_value; ///< INT_LIT / TIME_LIT / DATE_LIT / TOD_LIT / DT_LIT 的字面量值
|
||||
double double_value; ///< FLOAT_LIT 的字面量值
|
||||
std::string source_file; ///< 来源文件名
|
||||
uint32_t line; ///< 起始行(从 1 起)
|
||||
uint32_t col; ///< 起始列(从 1 起)
|
||||
};
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "compiler/MachineConfig.h"
|
||||
#include "compiler/Parser.h"
|
||||
#include "compiler/Project.h"
|
||||
|
||||
namespace compiler {
|
||||
|
||||
/**
|
||||
* @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; ///< 声明列号
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief FB 类型字段(实例布局用;V2 字段类型为标量类型名小写)。
|
||||
*/
|
||||
struct FbField {
|
||||
std::string name; ///< 字段名
|
||||
std::string type_name; ///< 字段类型名(小写,如 "bool"/"time")
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 一个 FB 类型的字段布局(供实例分配与字段引用解析)。
|
||||
*/
|
||||
struct FbLayout {
|
||||
std::string type_name; ///< 类型名(小写)
|
||||
std::vector<FbField> fields; ///< 段序:input → output → 内部 var
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 一个源文件的 AST(供链接输入)。
|
||||
*/
|
||||
struct SourceUnit {
|
||||
std::string path; ///< 源文件路径
|
||||
Unit ast; ///< 解析出的 AST
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 链接结果:全局槽表 + FB 类型布局 + 各 POU 作用域 + 函数表顺序。
|
||||
*/
|
||||
struct LinkResult {
|
||||
std::vector<Symbol> globals; ///< 全局槽表:下标即槽号(声明顺序)
|
||||
std::map<std::string, uint32_t> global_index; ///< 名 → 槽号
|
||||
|
||||
std::map<std::string, FbLayout> fb_types; ///< 用户 FB 类型布局(按类型名)
|
||||
|
||||
/**
|
||||
* @brief 每 POU 的局部符号与 FB 实例。
|
||||
*/
|
||||
struct PouScope {
|
||||
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; ///< 全部 POU 作用域
|
||||
|
||||
std::vector<std::string> fn_order; ///< 函数表顺序(PROGRAM / FUNCTION / FUNCTION_BLOCK 名,收集序)
|
||||
};
|
||||
|
||||
/**
|
||||
* @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,
|
||||
const MachineConfig& cfg, LinkResult* out, std::string* err);
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @brief 符号表文本。
|
||||
* @details 每行 "name kind type address file"(全局表 + 各 POU 局部表)。
|
||||
* @param r 链接结果
|
||||
* @return 多行文本:全局表逐行;每个 POU 以 "[名字]" 开头后接其局部符号行
|
||||
*/
|
||||
std::string dump_symbols(const LinkResult& r);
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* @file MachineConfig.h
|
||||
* @brief 机器定义(machine.toml V2)加载与强校验
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 见 Doc/isa/指令配置.md(V2 schema):
|
||||
* - 配置只给编译器(toml++ 运行时加载);vm 不读配置(内建全指令集)
|
||||
* - 强校验 5 项:meta(version=2)/ type 19 条全集·base 命中基元·kind 合法·
|
||||
* func 与 base (width,signed) 互锁 / op prefix 合法·prefix↔fmt 互锁·
|
||||
* op 分区内唯一·func 类别合法·params 数量与 fmt 互锁 /
|
||||
* fb 15 条全集(fb_id 0..14)·字段类型命中 type 表·字段名唯一 /
|
||||
* op-fb 互锁(CAL 存在 ↔ fb 表非空)
|
||||
* - 基元内置(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 配置类型 = 基元别名 + kind 语义 + func 映射(V2)。
|
||||
* @details 对应 machine.toml [[type]] 行:base 必为内建基元名;kind 为语言语义
|
||||
* (bool/bit/int/uint/float/time/date);func 为 type 位段值(0..9),
|
||||
* 与 base 的 (width, signed, is_float) 互锁强校验。
|
||||
*/
|
||||
struct ConfigType {
|
||||
std::string name; ///< 配置类型名(大写,如 BOOL/INT/TIME)
|
||||
std::string base; ///< 基元名(命中内建基元表)
|
||||
std::string kind; ///< 语言语义:bool/bit/int/uint/float/time/date
|
||||
uint32_t func = 0; ///< type 位段值(0..9)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 配置指令(V2:prefix + op + fmt + func 类别)。
|
||||
* @details 对应 machine.toml [[op]] 行:prefix 为形态前缀(11/10/011/010000..
|
||||
* 010101/0010/0011/0001),op 为分区内编号(0 起);prefix 与 fmt 互锁;
|
||||
* func_mode 为数据语义类别(none/width/uint/full/float/dst)。
|
||||
*/
|
||||
struct ConfigOp {
|
||||
std::string name; ///< 助记符(与 .stb / isa 一致)
|
||||
std::string prefix; ///< 形态前缀(长度+形态分区)
|
||||
uint32_t op = 0; ///< 分区内编号(0 起)
|
||||
std::string fmt; ///< 操作数形态:RR/NONE/RRR/IMM/SLOT/JMP/JC/CALL/CAL/SLOT+off/IMM64/预留
|
||||
std::string func_mode; ///< func 类别:none/width/uint/full/float/dst
|
||||
std::vector<std::string> params; ///< 参数名(数量与 fmt 一致)
|
||||
bool enabled = false; ///< 是否启用
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 内建 FB 的一个字段。
|
||||
*/
|
||||
struct ConfigFbField {
|
||||
std::string name; ///< 字段名(如 in/pt/q/et/cu/cv)
|
||||
std::string type; ///< 配置类型名(BOOL/INT/TIME/...)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 内建功能块(FB)布局登记(V2:fb_id)。
|
||||
* @details 对应 machine.toml [[fb]] 行;fb_id 唯一(0..14,15 条全集);
|
||||
* CAL 指令(010101)的 fb_id 由本表提供。
|
||||
*/
|
||||
struct ConfigFb {
|
||||
std::string name; ///< FB 名(大写,如 TON/CTU)
|
||||
uint32_t fb_id = 0; ///< FB 类型 id(0..14)
|
||||
std::vector<ConfigFbField> fields; ///< 字段列表(名称唯一,类型命中 type 表)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 机器定义:加载 machine.toml 并做强校验(V2)。
|
||||
* @details 编译器指令集 / 类型的唯一事实来源;校验失败时 ok() 为 false,
|
||||
* err 前缀 "machine error"。vm 不读配置(内建全指令集)。
|
||||
*/
|
||||
class MachineConfig {
|
||||
public:
|
||||
/**
|
||||
* @brief 加载 + 强校验(V2)。
|
||||
* @param path machine.toml 路径
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false(err 已写,前缀 "machine error")
|
||||
* @details 强校验项:meta(name/version=2)/ type 19 条全集·base 命中基元·
|
||||
* kind 合法·func 与 (width,signed,float) 互锁 / op prefix 合法·
|
||||
* prefix↔fmt 互锁·(prefix,op) 分区内唯一·func 类别合法·
|
||||
* params 数量与 fmt 互锁 / fb 15 条全集·fb_id 唯一·字段类型命中
|
||||
* type 表·字段名唯一 / op-fb 互锁(CAL ↔ fb 表非空)。
|
||||
*/
|
||||
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,= 2,参与 .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 配置类型名(如 "INT")
|
||||
* @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 按 (prefix, op) 键查指令行。
|
||||
* @param prefix 形态前缀(如 "11"/"010001")
|
||||
* @param op 分区内编号
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigOp* find_op_by_key(const std::string& prefix, uint32_t op) const;
|
||||
|
||||
/**
|
||||
* @brief 查询某 (prefix, op) 是否启用。
|
||||
* @param prefix 形态前缀
|
||||
* @param op 分区内编号
|
||||
* @return 指令存在且 enabled 为 true;未知返回 false
|
||||
*/
|
||||
bool op_enabled(const std::string& prefix, uint32_t op) const;
|
||||
|
||||
/**
|
||||
* @brief 按名称查内建 FB。
|
||||
* @param name FB 名(如 "TON")
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigFb* find_fb(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief 按 fb_id 查内建 FB。
|
||||
* @param fb_id FB 类型 id(0..14)
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigFb* find_fb_by_id(uint32_t fb_id) 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 表
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* @file Parser.h
|
||||
* @brief ST 子集递归下降语法分析(AST 定义 + 入口)
|
||||
* @details 本文件定义语法层全部公开类型(AST 节点与相关枚举)与两个
|
||||
* 解析入口(parse_pous / parse_pous_file)。解析器实现见
|
||||
* Parser.cpp,文法详见 Doc/compiler/语法.md。
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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 { Scalar, FbBuiltin, FbUser };
|
||||
|
||||
/**
|
||||
* @brief 类型引用:种类 + (用户 FB 的)类型名。
|
||||
*/
|
||||
struct TypeRef {
|
||||
TypeKind kind = TypeKind::Scalar; ///< 类型种类(Scalar=标量 19 型 / FbBuiltin / FbUser)
|
||||
std::string name; ///< 类型名(小写:标量 = machine.toml type 键;FB = 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; ///< 声明起始列
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 变量段种类。
|
||||
* @details 枚举值:Local=VAR、Input=VAR_INPUT、Output=VAR_OUTPUT、
|
||||
* Global=VAR_GLOBAL、External=VAR_EXTERNAL、Temp=VAR_TEMP。
|
||||
*/
|
||||
enum class VarSection { Local, Input, Output, Global, External, Temp };
|
||||
|
||||
/**
|
||||
* @brief 一段 VAR_* … END_VAR:段种类 + 声明列表。
|
||||
*/
|
||||
struct VarBlock {
|
||||
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,含进制已换算)
|
||||
LitReal, ///< 浮点字面量(double_value)
|
||||
LitTime, ///< 时间字面量(int_value 毫秒)
|
||||
LitDate, ///< 日期字面量(int_value 天数,1970 纪元)
|
||||
LitTod, ///< 时刻字面量(int_value 当日毫秒)
|
||||
LitDt, ///< 日期时刻字面量(int64 = date<<32|tod)
|
||||
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; ///< 二目运算(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 ms、DATE 天、TOD ms、DT)
|
||||
double double_value = 0; ///< LitReal 字面量值
|
||||
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 };
|
||||
|
||||
/** @brief 语句节点(前置声明;Stmt 与 IfBranch 相互引用)。 */
|
||||
struct Stmt;
|
||||
|
||||
/**
|
||||
* @brief FB 调用实参:命名参数(名 := 表达式)。
|
||||
*/
|
||||
struct FbArg {
|
||||
std::string name; ///< 形参名(小写)
|
||||
std::unique_ptr<Expr> value; ///< 实参表达式
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief IF 语句的一个 ELSIF 分支:条件 + 分支体。
|
||||
*/
|
||||
struct IfBranch {
|
||||
std::unique_ptr<Expr> cond; ///< 分支条件
|
||||
std::vector<Stmt> body; ///< 分支体(条件成立时执行)
|
||||
};
|
||||
|
||||
/**
|
||||
* @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; ///< 语句种类
|
||||
// Assign
|
||||
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; ///< ELSIF 分支列表
|
||||
std::vector<Stmt> else_body; ///< ELSE 分支体
|
||||
// FbCall
|
||||
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; ///< POU 种类
|
||||
std::string name; ///< POU 名(小写)
|
||||
TypeRef result_type; ///< 返回类型(仅 FUNCTION 才有)
|
||||
std::vector<VarBlock> blocks; ///< 变量段(按声明顺序)
|
||||
std::vector<Stmt> body; ///< 语句体
|
||||
std::string source_file; ///< 来源文件名
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 一个 .st 文件的解析结果。
|
||||
* @details GVL 文件形态(无 POU)时只有 globals 非空。
|
||||
*/
|
||||
struct Unit {
|
||||
std::vector<VarBlock> globals; ///< 顶层 VAR_GLOBAL 段(仅 GVL 文件)
|
||||
std::vector<POU> pous; ///< POU 列表(按出现顺序)
|
||||
};
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace compiler {
|
||||
|
||||
/**
|
||||
* @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]]
|
||||
};
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @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,275 @@
|
||||
/**
|
||||
* @file Stb.h
|
||||
* @brief 编译器自带的 .stb 映像规范(写侧,V2)+ 只读视图 + FNV-1a
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 执行器(vm)各有自己的读写实现;格式契约见 Doc/isa/指令与映像.md(V2)。
|
||||
* V2:头 128B(version=2、型号标识 STATOR2 @72、n_slots/values_size/offset_meta/max_stack),
|
||||
* 常量表 8B/条(无 tag,LOADK 按 func 解释),槽表段 8B/条 {addr,预留},
|
||||
* 值段变宽紧凑;文件尾 SHA-256[32];工程哈希 FNV-1a 64。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "compiler/Project.h"
|
||||
|
||||
namespace compiler {
|
||||
|
||||
// ---- .stb 格式常量(V2 契约;头 128 = 原 72 字段 + 型号标识[32]@72 + 新增 24)----
|
||||
/// 映像魔数:"STSC" 小端。
|
||||
static const uint32_t kMagic = 0x43545353u;
|
||||
/// 映像格式版本(V2)。
|
||||
static const uint32_t kVersion = 2;
|
||||
/// 映像头字节数(V2)。
|
||||
static const size_t kHeaderSize = 128;
|
||||
/// 常量表一行字节数(8B 原始值,无 tag)。
|
||||
static const size_t kConstEntrySize = 8;
|
||||
/// 函数表一行字节数(nregs / code_offset / code_len 各 4)。
|
||||
static const size_t kFuncRowSize = 12;
|
||||
/// 槽表一行字节数(addr:4 + 预留:4)。
|
||||
static const size_t kSlotRowSize = 8;
|
||||
/// 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;
|
||||
|
||||
// ---- 头字段偏移(与 Doc/isa/指令与映像.md §8.1 字段表一致;vm 侧另有同值一份)----
|
||||
inline constexpr size_t kOffMagic = 0; ///< 魔数 "STSC"
|
||||
inline constexpr size_t kOffVersion = 4; ///< 格式版本(V2 = 2)
|
||||
inline constexpr size_t kOffCycleLimit = 8; ///< 每周期指令数上限
|
||||
inline constexpr size_t kOffDtMs = 12; ///< 周期时长(毫秒)
|
||||
inline constexpr size_t kOffProjectHash = 16; ///< 工程哈希(8 字节)
|
||||
inline constexpr size_t kOffEntryFnId = 24; ///< 入口函数 fn_id
|
||||
inline constexpr size_t kOffNGlobals = 28; ///< 全局槽数
|
||||
inline constexpr size_t kOffNI = 32; ///< 输入(I)槽数(保留恒 0)
|
||||
inline constexpr size_t kOffNQ = 36; ///< 输出(Q)槽数(保留恒 0)
|
||||
inline constexpr size_t kOffNM = 40; ///< 中间(M)槽数(保留恒 0)
|
||||
inline constexpr size_t kOffNConsts = 44; ///< 常量表条目数
|
||||
inline constexpr size_t kOffNFuncs = 48; ///< 函数表行数
|
||||
inline constexpr size_t kOffConst = 52; ///< 常量表段偏移
|
||||
inline constexpr size_t kOffFuncs = 56; ///< 函数表段偏移
|
||||
inline constexpr size_t kOffCode = 60; ///< 字节码段偏移(16 对齐)
|
||||
inline constexpr size_t kOffSlots = 64; ///< 槽表段偏移
|
||||
inline constexpr size_t kOffValues = 68; ///< 值段偏移(16 对齐)
|
||||
inline constexpr size_t kOffModelId = 72; ///< 型号标识[32](V2 = "STATOR2")
|
||||
inline constexpr size_t kOffNSlots = 104; ///< 槽表条目数(= n_globals + 实例槽数)
|
||||
inline constexpr size_t kOffValuesSize = 108; ///< 值段字节数
|
||||
inline constexpr size_t kOffMeta = 112; ///< 元数据段偏移(v1 = 0 空段)
|
||||
inline constexpr size_t kOffMaxStack = 116; ///< 栈区字节数(编译期算)
|
||||
|
||||
// ---- 段内行偏移 ----
|
||||
inline constexpr size_t kConstValueOff = 0; ///< 常量表行内:value(8 字节,无 tag)
|
||||
inline constexpr size_t kFuncNregsOff = 0; ///< 函数表行内:nregs
|
||||
inline constexpr size_t kFuncCodeOff = 4; ///< 函数表行内:code_offset(字节)
|
||||
inline constexpr size_t kFuncLenOff = 8; ///< 函数表行内:code_len(字节)
|
||||
inline constexpr size_t kSlotAddrOff = 0; ///< 槽表行内:addr(值段内偏移)
|
||||
inline constexpr size_t kSlotPadOff = 4; ///< 槽表行内:预留
|
||||
|
||||
// 防漂移:头布局自洽性(与 Doc/isa/指令与映像.md §8.1 冻结表核对)
|
||||
static_assert(kHeaderSize == kOffModelId + kModelIdSize + 24, "header = 72 + model id + 24");
|
||||
static_assert(kOffValues > kOffSlots && kOffSlots > kOffCode && kOffCode > kOffFuncs &&
|
||||
kOffFuncs > kOffConst,
|
||||
"header field offsets monotonic");
|
||||
static_assert(kOffModelId == kOffValues + 4, "model id follows values offset field");
|
||||
static_assert(kOffMaxStack == kOffMeta + 4, "max_stack follows meta offset");
|
||||
static_assert(kConstEntrySize == 8, "const row: value:8 (no tag)");
|
||||
static_assert(kFuncRowSize == kFuncLenOff + 4, "func row: nregs/code_offset/code_len");
|
||||
static_assert(kSlotRowSize == kSlotPadOff + 4, "slot row: addr + pad");
|
||||
|
||||
/**
|
||||
* @brief 常量表一行(8B 原始值,无 tag——LOADK 按 func 位段解释)。
|
||||
*/
|
||||
struct ConstEntry {
|
||||
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(如 "STATOR2")。
|
||||
* @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 用,V2)。
|
||||
* @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; ///< 字节码段内偏移(字节)
|
||||
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_; }
|
||||
/// 常量表段偏移(头 @52)。
|
||||
uint32_t offset_const() const { return offs_of_const(); }
|
||||
/// 函数表段偏移(头 @56)。
|
||||
uint32_t offset_funcs() const { return static_cast<uint32_t>(get_le32_(56)); }
|
||||
/// 字节码段偏移(头 @60)。
|
||||
uint32_t offset_code() const { return offset_code_; }
|
||||
/// 槽表段偏移(头 @64)。
|
||||
uint32_t offset_slots() const { return offset_slots_; }
|
||||
/// 值段偏移(头 @68)。
|
||||
uint32_t offset_values() const { return offset_values_; }
|
||||
/// 槽表条目数(头 @104)。
|
||||
uint32_t n_slots() const { return n_slots_; }
|
||||
/// 值段字节数(头 @108)。
|
||||
uint32_t values_size() const { return values_size_; }
|
||||
/// 元数据段偏移(头 @112;0 = 空段)。
|
||||
uint32_t offset_meta() const { return offset_meta_; }
|
||||
/// 栈区字节数(头 @116)。
|
||||
uint32_t max_stack() const { return max_stack_; }
|
||||
|
||||
/// 读常量表一行(8B 原始值);越界时返回全 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* slots_bytes() const;
|
||||
/// 值段起点。
|
||||
const uint8_t* data_bytes() const;
|
||||
/// 值段字节数(不含文件尾 SHA-256)。
|
||||
size_t data_len() const;
|
||||
|
||||
/// 型号标识字符串(头 72..103,截断到首个 '\0')。
|
||||
std::string model_id() const;
|
||||
/// 型号标识是否匹配 name + version。
|
||||
bool model_matches(const std::string& name, uint32_t version) const;
|
||||
/// 文件尾 32 字节 SHA-256 校验(对文件尾之前全部内容重算)。
|
||||
bool sha_ok() const;
|
||||
|
||||
private:
|
||||
/// 私有构造(只能经 from() 创建)。
|
||||
StbView() : buf_(0), len_(0) {}
|
||||
|
||||
/// 常量表偏移(from 已校验的段起点)。
|
||||
uint32_t offs_of_const() const;
|
||||
/// 读头内 u32(小端;内部辅助)。
|
||||
uint32_t get_le32_(size_t off) const {
|
||||
return static_cast<uint32_t>(buf_[off])
|
||||
| (static_cast<uint32_t>(buf_[off + 1]) << 8)
|
||||
| (static_cast<uint32_t>(buf_[off + 2]) << 16)
|
||||
| (static_cast<uint32_t>(buf_[off + 3]) << 24);
|
||||
}
|
||||
|
||||
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_slots_ = 0; ///< 槽表段偏移
|
||||
uint32_t offset_values_ = 0; ///< 值段偏移
|
||||
uint32_t n_slots_ = 0; ///< 槽表条目数
|
||||
uint32_t values_size_ = 0; ///< 值段字节数
|
||||
uint32_t offset_meta_ = 0; ///< 元数据段偏移(0 = 空段)
|
||||
uint32_t max_stack_ = 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,56 @@
|
||||
/**
|
||||
* @file TypeInfo.h
|
||||
* @brief 语言类型元数据(machine.toml 别名 + 内建基元解析)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 类型定义数据化(配置),运算/类型检查语义代码化:
|
||||
* - 元数据(宽度/符号/浮点/kind/func)全部来自 machine.toml + 内建基元表
|
||||
* - 查询大小写不敏感(配置名大写,Linker type_name 小写)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "compiler/MachineConfig.h"
|
||||
|
||||
namespace compiler {
|
||||
|
||||
/**
|
||||
* @brief 语言类型元数据(V2)。
|
||||
* @details 由配置类型行 + 内建基元合成;config 或 prim 未命中时
|
||||
* 对应访问器取安全默认值,operator bool 为 false。
|
||||
*/
|
||||
struct TypeMeta {
|
||||
const ConfigType* config = nullptr; ///< 配置类型行(别名 + kind + func)
|
||||
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 type 位段值 func(0..9);无配置返回 0。
|
||||
uint32_t func() const { return config ? config->func : 0; }
|
||||
/// @brief 语言语义 kind(bool/bit/int/uint/float/time/date);无配置返回空串。
|
||||
const std::string& kind() const {
|
||||
static const std::string kEmpty;
|
||||
return config ? config->kind : kEmpty;
|
||||
}
|
||||
|
||||
/// @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,47 @@
|
||||
/**
|
||||
* @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/MachineConfig.h"
|
||||
#include "compiler/Parser.h"
|
||||
#include "compiler/Project.h"
|
||||
|
||||
namespace compiler {
|
||||
|
||||
/**
|
||||
* @brief 表达式求值类型(V2:19 标量 + 未定型)。
|
||||
* @details 无隐式转换(赋值/算术/比较严格同型);字面量未定型(Unresolved),
|
||||
* 由消费点(赋值目标/实参/条件/另一操作数)按上下文定型 + 值域检查;
|
||||
* BOOL 编译器强制 0/1。
|
||||
*/
|
||||
enum class TType {
|
||||
Bool, Byte, Word, Dword, Lword, Sint, Int, Dint, Lint,
|
||||
Usint, Uint, Udint, Ulint, Time, Real, Lreal, Date, Tod, Dt,
|
||||
Unresolved, ///< 字面量未定型(待消费点定型)
|
||||
};
|
||||
|
||||
/**
|
||||
* @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, const MachineConfig& cfg, std::string* err);
|
||||
}
|
||||
@@ -0,0 +1,886 @@
|
||||
# 机器定义:指令集与类型的唯一事实来源(V2,STATOR2)
|
||||
# schema 见 Doc/isa/指令配置.md;全属性必填,与代码强校验一致。
|
||||
# V2 变更:op 用 prefix+op(分区内编号);func 类别表达数据语义;
|
||||
# 类型 19 条(base 基元 + kind 语义 + func 映射);FB 14 条(fb_id 0..14)。
|
||||
|
||||
[meta]
|
||||
name = "STATOR" # 型号名(参与 .stb 型号标识[32])
|
||||
version = 2 # 格式版本(型号标识 = "STATOR2";V1 = 1)
|
||||
|
||||
# ---- 类型:19 种 IEC 类型(V2)
|
||||
# base = 编译器内建基元(bit/int8/int16/int32/int64/uint8/uint16/uint32/uint64/float32/float64)
|
||||
# kind = 语言语义(bool/bit/int/uint/float/time/date)——同 func 不同 kind 区分
|
||||
# func = type 位段值(0..9),与 base 的 (width, signed) 互锁强校验
|
||||
|
||||
[[type]]
|
||||
name = "BOOL"
|
||||
base = "uint8"
|
||||
kind = "bool"
|
||||
func = 0
|
||||
|
||||
[[type]]
|
||||
name = "BYTE"
|
||||
base = "uint8"
|
||||
kind = "bit"
|
||||
func = 0
|
||||
|
||||
[[type]]
|
||||
name = "WORD"
|
||||
base = "uint16"
|
||||
kind = "bit"
|
||||
func = 2
|
||||
|
||||
[[type]]
|
||||
name = "DWORD"
|
||||
base = "uint32"
|
||||
kind = "bit"
|
||||
func = 4
|
||||
|
||||
[[type]]
|
||||
name = "LWORD"
|
||||
base = "uint64"
|
||||
kind = "bit"
|
||||
func = 6
|
||||
|
||||
[[type]]
|
||||
name = "SINT"
|
||||
base = "int8"
|
||||
kind = "int"
|
||||
func = 1
|
||||
|
||||
[[type]]
|
||||
name = "INT"
|
||||
base = "int16"
|
||||
kind = "int"
|
||||
func = 3
|
||||
|
||||
[[type]]
|
||||
name = "DINT"
|
||||
base = "int32"
|
||||
kind = "int"
|
||||
func = 5
|
||||
|
||||
[[type]]
|
||||
name = "LINT"
|
||||
base = "int64"
|
||||
kind = "int"
|
||||
func = 7
|
||||
|
||||
[[type]]
|
||||
name = "USINT"
|
||||
base = "uint8"
|
||||
kind = "uint"
|
||||
func = 0
|
||||
|
||||
[[type]]
|
||||
name = "UINT"
|
||||
base = "uint16"
|
||||
kind = "uint"
|
||||
func = 2
|
||||
|
||||
[[type]]
|
||||
name = "UDINT"
|
||||
base = "uint32"
|
||||
kind = "uint"
|
||||
func = 4
|
||||
|
||||
[[type]]
|
||||
name = "ULINT"
|
||||
base = "uint64"
|
||||
kind = "uint"
|
||||
func = 6
|
||||
|
||||
[[type]]
|
||||
name = "TIME"
|
||||
base = "int64"
|
||||
kind = "time"
|
||||
func = 7
|
||||
|
||||
[[type]]
|
||||
name = "REAL"
|
||||
base = "float32"
|
||||
kind = "float"
|
||||
func = 8
|
||||
|
||||
[[type]]
|
||||
name = "LREAL"
|
||||
base = "float64"
|
||||
kind = "float"
|
||||
func = 9
|
||||
|
||||
[[type]]
|
||||
name = "DATE"
|
||||
base = "uint32"
|
||||
kind = "date"
|
||||
func = 4
|
||||
|
||||
[[type]]
|
||||
name = "TOD"
|
||||
base = "uint32"
|
||||
kind = "date"
|
||||
func = 4
|
||||
|
||||
[[type]]
|
||||
name = "DT"
|
||||
base = "uint64"
|
||||
kind = "date"
|
||||
func = 6
|
||||
|
||||
# ---- 指令(V2:prefix + op + fmt + func 类别,全属性必填)
|
||||
# prefix:形态前缀(11/10/011/010000..010101/0010/0011/0001)
|
||||
# op:分区内编号(0 起);fmt:操作数形态;func:数据语义类别
|
||||
# (none=无类型 / width=宽度 0..3 / uint=无符号宽度 0/2/4/6 /
|
||||
# full=全矩阵 0..9 / float=浮点 8/9 / dst=目标类型 0..9)
|
||||
# params:参数名(与 fmt 互锁,见指令配置.md 强校验)
|
||||
|
||||
# ---- 4B RR(前缀 11,op 6 位)
|
||||
[[op]]
|
||||
name = "MOVE"
|
||||
prefix = "11"
|
||||
op = 0
|
||||
fmt = "RR"
|
||||
func = "width"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "NOT"
|
||||
prefix = "11"
|
||||
op = 1
|
||||
fmt = "RR"
|
||||
func = "uint"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "NEG"
|
||||
prefix = "11"
|
||||
op = 2
|
||||
fmt = "RR"
|
||||
func = "full"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "ABS"
|
||||
prefix = "11"
|
||||
op = 3
|
||||
fmt = "RR"
|
||||
func = "full"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "INC"
|
||||
prefix = "11"
|
||||
op = 4
|
||||
fmt = "RR"
|
||||
func = "full"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "DEC"
|
||||
prefix = "11"
|
||||
op = 5
|
||||
fmt = "RR"
|
||||
func = "full"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "SQRT"
|
||||
prefix = "11"
|
||||
op = 6
|
||||
fmt = "RR"
|
||||
func = "float"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "SIN"
|
||||
prefix = "11"
|
||||
op = 7
|
||||
fmt = "RR"
|
||||
func = "float"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "COS"
|
||||
prefix = "11"
|
||||
op = 8
|
||||
fmt = "RR"
|
||||
func = "float"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "TAN"
|
||||
prefix = "11"
|
||||
op = 9
|
||||
fmt = "RR"
|
||||
func = "float"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "ASIN"
|
||||
prefix = "11"
|
||||
op = 10
|
||||
fmt = "RR"
|
||||
func = "float"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "ACOS"
|
||||
prefix = "11"
|
||||
op = 11
|
||||
fmt = "RR"
|
||||
func = "float"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "ATAN"
|
||||
prefix = "11"
|
||||
op = 12
|
||||
fmt = "RR"
|
||||
func = "float"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "LN"
|
||||
prefix = "11"
|
||||
op = 13
|
||||
fmt = "RR"
|
||||
func = "float"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "LOG"
|
||||
prefix = "11"
|
||||
op = 14
|
||||
fmt = "RR"
|
||||
func = "float"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "EXP"
|
||||
prefix = "11"
|
||||
op = 15
|
||||
fmt = "RR"
|
||||
func = "float"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "ROUND"
|
||||
prefix = "11"
|
||||
op = 16
|
||||
fmt = "RR"
|
||||
func = "float"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "TRUNC"
|
||||
prefix = "11"
|
||||
op = 17
|
||||
fmt = "RR"
|
||||
func = "float"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "FLOOR"
|
||||
prefix = "11"
|
||||
op = 18
|
||||
fmt = "RR"
|
||||
func = "float"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CEIL"
|
||||
prefix = "11"
|
||||
op = 19
|
||||
fmt = "RR"
|
||||
func = "float"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CONV"
|
||||
prefix = "11"
|
||||
op = 20
|
||||
fmt = "RR"
|
||||
func = "dst"
|
||||
params = ["rd", "rs"]
|
||||
enabled = true
|
||||
|
||||
# ---- 4B NONE(前缀 10,op 6 位)
|
||||
[[op]]
|
||||
name = "NOP"
|
||||
prefix = "10"
|
||||
op = 0
|
||||
fmt = "NONE"
|
||||
func = "none"
|
||||
params = []
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "RET"
|
||||
prefix = "10"
|
||||
op = 1
|
||||
fmt = "NONE"
|
||||
func = "none"
|
||||
params = []
|
||||
enabled = true
|
||||
|
||||
# ---- 8B RRR(前缀 011,op 5 位)
|
||||
[[op]]
|
||||
name = "ADD"
|
||||
prefix = "011"
|
||||
op = 0
|
||||
fmt = "RRR"
|
||||
func = "full"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "SUB"
|
||||
prefix = "011"
|
||||
op = 1
|
||||
fmt = "RRR"
|
||||
func = "full"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "MUL"
|
||||
prefix = "011"
|
||||
op = 2
|
||||
fmt = "RRR"
|
||||
func = "full"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "DIV"
|
||||
prefix = "011"
|
||||
op = 3
|
||||
fmt = "RRR"
|
||||
func = "full"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "MOD"
|
||||
prefix = "011"
|
||||
op = 4
|
||||
fmt = "RRR"
|
||||
func = "full"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "EXPT"
|
||||
prefix = "011"
|
||||
op = 5
|
||||
fmt = "RRR"
|
||||
func = "full"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "AND"
|
||||
prefix = "011"
|
||||
op = 6
|
||||
fmt = "RRR"
|
||||
func = "uint"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "OR"
|
||||
prefix = "011"
|
||||
op = 7
|
||||
fmt = "RRR"
|
||||
func = "uint"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "XOR"
|
||||
prefix = "011"
|
||||
op = 8
|
||||
fmt = "RRR"
|
||||
func = "uint"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "NAND"
|
||||
prefix = "011"
|
||||
op = 9
|
||||
fmt = "RRR"
|
||||
func = "uint"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "NOR"
|
||||
prefix = "011"
|
||||
op = 10
|
||||
fmt = "RRR"
|
||||
func = "uint"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "XNOR"
|
||||
prefix = "011"
|
||||
op = 11
|
||||
fmt = "RRR"
|
||||
func = "uint"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "SHL"
|
||||
prefix = "011"
|
||||
op = 12
|
||||
fmt = "RRR"
|
||||
func = "uint"
|
||||
params = ["rd", "rs", "n"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "SHR"
|
||||
prefix = "011"
|
||||
op = 13
|
||||
fmt = "RRR"
|
||||
func = "full"
|
||||
params = ["rd", "rs", "n"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "ROL"
|
||||
prefix = "011"
|
||||
op = 14
|
||||
fmt = "RRR"
|
||||
func = "uint"
|
||||
params = ["rd", "rs", "n"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "ROR"
|
||||
prefix = "011"
|
||||
op = 15
|
||||
fmt = "RRR"
|
||||
func = "uint"
|
||||
params = ["rd", "rs", "n"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CMP_EQ"
|
||||
prefix = "011"
|
||||
op = 16
|
||||
fmt = "RRR"
|
||||
func = "full"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CMP_NE"
|
||||
prefix = "011"
|
||||
op = 17
|
||||
fmt = "RRR"
|
||||
func = "full"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CMP_LT"
|
||||
prefix = "011"
|
||||
op = 18
|
||||
fmt = "RRR"
|
||||
func = "full"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CMP_LE"
|
||||
prefix = "011"
|
||||
op = 19
|
||||
fmt = "RRR"
|
||||
func = "full"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CMP_GT"
|
||||
prefix = "011"
|
||||
op = 20
|
||||
fmt = "RRR"
|
||||
func = "full"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CMP_GE"
|
||||
prefix = "011"
|
||||
op = 21
|
||||
fmt = "RRR"
|
||||
func = "full"
|
||||
params = ["rd", "ra", "rb"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "GETBIT"
|
||||
prefix = "011"
|
||||
op = 22
|
||||
fmt = "RRR"
|
||||
func = "none"
|
||||
params = ["rd", "rs", "bit"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "SETBIT"
|
||||
prefix = "011"
|
||||
op = 23
|
||||
fmt = "RRR"
|
||||
func = "none"
|
||||
params = ["rd", "rs", "bit"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CLRBIT"
|
||||
prefix = "011"
|
||||
op = 24
|
||||
fmt = "RRR"
|
||||
func = "none"
|
||||
params = ["rd", "rs", "bit"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "TOGBIT"
|
||||
prefix = "011"
|
||||
op = 25
|
||||
fmt = "RRR"
|
||||
func = "none"
|
||||
params = ["rd", "rs", "bit"]
|
||||
enabled = true
|
||||
|
||||
# ---- 8B 010 区(形态位 3 位 + 子 op 2 位)
|
||||
[[op]]
|
||||
name = "LOADK"
|
||||
prefix = "010000"
|
||||
op = 0
|
||||
fmt = "IMM"
|
||||
func = "full"
|
||||
params = ["rd", "const_id"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "LOAD"
|
||||
prefix = "010001"
|
||||
op = 0
|
||||
fmt = "SLOT"
|
||||
func = "full"
|
||||
params = ["rd", "slot"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "STORE"
|
||||
prefix = "010001"
|
||||
op = 1
|
||||
fmt = "SLOT"
|
||||
func = "full"
|
||||
params = ["slot", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "JMP"
|
||||
prefix = "010010"
|
||||
op = 0
|
||||
fmt = "JMP"
|
||||
func = "none"
|
||||
params = ["off"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "JT"
|
||||
prefix = "010011"
|
||||
op = 0
|
||||
fmt = "JC"
|
||||
func = "none"
|
||||
params = ["rd", "off"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "JF"
|
||||
prefix = "010011"
|
||||
op = 1
|
||||
fmt = "JC"
|
||||
func = "none"
|
||||
params = ["rd", "off"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CALL"
|
||||
prefix = "010100"
|
||||
op = 0
|
||||
fmt = "CALL"
|
||||
func = "none"
|
||||
params = ["fn_id"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "CAL"
|
||||
prefix = "010101"
|
||||
op = 0
|
||||
fmt = "CAL"
|
||||
func = "none"
|
||||
params = ["fb_id", "slot"]
|
||||
enabled = true
|
||||
|
||||
# ---- 16B SLOT+off(前缀 0010,op 4 位)
|
||||
[[op]]
|
||||
name = "LOAD_OFF"
|
||||
prefix = "0010"
|
||||
op = 0
|
||||
fmt = "SLOT+off"
|
||||
func = "full"
|
||||
params = ["rd", "slot", "off"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "STORE_OFF"
|
||||
prefix = "0010"
|
||||
op = 1
|
||||
fmt = "SLOT+off"
|
||||
func = "full"
|
||||
params = ["slot", "off", "rs"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "LOAD_PTR"
|
||||
prefix = "0010"
|
||||
op = 8
|
||||
fmt = "RS+off"
|
||||
func = "full"
|
||||
params = ["rd", "rs", "off"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "STORE_PTR"
|
||||
prefix = "0010"
|
||||
op = 9
|
||||
fmt = "RS+off"
|
||||
func = "full"
|
||||
params = ["rd", "rs", "off"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "STR_LEN"
|
||||
prefix = "0010"
|
||||
op = 2
|
||||
fmt = "SLOT+off"
|
||||
func = "none"
|
||||
params = ["rd", "slot", "off"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "STR_CMP"
|
||||
prefix = "0010"
|
||||
op = 3
|
||||
fmt = "SLOT+off"
|
||||
func = "none"
|
||||
params = ["rd", "slot", "off"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "STR_EQ"
|
||||
prefix = "0010"
|
||||
op = 4
|
||||
fmt = "SLOT+off"
|
||||
func = "none"
|
||||
params = ["rd", "slot", "off"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "STR_MID"
|
||||
prefix = "0010"
|
||||
op = 5
|
||||
fmt = "SLOT+off"
|
||||
func = "none"
|
||||
params = ["rd", "slot", "off"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "STR_LEFT"
|
||||
prefix = "0010"
|
||||
op = 6
|
||||
fmt = "SLOT+off"
|
||||
func = "none"
|
||||
params = ["rd", "slot", "off"]
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "STR_RIGHT"
|
||||
prefix = "0010"
|
||||
op = 7
|
||||
fmt = "SLOT+off"
|
||||
func = "none"
|
||||
params = ["rd", "slot", "off"]
|
||||
enabled = true
|
||||
|
||||
# ---- 16B IMM64(前缀 0011,op 4 位;预留不发射)
|
||||
[[op]]
|
||||
name = "LOADK64"
|
||||
prefix = "0011"
|
||||
op = 0
|
||||
fmt = "IMM64"
|
||||
func = "full"
|
||||
params = ["rd", "imm64"]
|
||||
enabled = true
|
||||
|
||||
# ---- 32B 预留(前缀 0001,op 4 位;形态待定,预留不发射)
|
||||
[[op]]
|
||||
name = "JTBL"
|
||||
prefix = "0001"
|
||||
op = 0
|
||||
fmt = "预留"
|
||||
func = "none"
|
||||
params = []
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "STR_CONCAT"
|
||||
prefix = "0001"
|
||||
op = 1
|
||||
fmt = "预留"
|
||||
func = "none"
|
||||
params = []
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "STR_FIND"
|
||||
prefix = "0001"
|
||||
op = 2
|
||||
fmt = "预留"
|
||||
func = "none"
|
||||
params = []
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "STR_REPLACE"
|
||||
prefix = "0001"
|
||||
op = 3
|
||||
fmt = "预留"
|
||||
func = "none"
|
||||
params = []
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "ARR_LOAD"
|
||||
prefix = "0001"
|
||||
op = 4
|
||||
fmt = "预留"
|
||||
func = "none"
|
||||
params = []
|
||||
enabled = true
|
||||
|
||||
[[op]]
|
||||
name = "ARR_STORE"
|
||||
prefix = "0001"
|
||||
op = 5
|
||||
fmt = "预留"
|
||||
func = "none"
|
||||
params = []
|
||||
enabled = true
|
||||
|
||||
# ---- 内建 FB(14 条;fb_id 唯一 0..14;字段类型命中 type 表;
|
||||
# 布局偏移编译期按 1/2/4/8 对齐计算,见指令集清单 §8.1)
|
||||
[[fb]]
|
||||
name = "TON"
|
||||
fb_id = 0
|
||||
fields = [["in", "BOOL"], ["pt", "TIME"], ["q", "BOOL"], ["et", "TIME"]]
|
||||
|
||||
[[fb]]
|
||||
name = "TOF"
|
||||
fb_id = 1
|
||||
fields = [["in", "BOOL"], ["pt", "TIME"], ["q", "BOOL"], ["et", "TIME"]]
|
||||
|
||||
[[fb]]
|
||||
name = "TP"
|
||||
fb_id = 2
|
||||
fields = [["in", "BOOL"], ["pt", "TIME"], ["q", "BOOL"], ["et", "TIME"]]
|
||||
|
||||
[[fb]]
|
||||
name = "CTU"
|
||||
fb_id = 3
|
||||
fields = [["cu", "BOOL"], ["r", "BOOL"], ["pv", "INT"], ["q", "BOOL"], ["cv", "INT"]]
|
||||
|
||||
[[fb]]
|
||||
name = "DCTU"
|
||||
fb_id = 4
|
||||
fields = [["cu", "BOOL"], ["r", "BOOL"], ["pv", "DINT"], ["q", "BOOL"], ["cv", "DINT"]]
|
||||
|
||||
[[fb]]
|
||||
name = "CTD"
|
||||
fb_id = 5
|
||||
fields = [["cd", "BOOL"], ["ld", "BOOL"], ["pv", "INT"], ["q", "BOOL"], ["cv", "INT"]]
|
||||
|
||||
[[fb]]
|
||||
name = "DCTD"
|
||||
fb_id = 6
|
||||
fields = [["cd", "BOOL"], ["ld", "BOOL"], ["pv", "DINT"], ["q", "BOOL"], ["cv", "DINT"]]
|
||||
|
||||
[[fb]]
|
||||
name = "CTUD"
|
||||
fb_id = 7
|
||||
fields = [["cu", "BOOL"], ["cd", "BOOL"], ["r", "BOOL"], ["lu", "BOOL"],
|
||||
["pv", "INT"], ["qu", "BOOL"], ["qd", "BOOL"], ["cv", "INT"]]
|
||||
|
||||
[[fb]]
|
||||
name = "DCTUD"
|
||||
fb_id = 8
|
||||
fields = [["cu", "BOOL"], ["cd", "BOOL"], ["r", "BOOL"], ["lu", "BOOL"],
|
||||
["pv", "DINT"], ["qu", "BOOL"], ["qd", "BOOL"], ["cv", "DINT"]]
|
||||
|
||||
[[fb]]
|
||||
name = "R_TRIG"
|
||||
fb_id = 9
|
||||
fields = [["clk", "BOOL"], ["q", "BOOL"]]
|
||||
|
||||
[[fb]]
|
||||
name = "F_TRIG"
|
||||
fb_id = 10
|
||||
fields = [["clk", "BOOL"], ["q", "BOOL"]]
|
||||
|
||||
[[fb]]
|
||||
name = "SR"
|
||||
fb_id = 11
|
||||
fields = [["S1", "BOOL"], ["R", "BOOL"], ["Q1", "BOOL"]]
|
||||
|
||||
[[fb]]
|
||||
name = "RS"
|
||||
fb_id = 12
|
||||
fields = [["SET", "BOOL"], ["RESET1", "BOOL"], ["Q1", "BOOL"]]
|
||||
|
||||
[[fb]]
|
||||
name = "PWM"
|
||||
fb_id = 13
|
||||
fields = [["in", "BOOL"], ["pt", "TIME"], ["duty", "REAL"], ["q", "BOOL"], ["et", "TIME"]]
|
||||
|
||||
[[fb]]
|
||||
name = "RTC"
|
||||
fb_id = 14
|
||||
fields = [["enable", "BOOL"], ["date", "DATE"], ["tod", "TOD"]]
|
||||
@@ -0,0 +1,350 @@
|
||||
/**
|
||||
* @file Codec.cpp
|
||||
* @brief 指令字编解码 + 配置驱动反汇编(编译器侧,V2)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 变长指令字(前缀分区)的位号读写与 disasm;位号见 Doc/isa/指令编码.md:
|
||||
* - 字节 0 = [长度前缀+形态分区 | op];字节 1 = [func:4 | 空:4]
|
||||
* - 操作数区 bit16 起(rd=23..16、imm32/slot32=55..24、off32/fn_id32=47..16 等)
|
||||
*/
|
||||
|
||||
#include "compiler/Codec.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace compiler {
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief func → 类型后缀(disasm 用,A7 定案)。
|
||||
* @param func func 值(0..9)
|
||||
* @return 后缀串("U8"/"I8"/.../"F64");越界返回 "?"
|
||||
*/
|
||||
const char* type_suffix(uint8_t func) {
|
||||
static const char* kSuffix[16] = {
|
||||
"U8", "I8", "U16", "I16", "U32", "I32", "U64", "I64",
|
||||
"F32", "F64", "?", "?", "?", "?", "?", "?",
|
||||
};
|
||||
return kSuffix[func & 0x0F];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 形态前缀是否带类型后缀(disasm 显示 .TYPE)。
|
||||
* @param mode func 类别(none/width/uint/full/float/dst)
|
||||
* @return full/float/dst → true(显示类型后缀)
|
||||
*/
|
||||
bool shows_type_suffix(const std::string& mode) {
|
||||
return mode == "full" || mode == "float" || mode == "dst";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief 前缀定长:读字节 0 判指令长度。
|
||||
* @param code 指令字节(至少 1 字节)
|
||||
* @return 4/8/16/32(前缀 `1`/`01`/`001`/`0001`)
|
||||
*/
|
||||
size_t instr_len(const uint8_t* code) {
|
||||
const uint8_t b = code[0];
|
||||
if (b & 0x80) return 4; // 1xxxxxxx → 4B
|
||||
if ((b & 0xC0) == 0x40) return 8; // 01xxxxxx → 8B
|
||||
if ((b & 0xE0) == 0x20) return 16; // 001xxxxx → 16B
|
||||
return 32; // 0001xxxx → 32B
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 推导形态前缀字符串。
|
||||
* @param code 指令字节
|
||||
* @return "11"/"10"/"011"/"010000".."010101"/"0010"/"0011"/"0001"
|
||||
*/
|
||||
std::string prefix_of(const uint8_t* code) {
|
||||
const uint8_t b = code[0];
|
||||
if (b & 0x80) {
|
||||
return (b & 0x40) ? "11" : "10"; // 4B:bit6 区分 11/10
|
||||
}
|
||||
if ((b & 0xC0) == 0x40) {
|
||||
if (b & 0x20) {
|
||||
return "011"; // 8B RRR
|
||||
}
|
||||
static const char* kShapes[8] = {
|
||||
"010000", "010001", "010010", "010011", "010100", "010101", "010110", "010111",
|
||||
};
|
||||
return kShapes[(b >> 2) & 0x07]; // 8B 010:形态位 3 位
|
||||
}
|
||||
if ((b & 0xE0) == 0x20) {
|
||||
return (b & 0x10) ? "0011" : "0010"; // 16B
|
||||
}
|
||||
return "0001"; // 32B
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取分区内操作码。
|
||||
* @param code 指令字节
|
||||
* @return op(4B 6 位 / 8B 5 位或 2 位 / 16B·32B 4 位)
|
||||
*/
|
||||
uint8_t op_of(const uint8_t* code) {
|
||||
const uint8_t b = code[0];
|
||||
if (b & 0x80) return b & 0x3F; // 4B:6 位
|
||||
if ((b & 0xC0) == 0x40) {
|
||||
return (b & 0x20) ? (b & 0x1F) : (b & 0x03); // 8B:011 → 5 位;010 → 2 位
|
||||
}
|
||||
return b & 0x0F; // 16B/32B:4 位
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取 func 位段(字节 1 高 4 位)。
|
||||
* @param code 指令字节
|
||||
* @return 0..15(0..9 有效)
|
||||
*/
|
||||
uint8_t func_of(const uint8_t* code) {
|
||||
return static_cast<uint8_t>((code[1] >> 4) & 0x0F);
|
||||
}
|
||||
|
||||
uint8_t rd_of(const uint8_t* code) { return code[2]; }
|
||||
uint8_t rs1_of(const uint8_t* code) { return code[3]; }
|
||||
uint8_t rs2_of(const uint8_t* code) { return code[4]; }
|
||||
|
||||
/**
|
||||
* @brief 取 imm32(55..24,小端 4 字节)。
|
||||
* @param code 指令字节
|
||||
* @return 32 位立即数
|
||||
*/
|
||||
uint32_t imm32_of(const uint8_t* code) {
|
||||
return static_cast<uint32_t>(code[3])
|
||||
| (static_cast<uint32_t>(code[4]) << 8)
|
||||
| (static_cast<uint32_t>(code[5]) << 16)
|
||||
| (static_cast<uint32_t>(code[6]) << 24);
|
||||
}
|
||||
|
||||
uint32_t slot32_of(const uint8_t* code) { return imm32_of(code); }
|
||||
|
||||
/**
|
||||
* @brief 取 off32(47..16,JMP 形态,小端 4 字节,有符号)。
|
||||
* @param code 指令字节
|
||||
* @return 字节偏移(相对下一条)
|
||||
*/
|
||||
int32_t off32_of(const uint8_t* code) {
|
||||
return static_cast<int32_t>(static_cast<uint32_t>(code[2])
|
||||
| (static_cast<uint32_t>(code[3]) << 8)
|
||||
| (static_cast<uint32_t>(code[4]) << 16)
|
||||
| (static_cast<uint32_t>(code[5]) << 24));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取 off32(55..24,JC 形态,小端 4 字节,有符号)。
|
||||
* @param code 指令字节
|
||||
* @return 字节偏移(相对下一条)
|
||||
*/
|
||||
int32_t off32_jc_of(const uint8_t* code) {
|
||||
return static_cast<int32_t>(static_cast<uint32_t>(code[3])
|
||||
| (static_cast<uint32_t>(code[4]) << 8)
|
||||
| (static_cast<uint32_t>(code[5]) << 16)
|
||||
| (static_cast<uint32_t>(code[6]) << 24));
|
||||
}
|
||||
|
||||
uint32_t fn_id32_of(const uint8_t* code) {
|
||||
return static_cast<uint32_t>(code[2])
|
||||
| (static_cast<uint32_t>(code[3]) << 8)
|
||||
| (static_cast<uint32_t>(code[4]) << 16)
|
||||
| (static_cast<uint32_t>(code[5]) << 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取 fb_id16(31..16)。
|
||||
* @param code 指令字节
|
||||
* @return FB 类型 id
|
||||
*/
|
||||
uint16_t fb_id16_of(const uint8_t* code) {
|
||||
return static_cast<uint16_t>(code[2] | (static_cast<uint16_t>(code[3]) << 8));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取 CAL 的实例槽号(63..32)。
|
||||
* @param code 指令字节
|
||||
* @return 槽号
|
||||
*/
|
||||
uint32_t cal_slot32_of(const uint8_t* code) {
|
||||
return static_cast<uint32_t>(code[4])
|
||||
| (static_cast<uint32_t>(code[5]) << 8)
|
||||
| (static_cast<uint32_t>(code[6]) << 16)
|
||||
| (static_cast<uint32_t>(code[7]) << 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取 16B 形态的 off32(87..56)。
|
||||
* @param code 指令字节
|
||||
* @return 字段偏移(字节)
|
||||
*/
|
||||
int32_t off32_hi_of(const uint8_t* code) {
|
||||
return static_cast<int32_t>(static_cast<uint32_t>(code[7])
|
||||
| (static_cast<uint32_t>(code[8]) << 8)
|
||||
| (static_cast<uint32_t>(code[9]) << 16)
|
||||
| (static_cast<uint32_t>(code[10]) << 24));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取 imm64(87..24)。
|
||||
* @param code 指令字节
|
||||
* @return 64 位立即数
|
||||
*/
|
||||
uint64_t imm64_of(const uint8_t* code) {
|
||||
uint64_t v = 0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
v |= static_cast<uint64_t>(code[3 + i]) << (8 * i);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 写字节 0(前缀 + 操作码)。
|
||||
* @param out 指令缓冲(≥16B)
|
||||
* @param prefix 形态前缀
|
||||
* @param op 分区内编号
|
||||
*/
|
||||
void set_byte0(uint8_t* out, const std::string& prefix, uint32_t op) {
|
||||
if (prefix == "11") {
|
||||
out[0] = static_cast<uint8_t>(0xC0 | (op & 0x3F)); // bit7..6 = 11
|
||||
} else if (prefix == "10") {
|
||||
out[0] = static_cast<uint8_t>(0x80 | (op & 0x3F)); // bit7..6 = 10
|
||||
} else if (prefix == "011") {
|
||||
out[0] = static_cast<uint8_t>(0x60 | (op & 0x1F)); // bit7..5 = 011
|
||||
} else if (prefix.size() == 6 && prefix.compare(0, 3, "010") == 0) {
|
||||
const uint32_t shape = static_cast<uint32_t>(prefix[3] - '0') * 4 +
|
||||
static_cast<uint32_t>(prefix[4] - '0') * 2 +
|
||||
static_cast<uint32_t>(prefix[5] - '0');
|
||||
out[0] = static_cast<uint8_t>(0x40 | (shape << 2) | (op & 0x03));
|
||||
} else if (prefix == "0010") {
|
||||
out[0] = static_cast<uint8_t>(0x20 | (op & 0x0F));
|
||||
} else if (prefix == "0011") {
|
||||
out[0] = static_cast<uint8_t>(0x30 | (op & 0x0F));
|
||||
} else { // "0001"
|
||||
out[0] = static_cast<uint8_t>(0x10 | (op & 0x0F));
|
||||
}
|
||||
}
|
||||
|
||||
void set_func(uint8_t* out, uint8_t func) {
|
||||
out[1] = static_cast<uint8_t>((out[1] & 0x0F) | ((func & 0x0F) << 4));
|
||||
}
|
||||
void set_rd(uint8_t* out, uint8_t rd) { out[2] = rd; }
|
||||
void set_rs1(uint8_t* out, uint8_t rs1) { out[3] = rs1; }
|
||||
void set_rs2(uint8_t* out, uint8_t rs2) { out[4] = rs2; }
|
||||
void set_imm32(uint8_t* out, uint32_t v) {
|
||||
out[3] = static_cast<uint8_t>(v & 0xFF);
|
||||
out[4] = static_cast<uint8_t>((v >> 8) & 0xFF);
|
||||
out[5] = static_cast<uint8_t>((v >> 16) & 0xFF);
|
||||
out[6] = static_cast<uint8_t>((v >> 24) & 0xFF);
|
||||
}
|
||||
void set_slot32(uint8_t* out, uint32_t v) { set_imm32(out, v); }
|
||||
void set_off32(uint8_t* out, int32_t v) {
|
||||
const uint32_t u = static_cast<uint32_t>(v);
|
||||
out[2] = static_cast<uint8_t>(u & 0xFF);
|
||||
out[3] = static_cast<uint8_t>((u >> 8) & 0xFF);
|
||||
out[4] = static_cast<uint8_t>((u >> 16) & 0xFF);
|
||||
out[5] = static_cast<uint8_t>((u >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
void set_off32_jc(uint8_t* out, int32_t v) {
|
||||
const uint32_t u = static_cast<uint32_t>(v);
|
||||
out[3] = static_cast<uint8_t>(u & 0xFF);
|
||||
out[4] = static_cast<uint8_t>((u >> 8) & 0xFF);
|
||||
out[5] = static_cast<uint8_t>((u >> 16) & 0xFF);
|
||||
out[6] = static_cast<uint8_t>((u >> 24) & 0xFF);
|
||||
}
|
||||
void set_fn_id32(uint8_t* out, uint32_t v) { set_off32(out, static_cast<int32_t>(v)); }
|
||||
void set_fb_id16(uint8_t* out, uint16_t v) {
|
||||
out[2] = static_cast<uint8_t>(v & 0xFF);
|
||||
out[3] = static_cast<uint8_t>((v >> 8) & 0xFF);
|
||||
}
|
||||
void set_cal_slot32(uint8_t* out, uint32_t v) {
|
||||
out[4] = static_cast<uint8_t>(v & 0xFF);
|
||||
out[5] = static_cast<uint8_t>((v >> 8) & 0xFF);
|
||||
out[6] = static_cast<uint8_t>((v >> 16) & 0xFF);
|
||||
out[7] = static_cast<uint8_t>((v >> 24) & 0xFF);
|
||||
}
|
||||
void set_off32_hi(uint8_t* out, int32_t v) {
|
||||
const uint32_t u = static_cast<uint32_t>(v);
|
||||
out[7] = static_cast<uint8_t>(u & 0xFF);
|
||||
out[8] = static_cast<uint8_t>((u >> 8) & 0xFF);
|
||||
out[9] = static_cast<uint8_t>((u >> 16) & 0xFF);
|
||||
out[10] = static_cast<uint8_t>((u >> 24) & 0xFF);
|
||||
}
|
||||
void set_imm64(uint8_t* out, uint64_t v) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
out[3 + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 配置驱动反汇编:按 machine.toml 的 prefix/op/func/fmt 输出一行文本。
|
||||
* @param cfg 机器配置
|
||||
* @param code 指令字节
|
||||
* @param len 指令长度
|
||||
* @param out 输出缓冲
|
||||
* @param cap 缓冲容量(含 '\0');cap==0 时直接返回、不写 out
|
||||
*/
|
||||
void disasm(const MachineConfig& cfg, const uint8_t* code, size_t len,
|
||||
char* out, size_t cap) {
|
||||
if (cap == 0) {
|
||||
return;
|
||||
}
|
||||
out[0] = '\0';
|
||||
const std::string prefix = prefix_of(code);
|
||||
const ConfigOp* op = cfg.find_op_by_key(prefix, op_of(code));
|
||||
if (op == nullptr) {
|
||||
snprintf(out, cap, "??? 0x");
|
||||
size_t used = 4;
|
||||
for (size_t i = 0; i < len && used + 2 < cap; ++i) {
|
||||
snprintf(out + used, cap - used, "%02x", static_cast<unsigned>(code[i]));
|
||||
used += 2;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const uint8_t func = func_of(code);
|
||||
const std::string suffix =
|
||||
shows_type_suffix(op->func_mode) ? std::string(".") + type_suffix(func) : "";
|
||||
const std::string& f = op->fmt;
|
||||
if (f == "RR") {
|
||||
snprintf(out, cap, "%s%s r%u, r%u", op->name.c_str(), suffix.c_str(),
|
||||
static_cast<unsigned>(rd_of(code)), static_cast<unsigned>(rs1_of(code)));
|
||||
} else if (f == "RRR") {
|
||||
snprintf(out, cap, "%s%s r%u, r%u, r%u", op->name.c_str(), suffix.c_str(),
|
||||
static_cast<unsigned>(rd_of(code)), static_cast<unsigned>(rs1_of(code)),
|
||||
static_cast<unsigned>(rs2_of(code)));
|
||||
} else if (f == "IMM") {
|
||||
snprintf(out, cap, "%s%s r%u, %u", op->name.c_str(), suffix.c_str(),
|
||||
static_cast<unsigned>(rd_of(code)), static_cast<unsigned>(imm32_of(code)));
|
||||
} else if (f == "SLOT") {
|
||||
snprintf(out, cap, "%s%s r%u, s%u", op->name.c_str(), suffix.c_str(),
|
||||
static_cast<unsigned>(rd_of(code)), static_cast<unsigned>(slot32_of(code)));
|
||||
} else if (f == "JMP") {
|
||||
snprintf(out, cap, "%s %+d", op->name.c_str(), static_cast<int>(off32_of(code)));
|
||||
} else if (f == "JC") {
|
||||
snprintf(out, cap, "%s r%u, %+d", op->name.c_str(),
|
||||
static_cast<unsigned>(rd_of(code)), static_cast<int>(off32_jc_of(code)));
|
||||
} else if (f == "CALL") {
|
||||
snprintf(out, cap, "%s %u", op->name.c_str(), static_cast<unsigned>(fn_id32_of(code)));
|
||||
} else if (f == "CAL") {
|
||||
const ConfigFb* fb = cfg.find_fb_by_id(fb_id16_of(code));
|
||||
if (fb != nullptr) {
|
||||
snprintf(out, cap, "CAL.%s s%u", fb->name.c_str(),
|
||||
static_cast<unsigned>(cal_slot32_of(code)));
|
||||
} else {
|
||||
snprintf(out, cap, "CAL ?%u s%u", static_cast<unsigned>(fb_id16_of(code)),
|
||||
static_cast<unsigned>(cal_slot32_of(code)));
|
||||
}
|
||||
} else if (f == "SLOT+off") {
|
||||
snprintf(out, cap, "%s%s r%u, s%u, %d", op->name.c_str(), suffix.c_str(),
|
||||
static_cast<unsigned>(rd_of(code)), static_cast<unsigned>(slot32_of(code)),
|
||||
static_cast<int>(off32_hi_of(code)));
|
||||
} else if (f == "RS+off") {
|
||||
snprintf(out, cap, "%s%s r%u, r%u, %d", op->name.c_str(), suffix.c_str(),
|
||||
static_cast<unsigned>(rd_of(code)), static_cast<unsigned>(rs1_of(code)),
|
||||
static_cast<int>(off32_hi_of(code)));
|
||||
} else { // NONE / 预留
|
||||
snprintf(out, cap, "%s", op->name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
File diff suppressed because it is too large
Load Diff
+719
-2
@@ -1,8 +1,725 @@
|
||||
/**
|
||||
* @file Lexer.cpp
|
||||
* @brief 词法分析(12.4 实现)
|
||||
* @date 2026-08-19
|
||||
* @brief ST 子集词法分析
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 设计说明(详见 Doc/compiler/词法.md):
|
||||
* - 主循环:跳过空白/注释 → 文件末尾推 END → 否则 lex_one 识别一个 token
|
||||
* - 大小写:关键字匹配与标识符存储统一折小写(与 IEC 一致)
|
||||
* - 行列:line/col 从 1 起;token 记录【起始】位置(消费会推进行列,
|
||||
* 故 lex_one 入口先捕获 tok_line_/tok_col_)
|
||||
* - 错误:稳定前缀 "lex error" + 文件 + 行列,供测试按类别匹配
|
||||
*
|
||||
* 函数清单:
|
||||
* - time_unit_ms TIME 字面量单位 → 毫秒倍数(未知返回 -1)
|
||||
* - Lexer::Lexer (构造)存源文本/文件名/输出指针,行列初始 (1,1)
|
||||
* - Lexer::run 主循环:跳空白注释 → 文件尾推 END → lex_one
|
||||
* - Lexer::fail 组装 "lex error: <msg> (file, line, col)" 返回 false
|
||||
* - Lexer::cur 当前字符(不消费)
|
||||
* - Lexer::at_end 是否已到源文末尾
|
||||
* - Lexer::advance 消费一个字符并推进行列(\n 行+1 列复位)
|
||||
* - Lexer::skip_space_and_comments 跳过空白与 (* *) 注释(不嵌套,未闭合报错)
|
||||
* - Lexer::push 用 token 起始位置产出 token 入流
|
||||
* - Lexer::lex_one 识别一个 token(T# → 标识符/关键字 → 整数 → 符号 → 报错)
|
||||
* - Lexer::lex_time 解析 T#<int><单位>... 段式 TIME 字面量,累加毫秒
|
||||
* - lex 对外入口:清空输出 → 构造 Lexer → run
|
||||
* - lex_file 读文件后调 lex;打不开算 lex error
|
||||
*/
|
||||
|
||||
#include "compiler/Lexer.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
namespace compiler {
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief 关键字表:小写键 → Tok。
|
||||
* @details 与 Doc/compiler/词法.md 的冻结表一致(12.5 修订补入
|
||||
* then/do);数值即 token 类型,只追加不删改。
|
||||
*/
|
||||
const struct {
|
||||
const char* key;
|
||||
Tok tok;
|
||||
} kKeywords[] = {
|
||||
// POU 外壳
|
||||
{"program", Tok::PROGRAM},
|
||||
{"function", Tok::FUNCTION},
|
||||
{"function_block", Tok::FUNCTION_BLOCK},
|
||||
{"end_program", Tok::END_PROGRAM},
|
||||
{"end_function", Tok::END_FUNCTION},
|
||||
{"end_function_block", Tok::END_FUNCTION_BLOCK},
|
||||
// 变量段
|
||||
{"var", Tok::VAR},
|
||||
{"var_input", Tok::VAR_INPUT},
|
||||
{"var_output", Tok::VAR_OUTPUT},
|
||||
{"var_global", Tok::VAR_GLOBAL},
|
||||
{"var_external", Tok::VAR_EXTERNAL},
|
||||
{"var_temp", Tok::VAR_TEMP},
|
||||
{"end_var", Tok::END_VAR},
|
||||
// 类型(V2:19 种 IEC 类型)
|
||||
{"bool", Tok::BOOL},
|
||||
{"byte", Tok::BYTE},
|
||||
{"word", Tok::WORD},
|
||||
{"dword", Tok::DWORD},
|
||||
{"lword", Tok::LWORD},
|
||||
{"sint", Tok::SINT},
|
||||
{"int", Tok::INT},
|
||||
{"dint", Tok::DINT},
|
||||
{"lint", Tok::LINT},
|
||||
{"usint", Tok::USINT},
|
||||
{"uint", Tok::UINT},
|
||||
{"udint", Tok::UDINT},
|
||||
{"ulint", Tok::ULINT},
|
||||
{"time", Tok::TIME},
|
||||
{"real", Tok::REAL},
|
||||
{"lreal", Tok::LREAL},
|
||||
{"date", Tok::DATE},
|
||||
{"tod", Tok::TOD},
|
||||
{"dt", Tok::DT},
|
||||
// 控制
|
||||
{"if", Tok::IF},
|
||||
{"elsif", Tok::ELSIF},
|
||||
{"else", Tok::ELSE},
|
||||
{"end_if", Tok::END_IF},
|
||||
{"while", Tok::WHILE},
|
||||
{"end_while", Tok::END_WHILE},
|
||||
{"then", Tok::THEN}, // 12.5 修订:IF…THEN 语法需要
|
||||
{"do", Tok::DO}, // 12.5 修订:WHILE…DO 语法需要
|
||||
// 逻辑
|
||||
{"and", Tok::AND},
|
||||
{"or", Tok::OR},
|
||||
{"not", Tok::NOT},
|
||||
// 字面量
|
||||
{"true", Tok::TRUE},
|
||||
{"false", Tok::FALSE},
|
||||
// FB 类型名不再设关键字(V2:IDENT + Typecheck 查 machine.toml [[fb]] 表)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief TIME 字面量单位换算为毫秒倍数
|
||||
* @param unit 单位字符串(小写):ms / s / m / h / d
|
||||
* @return 对应毫秒倍数;未知单位返回 -1
|
||||
*/
|
||||
int64_t time_unit_ms(const std::string& unit) {
|
||||
if (unit == "ms") return 1;
|
||||
if (unit == "s") return 1000;
|
||||
if (unit == "m") return 60000;
|
||||
if (unit == "h") return 3600000;
|
||||
if (unit == "d") return 86400000;
|
||||
return -1; // 未知单位
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ST 单遍扫描器
|
||||
*
|
||||
* @details pos_ 指向当前字符,line_/col_ 是它所在的 1 起行列。
|
||||
* 所有成员函数不抛异常,出错即返回 false 并写 err_。
|
||||
*/
|
||||
class Lexer {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造扫描器
|
||||
* @param source_file 源文件名(报错与 token 的 source_file 用)
|
||||
* @param content 源文本(引用外部存储,不拷贝)
|
||||
* @param out token 流输出(run 前应为空)
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
*/
|
||||
Lexer(const std::string& source_file, const std::string& content,
|
||||
std::vector<Token>* out, std::string* err)
|
||||
: src_(content), sf_(source_file), out_(out), err_(err),
|
||||
pos_(0), line_(1), col_(1) {}
|
||||
|
||||
/**
|
||||
* @brief 主循环:跳空白/注释 → 文件尾推 END → lex_one
|
||||
* @return true 成功(out 以 END 收尾);false 词法错误(err 已写)
|
||||
*/
|
||||
bool run() {
|
||||
while (true) {
|
||||
if (!skip_space_and_comments()) {
|
||||
return false;
|
||||
}
|
||||
if (pos_ >= src_.size()) {
|
||||
push(Tok::END, "", 0);
|
||||
return true;
|
||||
}
|
||||
if (!lex_one()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 组装 "lex error: <msg> (file, line, col)" 写入 err
|
||||
* @param msg 错误描述(不含前缀与位置)
|
||||
* @return 恒 false(便于 return fail(...) 一行退出)
|
||||
*/
|
||||
bool fail(const std::string& msg) {
|
||||
if (err_) {
|
||||
char buf[128];
|
||||
std::snprintf(buf, sizeof buf, " (%s, line %u, col %u)",
|
||||
sf_.c_str(), line_, col_);
|
||||
*err_ = "lex error: " + msg + buf;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 当前字符(不消费)
|
||||
* @return src_[pos_];调用前需保证 !at_end()
|
||||
*/
|
||||
char cur() const { return src_[pos_]; }
|
||||
|
||||
/**
|
||||
* @brief 是否已到源文末尾
|
||||
* @return pos_ >= src_.size()
|
||||
*/
|
||||
bool at_end() const { return pos_ >= src_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 消费一个字符并推进行列
|
||||
*
|
||||
* @details '\n' → 行 +1、列复位 1;其他字符 → 列 +1。
|
||||
* 注意:token 的起始行列要在此前捕获(见 lex_one)。
|
||||
*/
|
||||
void advance() {
|
||||
if (cur() == '\n') {
|
||||
++line_;
|
||||
col_ = 1;
|
||||
} else {
|
||||
++col_;
|
||||
}
|
||||
++pos_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 跳过空白与 (* *) 注释
|
||||
*
|
||||
* @details 第一版注释不嵌套:遇到第一个 "*) 即结束
|
||||
* (IEC 允许嵌套,留待以后)。未闭合 → lex error。
|
||||
* @return true 正常;false 未闭合注释(err 已写)
|
||||
*/
|
||||
bool skip_space_and_comments() {
|
||||
while (!at_end()) {
|
||||
if (std::isspace(static_cast<unsigned char>(cur()))) {
|
||||
advance();
|
||||
} else if (cur() == '(' && pos_ + 1 < src_.size() &&
|
||||
src_[pos_ + 1] == '*') {
|
||||
advance();
|
||||
advance(); // 跳过 "(*"
|
||||
bool closed = false;
|
||||
while (!at_end()) {
|
||||
if (cur() == '*' && pos_ + 1 < src_.size() &&
|
||||
src_[pos_ + 1] == ')') {
|
||||
advance();
|
||||
advance();
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
advance();
|
||||
}
|
||||
if (!closed) {
|
||||
return fail("unterminated comment");
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 产出 token 入流
|
||||
*
|
||||
* @details 位置用 tok_line_/tok_col_(本 token 起始位置,
|
||||
* 而非 push 时刻的位置——此时消费已推进了行列)。
|
||||
* @param type token 类型
|
||||
* @param text 文本(标识符/关键字的小写形;字面量原文)
|
||||
* @param value 字面量值(INT_LIT / TIME_LIT 毫秒;其他为 0)
|
||||
*/
|
||||
void push(Tok type, const std::string& text, int64_t value) {
|
||||
Token t;
|
||||
t.type = type;
|
||||
t.text = text;
|
||||
t.int_value = value;
|
||||
t.source_file = sf_;
|
||||
t.line = tok_line_;
|
||||
t.col = tok_col_;
|
||||
out_->push_back(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 识别一个 token(当前字符开始)
|
||||
*
|
||||
* @details 分支顺序有讲究:
|
||||
* 1. T#(TIME 字面量)必须优先于标识符——'T' 是字母,否则会被
|
||||
* 标识符分支吃掉;"timer" 这类普通词不受影响(只匹配 T#)。
|
||||
* 2. 标识符/关键字(含下划线,字母/数字/下划线组成)。
|
||||
* 3. 整数(纯数字;负数不是字面量,-5 是 MINUS + 5)。
|
||||
* 4. 符号::= <> <= >= 最长匹配优先。
|
||||
* @return true 成功(已消费并 push);false 非法字符(err 已写)
|
||||
*/
|
||||
bool lex_one() {
|
||||
// 记录本 token 起始位置(push 时消费已经推进了行列)
|
||||
tok_line_ = line_;
|
||||
tok_col_ = col_;
|
||||
const char c = cur();
|
||||
|
||||
// 日期/TIME 字面量前缀:D# / T# / TOD# / DT#(大小写不敏感,最长匹配)
|
||||
// 字母序列后跟 '#' 才进入;"ton"、"timer" 等普通词走标识符分支。
|
||||
if (std::isalpha(static_cast<unsigned char>(c))) {
|
||||
const uint32_t wstart = pos_;
|
||||
size_t j = pos_;
|
||||
while (j < src_.size() &&
|
||||
std::isalpha(static_cast<unsigned char>(src_[j]))) {
|
||||
++j;
|
||||
}
|
||||
if (j < src_.size() && src_[j] == '#') {
|
||||
std::string w = src_.substr(wstart, j - wstart);
|
||||
for (char& ch : w) {
|
||||
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||
}
|
||||
if (w == "t") return lex_time(); // T#
|
||||
if (w == "d") return lex_date(); // D#
|
||||
if (w == "tod") return lex_tod(); // TOD#
|
||||
if (w == "dt") return lex_dt(); // DT#
|
||||
}
|
||||
}
|
||||
|
||||
// 标识符 / 关键字:统一折小写存入 text(内部形),
|
||||
// 关键字命中即分类,否则 IDENT(用户定义名 / 内建类型名都走这里)。
|
||||
if (std::isalpha(static_cast<unsigned char>(c)) || c == '_') {
|
||||
const uint32_t start = pos_;
|
||||
while (!at_end() &&
|
||||
(std::isalnum(static_cast<unsigned char>(cur())) || cur() == '_')) {
|
||||
advance();
|
||||
}
|
||||
std::string word = src_.substr(start, pos_ - start);
|
||||
for (char& ch : word) {
|
||||
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||
}
|
||||
for (const auto& kw : kKeywords) {
|
||||
if (word == kw.key) {
|
||||
push(kw.tok, kw.key, 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
push(Tok::IDENT, word, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 数字:进制整数(2#/8#/16#)→ 浮点(. / e)→ 十进制整数
|
||||
if (std::isdigit(static_cast<unsigned char>(c))) {
|
||||
const uint32_t start = pos_;
|
||||
while (!at_end() && std::isdigit(static_cast<unsigned char>(cur()))) {
|
||||
advance();
|
||||
}
|
||||
// 进制前缀:<基数>#<数字>(2/8/16)
|
||||
if (!at_end() && cur() == '#') {
|
||||
const std::string base_str = src_.substr(start, pos_ - start);
|
||||
const uint32_t base = static_cast<uint32_t>(std::stoul(base_str));
|
||||
if (base != 2 && base != 8 && base != 16) {
|
||||
return fail("bad radix prefix");
|
||||
}
|
||||
return lex_base_int(start, base);
|
||||
}
|
||||
// 浮点:小数点或指数(.5 需数字前缀,由本分支覆盖)
|
||||
bool is_float = false;
|
||||
if (!at_end() && cur() == '.' && pos_ + 1 < src_.size() &&
|
||||
std::isdigit(static_cast<unsigned char>(src_[pos_ + 1]))) {
|
||||
is_float = true;
|
||||
advance(); // .
|
||||
while (!at_end() && std::isdigit(static_cast<unsigned char>(cur()))) {
|
||||
advance();
|
||||
}
|
||||
}
|
||||
if (!at_end() && (cur() == 'e' || cur() == 'E')) {
|
||||
is_float = true;
|
||||
advance();
|
||||
if (!at_end() && (cur() == '+' || cur() == '-')) {
|
||||
advance();
|
||||
}
|
||||
if (at_end() || !std::isdigit(static_cast<unsigned char>(cur()))) {
|
||||
return fail("bad float literal");
|
||||
}
|
||||
while (!at_end() && std::isdigit(static_cast<unsigned char>(cur()))) {
|
||||
advance();
|
||||
}
|
||||
}
|
||||
const std::string digits = src_.substr(start, pos_ - start);
|
||||
if (is_float) {
|
||||
push(Tok::FLOAT_LIT, digits, 0);
|
||||
out_->back().double_value = std::strtod(digits.c_str(), nullptr);
|
||||
return true;
|
||||
}
|
||||
uint64_t v = 0;
|
||||
for (const char ch : digits) {
|
||||
const uint64_t d = static_cast<uint64_t>(ch - '0');
|
||||
if (v > (static_cast<uint64_t>(INT64_MAX) - d) / 10) {
|
||||
return fail("integer literal too large");
|
||||
}
|
||||
v = v * 10 + d;
|
||||
}
|
||||
push(Tok::INT_LIT, digits, static_cast<int64_t>(v));
|
||||
return true;
|
||||
}
|
||||
|
||||
// 符号:两字符符(:= <> <= >=)优先于单字符;
|
||||
// 其余单字符各归一类,非法字符直接报错。
|
||||
switch (c) {
|
||||
case ':':
|
||||
if (pos_ + 1 < src_.size() && src_[pos_ + 1] == '=') {
|
||||
advance();
|
||||
advance();
|
||||
push(Tok::ASSIGN, ":=", 0);
|
||||
} else {
|
||||
advance();
|
||||
push(Tok::COLON, ":", 0);
|
||||
}
|
||||
return true;
|
||||
case '=':
|
||||
advance();
|
||||
push(Tok::EQ, "=", 0);
|
||||
return true;
|
||||
case '<':
|
||||
if (pos_ + 1 < src_.size() && src_[pos_ + 1] == '=') {
|
||||
advance();
|
||||
advance();
|
||||
push(Tok::LE, "<=", 0);
|
||||
} else if (pos_ + 1 < src_.size() && src_[pos_ + 1] == '>') {
|
||||
advance();
|
||||
advance();
|
||||
push(Tok::NE, "<>", 0);
|
||||
} else {
|
||||
advance();
|
||||
push(Tok::LT, "<", 0);
|
||||
}
|
||||
return true;
|
||||
case '>':
|
||||
if (pos_ + 1 < src_.size() && src_[pos_ + 1] == '=') {
|
||||
advance();
|
||||
advance();
|
||||
push(Tok::GE, ">=", 0);
|
||||
} else {
|
||||
advance();
|
||||
push(Tok::GT, ">", 0);
|
||||
}
|
||||
return true;
|
||||
case '+':
|
||||
advance();
|
||||
push(Tok::PLUS, "+", 0);
|
||||
return true;
|
||||
case '-':
|
||||
advance();
|
||||
push(Tok::MINUS, "-", 0);
|
||||
return true;
|
||||
case '*':
|
||||
advance();
|
||||
push(Tok::STAR, "*", 0);
|
||||
return true;
|
||||
case '/':
|
||||
advance();
|
||||
push(Tok::SLASH, "/", 0);
|
||||
return true;
|
||||
case '(':
|
||||
advance();
|
||||
push(Tok::LPAREN, "(", 0);
|
||||
return true;
|
||||
case ')':
|
||||
advance();
|
||||
push(Tok::RPAREN, ")", 0);
|
||||
return true;
|
||||
case ',':
|
||||
advance();
|
||||
push(Tok::COMMA, ",", 0);
|
||||
return true;
|
||||
case ';':
|
||||
advance();
|
||||
push(Tok::SEMI, ";", 0);
|
||||
return true;
|
||||
case '.':
|
||||
advance();
|
||||
push(Tok::DOT, ".", 0);
|
||||
return true;
|
||||
default:
|
||||
return fail(std::string("illegal character '") + c + "'");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析 T#<整数><单位> 段式 TIME 字面量
|
||||
*
|
||||
* @details 段可多个,每段 = 数字 + 单位(ms/s/m/h/d,大小写不敏感):
|
||||
* T#10ms → 10;T#1s250ms → 1250;T#2h → 7200000。
|
||||
* @return true 成功(push TIME_LIT,int_value 为毫秒);
|
||||
* false 缺段 / 未知单位 / 数字溢出(err 已写)
|
||||
*/
|
||||
bool lex_time() {
|
||||
const uint32_t start = pos_;
|
||||
advance(); // T
|
||||
advance(); // #
|
||||
int64_t total = 0;
|
||||
bool any = false;
|
||||
while (!at_end() && std::isdigit(static_cast<unsigned char>(cur()))) {
|
||||
uint64_t v = 0;
|
||||
while (!at_end() && std::isdigit(static_cast<unsigned char>(cur()))) {
|
||||
const uint64_t d = static_cast<uint64_t>(cur() - '0');
|
||||
if (v > (static_cast<uint64_t>(INT64_MAX) - d) / 10) {
|
||||
return fail("bad time literal");
|
||||
}
|
||||
v = v * 10 + d;
|
||||
advance();
|
||||
}
|
||||
std::string unit;
|
||||
while (!at_end() && std::isalpha(static_cast<unsigned char>(cur()))) {
|
||||
unit += static_cast<char>(
|
||||
std::tolower(static_cast<unsigned char>(cur())));
|
||||
advance();
|
||||
}
|
||||
const int64_t mult = time_unit_ms(unit);
|
||||
if (mult < 0) {
|
||||
return fail("bad time literal");
|
||||
}
|
||||
total += static_cast<int64_t>(v) * mult;
|
||||
any = true;
|
||||
}
|
||||
if (!any) {
|
||||
return fail("bad time literal");
|
||||
}
|
||||
push(Tok::TIME_LIT, src_.substr(start, pos_ - start), total);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析进制整数字面量 <基数>#<数字>(2/8/16)。
|
||||
* @param start 起始下标(含基数数字)
|
||||
* @param base 进制(2/8/16,已校验)
|
||||
* @return true 成功(push INT_LIT,值已换算十进制);
|
||||
* false 位数非法 / 溢出(err 已写)
|
||||
*/
|
||||
bool lex_base_int(uint32_t start, uint32_t base) {
|
||||
advance(); // #
|
||||
uint64_t v = 0;
|
||||
bool any = false;
|
||||
while (!at_end()) {
|
||||
const char ch = cur();
|
||||
uint64_t d = 0;
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
d = static_cast<uint64_t>(ch - '0');
|
||||
} else if (ch >= 'a' && ch <= 'f') {
|
||||
d = static_cast<uint64_t>(ch - 'a' + 10);
|
||||
} else if (ch >= 'A' && ch <= 'F') {
|
||||
d = static_cast<uint64_t>(ch - 'A' + 10);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if (d >= base) {
|
||||
return fail("bad base literal digit");
|
||||
}
|
||||
if (v > (static_cast<uint64_t>(INT64_MAX) - d) / base) {
|
||||
return fail("base literal too large");
|
||||
}
|
||||
v = v * base + d;
|
||||
any = true;
|
||||
advance();
|
||||
}
|
||||
if (!any) {
|
||||
return fail("bad base literal");
|
||||
}
|
||||
push(Tok::INT_LIT, src_.substr(start, pos_ - start), static_cast<int64_t>(v));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 公历 → 1970-01-01 起的天数(Hinnant 算法;1970-01-01 = 0)。
|
||||
static int64_t days_from_civil(int64_t y, unsigned m, unsigned d) {
|
||||
y -= m <= 2;
|
||||
const int64_t era = (y >= 0 ? y : y - 399) / 400;
|
||||
const unsigned yoe = static_cast<unsigned>(y - era * 400);
|
||||
const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
|
||||
const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
|
||||
return era * 146097 + static_cast<int64_t>(doe) - 719468;
|
||||
}
|
||||
|
||||
/// 某年某月的天数(含闰年)。
|
||||
static unsigned days_in_month(int64_t y, unsigned m) {
|
||||
static const unsigned kDays[12] = {31, 28, 31, 30, 31, 30,
|
||||
31, 31, 30, 31, 30, 31};
|
||||
if (m == 2 && (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0))) {
|
||||
return 29;
|
||||
}
|
||||
return kDays[m - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析日期段(y-m-d),换算为天数(1970 纪元)。
|
||||
* @param out_days 输出天数
|
||||
* @return true 成功;false 格式/值域非法
|
||||
*/
|
||||
bool parse_date_part(int64_t* out_days) {
|
||||
int64_t y = 0, m = 0, d = 0;
|
||||
if (!read_uint(&y)) return false;
|
||||
if (!expect_char('-')) return false;
|
||||
if (!read_uint(&m)) return false;
|
||||
if (!expect_char('-')) return false;
|
||||
if (!read_uint(&d)) return false;
|
||||
if (y < 1 || y > 9999) return false;
|
||||
if (m < 1 || m > 12) return false;
|
||||
if (d < 1 || d > days_in_month(y, static_cast<unsigned>(m))) return false;
|
||||
*out_days = days_from_civil(y, static_cast<unsigned>(m), static_cast<unsigned>(d));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 读一个无符号整数段(消费数字)。
|
||||
bool read_uint(int64_t* out) {
|
||||
if (at_end() || !std::isdigit(static_cast<unsigned char>(cur()))) {
|
||||
return false;
|
||||
}
|
||||
int64_t v = 0;
|
||||
while (!at_end() && std::isdigit(static_cast<unsigned char>(cur()))) {
|
||||
v = v * 10 + static_cast<int64_t>(cur() - '0');
|
||||
advance();
|
||||
}
|
||||
*out = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 期望指定字符并消费;不匹配返回 false。
|
||||
bool expect_char(char want) {
|
||||
if (at_end() || cur() != want) {
|
||||
return false;
|
||||
}
|
||||
advance();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析 D#<年>-<月>-<日> 日期字面量。
|
||||
* @return true 成功(push DATE_LIT,int_value = 天数);
|
||||
* false 格式/值域非法(err 已写)
|
||||
*/
|
||||
bool lex_date() {
|
||||
const uint32_t start = pos_;
|
||||
advance(); // D
|
||||
advance(); // #
|
||||
int64_t days = 0;
|
||||
if (!parse_date_part(&days)) {
|
||||
return fail("bad date literal");
|
||||
}
|
||||
push(Tok::DATE_LIT, src_.substr(start, pos_ - start), days);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析 TOD#<时>:<分>:<秒> 时刻字面量(当日毫秒,无小数秒)。
|
||||
* @return true 成功(push TOD_LIT,int_value = 当日毫秒);
|
||||
* false 格式/值域非法(err 已写)
|
||||
*/
|
||||
bool lex_tod() {
|
||||
const uint32_t start = pos_;
|
||||
advance(); // T
|
||||
advance(); // O
|
||||
advance(); // D
|
||||
advance(); // #
|
||||
int64_t h = 0, m = 0, s = 0;
|
||||
if (!read_uint(&h)) return fail("bad tod literal");
|
||||
if (!expect_char(':')) return fail("bad tod literal");
|
||||
if (!read_uint(&m)) return fail("bad tod literal");
|
||||
if (!expect_char(':')) return fail("bad tod literal");
|
||||
if (!read_uint(&s)) return fail("bad tod literal");
|
||||
if (h > 23 || m > 59 || s > 59) {
|
||||
return fail("bad tod literal");
|
||||
}
|
||||
const int64_t ms = h * 3600000 + m * 60000 + s * 1000;
|
||||
push(Tok::TOD_LIT, src_.substr(start, pos_ - start), ms);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析 DT#<年>-<月>-<日>-<时>:<分>:<秒> 日期时刻字面量。
|
||||
* @return true 成功(push DT_LIT,int64 = date<<32 | tod);
|
||||
* false 格式/值域非法(err 已写)
|
||||
*/
|
||||
bool lex_dt() {
|
||||
const uint32_t start = pos_;
|
||||
advance(); // D
|
||||
advance(); // T
|
||||
advance(); // #
|
||||
int64_t days = 0;
|
||||
if (!parse_date_part(&days)) {
|
||||
return fail("bad dt literal");
|
||||
}
|
||||
if (!expect_char('-')) return fail("bad dt literal");
|
||||
int64_t h = 0, m = 0, s = 0;
|
||||
if (!read_uint(&h)) return fail("bad dt literal");
|
||||
if (!expect_char(':')) return fail("bad dt literal");
|
||||
if (!read_uint(&m)) return fail("bad dt literal");
|
||||
if (!expect_char(':')) return fail("bad dt literal");
|
||||
if (!read_uint(&s)) return fail("bad dt literal");
|
||||
if (h > 23 || m > 59 || s > 59) {
|
||||
return fail("bad dt literal");
|
||||
}
|
||||
const uint64_t tod = static_cast<uint64_t>(h * 3600000 + m * 60000 + s * 1000);
|
||||
const int64_t dt = (static_cast<uint64_t>(days) << 32) | tod;
|
||||
push(Tok::DT_LIT, src_.substr(start, pos_ - start), dt);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 状态 ----
|
||||
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
|
||||
|
||||
/**
|
||||
* @brief 对源文本做词法分析(对外入口)
|
||||
*
|
||||
* @details 成功时 out 以 END token 结尾;失败时 out 内容不保证
|
||||
* (调用方以返回值为准)。
|
||||
* @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) {
|
||||
out->clear();
|
||||
Lexer l(source_file, content, out, err);
|
||||
return l.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
if (!in) {
|
||||
if (err) {
|
||||
*err = "lex error: cannot open file (" + path + ")";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
std::string content((std::istreambuf_iterator<char>(in)),
|
||||
std::istreambuf_iterator<char>());
|
||||
return lex(path, content, out, err);
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
|
||||
@@ -0,0 +1,761 @@
|
||||
/**
|
||||
* @file Linker.cpp
|
||||
* @brief 符号表与链接
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 设计说明(详见 Doc/compiler/符号表与链接.md):
|
||||
* - 流程:收集导出(gvl 校验/全局定槽/POU 登记)→ 用户 FB 类型布局
|
||||
* → 逐 POU 局部符号与引用解析 → 函数调用图环检测 → io 校验 → 入口校验
|
||||
* - 数据区定址:v1 单一数据区,全局(含 I/Q/M)按声明顺序编槽号
|
||||
* - 错误:稳定前缀 "link error" + 文件(+ 行列可选)
|
||||
*
|
||||
* 函数清单:
|
||||
* - kind_name 符号种类 → 文本名(global/external/local/…)
|
||||
* - type_str 类型引用 → 文本名(bool/int/time/fb 名)
|
||||
* - is_scalar 是否为标量类型(BOOL/INT/TIME)
|
||||
* - builtin_layout 内建 FB 名 → 冻结布局(ton/tof/ctu),未知返回 nullptr
|
||||
* - Linker::Linker (构造)存工程/源文件/输出指针,规范化 gvl 路径
|
||||
* - Linker::run 链接主流程(6 步,任一步失败即停)
|
||||
* - Linker::fail 组装 "link error: <msg> (file[, line, col])" 返回 false
|
||||
* - Linker::is_gvl_file 路径是否工程指定的 gvl 文件(规范化比较)
|
||||
* - collect_exports 收集全局(仅 gvl 顶层)与 POU 导出,重复名拒绝
|
||||
* - collect_fb_types 收集用户 FUNCTION_BLOCK 的字段布局
|
||||
* - resolve_scopes 逐 POU 建局部符号并解析语句引用
|
||||
* - scope_of 按 POU 名取作用域(找不到返回静态空作用域)
|
||||
* - build_scope_syms 建一个 POU 的局部符号(含 EXTERNAL 接槽 / FB 实例布局)
|
||||
* - find_sym 在 POU 局部符号里按名查找
|
||||
* - check_stmt 校验一条语句(赋值/FB 调用/IF/WHILE 递归)
|
||||
* - check_expr 校验一个表达式(变量/字段/函数调用/递归子树)
|
||||
* - find_function 按名查 POU 表并回传种类
|
||||
* - check_cycles 函数依赖图 DFS 环检测入口
|
||||
* - dfs 单个函数的环检测(visiting/done 三色标记)
|
||||
* - check_io io 绑定必须已在 GVL(大小写折叠比较)
|
||||
* - has_program_main 必须存在 PROGRAM main(入口)
|
||||
* - link_project 对外入口:清空输出 → 构造 Linker → run
|
||||
* - load_unit 读取并解析一个 .st 成 SourceUnit
|
||||
* - dump_symbols 符号表文本:每行 "name kind type address file"
|
||||
*/
|
||||
|
||||
#include "compiler/Linker.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace compiler {
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief 符号种类 → 文本名(dump_symbols 用)
|
||||
* @param k 符号种类
|
||||
* @return "global" / "external" / "local" / "input" / "output" /
|
||||
* "pou" / "fb_instance" / "const";未知返回 "?"
|
||||
*/
|
||||
const char* kind_name(SymbolKind k) {
|
||||
switch (k) {
|
||||
case SymbolKind::Global: return "global";
|
||||
case SymbolKind::External: return "external";
|
||||
case SymbolKind::Local: return "local";
|
||||
case SymbolKind::Input: return "input";
|
||||
case SymbolKind::Output: return "output";
|
||||
case SymbolKind::Pou: return "pou";
|
||||
case SymbolKind::FbInstance: return "fb_instance";
|
||||
case SymbolKind::Const: return "const";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 类型引用 → 文本名(符号表 / 类型比较用)
|
||||
* @param t 类型引用
|
||||
* @return "bool" / "int" / "time" / FB 类型名(小写);未知返回 "?"
|
||||
*/
|
||||
std::string type_str(const TypeRef& t) {
|
||||
return t.name; // V2:所有种类都存小写类型名(标量 = type 表键 / FB 名)
|
||||
}
|
||||
|
||||
/// 小写化(内建 FB 名 → fb 表名映射用)。
|
||||
std::string lower_of(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 内建 FB 名 → 布局(查 machine.toml [[fb]] 表,V2 配置驱动)。
|
||||
* @param name FB 类型名(小写)
|
||||
* @return 布局指针(缓存);未知返回 nullptr
|
||||
*/
|
||||
const FbLayout* builtin_layout(const MachineConfig& cfg, const std::string& name) {
|
||||
static std::map<std::string, FbLayout> cache;
|
||||
auto it = cache.find(name);
|
||||
if (it != cache.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
std::string up = name;
|
||||
for (char& ch : up) {
|
||||
ch = static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
|
||||
}
|
||||
const ConfigFb* fb = cfg.find_fb(up);
|
||||
if (fb == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
FbLayout lay;
|
||||
lay.type_name = name;
|
||||
for (const ConfigFbField& f : fb->fields) {
|
||||
FbField field;
|
||||
field.name = f.name;
|
||||
field.type_name = lower_of(f.type);
|
||||
lay.fields.push_back(field);
|
||||
}
|
||||
auto r = cache.emplace(name, std::move(lay));
|
||||
return &r.first->second;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 链接器
|
||||
*
|
||||
* @details 六步流程见 run()。错误即停(首个错误优先,不做恢复)。
|
||||
*/
|
||||
class Linker {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造链接器
|
||||
* @param proj 工程定义(toml 解析结果;io 绑定 / gvl 文件 / base_dir)
|
||||
* @param units 全部源文件的 AST(compile_files 集合逐一 load_unit)
|
||||
* @param out 链接结果(调用方先构造空 LinkResult)
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
*/
|
||||
Linker(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
const MachineConfig& cfg, LinkResult* out, std::string* err)
|
||||
: proj_(proj), units_(units), cfg_(cfg), out_(out), err_(err) {
|
||||
// gvl 路径规范化一次,供 is_gvl_file 比较(容忍相对/绝对写法差异)
|
||||
gvl_path_ = std::filesystem::weakly_canonical(
|
||||
std::filesystem::path(proj.base_dir) / proj.gvl_file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 链接主流程(6 步)
|
||||
* @details 1. 收集导出 → 2. 用户 FB 类型布局 → 3. 逐 POU 符号与引用
|
||||
* → 4. 循环调用检测 → 5. io 校验 → 6. 入口校验
|
||||
* @return true 链接成功;false 任一步失败(err 已写)
|
||||
*/
|
||||
bool run() {
|
||||
if (!collect_exports()) return false;
|
||||
if (!collect_fb_types()) return false;
|
||||
if (!resolve_scopes()) return false;
|
||||
if (!check_cycles()) return false;
|
||||
if (!check_io()) return false;
|
||||
if (!has_program_main()) {
|
||||
return fail("", "missing entry 'program MAIN'", 0, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 组装 "link error: <msg> (file[, line, col])" 写入 err
|
||||
* @param file 出错文件(可为空串,用于无位置错误)
|
||||
* @param msg 错误描述(不含前缀与位置)
|
||||
* @param line 行列(1 起);0 表示不带行列只带文件
|
||||
* @param col 列号
|
||||
* @return 恒 false(便于 return fail(...) 一行退出)
|
||||
*/
|
||||
bool fail(const std::string& file, const std::string& msg,
|
||||
uint32_t line, uint32_t col) {
|
||||
if (err_) {
|
||||
char buf[256];
|
||||
if (line) {
|
||||
std::snprintf(buf, sizeof buf, " (%s, line %u, col %u)",
|
||||
file.c_str(), line, col);
|
||||
} else {
|
||||
std::snprintf(buf, sizeof buf, " (%s)", file.c_str());
|
||||
}
|
||||
*err_ = "link error: " + msg + buf;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 路径是否工程指定的 gvl 文件
|
||||
* @param path 待比较路径
|
||||
* @return true 是 gvl 文件(规范化后与 gvl_path_ 相同);
|
||||
* 工程未配置 gvl 时恒 false
|
||||
*/
|
||||
bool is_gvl_file(const std::string& path) const {
|
||||
if (proj_.gvl_file.empty()) {
|
||||
return false;
|
||||
}
|
||||
return std::filesystem::weakly_canonical(path) == gvl_path_;
|
||||
}
|
||||
|
||||
// ---- 1. 收集导出 ----
|
||||
|
||||
/**
|
||||
* @brief 收集导出:顶层全局(仅 gvl 文件)与 POU 登记
|
||||
* @details 全局按声明顺序编槽号(下标即槽号);同名全局 / 同名 POU 拒绝
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool collect_exports() {
|
||||
for (const SourceUnit& u : units_) {
|
||||
const bool is_gvl = is_gvl_file(u.path);
|
||||
|
||||
for (const VarBlock& b : u.ast.globals) {
|
||||
if (!is_gvl) {
|
||||
return fail(u.path, "VAR_GLOBAL only allowed in gvl file",
|
||||
b.vars.empty() ? 0 : b.vars[0].line,
|
||||
b.vars.empty() ? 0 : b.vars[0].col);
|
||||
}
|
||||
if (b.section == VarSection::Temp) {
|
||||
return fail(u.path, "VAR_TEMP only allowed in POU",
|
||||
b.vars.empty() ? 0 : b.vars[0].line,
|
||||
b.vars.empty() ? 0 : b.vars[0].col);
|
||||
}
|
||||
for (const VarDecl& d : b.vars) {
|
||||
if (out_->global_index.count(d.name)) {
|
||||
return fail(u.path, "duplicate global '" + d.name + "'",
|
||||
d.line, d.col);
|
||||
}
|
||||
Symbol s;
|
||||
s.kind = SymbolKind::Global;
|
||||
s.name = d.name;
|
||||
s.type_name = type_str(d.type);
|
||||
s.address = static_cast<uint32_t>(out_->globals.size());
|
||||
s.source_file = u.path;
|
||||
s.line = d.line;
|
||||
s.col = d.col;
|
||||
out_->global_index[s.name] = s.address;
|
||||
out_->globals.push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
for (const POU& p : u.ast.pous) {
|
||||
for (const LinkResult::PouScope& sc : out_->scopes) {
|
||||
if (sc.name == p.name) {
|
||||
return fail(u.path, "duplicate POU '" + p.name + "'",
|
||||
0, 0);
|
||||
}
|
||||
}
|
||||
LinkResult::PouScope sc;
|
||||
sc.name = p.name;
|
||||
sc.kind = p.kind;
|
||||
out_->scopes.push_back(std::move(sc));
|
||||
out_->fn_order.push_back(p.name);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 2. 用户 FB 类型布局 ----
|
||||
|
||||
/**
|
||||
* @brief 收集用户 FUNCTION_BLOCK 的字段布局
|
||||
* @details 字段 = input / output / 内部 var(段序);
|
||||
* external 与 global 不是字段;字段类型仅允许标量
|
||||
* @return true 成功;false(FB 字段类型非法,err 已写)
|
||||
*/
|
||||
bool collect_fb_types() {
|
||||
for (const SourceUnit& u : units_) {
|
||||
for (const POU& p : u.ast.pous) {
|
||||
if (p.kind != PouKind::FunctionBlock) {
|
||||
continue;
|
||||
}
|
||||
FbLayout lay;
|
||||
lay.type_name = p.name;
|
||||
for (const VarBlock& b : p.blocks) {
|
||||
// 字段 = input / output / 内部 var;external/global/temp 不是字段
|
||||
//(VAR_TEMP 不持久,每次调用重新初始化,走帧寄存器)
|
||||
if (b.section == VarSection::External ||
|
||||
b.section == VarSection::Global ||
|
||||
b.section == VarSection::Temp) {
|
||||
continue;
|
||||
}
|
||||
for (const VarDecl& d : b.vars) {
|
||||
// FB 字段必须是标量类型(FB 实例字段不允许)
|
||||
if (d.type.kind != TypeKind::Scalar) {
|
||||
return fail(u.path,
|
||||
"FB field type must be scalar ('" + d.name +
|
||||
"')",
|
||||
d.line, d.col);
|
||||
}
|
||||
FbField f;
|
||||
f.name = d.name;
|
||||
f.type_name = d.type.name; // 标量类型名(小写)
|
||||
lay.fields.push_back(f);
|
||||
}
|
||||
}
|
||||
out_->fb_types[p.name] = lay;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 3. 逐 POU 建局部符号并解析引用 ----
|
||||
|
||||
/**
|
||||
* @brief 逐 POU 建局部符号并解析语句引用
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool resolve_scopes() {
|
||||
for (SourceUnit& u : const_cast<std::vector<SourceUnit>&>(units_)) {
|
||||
for (const POU& p : u.ast.pous) {
|
||||
LinkResult::PouScope& sc = scope_of(p.name);
|
||||
if (!build_scope_syms(u.path, p, sc)) {
|
||||
return false;
|
||||
}
|
||||
for (const Stmt& st : p.body) {
|
||||
if (!check_stmt(u.path, sc, p, st)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 POU 名取作用域
|
||||
* @param name POU 名(小写)
|
||||
* @return 对应 PouScope 引用;找不到返回静态空作用域(不应发生)
|
||||
*/
|
||||
LinkResult::PouScope& scope_of(const std::string& name) {
|
||||
for (LinkResult::PouScope& sc : out_->scopes) {
|
||||
if (sc.name == name) {
|
||||
return sc;
|
||||
}
|
||||
}
|
||||
static LinkResult::PouScope kEmpty;
|
||||
return kEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 建一个 POU 的局部符号
|
||||
* @details VAR/INPUT/OUTPUT → 对应种类;VAR_TEMP → Local(帧寄存器,
|
||||
* 不持久);VAR_EXTERNAL → 查全局表接同一槽(类型必须一致);
|
||||
* FB 类型 → FbInstance + 布局(内建取冻结表,用户类型查 fb_types);
|
||||
* POU 内 VAR_GLOBAL → 拒绝
|
||||
* @param file 出错文件
|
||||
* @param p POU AST
|
||||
* @param sc 输出作用域(syms / fb_instances 被填充)
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool build_scope_syms(const std::string& file, const POU& p,
|
||||
LinkResult::PouScope& sc) {
|
||||
for (const VarBlock& b : p.blocks) {
|
||||
for (const VarDecl& d : b.vars) {
|
||||
switch (b.section) {
|
||||
case VarSection::Global:
|
||||
return fail(file, "VAR_GLOBAL only allowed in gvl file",
|
||||
d.line, d.col);
|
||||
case VarSection::External: {
|
||||
const auto it = out_->global_index.find(d.name);
|
||||
if (it == out_->global_index.end()) {
|
||||
return fail(file, "undeclared external '" + d.name + "'",
|
||||
d.line, d.col);
|
||||
}
|
||||
const Symbol& g = out_->globals[it->second];
|
||||
if (g.type_name != type_str(d.type)) {
|
||||
return fail(file, "external type mismatch for '" + d.name + "'",
|
||||
d.line, d.col);
|
||||
}
|
||||
Symbol s;
|
||||
s.kind = SymbolKind::External;
|
||||
s.name = d.name;
|
||||
s.type_name = g.type_name;
|
||||
s.address = g.address;
|
||||
s.source_file = file;
|
||||
s.line = d.line;
|
||||
s.col = d.col;
|
||||
sc.syms.push_back(s);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const bool is_fb = d.type.kind == TypeKind::FbUser ||
|
||||
d.type.kind == TypeKind::FbBuiltin;
|
||||
Symbol s;
|
||||
s.kind = is_fb ? SymbolKind::FbInstance
|
||||
: (b.section == VarSection::Input
|
||||
? SymbolKind::Input
|
||||
: b.section == VarSection::Output
|
||||
? SymbolKind::Output
|
||||
: SymbolKind::Local);
|
||||
s.name = d.name;
|
||||
s.type_name = type_str(d.type);
|
||||
s.source_file = file;
|
||||
s.line = d.line;
|
||||
s.col = d.col;
|
||||
sc.syms.push_back(s);
|
||||
if (is_fb) {
|
||||
FbLayout lay;
|
||||
// 内建判定:查 machine.toml [[fb]] 表(V2 配置驱动)
|
||||
std::string up = d.type.name;
|
||||
for (char& ch : up) {
|
||||
ch = static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
|
||||
}
|
||||
if (cfg_.find_fb(up) != nullptr) {
|
||||
const FbLayout* bl = builtin_layout(cfg_, d.type.name);
|
||||
if (!bl) {
|
||||
return fail(file, "unknown builtin FB '" + d.type.name + "'",
|
||||
d.line, d.col);
|
||||
}
|
||||
lay = *bl;
|
||||
} else {
|
||||
const auto it = out_->fb_types.find(d.type.name);
|
||||
if (it == out_->fb_types.end()) {
|
||||
return fail(file, "unknown FB type '" + d.type.name + "'",
|
||||
d.line, d.col);
|
||||
}
|
||||
lay = it->second;
|
||||
}
|
||||
sc.fb_instances[d.name] = lay;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 引用解析 ----
|
||||
|
||||
/**
|
||||
* @brief 在 POU 局部符号里按名查找
|
||||
* @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 校验一条语句
|
||||
* @details Assign:左值必须已声明(FUNCTION 内函数名赋值除外);
|
||||
* FbCall:实例必须存在且是 FB,命名实参必须是该实例的字段;
|
||||
* If / While:递归校验条件与各分支体
|
||||
* @param file 出错文件
|
||||
* @param sc 当前 POU 作用域
|
||||
* @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: {
|
||||
// FUNCTION 内对函数名赋值 = 结果值写入(用例 13 约定)
|
||||
const bool is_result_assign =
|
||||
pou.kind == PouKind::Function && st.target == pou.name;
|
||||
if (!is_result_assign && find_sym(sc, st.target) == nullptr &&
|
||||
out_->global_index.count(st.target) == 0) {
|
||||
return fail(file, "undeclared identifier '" + st.target + "'",
|
||||
0, 0);
|
||||
}
|
||||
return check_expr(file, sc, *st.value);
|
||||
}
|
||||
case StmtKind::FbCall: {
|
||||
const Symbol* inst = find_sym(sc, st.instance);
|
||||
if (inst == nullptr) {
|
||||
return fail(file, "undeclared FB instance '" + st.instance + "'",
|
||||
0, 0);
|
||||
}
|
||||
if (inst->kind != SymbolKind::FbInstance) {
|
||||
return fail(file, "'" + st.instance + "' is not an FB instance",
|
||||
0, 0);
|
||||
}
|
||||
const auto it = sc.fb_instances.find(st.instance);
|
||||
const FbLayout& lay = it->second;
|
||||
for (const FbArg& a : st.args) {
|
||||
bool found = false;
|
||||
for (const FbField& f : lay.fields) {
|
||||
if (f.name == a.name) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return fail(file,
|
||||
"unknown input '" + a.name + "' for FB '" +
|
||||
st.instance + "'",
|
||||
0, 0);
|
||||
}
|
||||
if (!check_expr(file, sc, *a.value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case StmtKind::If:
|
||||
if (!check_expr(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_expr(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_expr(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 校验一个表达式
|
||||
* @details VarRef:局部或全局表查得到;Field:对象必须是 FB 实例
|
||||
* 且字段存在;Call:目标必须存在且是 FUNCTION(记调用边);
|
||||
* 其他节点递归校验子树
|
||||
* @param file 出错文件
|
||||
* @param sc 当前 POU 作用域
|
||||
* @param e 表达式 AST
|
||||
* @return true 合法;false(err 已写)
|
||||
*/
|
||||
bool check_expr(const std::string& file, const LinkResult::PouScope& sc,
|
||||
const Expr& e) {
|
||||
switch (e.kind) {
|
||||
case ExprKind::VarRef:
|
||||
if (find_sym(sc, e.name) == nullptr &&
|
||||
out_->global_index.count(e.name) == 0) {
|
||||
return fail(file, "undeclared identifier '" + e.name + "'", 0, 0);
|
||||
}
|
||||
return true;
|
||||
case ExprKind::Field: {
|
||||
const Symbol* inst = find_sym(sc, e.name);
|
||||
if (inst == nullptr || inst->kind != SymbolKind::FbInstance) {
|
||||
return fail(file, "undeclared FB instance '" + e.name + "'", 0, 0);
|
||||
}
|
||||
const auto it = sc.fb_instances.find(e.name);
|
||||
bool found = false;
|
||||
for (const FbField& f : it->second.fields) {
|
||||
if (f.name == e.field) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return fail(file, "unknown field '" + e.field + "' for FB '" +
|
||||
e.name + "'",
|
||||
0, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case ExprKind::Call: {
|
||||
PouKind k = PouKind::Program;
|
||||
if (!find_function(e.name, &k)) {
|
||||
return fail(file, "undeclared function '" + e.name + "'", 0, 0);
|
||||
}
|
||||
if (k != PouKind::Function) {
|
||||
return fail(file, "'" + e.name + "' is not a function", 0, 0);
|
||||
}
|
||||
call_edges_[sc.name].insert(e.name);
|
||||
for (const auto& a : e.args) {
|
||||
if (!check_expr(file, sc, *a)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (e.lhs && !check_expr(file, sc, *e.lhs)) return false;
|
||||
if (e.rhs && !check_expr(file, sc, *e.rhs)) return false;
|
||||
if (e.operand && !check_expr(file, sc, *e.operand)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按名查 POU 表并回传种类
|
||||
* @param name POU 名(小写)
|
||||
* @param kind 输出该 POU 的种类(PROGRAM / FUNCTION / FUNCTION_BLOCK)
|
||||
* @return true 找到;false 未找到
|
||||
*/
|
||||
bool find_function(const std::string& name, PouKind* kind) const {
|
||||
for (const LinkResult::PouScope& sc : out_->scopes) {
|
||||
if (sc.name == name) {
|
||||
*kind = sc.kind;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- 4. 循环调用检测(函数依赖图 DFS)----
|
||||
|
||||
/**
|
||||
* @brief 函数依赖图环检测入口
|
||||
* @details 对每个有出边的函数做 DFS;任一条路径回到栈上节点即环
|
||||
* @return true 无环;false 发现环(err 已写)
|
||||
*/
|
||||
bool check_cycles() {
|
||||
std::set<std::string> visiting;
|
||||
std::set<std::string> done;
|
||||
for (const auto& [fn, _] : call_edges_) {
|
||||
if (!dfs(fn, visiting, done)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 单个函数的环检测(三色标记 DFS)
|
||||
* @param fn 当前函数名
|
||||
* @param visiting 栈上节点(灰色,正在展开)
|
||||
* @param done 已确认无环的节点(黑色)
|
||||
* @return true 无环;false 命中灰色节点 = 环(err 已写)
|
||||
*/
|
||||
bool dfs(const std::string& fn, std::set<std::string>& visiting,
|
||||
std::set<std::string>& done) {
|
||||
if (done.count(fn)) {
|
||||
return true;
|
||||
}
|
||||
if (visiting.count(fn)) {
|
||||
return fail("", "recursive call involving '" + fn + "'", 0, 0);
|
||||
}
|
||||
visiting.insert(fn);
|
||||
const auto it = call_edges_.find(fn);
|
||||
if (it != call_edges_.end()) {
|
||||
for (const std::string& callee : it->second) {
|
||||
if (!dfs(callee, visiting, done)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
visiting.erase(fn);
|
||||
done.insert(fn);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 5. io 校验 ----
|
||||
|
||||
/**
|
||||
* @brief io 绑定必须已在 GVL 声明
|
||||
* @details 绑定名来自 toml(原始大小写),与全局表比较前折小写;
|
||||
* io 不创造变量,只做 channel/bit → 槽号映射
|
||||
* @return true 全部绑定合法;false(err 已写)
|
||||
*/
|
||||
bool check_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 (out_->global_index.count(key) == 0) {
|
||||
return fail(proj_.name + "/project.toml",
|
||||
"io.var '" + b.var + "' not in GVL", 0, 0);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 6. 入口 ----
|
||||
|
||||
/**
|
||||
* @brief 必须存在 PROGRAM main(与 toml entry = "program MAIN" 呼应)
|
||||
* @return true 存在
|
||||
*/
|
||||
bool has_program_main() const {
|
||||
for (const LinkResult::PouScope& sc : out_->scopes) {
|
||||
if (sc.kind == PouKind::Program && sc.name == "main") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- 成员 ----
|
||||
const Project& proj_; ///< 工程定义(toml)
|
||||
const std::vector<SourceUnit>& units_; ///< 全部源文件 AST
|
||||
const MachineConfig& cfg_; ///< 机器定义(内建 FB 布局来源)
|
||||
LinkResult* out_; ///< 链接结果
|
||||
std::string* err_; ///< 错误输出(可空)
|
||||
std::filesystem::path gvl_path_; ///< 规范化后的 gvl 路径
|
||||
std::map<std::string, std::set<std::string>> call_edges_; ///< 调用者 → 被调函数集
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief 链接一个工程(对外入口)
|
||||
* @details 校验 GVL、定全局槽、解析 VAR_EXTERNAL、FB 实例与字段、
|
||||
* 函数循环调用检测、io 校验、入口校验
|
||||
* @param proj 工程定义(toml 解析结果)
|
||||
* @param units 全部源文件的 AST(compile_files 集合逐一 load_unit 得到)
|
||||
* @param out 链接结果(各字段先清空后填充)
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false 失败(err 前缀 "link error")
|
||||
*/
|
||||
bool link_project(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
const MachineConfig& cfg, LinkResult* out, std::string* err) {
|
||||
out->globals.clear();
|
||||
out->global_index.clear();
|
||||
out->fb_types.clear();
|
||||
out->scopes.clear();
|
||||
out->fn_order.clear();
|
||||
Linker l(proj, units, cfg, out, err);
|
||||
return l.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取并解析一个 .st 文件成 SourceUnit
|
||||
* @param path 文件路径
|
||||
* @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) {
|
||||
out->path = path;
|
||||
return parse_pous_file(path, &out->ast, err);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 符号表文本(完成标准:能打印 name kind type address file)
|
||||
* @param r 链接结果
|
||||
* @return 多行文本:全局表逐行 "name kind type address file",
|
||||
* 每个 POU 以 "[名字]" 开头后接其局部符号行
|
||||
*/
|
||||
std::string dump_symbols(const LinkResult& r) {
|
||||
std::string out;
|
||||
for (const Symbol& s : r.globals) {
|
||||
out += s.name + " " + kind_name(s.kind) + " " + s.type_name + " " +
|
||||
std::to_string(s.address) + " " + s.source_file + "\n";
|
||||
}
|
||||
for (const LinkResult::PouScope& sc : r.scopes) {
|
||||
out += "[" + sc.name + "]\n";
|
||||
for (const Symbol& s : sc.syms) {
|
||||
out += s.name + " " + kind_name(s.kind) + " " + s.type_name + " " +
|
||||
std::to_string(s.address) + " " + s.source_file + "\n";
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
@@ -0,0 +1,616 @@
|
||||
/**
|
||||
* @file MachineConfig.cpp
|
||||
* @brief 机器定义(machine.toml V2)加载与强校验
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 校验项(见 Doc/isa/指令配置.md 强校验清单 1~5,V2):
|
||||
* 1. meta:name 非空、version = 2
|
||||
* 2. type:19 条必填全集(BOOL..DT);base 命中内建基元;kind 合法;
|
||||
* func ∈ 0..9 且与 base 的 (width, signed, is_float) 互锁
|
||||
* 3. op:prefix 合法;prefix ↔ fmt 互锁;op 分区内唯一;func 类别合法;
|
||||
* params 数量与 fmt 互锁;enabled 布尔
|
||||
* 4. fb:15 条必填全集(fb_id 0..14);fb_id 唯一;字段名唯一;字段类型命中 type 表
|
||||
* 5. op-fb 互锁:CAL(010101)存在 ↔ fb 表非空
|
||||
*/
|
||||
|
||||
#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 fmt → 期望参数数量(解析时校验)。
|
||||
* @param fmt 操作数形态(RR/NONE/RRR/IMM/SLOT/JMP/JC/CALL/CAL/SLOT+off/IMM64/预留)
|
||||
* @return 参数数量;未知 fmt 返回 -1
|
||||
*/
|
||||
int params_of(const std::string& fmt) {
|
||||
if (fmt == "RR") return 2;
|
||||
if (fmt == "NONE") return 0;
|
||||
if (fmt == "RRR") return 3;
|
||||
if (fmt == "IMM") return 2;
|
||||
if (fmt == "SLOT") return 2;
|
||||
if (fmt == "JMP") return 1;
|
||||
if (fmt == "JC") return 2;
|
||||
if (fmt == "CALL") return 1;
|
||||
if (fmt == "CAL") return 2;
|
||||
if (fmt == "SLOT+off") return 3;
|
||||
if (fmt == "RS+off") return 3;
|
||||
if (fmt == "IMM64") return 2;
|
||||
if (fmt == "预留") return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// @brief fmt 是否已知(params_of 返回非负)。
|
||||
bool is_known_format(const std::string& f) {
|
||||
return params_of(f) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief prefix 是否合法(V2 形态前缀集合)。
|
||||
* @param prefix 前缀字符串
|
||||
* @return 是否在 11/10/011/010000..010101/0010/0011/0001 内
|
||||
*/
|
||||
bool is_known_prefix(const std::string& prefix) {
|
||||
static const char* kPrefixes[] = {
|
||||
"11", "10", "011", "010000", "010001", "010010", "010011",
|
||||
"010100", "010101", "0010", "0011", "0001",
|
||||
};
|
||||
for (const char* p : kPrefixes) {
|
||||
if (prefix == p) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief prefix ↔ fmt 互锁表查询。
|
||||
* @param prefix 形态前缀
|
||||
* @return 期望 fmt;未知 prefix 返回空串
|
||||
*/
|
||||
const char* fmt_of_prefix(const std::string& prefix) {
|
||||
if (prefix == "11") return "RR";
|
||||
if (prefix == "10") return "NONE";
|
||||
if (prefix == "011") return "RRR";
|
||||
if (prefix == "010000") return "IMM";
|
||||
if (prefix == "010001") return "SLOT";
|
||||
if (prefix == "010010") return "JMP";
|
||||
if (prefix == "010011") return "JC";
|
||||
if (prefix == "010100") return "CALL";
|
||||
if (prefix == "010101") return "CAL";
|
||||
if (prefix == "0010") return "SLOT+off"; // 或 RS+off(LOAD_PTR/STORE_PTR)
|
||||
if (prefix == "0011") return "IMM64";
|
||||
if (prefix == "0001") return "预留";
|
||||
return "";
|
||||
}
|
||||
|
||||
/// @brief func 类别是否合法(V2:none/width/uint/full/float/dst)。
|
||||
bool is_known_func_mode(const std::string& m) {
|
||||
return m == "none" || m == "width" || m == "uint" || m == "full" ||
|
||||
m == "float" || m == "dst";
|
||||
}
|
||||
|
||||
/// @brief kind 是否合法(V2:bool/bit/int/uint/float/time/date)。
|
||||
bool is_known_kind(const std::string& k) {
|
||||
return k == "bool" || k == "bit" || k == "int" || k == "uint" ||
|
||||
k == "float" || k == "time" || k == "date";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief func 与基元的 (width, signed, is_float) 互锁校验。
|
||||
* @param type_name 类型名(错误信息用)
|
||||
* @param prim 基元
|
||||
* @param func func 值
|
||||
* @param err 错误输出
|
||||
* @return true 匹配;false(err 已写)
|
||||
*/
|
||||
bool func_matches_prim(const std::string& type_name, const PrimType& prim,
|
||||
uint32_t func, std::string* err) {
|
||||
if (prim.is_float) {
|
||||
if (func == (prim.width == 4 ? 8 : 9)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
uint32_t want = 0;
|
||||
if (prim.width == 1 && !prim.is_signed) want = 0;
|
||||
else if (prim.width == 1 && prim.is_signed) want = 1;
|
||||
else if (prim.width == 2 && !prim.is_signed) want = 2;
|
||||
else if (prim.width == 2 && prim.is_signed) want = 3;
|
||||
else if (prim.width == 4 && !prim.is_signed) want = 4;
|
||||
else if (prim.width == 4 && prim.is_signed) want = 5;
|
||||
else if (prim.width == 8 && !prim.is_signed) want = 6;
|
||||
else if (prim.width == 8 && prim.is_signed) want = 7;
|
||||
if (func == want) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
fail(err, "type '" + type_name + "': func " + std::to_string(func) +
|
||||
" mismatches base '" + prim.name + "' (width/sign)");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(V2)并做强校验。
|
||||
* @param path machine.toml 路径
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false(err 已写,前缀 "machine error")
|
||||
* @details 校验顺序与文件头清单 1~5 一致(V2):
|
||||
* 1. meta:name 非空、version = 2
|
||||
* 2. type:19 条必填全集;base 命中基元;kind 合法;func 与 base 互锁
|
||||
* 3. op:prefix 合法;prefix↔fmt 互锁;(prefix,op) 分区内唯一;
|
||||
* func 类别合法;params 数量与 fmt 互锁
|
||||
* 4. fb:15 条必填全集(fb_id 0..14);fb_id 唯一;字段类型命中 type 表
|
||||
* 5. op-fb 互锁:CAL(010101)存在 ↔ fb 表非空
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
// ---- 1. 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;
|
||||
if (model_name_.empty()) {
|
||||
fail(err, "empty name in [meta]");
|
||||
return false;
|
||||
}
|
||||
int64_t ver = 0;
|
||||
if (!req_int(*meta, "[meta]", "version", &ver, err)) return false;
|
||||
if (ver != 2) {
|
||||
fail(err, "version must be 2 (V2, STATOR2)");
|
||||
return false;
|
||||
}
|
||||
version_ = static_cast<uint32_t>(ver);
|
||||
|
||||
// ---- 2. type(19 条全集)----
|
||||
{
|
||||
const toml::array* arr = root["type"].as_array();
|
||||
if (!arr || arr->empty()) {
|
||||
fail(err, "missing 'type' table");
|
||||
return false;
|
||||
}
|
||||
const char* kAllTypes[19] = {
|
||||
"BOOL", "BYTE", "WORD", "DWORD", "LWORD", "SINT", "INT", "DINT",
|
||||
"LINT", "USINT", "UINT", "UDINT", "ULINT", "TIME", "REAL",
|
||||
"LREAL", "DATE", "TOD", "DT",
|
||||
};
|
||||
std::map<std::string, bool> seen;
|
||||
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 (!req_string(*t, "type", "kind", &ct.kind, err)) return false;
|
||||
int64_t func = 0;
|
||||
if (!req_int(*t, "type", "func", &func, err)) return false;
|
||||
if (func < 0 || func > 9) {
|
||||
fail(err, "type '" + ct.name + "': func out of range (0..9)");
|
||||
return false;
|
||||
}
|
||||
ct.func = static_cast<uint32_t>(func);
|
||||
if (seen.count(ct.name)) {
|
||||
fail(err, "duplicate type '" + ct.name + "'");
|
||||
return false;
|
||||
}
|
||||
seen[ct.name] = true;
|
||||
if (!is_known_kind(ct.kind)) {
|
||||
fail(err, "type '" + ct.name + "': bad kind '" + ct.kind + "'");
|
||||
return false;
|
||||
}
|
||||
const PrimType* prim = find_prim(ct.base);
|
||||
if (prim == nullptr) {
|
||||
fail(err, "type '" + ct.name + "': unknown base '" + ct.base + "'");
|
||||
return false;
|
||||
}
|
||||
// kind 与基元语义互锁:float ↔ is_float
|
||||
const bool kind_float = ct.kind == "float";
|
||||
if (kind_float != prim->is_float) {
|
||||
fail(err, "type '" + ct.name + "': kind '" + ct.kind +
|
||||
"' mismatches base '" + ct.base + "'");
|
||||
return false;
|
||||
}
|
||||
if (!func_matches_prim(ct.name, *prim, ct.func, err)) return false;
|
||||
types_.push_back(ct);
|
||||
}
|
||||
// 19 条全集必填
|
||||
for (const char* need : kAllTypes) {
|
||||
if (!seen.count(need)) {
|
||||
fail(err, std::string("type contract broken: missing '") + need + "'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 3. op ----
|
||||
{
|
||||
const toml::array* arr = root["op"].as_array();
|
||||
if (!arr || arr->empty()) {
|
||||
fail(err, "missing 'op' table");
|
||||
return false;
|
||||
}
|
||||
std::map<std::string, bool> key_owner; // "prefix/op" → 已存在
|
||||
std::map<std::string, bool> 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;
|
||||
if (!req_string(*t, "op", "prefix", &op.prefix, err)) return false;
|
||||
if (!req_string(*t, "op", "fmt", &op.fmt, err)) return false;
|
||||
if (!req_string(*t, "op", "func", &op.func_mode, err)) return false;
|
||||
int64_t opno = 0;
|
||||
if (!req_int(*t, "op", "op", &opno, err)) return false;
|
||||
if (opno < 0 || opno > 63) {
|
||||
fail(err, "op '" + op.name + "': op out of range (0..63)");
|
||||
return false;
|
||||
}
|
||||
op.op = static_cast<uint32_t>(opno);
|
||||
if (name_owner.count(op.name)) {
|
||||
fail(err, "duplicate op name '" + op.name + "'");
|
||||
return false;
|
||||
}
|
||||
name_owner[op.name] = true;
|
||||
if (!is_known_prefix(op.prefix)) {
|
||||
fail(err, "op '" + op.name + "': bad prefix '" + op.prefix + "'");
|
||||
return false;
|
||||
}
|
||||
// prefix ↔ fmt 互锁(0010 允许 SLOT+off 与 RS+off 两种形态)
|
||||
const char* want_fmt = fmt_of_prefix(op.prefix);
|
||||
const bool ok_fmt =
|
||||
op.fmt == want_fmt ||
|
||||
(op.prefix == "0010" && op.fmt == "RS+off");
|
||||
if (!ok_fmt) {
|
||||
fail(err, "op '" + op.name + "': prefix '" + op.prefix +
|
||||
"' must pair with fmt '" + std::string(want_fmt ? want_fmt : "?") + "'");
|
||||
return false;
|
||||
}
|
||||
if (!is_known_format(op.fmt)) {
|
||||
fail(err, "op '" + op.name + "': bad fmt '" + op.fmt + "'");
|
||||
return false;
|
||||
}
|
||||
// (prefix, op) 分区内唯一
|
||||
const std::string key = op.prefix + "/" + std::to_string(op.op);
|
||||
if (key_owner.count(key)) {
|
||||
fail(err, "duplicate (prefix, op) '" + key + "'");
|
||||
return false;
|
||||
}
|
||||
key_owner[key] = true;
|
||||
// func 类别合法
|
||||
if (!is_known_func_mode(op.func_mode)) {
|
||||
fail(err, "op '" + op.name + "': bad func mode '" + op.func_mode + "'");
|
||||
return false;
|
||||
}
|
||||
if (!req_bool(*t, "op", "enabled", &op.enabled, err)) return false;
|
||||
// params 数量与 fmt 互锁
|
||||
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.fmt);
|
||||
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()) + " != fmt expects " +
|
||||
std::to_string(want));
|
||||
return false;
|
||||
}
|
||||
ops_.push_back(op);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 4. fb(15 条全集 fb_id 0..14)----
|
||||
{
|
||||
const toml::array* arr = root["fb"].as_array();
|
||||
if (!arr || arr->empty()) {
|
||||
fail(err, "missing 'fb' table");
|
||||
return false;
|
||||
}
|
||||
std::map<uint32_t, bool> id_owner;
|
||||
std::map<std::string, bool> name_owner;
|
||||
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 fid = 0;
|
||||
if (!req_int(*t, "fb", "fb_id", &fid, err)) return false;
|
||||
if (fid < 0 || fid > 14) {
|
||||
fail(err, "fb '" + fb.name + "': fb_id out of range (0..14)");
|
||||
return false;
|
||||
}
|
||||
fb.fb_id = static_cast<uint32_t>(fid);
|
||||
if (id_owner.count(fb.fb_id)) {
|
||||
fail(err, "duplicate fb_id " + std::to_string(fb.fb_id));
|
||||
return false;
|
||||
}
|
||||
id_owner[fb.fb_id] = true;
|
||||
if (name_owner.count(fb.name)) {
|
||||
fail(err, "duplicate fb name '" + fb.name + "'");
|
||||
return false;
|
||||
}
|
||||
name_owner[fb.name] = true;
|
||||
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);
|
||||
}
|
||||
// 15 条全集必填(fb_id 0..14)
|
||||
for (uint32_t i = 0; i < 15; ++i) {
|
||||
if (!id_owner.count(i)) {
|
||||
fail(err, "fb contract broken: missing fb_id " + std::to_string(i));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 5. op-fb 互锁:CAL(010101)存在 ↔ fb 表非空 ----
|
||||
const ConfigOp* cal = find_op_by_key("010101", 0);
|
||||
if (cal == nullptr || !cal->enabled) {
|
||||
fail(err, "op-fb interlock: CAL (010101) must exist and be enabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
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 按 (prefix, op) 键查指令行。
|
||||
* @param prefix 形态前缀(如 "11"/"010001")
|
||||
* @param op 分区内编号
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigOp* MachineConfig::find_op_by_key(const std::string& prefix,
|
||||
uint32_t op) const {
|
||||
for (const ConfigOp& o : ops_) {
|
||||
if (o.prefix == prefix && o.op == op) {
|
||||
return &o;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查询某 (prefix, op) 是否启用。
|
||||
* @param prefix 形态前缀
|
||||
* @param op 分区内编号
|
||||
* @return 指令存在且 enabled 为 true;未知返回 false
|
||||
*/
|
||||
bool MachineConfig::op_enabled(const std::string& prefix, uint32_t op) const {
|
||||
const ConfigOp* o = find_op_by_key(prefix, op);
|
||||
return o != nullptr && o->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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 fb_id 查内建 FB。
|
||||
* @param fb_id FB 类型 id(0..14)
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigFb* MachineConfig::find_fb_by_id(uint32_t fb_id) const {
|
||||
for (const ConfigFb& f : fbs_) {
|
||||
if (f.fb_id == fb_id) {
|
||||
return &f;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,527 @@
|
||||
/**
|
||||
* @file Project.cpp
|
||||
* @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"
|
||||
|
||||
#include <toml++/toml.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "compiler/Stb.h"
|
||||
|
||||
namespace compiler {
|
||||
namespace {
|
||||
|
||||
// ---- 错误收集:稳定类别前缀 + 行号 ----
|
||||
|
||||
/**
|
||||
* @brief 写错误消息。
|
||||
* @details err 为 nullptr 时静默忽略(调用方可不关心原因)。
|
||||
* @param err 错误输出(可空)
|
||||
* @param msg 完整错误消息(已带类别前缀与行号)
|
||||
*/
|
||||
void fail(std::string* err, const std::string& msg) {
|
||||
if (err) {
|
||||
*err = msg;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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]) {
|
||||
if (!nv.is_string()) {
|
||||
fail(err, std::string("invalid value for '") + key + "' in [" + sec +
|
||||
"] (expect string)" + at(*nv.node()));
|
||||
return false;
|
||||
}
|
||||
*out = nv.value_or(std::string());
|
||||
return true;
|
||||
}
|
||||
fail(err, std::string("missing field '") + key + "' in [" + sec + "]");
|
||||
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]) {
|
||||
if (!nv.is_integer()) {
|
||||
fail(err, std::string("invalid value for '") + key + "' in [" + sec +
|
||||
"] (expect integer)" + at(*nv.node()));
|
||||
return false;
|
||||
}
|
||||
const int64_t v = nv.value_or<int64_t>(0);
|
||||
if (v <= 0) {
|
||||
fail(err, std::string("invalid value for '") + key + "' in [" + sec +
|
||||
"] (expect > 0)" + at(*nv.node()));
|
||||
return false;
|
||||
}
|
||||
*out = static_cast<uint32_t>(v);
|
||||
return true;
|
||||
}
|
||||
fail(err, std::string("missing field '") + key + "' in [" + sec + "]");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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]) {
|
||||
if (!nv.is_integer()) {
|
||||
fail(err, std::string("invalid value for '") + key + "' in [" + sec +
|
||||
"] (expect integer)" + at(*nv.node()));
|
||||
return false;
|
||||
}
|
||||
const int64_t v = nv.value_or<int64_t>(0);
|
||||
if (v < 0) {
|
||||
fail(err, std::string("invalid value for '") + key + "' in [" + sec +
|
||||
"] (expect >= 0)" + at(*nv.node()));
|
||||
return false;
|
||||
}
|
||||
*out = static_cast<uint32_t>(v);
|
||||
return true;
|
||||
}
|
||||
fail(err, std::string("missing field '") + key + "' in [" + sec + "]");
|
||||
return 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) {
|
||||
for (const auto& [key, node] : tbl) {
|
||||
bool ok = false;
|
||||
for (size_t i = 0; i < n_allowed; ++i) {
|
||||
if (key == allowed[i]) {
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
fail(err, std::string("unknown key '") + std::string(key) + "' in [" +
|
||||
sec + "]" + at(node));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- [project] ----
|
||||
|
||||
/**
|
||||
* @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"};
|
||||
|
||||
if (const auto nv = root["project"]) {
|
||||
if (!nv.is_table()) {
|
||||
fail(err, "invalid value for 'project' (expect table)" + at(*nv.node()));
|
||||
return false;
|
||||
}
|
||||
const toml::table& sec = *nv.as_table();
|
||||
if (!check_keys(sec, "project", allowed, 4, err)) {
|
||||
return false;
|
||||
}
|
||||
if (!req_string(sec, "project", "name", &out->name, err)) {
|
||||
return false;
|
||||
}
|
||||
if (!req_string(sec, "project", "entry", &out->entry, err)) {
|
||||
return false;
|
||||
}
|
||||
if (out->entry != "program MAIN") {
|
||||
fail(err, "invalid value for 'entry' in [project] (expect \"program MAIN\")" +
|
||||
at(*sec["entry"].node()));
|
||||
return false;
|
||||
}
|
||||
if (!req_pos_int(sec, "project", "cycle_limit", &out->cycle_limit, err)) {
|
||||
return false;
|
||||
}
|
||||
if (!req_pos_int(sec, "project", "dt_ms", &out->dt_ms, err)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
fail(err, "missing field 'project' in <root>");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- [files] ----
|
||||
|
||||
/**
|
||||
* @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"};
|
||||
|
||||
if (const auto nv = root["files"]) {
|
||||
if (!nv.is_table()) {
|
||||
fail(err, "invalid value for 'files' (expect table)" + at(*nv.node()));
|
||||
return false;
|
||||
}
|
||||
const toml::table& sec = *nv.as_table();
|
||||
if (!check_keys(sec, "files", allowed, 1, err)) {
|
||||
return false;
|
||||
}
|
||||
if (const auto st = sec["st"]) {
|
||||
if (!st.is_array()) {
|
||||
fail(err, "invalid value for 'st' in [files] (expect array)" + at(*st.node()));
|
||||
return false;
|
||||
}
|
||||
const toml::array& arr = *st.as_array();
|
||||
if (arr.empty()) {
|
||||
fail(err, "invalid value for 'st' in [files] (expect non-empty array)");
|
||||
return false;
|
||||
}
|
||||
for (const auto& el : arr) {
|
||||
if (!el.is_string()) {
|
||||
fail(err, "invalid value in 'st' in [files] (expect array of strings)" +
|
||||
at(el));
|
||||
return false;
|
||||
}
|
||||
out->files_st.push_back(el.value_or(std::string()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
fail(err, "missing field 'st' in [files]");
|
||||
return false;
|
||||
}
|
||||
fail(err, "missing field 'files' in <root>");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- [gvl](可选)----
|
||||
|
||||
/**
|
||||
* @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"};
|
||||
|
||||
if (const auto nv = root["gvl"]) {
|
||||
if (!nv.is_table()) {
|
||||
fail(err, "invalid value for 'gvl' (expect table)" + at(*nv.node()));
|
||||
return false;
|
||||
}
|
||||
const toml::table& sec = *nv.as_table();
|
||||
if (!check_keys(sec, "gvl", allowed, 1, err)) {
|
||||
return false;
|
||||
}
|
||||
return req_string(sec, "gvl", "file", &out->gvl_file, err);
|
||||
}
|
||||
return true; // gvl 可选
|
||||
}
|
||||
|
||||
// ---- [[io.input]] / [[io.output]](可选)----
|
||||
|
||||
/**
|
||||
* @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"};
|
||||
|
||||
if (!el.is_table()) {
|
||||
fail(err, std::string("bad entry in [[io.") + (is_input ? "input" : "output") +
|
||||
"]] (expect table)" + at(el));
|
||||
return false;
|
||||
}
|
||||
const toml::table& sec = *el.as_table();
|
||||
if (!check_keys(sec, is_input ? "io.input" : "io.output", allowed, 3, err)) {
|
||||
return false;
|
||||
}
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
if (!req_uint(sec, is_input ? "io.input" : "io.output", "channel", &b.channel, err)) {
|
||||
return false;
|
||||
}
|
||||
if (!req_uint(sec, is_input ? "io.input" : "io.output", "bit", &b.bit, err)) {
|
||||
return false;
|
||||
}
|
||||
out->io.push_back(b);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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"};
|
||||
|
||||
if (const auto nv = root["io"]) {
|
||||
if (!nv.is_table()) {
|
||||
fail(err, "invalid value for 'io' (expect table)" + at(*nv.node()));
|
||||
return false;
|
||||
}
|
||||
const toml::table& sec = *nv.as_table();
|
||||
if (!check_keys(sec, "io", allowed, 2, err)) {
|
||||
return false;
|
||||
}
|
||||
for (const char* which : {"input", "output"}) {
|
||||
if (const auto arr = sec[which]) {
|
||||
if (!arr.is_array()) {
|
||||
fail(err, std::string("invalid value for '") + which +
|
||||
"' in [io] (expect array of tables)" + at(*arr.node()));
|
||||
return false;
|
||||
}
|
||||
const bool is_input = (which[0] == 'i');
|
||||
for (const auto& el : *arr.as_array()) {
|
||||
if (!parse_io_entry(el, is_input, out, err)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true; // io 可选
|
||||
}
|
||||
|
||||
// ---- 顶层 ----
|
||||
|
||||
/**
|
||||
* @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)) {
|
||||
return false;
|
||||
}
|
||||
if (!parse_project_section(root, out, err)) {
|
||||
return false;
|
||||
}
|
||||
if (!parse_files_section(root, out, err)) {
|
||||
return false;
|
||||
}
|
||||
if (!parse_gvl_section(root, out, err)) {
|
||||
return false;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
} // 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);
|
||||
|
||||
// toml++ 默认 TOML_EXCEPTIONS=1:parse_file 失败直接抛 parse_error
|
||||
toml::table tbl;
|
||||
try {
|
||||
tbl = toml::parse_file(toml_path);
|
||||
} catch (const toml::parse_error& e) {
|
||||
fail(err, std::string("parse error: ") + std::string(e.description()) +
|
||||
at_pos(e.source().begin));
|
||||
return false;
|
||||
}
|
||||
return parse_root(tbl, out, 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() &&
|
||||
std::find(out.begin(), out.end(), p.gvl_file) == out.end()) {
|
||||
out.push_back(p.gvl_file);
|
||||
}
|
||||
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);
|
||||
|
||||
// 路径排序(规格:路径只当排序键)
|
||||
std::vector<std::string> sorted = files;
|
||||
std::sort(sorted.begin(), sorted.end());
|
||||
|
||||
std::filesystem::path base(p.base_dir);
|
||||
uint64_t h = kFnvBasis;
|
||||
for (const std::string& f : sorted) {
|
||||
std::ifstream in(base / f, std::ios::binary);
|
||||
if (!in) {
|
||||
fail(err, "file missing: " + (base / f).string());
|
||||
return false;
|
||||
}
|
||||
char buf[4096];
|
||||
while (in) {
|
||||
in.read(buf, sizeof buf);
|
||||
const std::streamsize n = in.gcount();
|
||||
if (n > 0) {
|
||||
h = fnv1a64_update(h, reinterpret_cast<const uint8_t*>(buf),
|
||||
static_cast<size_t>(n));
|
||||
}
|
||||
}
|
||||
}
|
||||
*hash = h;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
@@ -0,0 +1,502 @@
|
||||
/**
|
||||
* @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 + kOffMagic) != kMagic) {
|
||||
v.err_ = "bad magic";
|
||||
return v;
|
||||
}
|
||||
if (get_le32(buf + kOffVersion) != kVersion) {
|
||||
v.err_ = "bad version";
|
||||
return v;
|
||||
}
|
||||
v.cycle_limit_ = get_le32(buf + kOffCycleLimit);
|
||||
v.dt_ms_ = get_le32(buf + kOffDtMs);
|
||||
v.project_hash_ = get_le64(buf + kOffProjectHash);
|
||||
v.entry_fn_id_ = get_le32(buf + kOffEntryFnId);
|
||||
v.n_globals_ = get_le32(buf + kOffNGlobals);
|
||||
v.n_consts_ = get_le32(buf + kOffNConsts);
|
||||
v.n_funcs_ = get_le32(buf + kOffNFuncs);
|
||||
v.offset_code_ = get_le32(buf + kOffCode);
|
||||
v.offset_slots_ = get_le32(buf + kOffSlots);
|
||||
v.offset_values_ = get_le32(buf + kOffValues);
|
||||
v.n_slots_ = get_le32(buf + kOffNSlots);
|
||||
v.values_size_ = get_le32(buf + kOffValuesSize);
|
||||
v.offset_meta_ = get_le32(buf + kOffMeta);
|
||||
v.max_stack_ = get_le32(buf + kOffMaxStack);
|
||||
|
||||
// 段校验(V2:值段之后是 SHA-256 文件尾;元数据段 v1 为 0 = 空段)
|
||||
if (len < static_cast<size_t>(v.offset_values_) + kSha256Size) {
|
||||
v.err_ = "missing sha256 tail";
|
||||
return v;
|
||||
}
|
||||
const uint64_t offs[5] = {get_le32(buf + kOffConst), get_le32(buf + kOffFuncs), v.offset_code_,
|
||||
v.offset_slots_, v.offset_values_};
|
||||
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 (offs[4] - offs[3] != static_cast<uint64_t>(v.n_slots_) * kSlotRowSize) {
|
||||
v.err_ = "slot table size mismatch";
|
||||
return v;
|
||||
}
|
||||
// 值段大小:meta 段为 0(空段)时值段到 SHA 尾;否则到 meta 起点
|
||||
if (v.offset_meta_ != 0) {
|
||||
if (static_cast<uint64_t>(v.offset_meta_) - offs[4] != v.values_size_) {
|
||||
v.err_ = "values segment size mismatch";
|
||||
return v;
|
||||
}
|
||||
} else if (static_cast<uint64_t>(len - kSha256Size) - offs[4] != v.values_size_) {
|
||||
v.err_ = "values segment size mismatch";
|
||||
return v;
|
||||
}
|
||||
if ((v.offset_slots_ - v.offset_code_) % 4 != 0) {
|
||||
v.err_ = "code segment not 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.value = get_le64(p + kConstValueOff);
|
||||
}
|
||||
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 + kFuncNregsOff);
|
||||
r.code_offset = get_le32(p + kFuncCodeOff);
|
||||
r.code_len = get_le32(p + kFuncLenOff);
|
||||
}
|
||||
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_slots_ - offset_code_ : 0;
|
||||
}
|
||||
|
||||
/// 数据段起点(未 ok() 时返回 nullptr)。
|
||||
const uint8_t* StbView::data_bytes() const {
|
||||
return ok_ ? buf_ + offset_values_ : nullptr;
|
||||
}
|
||||
|
||||
/// 数据段字节数(不含文件尾 SHA-256;未 ok() 时返回 0)。
|
||||
size_t StbView::data_len() const {
|
||||
return ok_ ? (len_ - kSha256Size) - offset_values_ : 0;
|
||||
}
|
||||
|
||||
/// 型号标识字符串(头 72..103,截断到首个 '\0';未 ok() 时返回空串)。
|
||||
const uint8_t* StbView::slots_bytes() const {
|
||||
return ok_ ? buf_ + offset_slots_ : nullptr;
|
||||
}
|
||||
|
||||
std::string StbView::model_id() const {
|
||||
if (!ok_) {
|
||||
return "";
|
||||
}
|
||||
std::string s(reinterpret_cast<const char*>(buf_ + kOffModelId), 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_ + kOffModelId, 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,690 @@
|
||||
/**
|
||||
* @file Typecheck.cpp
|
||||
* @brief 类型检查(V2)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 设计说明(详见 Doc/compiler/类型检查.md 与阶段3-语言层V2.md):
|
||||
* - 在链接成功后运行:符号/字段/函数存在性已由 12.6 保证,本阶段只查类型
|
||||
* - 19 种 IEC 标量类型 + Unresolved(字面量未定型);**禁隐式转换**
|
||||
* (赋值/算术/比较严格同型)
|
||||
* - 字面量机制:整数字面量 / 浮点字面量未定型(Unresolved),由消费点
|
||||
* (赋值目标/实参/条件/另一操作数)按上下文定型 + **值域检查**(冲突报错);
|
||||
* TRUE/FALSE(BOOL)、T#(TIME)、D#/TOD#/DT#(日期)字面量类型固定
|
||||
* - BOOL 编译器强制 0/1;IF/WHILE 条件必须 BOOL
|
||||
* - 错误:稳定前缀 "type error" + 文件
|
||||
*
|
||||
* 函数清单:
|
||||
* - tname / tname_lower TType ↔ 文本名(19 + Unresolved)
|
||||
* - to_type(string) 符号 type_name 字符串 → TType
|
||||
* - Checker::Checker (构造)存工程/源文件/链接结果/机器配置/错误输出
|
||||
* - Checker::run 逐 POU 逐语句检查
|
||||
* - fail 组装 "type error: <msg> (file)" 返回 false
|
||||
* - resolve_to 表达式定型:未定型 → 值域检查后按目标定型;
|
||||
* 已定型 → 必须与目标相同(禁隐式转换)
|
||||
* - literal_range_ok 字面量按目标类型的值域检查(宽度/符号/BOOL 0/1)
|
||||
* - arith_allowed 目标类型是否允许算术(int/uint/float 四则;
|
||||
* TIME 仅 Add/Sub;其他拒绝)
|
||||
* - check_stmt 语句规则分发(赋值/FB 调用/IF/WHILE)
|
||||
* - assign_target_type 解析赋值左值类型(局部/全局/函数名)
|
||||
* - check_cond 条件必须 BOOL(未定型按 BOOL 定型)
|
||||
* - check_expr 表达式定类型(递归)
|
||||
* - check_project 对外入口:逐 POU 检查
|
||||
*/
|
||||
|
||||
#include "compiler/Typecheck.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "compiler/TypeInfo.h"
|
||||
|
||||
namespace compiler {
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief TType → 文本名(错误报文用,大写)。
|
||||
* @param t 求值类型
|
||||
* @return 如 "DINT";Unresolved 返回 "?";未知返回 "?"
|
||||
*/
|
||||
const char* tname(TType t) {
|
||||
switch (t) {
|
||||
case TType::Bool: return "BOOL";
|
||||
case TType::Byte: return "BYTE";
|
||||
case TType::Word: return "WORD";
|
||||
case TType::Dword: return "DWORD";
|
||||
case TType::Lword: return "LWORD";
|
||||
case TType::Sint: return "SINT";
|
||||
case TType::Int: return "INT";
|
||||
case TType::Dint: return "DINT";
|
||||
case TType::Lint: return "LINT";
|
||||
case TType::Usint: return "USINT";
|
||||
case TType::Uint: return "UINT";
|
||||
case TType::Udint: return "UDINT";
|
||||
case TType::Ulint: return "ULINT";
|
||||
case TType::Time: return "TIME";
|
||||
case TType::Real: return "REAL";
|
||||
case TType::Lreal: return "LREAL";
|
||||
case TType::Date: return "DATE";
|
||||
case TType::Tod: return "TOD";
|
||||
case TType::Dt: return "DT";
|
||||
case TType::Unresolved: return "?";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief TType → 小写类型名(machine.toml type 表键)。
|
||||
* @param t 求值类型
|
||||
* @return 如 "dint";Unresolved/未知返回空串
|
||||
*/
|
||||
const char* tname_lower(TType t) {
|
||||
switch (t) {
|
||||
case TType::Bool: return "bool";
|
||||
case TType::Byte: return "byte";
|
||||
case TType::Word: return "word";
|
||||
case TType::Dword: return "dword";
|
||||
case TType::Lword: return "lword";
|
||||
case TType::Sint: return "sint";
|
||||
case TType::Int: return "int";
|
||||
case TType::Dint: return "dint";
|
||||
case TType::Lint: return "lint";
|
||||
case TType::Usint: return "usint";
|
||||
case TType::Uint: return "uint";
|
||||
case TType::Udint: return "udint";
|
||||
case TType::Ulint: return "ulint";
|
||||
case TType::Time: return "time";
|
||||
case TType::Real: return "real";
|
||||
case TType::Lreal: return "lreal";
|
||||
case TType::Date: return "date";
|
||||
case TType::Tod: return "tod";
|
||||
case TType::Dt: return "dt";
|
||||
case TType::Unresolved: return "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 符号 type_name 字符串 → TType(19 标量)。
|
||||
* @param name 小写类型名("bool".."dt")
|
||||
* @param out 输出求值类型
|
||||
* @return true 标量;未知(FB 类型名等)返回 false
|
||||
*/
|
||||
bool to_type(const std::string& name, TType* out) {
|
||||
for (int i = 0; i < 19; ++i) {
|
||||
const TType t = static_cast<TType>(i);
|
||||
if (name == tname_lower(t)) {
|
||||
*out = t;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 类型检查器(V2)
|
||||
*
|
||||
* @details 链接结果只读;不修改 AST 与符号表。
|
||||
*/
|
||||
class Checker {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造检查器
|
||||
* @param proj 工程定义(未用,保留接口对称)
|
||||
* @param units 全部源文件的 AST
|
||||
* @param link 链接结果(符号/布局已解析)
|
||||
* @param cfg 机器定义(类型元数据/值域来源)
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
*/
|
||||
Checker(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
const LinkResult& link, const MachineConfig& cfg, std::string* err)
|
||||
: proj_(proj), units_(units), link_(link), cfg_(cfg), 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
|
||||
* @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;
|
||||
}
|
||||
|
||||
const LinkResult::PouScope* find_scope(const std::string& name) const {
|
||||
for (const LinkResult::PouScope& sc : link_.scopes) {
|
||||
if (sc.name == name) {
|
||||
return ≻
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
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 e 表达式(已 check_expr,got 为其求值类型)
|
||||
* @param got 表达式当前类型(Unresolved = 字面量未定型)
|
||||
* @param want 目标类型(消费点)
|
||||
* @return true 定型成功;false(类型不匹配 / 字面量越域)
|
||||
*/
|
||||
bool resolve_to(const std::string& file, const Expr& e, TType got, TType want) {
|
||||
if (got == want) {
|
||||
return true;
|
||||
}
|
||||
if (got != TType::Unresolved) {
|
||||
return fail(file, "type mismatch: " + std::string(tname(got)) +
|
||||
" vs " + std::string(tname(want)));
|
||||
}
|
||||
// 未定型字面量 → 按目标类型值域检查后定型
|
||||
return literal_range_ok(file, e, want);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 字面量按目标类型的值域检查。
|
||||
* @details LitInt:整型目标按宽度/符号检查(BOOL 0/1、无符号负数拒绝、
|
||||
* 溢出拒绝);REAL/LREAL 目标合法。LitReal:仅 REAL/LREAL 目标
|
||||
* (REAL 检查 ±3.4e38);整数目标拒绝。
|
||||
* @return true 值域内;false(err 已写)
|
||||
*/
|
||||
bool literal_range_ok(const std::string& file, const Expr& e, TType want) {
|
||||
// 一元负 + 字面量:折叠负号后按负值检查(-1 赋无符号 → 越域)
|
||||
if (e.kind == ExprKind::Neg && e.operand != nullptr &&
|
||||
(e.operand->kind == ExprKind::LitInt ||
|
||||
e.operand->kind == ExprKind::LitReal)) {
|
||||
Expr folded;
|
||||
folded.kind = e.operand->kind;
|
||||
if (e.operand->kind == ExprKind::LitInt) {
|
||||
folded.int_value = -e.operand->int_value;
|
||||
} else {
|
||||
folded.double_value = -e.operand->double_value;
|
||||
}
|
||||
return literal_range_ok(file, folded, want);
|
||||
}
|
||||
if (e.kind == ExprKind::LitInt) {
|
||||
if (want == TType::Real || want == TType::Lreal) {
|
||||
return true; // 整数字面量 → 浮点目标合法
|
||||
}
|
||||
const TypeMeta tm = type_meta(cfg_, tname_lower(want));
|
||||
if (!tm) {
|
||||
return fail(file, "unknown target type for literal");
|
||||
}
|
||||
if (tm.kind() == "bool") {
|
||||
if (e.int_value != 0 && e.int_value != 1) {
|
||||
return fail(file, "BOOL literal must be 0 or 1");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!tm.is_signed() && e.int_value < 0) {
|
||||
return fail(file, "negative literal for unsigned type " +
|
||||
std::string(tname(want)));
|
||||
}
|
||||
const int w = static_cast<int>(tm.width());
|
||||
if (tm.is_signed()) {
|
||||
const int64_t minv =
|
||||
(w == 8) ? INT64_MIN : -(static_cast<int64_t>(1) << (8 * w - 1));
|
||||
const int64_t maxv =
|
||||
(w == 8) ? INT64_MAX : (static_cast<int64_t>(1) << (8 * w - 1)) - 1;
|
||||
if (e.int_value < minv || e.int_value > maxv) {
|
||||
return fail(file, "integer literal out of range for " +
|
||||
std::string(tname(want)));
|
||||
}
|
||||
} else {
|
||||
const uint64_t maxv =
|
||||
(w == 8) ? UINT64_MAX : ((static_cast<uint64_t>(1) << (8 * w)) - 1);
|
||||
if (static_cast<uint64_t>(e.int_value) > maxv) {
|
||||
return fail(file, "integer literal out of range for " +
|
||||
std::string(tname(want)));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (e.kind == ExprKind::LitReal) {
|
||||
if (want == TType::Real) {
|
||||
if (std::fabs(e.double_value) > 3.40282347e38) {
|
||||
return fail(file, "float literal out of range for REAL");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (want == TType::Lreal) {
|
||||
return true;
|
||||
}
|
||||
return fail(file, "float literal cannot be assigned to " +
|
||||
std::string(tname(want)));
|
||||
}
|
||||
return true; // 其他 Unresolved(不应发生)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 目标类型是否允许四则算术。
|
||||
* @param file 出错文件
|
||||
* @param t 目标类型(已定型)
|
||||
* @param is_add_sub 是否 Add/Sub(TIME 允许 ±,不允许 ×/÷)
|
||||
* @return true 允许;false(err 已写)
|
||||
*/
|
||||
bool arith_allowed(const std::string& file, TType t, bool is_add_sub) {
|
||||
if (t == TType::Unresolved) {
|
||||
return true; // 两侧都是字面量,向上传播
|
||||
}
|
||||
const TypeMeta tm = type_meta(cfg_, tname_lower(t));
|
||||
if (!tm) {
|
||||
return fail(file, "arithmetic on non-scalar type");
|
||||
}
|
||||
const std::string& kind = tm.kind();
|
||||
if (kind == "int" || kind == "uint" || kind == "float") {
|
||||
return true;
|
||||
}
|
||||
if (kind == "time") {
|
||||
if (is_add_sub) {
|
||||
return true;
|
||||
}
|
||||
return fail(file, "TIME has no multiply/divide");
|
||||
}
|
||||
return fail(file, std::string(tname(t)) + " has no arithmetic");
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 (!resolve_to(file, *st.value, rhs, lhs)) {
|
||||
return false;
|
||||
}
|
||||
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_name, &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 (!resolve_to(file, *a.value, got, want)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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 共用;未定型按 BOOL 定型)。
|
||||
*/
|
||||
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 (!resolve_to(file, e, t, TType::Bool)) {
|
||||
return fail(file, "condition must be BOOL");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析赋值左值类型。
|
||||
* @details 普通左值:局部/输入/输出/外部/全局 → 符号类型;
|
||||
* FUNCTION 内函数名 → 返回类型;**FUNCTION 内左值是全局/外部 → 拒绝**。
|
||||
*/
|
||||
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.name, 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 表达式定类型(递归;V2 字面量未定型机制)。
|
||||
*/
|
||||
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:
|
||||
case ExprKind::LitReal:
|
||||
*out = TType::Unresolved; // 未定型,消费点定型 + 值域检查
|
||||
return true;
|
||||
case ExprKind::LitTime:
|
||||
*out = TType::Time;
|
||||
return true;
|
||||
case ExprKind::LitDate:
|
||||
*out = TType::Date;
|
||||
return true;
|
||||
case ExprKind::LitTod:
|
||||
*out = TType::Tod;
|
||||
return true;
|
||||
case ExprKind::LitDt:
|
||||
*out = TType::Dt;
|
||||
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_name, 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 (!resolve_to(file, *e.operand, 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;
|
||||
}
|
||||
const char* op = (e.kind == ExprKind::And) ? "AND" : "OR";
|
||||
// 两侧都是未定型字面量 → BOOL 逻辑(TRUE/FALSE 或 0/1)
|
||||
if (l == TType::Unresolved && r == TType::Unresolved) {
|
||||
if (!resolve_to(file, *e.lhs, l, TType::Bool) ||
|
||||
!resolve_to(file, *e.rhs, r, TType::Bool)) {
|
||||
return fail(file, std::string(op) + " operands must be BOOL");
|
||||
}
|
||||
*out = TType::Bool;
|
||||
return true;
|
||||
}
|
||||
// 同型定型;允许 BOOL(逻辑)或位串(按宽度逐位)
|
||||
const TType want = (l == TType::Unresolved) ? r : l;
|
||||
if (!resolve_to(file, *e.lhs, l, want) ||
|
||||
!resolve_to(file, *e.rhs, r, want)) {
|
||||
return fail(file, std::string(op) + " operands must have same type");
|
||||
}
|
||||
const TypeMeta tm = type_meta(cfg_, tname_lower(want));
|
||||
if (want != TType::Bool && (!tm || tm.kind() != "bit")) {
|
||||
return fail(file, std::string(op) + " operands must be BOOL or bit-string");
|
||||
}
|
||||
*out = want;
|
||||
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;
|
||||
}
|
||||
// 比较同型:一侧未定型 → 按另一侧定型;两侧未定型 → 结果 BOOL
|
||||
if (l == TType::Unresolved && r == TType::Unresolved) {
|
||||
*out = TType::Bool;
|
||||
return true;
|
||||
}
|
||||
const TType want = (l == TType::Unresolved) ? r : l;
|
||||
if (!resolve_to(file, *e.lhs, l, want) ||
|
||||
!resolve_to(file, *e.rhs, r, want)) {
|
||||
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: {
|
||||
const bool is_add_sub =
|
||||
(e.kind == ExprKind::Add || e.kind == ExprKind::Sub);
|
||||
TType l, r;
|
||||
if (!check_expr(file, sc, *e.lhs, &l) || !check_expr(file, sc, *e.rhs, &r)) {
|
||||
return false;
|
||||
}
|
||||
// 同型定型:一侧未定型 → 按另一侧
|
||||
TType t = (l == TType::Unresolved) ? r : l;
|
||||
if (l == TType::Unresolved && r == TType::Unresolved) {
|
||||
t = TType::Unresolved; // 两侧字面量,向上传播
|
||||
} else if (!resolve_to(file, *e.lhs, l, t) ||
|
||||
!resolve_to(file, *e.rhs, r, t)) {
|
||||
return fail(file, "arithmetic operands must have same type (" +
|
||||
std::string(tname(l)) + " vs " +
|
||||
std::string(tname(r)) + ")");
|
||||
}
|
||||
if (!arith_allowed(file, t, is_add_sub)) {
|
||||
return false;
|
||||
}
|
||||
*out = t;
|
||||
return true;
|
||||
}
|
||||
case ExprKind::Neg: {
|
||||
TType t;
|
||||
if (!check_expr(file, sc, *e.operand, &t)) {
|
||||
return false;
|
||||
}
|
||||
if (t != TType::Unresolved) {
|
||||
const TypeMeta tm = type_meta(cfg_, tname_lower(t));
|
||||
const std::string& kind = tm ? tm.kind() : "";
|
||||
if (kind != "int" && kind != "uint" && kind != "float") {
|
||||
return fail(file, "unary minus operand must be numeric");
|
||||
}
|
||||
}
|
||||
*out = t;
|
||||
return true;
|
||||
}
|
||||
case ExprKind::Call: {
|
||||
const POU* f = find_pou(e.name);
|
||||
if (f == nullptr) {
|
||||
return fail(file, "undeclared function '" + e.name + "'");
|
||||
}
|
||||
// 实参类型 = 函数 VAR_INPUT 声明序
|
||||
std::vector<TType> params;
|
||||
for (const VarBlock& b : f->blocks) {
|
||||
if (b.section != VarSection::Input) {
|
||||
continue;
|
||||
}
|
||||
for (const VarDecl& d : b.vars) {
|
||||
TType pt;
|
||||
if (to_type(d.type.name, &pt)) {
|
||||
params.push_back(pt);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < e.args.size(); ++i) {
|
||||
TType at;
|
||||
if (!check_expr(file, sc, *e.args[i], &at)) {
|
||||
return false;
|
||||
}
|
||||
if (i < params.size()) {
|
||||
if (!resolve_to(file, *e.args[i], at, params[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!to_type(f->result_type.name, 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_; ///< 链接结果(只读)
|
||||
const MachineConfig& cfg_; ///< 机器定义(类型元数据/值域)
|
||||
std::string* err_; ///< 错误输出(可空)
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief 对工程做类型检查(对外入口,链接成功后调用;V2)
|
||||
* @param proj 工程定义
|
||||
* @param units 全部源文件的 AST
|
||||
* @param link 链接结果(符号/布局已解析)
|
||||
* @param cfg 机器定义(machine.toml)
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 全部通过;false 失败(err 前缀 "type error")
|
||||
*/
|
||||
bool check_project(const Project& proj, const std::vector<SourceUnit>& units,
|
||||
const LinkResult& link, const MachineConfig& cfg, std::string* err) {
|
||||
Checker c(proj, units, link, cfg, err);
|
||||
return c.run();
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
@@ -0,0 +1,324 @@
|
||||
/**
|
||||
* @file main.cpp
|
||||
* @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> [-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"
|
||||
" --dump-map 编译后打印变量/槽映射(对照源码↔反汇编用)\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)\n", path, bytes.size());
|
||||
std::printf("header:\n");
|
||||
std::printf(" magic = 0x%08x # 魔数 \"STSC\"\n",
|
||||
static_cast<unsigned>(compiler::kMagic));
|
||||
std::printf(" version = %u # 格式版本(V2)\n",
|
||||
static_cast<unsigned>(compiler::kVersion));
|
||||
std::printf(" model = %-14s # 型号标识\n", v.model_id().c_str());
|
||||
std::printf(" cycle_limit = %-14u # 每周期指令数上限\n", v.cycle_limit());
|
||||
std::printf(" dt_ms = %-14u # 周期时长(毫秒)\n", v.dt_ms());
|
||||
std::printf(" hash = 0x%016llx # 工程哈希(FNV-1a 64)\n",
|
||||
static_cast<unsigned long long>(v.project_hash()));
|
||||
std::printf(" entry_fn_id = %-14u # 入口函数(MAIN)fn_id\n", v.entry_fn_id());
|
||||
std::printf(" n_globals = %-14u # 全局槽数\n", v.n_globals());
|
||||
std::printf(" n_i = %-14u # 输入槽数(保留恒 0)\n", 0u);
|
||||
std::printf(" n_q = %-14u # 输出槽数(保留恒 0)\n", 0u);
|
||||
std::printf(" n_m = %-14u # 中间槽数(保留恒 0)\n", 0u);
|
||||
std::printf(" n_consts = %-14u # 常量表条目数\n", v.n_consts());
|
||||
std::printf(" n_funcs = %-14u # 函数表行数\n", v.n_funcs());
|
||||
std::printf(" offset_const = %-9u # 常量表段偏移\n", v.offset_const());
|
||||
std::printf(" offset_funcs = %-9u # 函数表段偏移\n", v.offset_funcs());
|
||||
std::printf(" offset_code = %-9u # 字节码段偏移\n", v.offset_code());
|
||||
std::printf(" offset_slots = %-9u # 槽表段偏移\n", v.offset_slots());
|
||||
std::printf(" offset_values = %-8u # 值段偏移\n", v.offset_values());
|
||||
std::printf(" n_slots = %-14u # 槽表条目数\n", v.n_slots());
|
||||
std::printf(" values_size = %-14u # 值段字节数\n", v.values_size());
|
||||
std::printf(" offset_meta = %-14u # 元数据段偏移(0 = 空段)\n", v.offset_meta());
|
||||
std::printf(" max_stack = %-14u # 栈区字节数(编译期算)\n", v.max_stack());
|
||||
std::printf(" sha = %-14s # 文件尾 SHA-256 完整性\n",
|
||||
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);
|
||||
std::printf(" [%u] %llu\n", i,
|
||||
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;
|
||||
uint32_t pc = 0;
|
||||
while (pc < r.code_len) {
|
||||
char buf[64];
|
||||
const size_t ilen = compiler::instr_len(base + pc);
|
||||
compiler::disasm(cfg, base + pc, ilen, buf, sizeof buf);
|
||||
std::printf(" 0x%04x %s\n",
|
||||
v.offset_code() + r.code_offset + pc, buf);
|
||||
pc += static_cast<uint32_t>(ilen);
|
||||
}
|
||||
}
|
||||
|
||||
if (v.n_slots()) {
|
||||
std::printf("slots:\n");
|
||||
for (uint32_t i = 0; i < v.n_slots(); ++i) {
|
||||
const uint32_t addr =
|
||||
static_cast<uint32_t>(v.slots_bytes()[i * 8])
|
||||
| (static_cast<uint32_t>(v.slots_bytes()[i * 8 + 1]) << 8)
|
||||
| (static_cast<uint32_t>(v.slots_bytes()[i * 8 + 2]) << 16)
|
||||
| (static_cast<uint32_t>(v.slots_bytes()[i * 8 + 3]) << 24);
|
||||
std::printf(" [%u] addr=%u\n", i, addr);
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
usage();
|
||||
return 0;
|
||||
}
|
||||
if (std::strcmp(argv[1], "--help") == 0) {
|
||||
usage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 参数收集
|
||||
std::string machine_path;
|
||||
std::string out_path;
|
||||
bool disasm_mode = false;
|
||||
bool dump_map = false;
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
if (std::strcmp(argv[i], "--machine") == 0 && i + 1 < argc) {
|
||||
machine_path = argv[i + 1];
|
||||
++i;
|
||||
} else if (std::strcmp(argv[i], "-o") == 0 && i + 1 < argc) {
|
||||
out_path = argv[i + 1];
|
||||
++i;
|
||||
} else if (std::strcmp(argv[i], "--dump-map") == 0) {
|
||||
dump_map = true;
|
||||
}
|
||||
}
|
||||
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(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);
|
||||
|
||||
// 打印模式(无 -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;
|
||||
}
|
||||
|
||||
// 完整管线:读 .st → 链接 → 类型检查 → 寄存器码
|
||||
std::vector<compiler::SourceUnit> units;
|
||||
for (const std::string& f : files) {
|
||||
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));
|
||||
}
|
||||
compiler::LinkResult link;
|
||||
if (!compiler::link_project(proj, units, cfg, &link, &err)) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (!compiler::check_project(proj, units, link, cfg, &err)) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::vector<uint8_t> image;
|
||||
compiler::CodegenMap cmap;
|
||||
compiler::CodegenMap* cmap_ptr = dump_map ? &cmap : nullptr;
|
||||
if (!compiler::codegen_project(proj, units, link, cfg, &image, &err, cmap_ptr)) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (dump_map) {
|
||||
std::printf("map:\n");
|
||||
for (const auto& f : cmap.funcs) {
|
||||
std::printf(" function %s:\n", f.first.c_str());
|
||||
for (const auto& v : f.second) {
|
||||
std::printf(" %-24s %s\n", v.first.c_str(), v.second.c_str());
|
||||
}
|
||||
}
|
||||
for (const auto& g : cmap.globals) {
|
||||
std::printf(" global %-24s %s\n", g.first.c_str(), g.second.c_str());
|
||||
}
|
||||
for (const auto& i : cmap.instances) {
|
||||
std::printf(" instance %-24s %s\n", i.first.c_str(), i.second.c_str());
|
||||
}
|
||||
}
|
||||
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
@@ -2,7 +2,7 @@
|
||||
|
||||
给人看的样例工程,**不是** CMake 库。每个子目录是一份完整工程:`project.toml` + 若干 `.st`。
|
||||
|
||||
供 `host` 直接打开,用来跑通最小闭环。自动化断言放 `tests/`。
|
||||
供 `STCompiler` 编译、`BytecodeExecutor` 执行,用来跑通最小闭环。自动化断言放 `tests/`。
|
||||
|
||||
## line1
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# CMake最低版本
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
# 工程名、版本号、工程描述
|
||||
project(Executor
|
||||
VERSION 0.1
|
||||
DESCRIPTION "字节码执行器:加载 .stb + sidecar 跑扫描周期")
|
||||
|
||||
add_executable(BytecodeExecutor
|
||||
./src/main.cpp)
|
||||
|
||||
# 只链 vm + isa,不链 compiler
|
||||
target_link_libraries(BytecodeExecutor PRIVATE vm)
|
||||
@@ -0,0 +1,5 @@
|
||||
# executor
|
||||
|
||||
BytecodeExecutor 模块:可执行入口。CMake 目标:`BytecodeExecutor`(`EXECUTABLE`),链接 `vm` 和 `isa`,**不链 `compiler`**。读取 `.stb` 时校验型号标识与 SHA-256。
|
||||
|
||||
职责与边界见 [`doc/executor/执行器入口.md`](../doc/executor/执行器入口.md)。
|
||||
@@ -0,0 +1,365 @@
|
||||
/**
|
||||
* @file main.cpp
|
||||
* @brief BytecodeExecutor 可执行入口
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details 12.10:加载 .stb + sidecar,按扫描周期执行。
|
||||
* - `BytecodeExecutor <name>.stb [--cycles N] [--replay <file>] [--step] [--sidecar <path>]`
|
||||
* - 只链 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] [--sidecar <path>]\n"
|
||||
" 加载 .stb + sidecar(缺省 <name>.runtime.toml),按扫描周期执行\n"
|
||||
" --cycles N 跑 N 个周期(缺省 10)\n"
|
||||
" --replay <file> 读录制的 I 序列(每行按 io.input 顺序的 0/1)\n"
|
||||
" --step 单步:逐指令打印 pc/fn/反汇编/寄存器(跑 1 个周期)\n"
|
||||
" --sidecar <path> 显式指定 I/O 绑定文件(缺省同名推导)\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()[m.slot_addr(slot)] ? 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()[m.slot_addr(slot)] = 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) {
|
||||
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 / --sidecar <path>
|
||||
//(缺省 cycles=10、无回放、非单步、sidecar 同名推导)
|
||||
uint32_t cycles = 10;
|
||||
bool step_mode = false;
|
||||
std::string replay;
|
||||
std::string sidecar_override;
|
||||
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;
|
||||
} else if (std::strcmp(argv[i], "--sidecar") == 0 && i + 1 < argc) {
|
||||
sidecar_override = argv[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
// 读 .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>());
|
||||
}
|
||||
// 信任链(Machine::create 内统一完成):Image::from 结构校验 → SHA-256 → 型号匹配
|
||||
vm::Machine machine;
|
||||
if (!vm::Machine::create(bytes, &machine, &err)) {
|
||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// sidecar 路径:--sidecar 显式指定,否则同名推导 <name>.stb → <name>.runtime.toml
|
||||
std::string sidecar_path = sidecar_override.empty()
|
||||
? stb_path
|
||||
: sidecar_override;
|
||||
if (sidecar_override.empty()) {
|
||||
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_bytes(), machine.cur_instr_len(), 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,10 +0,0 @@
|
||||
# CMake最低版本
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
# 工程名、版本号、工程描述
|
||||
project(Host
|
||||
VERSION 0.1
|
||||
DESCRIPTION "可执行入口:编译并跑扫描周期")
|
||||
|
||||
add_executable(host
|
||||
./src/main.cpp)
|
||||
@@ -1,5 +0,0 @@
|
||||
# host
|
||||
|
||||
可执行入口。CMake 目标:`host`(`EXECUTABLE`),链接 `compiler` 和 `vm`。
|
||||
|
||||
职责与边界见 [`doc/host/宿主入口.md`](../doc/host/宿主入口.md)。
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* @file main.cpp
|
||||
* @brief host 可执行入口
|
||||
* @date 2026-08-19
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
std::printf("Stator host 0.1\n");
|
||||
return 0;
|
||||
}
|
||||
+4
-7
@@ -6,11 +6,8 @@ project(ISA
|
||||
VERSION 0.1
|
||||
DESCRIPTION "指令集架构")
|
||||
|
||||
# C/C++ 标准版本
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
|
||||
include_directories(./include)
|
||||
|
||||
add_library(isa STATIC
|
||||
./src/Encode.cpp)
|
||||
./src/Encode.cpp
|
||||
./src/Op.cpp)
|
||||
|
||||
target_include_directories(isa PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
+23
-113
@@ -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
|
||||
host → compiler → isa
|
||||
host → 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,138 +17,48 @@ host → 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.hpp # 宽度、饱和、槽值
|
||||
Op.hpp # 操作码枚举 + 助记符声明
|
||||
Instr.hpp # 32-bit 打包/拆字段(inline)
|
||||
Encode.hpp # encode / decode / disasm
|
||||
Image.hpp # 头、段、FNV、读写缓冲
|
||||
Types.h # 宽度、饱和
|
||||
Op.h # 操作码枚举 + OpFormat/OpClass + kOpDefs 表 + 助记符
|
||||
Instr.h # 32-bit 打包/拆字段(inline)
|
||||
Encode.h # encode / decode / disasm
|
||||
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` / `host`。这三个目录若没有 `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`
|
||||
- `host`:`main` 打印 `Stator 0.1`,链 `compiler`、`vm`
|
||||
- 根 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.hpp`
|
||||
|
||||
`include/isa/Types.hpp`,inline,无对应 `.cpp`。命名空间 `isa`。C++11,不用 designated initializer。
|
||||
|
||||
- `Bool` = `uint8_t`,`Int` = `int16_t`,`Time` = `int32_t`(毫秒)
|
||||
- `TypeTag`:`Bool` / `Int` / `Time`
|
||||
- `sat_add` / `sat_sub` / `sat_mul` / `sat_div`(规则见规范文)
|
||||
- `pack_bool` / `pack_int` / `pack_time` → 4 字节 cell;对应 unpack
|
||||
|
||||
完成:单独包含该头能编译。
|
||||
|
||||
---
|
||||
|
||||
### 3. `Op.hpp` + `Instr.hpp`
|
||||
|
||||
- `Op.hpp`:`enum Op : uint8_t`,取值与规范文一致;声明 `op_mnemonic(Op)`(表可放 `Encode.cpp`)
|
||||
- `Instr.hpp`:`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.hpp` + `Encode.cpp`
|
||||
|
||||
给 host 打印和测试往返:
|
||||
|
||||
- `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.hpp` + `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`
|
||||
|
||||
小端 `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` + 本目录 + 测试,再改两端
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @file Encode.h
|
||||
* @brief 指令编解码与反汇编(V2)
|
||||
* @details 给 VM/测试/STCompiler --disasm 使用;文本格式见 Doc/isa/指令与映像.md。
|
||||
* @author
|
||||
* @date 2026-08-19
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "isa/Instr.h"
|
||||
#include "isa/Op.h"
|
||||
|
||||
namespace isa {
|
||||
|
||||
/**
|
||||
* @brief 解码后的字段视图(通用字段;具体形态语义由调用方按 OpFormat 解释)。
|
||||
*/
|
||||
struct Decoded {
|
||||
const char* prefix = "?"; ///< 形态前缀("11"/"010001" 等)
|
||||
uint8_t op = 0; ///< 分区内编号
|
||||
uint8_t func = 0; ///< func 位段
|
||||
uint8_t rd = 0; ///< rd(23..16)
|
||||
uint8_t rs1 = 0; ///< rs1(31..24)
|
||||
uint8_t rs2 = 0; ///< rs2(39..32)
|
||||
uint32_t imm32 = 0; ///< imm/slot/fn_id(按形态)
|
||||
int32_t off_hi = 0; ///< SLOT_OFF 的 off32(87..56)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 解码:指令字节 → 字段视图。
|
||||
* @param code 指令字节
|
||||
* @param len 指令长度(instr_len 定)
|
||||
* @return 字段视图(含 prefix/op/func/寄存器与 32 位数值字段)
|
||||
*/
|
||||
Decoded decode(const uint8_t* code, size_t len);
|
||||
|
||||
/**
|
||||
* @brief 编码:字段视图 → 指令字节。
|
||||
* @param d 字段视图
|
||||
* @param out 输出缓冲(≥16 字节)
|
||||
* @return 指令长度
|
||||
* @details 满足 decode(encode(d)) 字段一致(encode 填 prefix/op/func/rd/rs1/rs2/imm32/off_hi)。
|
||||
*/
|
||||
size_t encode(const Decoded& d, uint8_t* out);
|
||||
|
||||
/**
|
||||
* @brief 反汇编一条指令为一行文本。
|
||||
* @param code 指令字节
|
||||
* @param len 指令长度
|
||||
* @param out 输出缓冲
|
||||
* @param cap 缓冲容量(含 '\0');cap==0 时直接返回、不写 out
|
||||
* @details 按形态表驱动输出(类型后缀:full/float/dst 类显示 .U8/.I16/.F32 等;
|
||||
* CAL 显示 fb 名如 `CAL.TON s5`);未知 (prefix,op) 输出 `??? 0x<hex>`。
|
||||
*/
|
||||
void disasm(const uint8_t* code, size_t len, char* out, size_t cap);
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
/**
|
||||
* @file Instr.h
|
||||
* @brief 变长指令字:前缀定长 + 位号读写 + 发射器(V2)
|
||||
* @details 执行器侧实现(compiler 侧 Codec 各自一份,位号一致)。
|
||||
* - 字节 0 = [长度前缀+形态分区 | op];字节 1 = [func:4 | 空:4]
|
||||
* - 前缀定长:`1`→4B、`01`→8B、`001`→16B、`0001`→32B
|
||||
* - 操作数区位号(bit16 起):rd=23..16、imm32/slot32=55..24(字节 3..6)、
|
||||
* off32(JMP)=47..16(字节 2..5)、off32_jc(JC)=55..24(字节 3..6)、
|
||||
* fn_id32=47..16、fb_id16=31..16、CAL slot32=63..32、
|
||||
* SLOT_OFF off32=87..56(字节 7..10)、imm64=87..24
|
||||
* @author
|
||||
* @date 2026-08-19
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "isa/Op.h"
|
||||
|
||||
namespace isa {
|
||||
|
||||
/**
|
||||
* @brief 前缀定长:读字节 0 判指令长度。
|
||||
* @param code 指令字节(至少 1 字节)
|
||||
* @return 4/8/16/32
|
||||
*/
|
||||
inline size_t instr_len(const uint8_t* code) {
|
||||
const uint8_t b = code[0];
|
||||
if (b & 0x80) return 4; // 1xxxxxxx
|
||||
if ((b & 0xC0) == 0x40) return 8; // 01xxxxxx
|
||||
if ((b & 0xE0) == 0x20) return 16; // 001xxxxx
|
||||
return 32; // 0001xxxx
|
||||
}
|
||||
|
||||
/// @brief 取分区内操作码(4B 6 位 / 8B 5 位或 2 位 / 16B·32B 4 位)。
|
||||
inline uint8_t op_of(const uint8_t* code) {
|
||||
const uint8_t b = code[0];
|
||||
if (b & 0x80) return b & 0x3F;
|
||||
if ((b & 0xC0) == 0x40) {
|
||||
return (b & 0x20) ? (b & 0x1F) : (b & 0x03);
|
||||
}
|
||||
return b & 0x0F;
|
||||
}
|
||||
|
||||
/// @brief 取 func 位段(字节 1 高 4 位)。
|
||||
inline uint8_t func_of(const uint8_t* code) {
|
||||
return static_cast<uint8_t>((code[1] >> 4) & 0x0F);
|
||||
}
|
||||
|
||||
/// @brief 取 rd(23..16 = 字节 2)。
|
||||
inline uint8_t rd_of(const uint8_t* code) { return code[2]; }
|
||||
/// @brief 取 rs1(31..24 = 字节 3)。
|
||||
inline uint8_t rs1_of(const uint8_t* code) { return code[3]; }
|
||||
/// @brief 取 rs2(39..32 = 字节 4)。
|
||||
inline uint8_t rs2_of(const uint8_t* code) { return code[4]; }
|
||||
|
||||
/// @brief 取 imm32(55..24 = 字节 3..6,小端)。
|
||||
inline uint32_t imm32_of(const uint8_t* code) {
|
||||
return static_cast<uint32_t>(code[3])
|
||||
| (static_cast<uint32_t>(code[4]) << 8)
|
||||
| (static_cast<uint32_t>(code[5]) << 16)
|
||||
| (static_cast<uint32_t>(code[6]) << 24);
|
||||
}
|
||||
|
||||
/// @brief 取 slot32(同 imm32 位号)。
|
||||
inline uint32_t slot32_of(const uint8_t* code) { return imm32_of(code); }
|
||||
|
||||
/// @brief 取 off32(47..16 = 字节 2..5,JMP 形态,有符号)。
|
||||
inline int32_t off32_of(const uint8_t* code) {
|
||||
return static_cast<int32_t>(static_cast<uint32_t>(code[2])
|
||||
| (static_cast<uint32_t>(code[3]) << 8)
|
||||
| (static_cast<uint32_t>(code[4]) << 16)
|
||||
| (static_cast<uint32_t>(code[5]) << 24));
|
||||
}
|
||||
|
||||
/// @brief 取 off32(55..24 = 字节 3..6,JC 形态,有符号)。
|
||||
inline int32_t off32_jc_of(const uint8_t* code) {
|
||||
return static_cast<int32_t>(static_cast<uint32_t>(code[3])
|
||||
| (static_cast<uint32_t>(code[4]) << 8)
|
||||
| (static_cast<uint32_t>(code[5]) << 16)
|
||||
| (static_cast<uint32_t>(code[6]) << 24));
|
||||
}
|
||||
|
||||
/// @brief 取 fn_id32(47..16 = 字节 2..5)。
|
||||
inline uint32_t fn_id32_of(const uint8_t* code) {
|
||||
return static_cast<uint32_t>(code[2])
|
||||
| (static_cast<uint32_t>(code[3]) << 8)
|
||||
| (static_cast<uint32_t>(code[4]) << 16)
|
||||
| (static_cast<uint32_t>(code[5]) << 24);
|
||||
}
|
||||
|
||||
/// @brief 取 fb_id16(31..16 = 字节 2..3)。
|
||||
inline uint16_t fb_id16_of(const uint8_t* code) {
|
||||
return static_cast<uint16_t>(code[2] | (static_cast<uint16_t>(code[3]) << 8));
|
||||
}
|
||||
|
||||
/// @brief 取 CAL 实例槽号(63..32 = 字节 4..7)。
|
||||
inline uint32_t cal_slot32_of(const uint8_t* code) {
|
||||
return static_cast<uint32_t>(code[4])
|
||||
| (static_cast<uint32_t>(code[5]) << 8)
|
||||
| (static_cast<uint32_t>(code[6]) << 16)
|
||||
| (static_cast<uint32_t>(code[7]) << 24);
|
||||
}
|
||||
|
||||
/// @brief 取 SLOT_OFF 的 off32(87..56 = 字节 7..10,有符号)。
|
||||
inline int32_t off32_hi_of(const uint8_t* code) {
|
||||
return static_cast<int32_t>(static_cast<uint32_t>(code[7])
|
||||
| (static_cast<uint32_t>(code[8]) << 8)
|
||||
| (static_cast<uint32_t>(code[9]) << 16)
|
||||
| (static_cast<uint32_t>(code[10]) << 24));
|
||||
}
|
||||
|
||||
/// @brief 取 imm64(87..24 = 字节 3..10)。
|
||||
inline uint64_t imm64_of(const uint8_t* code) {
|
||||
uint64_t v = 0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
v |= static_cast<uint64_t>(code[3 + i]) << (8 * i);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// ---- 发射器(写字节,调用方提供 ≥16 字节缓冲;返回指令长度)----
|
||||
|
||||
/// @brief 写字节 0(前缀 + 分区内 op)。
|
||||
inline void set_byte0(uint8_t* out, const char* prefix, uint8_t op) {
|
||||
if (prefix[0] == '1') {
|
||||
// 11:bit7..6 = 11;10:bit7..6 = 10
|
||||
out[0] = static_cast<uint8_t>((prefix[1] == '1' ? 0xC0 : 0x80) | (op & 0x3F));
|
||||
} else if (prefix[0] == '0') {
|
||||
if (prefix[1] == '1' && prefix[2] == '1') {
|
||||
out[0] = static_cast<uint8_t>(0x60 | (op & 0x1F)); // 011
|
||||
} else if (prefix[1] == '1') {
|
||||
// 010xxx:形态位 3 位 + 子 op 2 位
|
||||
const uint8_t shape = static_cast<uint8_t>(
|
||||
(prefix[3] - '0') * 4 + (prefix[4] - '0') * 2 + (prefix[5] - '0'));
|
||||
out[0] = static_cast<uint8_t>(0x40 | (shape << 2) | (op & 0x03));
|
||||
} else if (prefix[2] == '1') {
|
||||
out[0] = static_cast<uint8_t>(0x20 | (op & 0x0F)); // 0010/0011
|
||||
} else {
|
||||
out[0] = static_cast<uint8_t>(0x10 | (op & 0x0F)); // 0001
|
||||
}
|
||||
} else {
|
||||
out[0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief 写 func(字节 1 高 4 位)。
|
||||
inline void set_func(uint8_t* out, uint8_t func) {
|
||||
out[1] = static_cast<uint8_t>((out[1] & 0x0F) | ((func & 0x0F) << 4));
|
||||
}
|
||||
|
||||
inline void set_rd(uint8_t* out, uint8_t rd) { out[2] = rd; }
|
||||
inline void set_rs1(uint8_t* out, uint8_t rs1) { out[3] = rs1; }
|
||||
inline void set_rs2(uint8_t* out, uint8_t rs2) { out[4] = rs2; }
|
||||
|
||||
inline void set_imm32(uint8_t* out, uint32_t v) {
|
||||
out[3] = static_cast<uint8_t>(v & 0xFF);
|
||||
out[4] = static_cast<uint8_t>((v >> 8) & 0xFF);
|
||||
out[5] = static_cast<uint8_t>((v >> 16) & 0xFF);
|
||||
out[6] = static_cast<uint8_t>((v >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
inline void set_slot32(uint8_t* out, uint32_t v) { set_imm32(out, v); }
|
||||
|
||||
inline void set_off32(uint8_t* out, int32_t v) { // JMP:字节 2..5
|
||||
const uint32_t u = static_cast<uint32_t>(v);
|
||||
out[2] = static_cast<uint8_t>(u & 0xFF);
|
||||
out[3] = static_cast<uint8_t>((u >> 8) & 0xFF);
|
||||
out[4] = static_cast<uint8_t>((u >> 16) & 0xFF);
|
||||
out[5] = static_cast<uint8_t>((u >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
inline void set_off32_jc(uint8_t* out, int32_t v) { // JC:字节 3..6
|
||||
const uint32_t u = static_cast<uint32_t>(v);
|
||||
out[3] = static_cast<uint8_t>(u & 0xFF);
|
||||
out[4] = static_cast<uint8_t>((u >> 8) & 0xFF);
|
||||
out[5] = static_cast<uint8_t>((u >> 16) & 0xFF);
|
||||
out[6] = static_cast<uint8_t>((u >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
inline void set_fn_id32(uint8_t* out, uint32_t v) { set_off32(out, static_cast<int32_t>(v)); }
|
||||
|
||||
inline void set_fb_id16(uint8_t* out, uint16_t v) {
|
||||
out[2] = static_cast<uint8_t>(v & 0xFF);
|
||||
out[3] = static_cast<uint8_t>((v >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
inline void set_cal_slot32(uint8_t* out, uint32_t v) {
|
||||
out[4] = static_cast<uint8_t>(v & 0xFF);
|
||||
out[5] = static_cast<uint8_t>((v >> 8) & 0xFF);
|
||||
out[6] = static_cast<uint8_t>((v >> 16) & 0xFF);
|
||||
out[7] = static_cast<uint8_t>((v >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
inline void set_off32_hi(uint8_t* out, int32_t v) { // SLOT_OFF:字节 7..10
|
||||
const uint32_t u = static_cast<uint32_t>(v);
|
||||
out[7] = static_cast<uint8_t>(u & 0xFF);
|
||||
out[8] = static_cast<uint8_t>((u >> 8) & 0xFF);
|
||||
out[9] = static_cast<uint8_t>((u >> 16) & 0xFF);
|
||||
out[10] = static_cast<uint8_t>((u >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
inline void set_imm64(uint8_t* out, uint64_t v) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
out[3 + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 便捷编码(vm_test 手拼映像用;输出 ≥16 字节缓冲)----
|
||||
|
||||
/// @brief 编码 RR 形态(MOVE/NOT/NEG 等),返回指令长度。
|
||||
inline size_t enc_rr(uint8_t* out, const char* prefix, uint8_t op, uint8_t func,
|
||||
uint8_t rd, uint8_t rs) {
|
||||
set_byte0(out, prefix, op);
|
||||
set_func(out, func);
|
||||
set_rd(out, rd);
|
||||
set_rs1(out, rs);
|
||||
return instr_len(out);
|
||||
}
|
||||
|
||||
/// @brief 编码 RRR 形态,返回指令长度。
|
||||
inline size_t enc_rrr(uint8_t* out, const char* prefix, uint8_t op, uint8_t func,
|
||||
uint8_t rd, uint8_t ra, uint8_t rb) {
|
||||
set_byte0(out, prefix, op);
|
||||
set_func(out, func);
|
||||
set_rd(out, rd);
|
||||
set_rs1(out, ra);
|
||||
set_rs2(out, rb);
|
||||
return instr_len(out);
|
||||
}
|
||||
|
||||
/// @brief 编码 SLOT 形态(LOAD/STORE),返回指令长度。
|
||||
inline size_t enc_slot(uint8_t* out, const char* prefix, uint8_t op, uint8_t func,
|
||||
uint8_t rd, uint32_t slot) {
|
||||
set_byte0(out, prefix, op);
|
||||
set_func(out, func);
|
||||
set_rd(out, rd);
|
||||
set_slot32(out, slot);
|
||||
return instr_len(out);
|
||||
}
|
||||
|
||||
/// @brief 编码 IMM 形态(LOADK),返回指令长度。
|
||||
inline size_t enc_imm(uint8_t* out, const char* prefix, uint8_t op, uint8_t func,
|
||||
uint8_t rd, uint32_t imm) {
|
||||
set_byte0(out, prefix, op);
|
||||
set_func(out, func);
|
||||
set_rd(out, rd);
|
||||
set_imm32(out, imm);
|
||||
return instr_len(out);
|
||||
}
|
||||
|
||||
/// @brief 编码 JMP(010010 区),返回指令长度。
|
||||
inline size_t enc_jmp(uint8_t* out, int32_t off) {
|
||||
set_byte0(out, "010010", 0);
|
||||
set_func(out, 0);
|
||||
set_off32(out, off);
|
||||
return instr_len(out);
|
||||
}
|
||||
|
||||
/// @brief 编码 JC 形态(JT/JF,010011 区),返回指令长度。
|
||||
inline size_t enc_jc(uint8_t* out, uint8_t op, uint8_t rd, int32_t off) {
|
||||
set_byte0(out, "010011", op);
|
||||
set_func(out, 0);
|
||||
set_rd(out, rd);
|
||||
set_off32_jc(out, off);
|
||||
return instr_len(out);
|
||||
}
|
||||
|
||||
/// @brief 编码 CALL(010100 区),返回指令长度。
|
||||
inline size_t enc_call(uint8_t* out, uint32_t fn_id) {
|
||||
set_byte0(out, "010100", 0);
|
||||
set_func(out, 0);
|
||||
set_fn_id32(out, fn_id);
|
||||
return instr_len(out);
|
||||
}
|
||||
|
||||
/// @brief 编码 CAL(010101 区),返回指令长度。
|
||||
inline size_t enc_cal(uint8_t* out, uint16_t fb_id, uint32_t slot) {
|
||||
set_byte0(out, "010101", 0);
|
||||
set_func(out, 0);
|
||||
set_fb_id16(out, fb_id);
|
||||
set_cal_slot32(out, slot);
|
||||
return instr_len(out);
|
||||
}
|
||||
|
||||
/// @brief 编码 SLOT_OFF(LOAD_OFF/STORE_OFF,0010 区),返回指令长度。
|
||||
inline size_t enc_slot_off(uint8_t* out, uint8_t op, uint8_t func,
|
||||
uint8_t rd, uint32_t slot, int32_t off) {
|
||||
set_byte0(out, "0010", op);
|
||||
set_func(out, func);
|
||||
set_rd(out, rd);
|
||||
set_slot32(out, slot);
|
||||
set_off32_hi(out, off);
|
||||
return instr_len(out);
|
||||
}
|
||||
|
||||
/// @brief 编码 RS_OFF(LOAD_PTR/STORE_PTR,0010 区),返回指令长度。
|
||||
inline size_t enc_rs_off(uint8_t* out, uint8_t op, uint8_t func,
|
||||
uint8_t rd, uint8_t rs, int32_t off) {
|
||||
set_byte0(out, "0010", op);
|
||||
set_func(out, func);
|
||||
set_rd(out, rd);
|
||||
set_rs1(out, rs);
|
||||
set_off32_hi(out, off);
|
||||
return instr_len(out);
|
||||
}
|
||||
|
||||
/// @brief 编码 RET(10 区),返回指令长度。
|
||||
inline size_t enc_ret(uint8_t* out) {
|
||||
set_byte0(out, "10", static_cast<uint8_t>(Op::RET));
|
||||
set_func(out, 0);
|
||||
return instr_len(out);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* @file Op.h
|
||||
* @brief 操作码、前缀分区、形态与指令表(V2)
|
||||
* @details 执行器侧指令定义(vm 依赖本表;compiler 不链 isa,其登记在
|
||||
* compiler/machine.toml,与本表强校验一致)。
|
||||
* - 前缀分区(长度+形态一体):`11`=RR、`10`=NONE、`011`=RRR、
|
||||
* `010 000..101`=IMM/SLOT/JMP/JC/CALL/CAL、`0010`=SLOT+off、
|
||||
* `0011`=IMM64、`0001`=预留(32B)
|
||||
* - uses_rd/uses_ra/uses_rb:形态 → 哪些字段是寄存器(前缀直推,零查表)
|
||||
* - 指令表 kOps:与 machine.toml 72 条 op 行对应(prefix/op/fmt/func_mode)
|
||||
* @author
|
||||
* @date 2026-08-19
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace isa {
|
||||
|
||||
/// 指令最大长度(字节)。
|
||||
static constexpr int kMaxInstrLen = 16;
|
||||
|
||||
/**
|
||||
* @brief 操作码(分区内独立编号:MOVE=0、ADD=0、LOADK=0 等不冲突,靠前缀区分)。
|
||||
* @details 每个分区的 op 从 0 起;查表用 (prefix, op) 键。
|
||||
*/
|
||||
enum class Op : uint8_t {
|
||||
// 4B RR(前缀 11,op 6 位)
|
||||
MOVE = 0, NOT = 1, NEG = 2, ABS = 3, INC = 4, DEC = 5,
|
||||
SQRT = 6, SIN = 7, COS = 8, TAN = 9, ASIN = 10, ACOS = 11,
|
||||
ATAN = 12, LN = 13, LOG = 14, EXP = 15, ROUND = 16, TRUNC = 17,
|
||||
FLOOR = 18, CEIL = 19, CONV = 20,
|
||||
// 4B NONE(前缀 10,op 6 位)
|
||||
NOP = 0, RET = 1,
|
||||
// 8B RRR(前缀 011,op 5 位)
|
||||
ADD = 0, SUB = 1, MUL = 2, DIV = 3, MOD = 4, EXPT = 5,
|
||||
AND = 6, OR = 7, XOR = 8, NAND = 9, NOR = 10, XNOR = 11,
|
||||
SHL = 12, SHR = 13, ROL = 14, ROR = 15,
|
||||
CMP_EQ = 16, CMP_NE = 17, CMP_LT = 18, CMP_LE = 19,
|
||||
CMP_GT = 20, CMP_GE = 21,
|
||||
GETBIT = 22, SETBIT = 23, CLRBIT = 24, TOGBIT = 25,
|
||||
// 8B 010 区(形态位 3 + 子 op 2 位)
|
||||
LOADK = 0, LOAD = 0, STORE = 1, JMP = 0, JT = 0, JF = 1,
|
||||
CALL = 0, CAL = 0,
|
||||
// 16B(前缀 0010/0011,op 4 位)
|
||||
LOAD_OFF = 0, STORE_OFF = 1, STR_LEN = 2, STR_CMP = 3,
|
||||
STR_EQ = 4, STR_MID = 5, STR_LEFT = 6, STR_RIGHT = 7,
|
||||
LOAD_PTR = 8, STORE_PTR = 9,
|
||||
LOADK64 = 0,
|
||||
// 32B 预留(前缀 0001,op 4 位)
|
||||
JTBL = 0, STR_CONCAT = 1, STR_FIND = 2, STR_REPLACE = 3,
|
||||
ARR_LOAD = 4, ARR_STORE = 5,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 操作数形态(前缀分区的语义标签)。
|
||||
*/
|
||||
enum class OpFormat : uint8_t {
|
||||
RR, ///< rd, rs(4B)
|
||||
NONE, ///< 无操作数(4B)
|
||||
RRR, ///< rd, ra, rb(8B)
|
||||
IMM, ///< rd, imm32(8B)
|
||||
SLOT, ///< rd, slot32(8B)
|
||||
JMP, ///< off32(8B)
|
||||
JC, ///< rd, off32(8B)
|
||||
CALL, ///< fn_id32(8B)
|
||||
CAL, ///< fb_id16, slot32(8B)
|
||||
SLOT_OFF, ///< rd, slot32, off32(16B)
|
||||
RS_OFF, ///< rd, rs(基址寄存器), off32(16B)
|
||||
IMM64, ///< rd, imm64(16B)
|
||||
RESERVED, ///< 32B 预留
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief func 语义类别(与 machine.toml op 表一致)。
|
||||
*/
|
||||
enum class FuncMode : uint8_t {
|
||||
None, ///< 无类型(func 恒 0)
|
||||
Width, ///< MOVE 类:func 0..3 = 1/2/4/8B 宽度
|
||||
Uint, ///< 逻辑/位串类:func 0/2/4/6(无符号宽度)
|
||||
Full, ///< LOAD/STORE/算术/CMP:func 0..9 全矩阵
|
||||
Float, ///< 浮点单目/舍入:func 8/9
|
||||
Dst, ///< CONV:func = 目标类型
|
||||
};
|
||||
|
||||
/// 前缀字符串。
|
||||
extern const char* kPrefixNames[12];
|
||||
|
||||
/**
|
||||
* @brief 指令定义(与 machine.toml [[op]] 行对应)。
|
||||
*/
|
||||
struct OpDef {
|
||||
const char* name; ///< 助记符(大写,与配置一致)
|
||||
const char* prefix; ///< 形态前缀("11"/"010001" 等)
|
||||
Op op; ///< 分区内编号
|
||||
OpFormat format; ///< 操作数形态
|
||||
FuncMode func_mode; ///< func 语义类别
|
||||
};
|
||||
|
||||
/// 指令表(72 条,与 machine.toml 强校验一致)。
|
||||
extern const OpDef kOps[];
|
||||
|
||||
/// 指令表条数(= 72)。
|
||||
static const int kOpCount = 72;
|
||||
|
||||
/**
|
||||
* @brief 按 (prefix, op) 查指令。
|
||||
* @param prefix 形态前缀
|
||||
* @param op 分区内编号
|
||||
* @return 命中下标;未找到返回 -1
|
||||
*/
|
||||
int find_op(const char* prefix, uint8_t op);
|
||||
|
||||
/**
|
||||
* @brief 按助记符查指令。
|
||||
* @param name 助记符(如 "MOVE")
|
||||
* @return 命中下标;未找到返回 -1
|
||||
*/
|
||||
int find_op_by_name(const char* name);
|
||||
|
||||
/// @brief 指令名(助记符);未知返回 "???"。
|
||||
const char* mnemonic(const char* prefix, uint8_t op);
|
||||
|
||||
/// @brief 指令形态;未知返回 OpFormat::RESERVED。
|
||||
OpFormat format_of(const char* prefix, uint8_t op);
|
||||
|
||||
/// @brief func 语义类别;未知返回 FuncMode::None。
|
||||
FuncMode func_mode_of(const char* prefix, uint8_t op);
|
||||
|
||||
/// @brief 该形态 rd 字段是否为寄存器。
|
||||
bool uses_rd(OpFormat f);
|
||||
/// @brief 该形态 rs1 字段是否为寄存器。
|
||||
bool uses_rs1(OpFormat f);
|
||||
/// @brief 该形态 rs2 字段是否为寄存器。
|
||||
bool uses_rs2(OpFormat f);
|
||||
|
||||
/// 内建 FB 名表(fb_id → 名;15 条,与 machine.toml [[fb]] 一致)。
|
||||
extern const char* kFbNames[15];
|
||||
}
|
||||
+102
-4
@@ -1,6 +1,12 @@
|
||||
/**
|
||||
* @file Types.h
|
||||
* @brief 类型定义
|
||||
* @brief 定宽类型、func 表与饱和算术(V2)
|
||||
* @details 虚拟机字长与类型契约的唯一来源。compiler 与 vm 共用同一套规则,
|
||||
* 详见 Doc/isa/指令与映像.md(V2)。
|
||||
* - func 0..9:宽度×符号(1B无/1B有/2B无/2B有/4B无/4B有/8B无/8B有)+ F32 + F64
|
||||
* - 饱和模板:T ∈ {int8_t, int16_t, int32_t, int64_t},除法 A 边界
|
||||
* (除零返回被除数、MIN/-1 饱和夹界、截断除法)
|
||||
* - TypeTag 已删除(V2 常量表 8B 无 tag,LOADK 按 func 解释)
|
||||
* @author
|
||||
* @date 2026-08-18
|
||||
*/
|
||||
@@ -8,13 +14,105 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace isa {
|
||||
namespace types {
|
||||
// 定宽类型。整数溢出规则:饱和(夹到 INT 最小/最大),
|
||||
// 由 compiler 与 vm 按同一规则实现,见 Doc/isa/指令与映像.md。
|
||||
/// 布尔值:8 位,只允许 0/1(0=FALSE,1=TRUE)。
|
||||
using BOOL = uint8_t;
|
||||
|
||||
/// IEC INT:16 位有符号整数,溢出按饱和规则处理。
|
||||
using INT = int16_t;
|
||||
using TIME = uint64_t;
|
||||
|
||||
/// 时间:64 位有符号整数,单位毫秒。
|
||||
using TIME = int64_t;
|
||||
|
||||
/**
|
||||
* @brief func 位段 → 数据宽度(字节)。
|
||||
* @details 下标 0..9 有效(0..7 = 1/2/4/8B × 有/无符号,8=F32、9=F64);
|
||||
* 10..15 预留返回 0。
|
||||
*/
|
||||
inline constexpr int kFuncWidth[16] = {
|
||||
1, 1, 2, 2, 4, 4, 8, 8, // 0..7:1B无/1B有/2B无/2B有/4B无/4B有/8B无/8B有
|
||||
4, 8, // 8=F32、9=F64
|
||||
0, 0, 0, 0, 0, 0, // 10..15 预留
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief func 位段 → 是否有符号(读取扩展方向)。
|
||||
* @details 浮点(8/9)按 true(位模式原样搬运,扩展无实际作用)。
|
||||
*/
|
||||
inline constexpr bool kFuncSigned[16] = {
|
||||
false, true, false, true, false, true, false, true,
|
||||
true, true, // 浮点位模式
|
||||
false, false, false, false, false, false,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 饱和加法。
|
||||
* @param a 加数
|
||||
* @param b 加数
|
||||
* @return a+b,超出 T 范围时夹到最小/最大值
|
||||
* @details 中间量用 int64 计算,避免窄类型溢出未定义行为。
|
||||
*/
|
||||
template <typename T>
|
||||
T sat_add(T a, T b) {
|
||||
const __int128 sum = static_cast<__int128>(a) + static_cast<__int128>(b);
|
||||
return static_cast<T>(sum < std::numeric_limits<T>::min()
|
||||
? std::numeric_limits<T>::min()
|
||||
: sum > std::numeric_limits<T>::max()
|
||||
? std::numeric_limits<T>::max()
|
||||
: sum);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 饱和减法。
|
||||
* @param a 被减数
|
||||
* @param b 减数
|
||||
* @return a-b,超出 T 范围时夹到最小/最大值
|
||||
*/
|
||||
template <typename T>
|
||||
T sat_sub(T a, T b) {
|
||||
const __int128 diff = static_cast<__int128>(a) - static_cast<__int128>(b);
|
||||
return static_cast<T>(diff < std::numeric_limits<T>::min()
|
||||
? std::numeric_limits<T>::min()
|
||||
: diff > std::numeric_limits<T>::max()
|
||||
? std::numeric_limits<T>::max()
|
||||
: diff);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 饱和乘法。
|
||||
* @param a 乘数
|
||||
* @param b 乘数
|
||||
* @return a*b,超出 T 范围时夹到最小/最大值
|
||||
*/
|
||||
template <typename T>
|
||||
T sat_mul(T a, T b) {
|
||||
const __int128 prod = static_cast<__int128>(a) * static_cast<__int128>(b);
|
||||
return static_cast<T>(prod < std::numeric_limits<T>::min()
|
||||
? std::numeric_limits<T>::min()
|
||||
: prod > std::numeric_limits<T>::max()
|
||||
? std::numeric_limits<T>::max()
|
||||
: prod);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 饱和除法(除法 A 定案)。
|
||||
* @param a 被除数
|
||||
* @param b 除数
|
||||
* @return a/b(截断除法,向零);b==0 → 返回 a(结果保持);
|
||||
* MIN/-1 → 饱和夹到 MAX
|
||||
*/
|
||||
template <typename T>
|
||||
T sat_div(T a, T b) {
|
||||
if (b == 0) {
|
||||
return a; // 除零返回被除数(Fail-safe)
|
||||
}
|
||||
if (a == std::numeric_limits<T>::min() && b == T(-1)) {
|
||||
return std::numeric_limits<T>::max(); // MIN/-1 溢出饱和
|
||||
}
|
||||
return static_cast<T>(a / b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* @file Encode.cpp
|
||||
* @brief 指令编解码与反汇编实现(V2)
|
||||
* @author
|
||||
* @date 2026-08-19
|
||||
*/
|
||||
|
||||
#include "isa/Encode.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
namespace isa {
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief func → 类型后缀(disasm 用)。
|
||||
* @param func func 值(0..9)
|
||||
* @return 后缀("U8"/"I8"/.../"F64");越界返回 "?"
|
||||
*/
|
||||
const char* type_suffix(uint8_t func) {
|
||||
static const char* kSuffix[16] = {
|
||||
"U8", "I8", "U16", "I16", "U32", "I32", "U64", "I64",
|
||||
"F32", "F64", "?", "?", "?", "?", "?", "?",
|
||||
};
|
||||
return kSuffix[func & 0x0F];
|
||||
}
|
||||
|
||||
/// 形态是否显示类型后缀(full/float/dst 类)。
|
||||
bool shows_type_suffix(FuncMode m) {
|
||||
return m == FuncMode::Full || m == FuncMode::Float || m == FuncMode::Dst;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief 解码:指令字节 → 字段视图。
|
||||
*/
|
||||
Decoded decode(const uint8_t* code, size_t len) {
|
||||
Decoded d;
|
||||
(void)len;
|
||||
// 前缀推导(按字节 0 位模式)
|
||||
const uint8_t b = code[0];
|
||||
if (b & 0x80) {
|
||||
d.prefix = (b & 0x40) ? "11" : "10";
|
||||
d.op = b & 0x3F;
|
||||
} else if ((b & 0xC0) == 0x40) {
|
||||
if (b & 0x20) {
|
||||
d.prefix = "011";
|
||||
d.op = b & 0x1F;
|
||||
} else {
|
||||
static const char* kShapes[8] = {
|
||||
"010000", "010001", "010010", "010011",
|
||||
"010100", "010101", "010110", "010111",
|
||||
};
|
||||
d.prefix = kShapes[(b >> 2) & 0x07];
|
||||
d.op = b & 0x03;
|
||||
}
|
||||
} else if ((b & 0xE0) == 0x20) {
|
||||
d.prefix = (b & 0x10) ? "0011" : "0010";
|
||||
d.op = b & 0x0F;
|
||||
} else {
|
||||
d.prefix = "0001";
|
||||
d.op = b & 0x0F;
|
||||
}
|
||||
d.func = func_of(code);
|
||||
d.rd = rd_of(code);
|
||||
d.rs1 = rs1_of(code);
|
||||
d.rs2 = rs2_of(code);
|
||||
// 数值字段(按形态解释):IMM/SLOT → imm32;JMP/JC → off;CALL → fn_id
|
||||
const OpFormat f = format_of(d.prefix, d.op);
|
||||
if (f == OpFormat::IMM || f == OpFormat::SLOT) {
|
||||
d.imm32 = imm32_of(code);
|
||||
} else if (f == OpFormat::JMP) {
|
||||
d.imm32 = static_cast<uint32_t>(off32_of(code));
|
||||
} else if (f == OpFormat::JC) {
|
||||
d.imm32 = static_cast<uint32_t>(off32_jc_of(code));
|
||||
} else if (f == OpFormat::CALL) {
|
||||
d.imm32 = fn_id32_of(code);
|
||||
} else if (f == OpFormat::CAL) {
|
||||
d.imm32 = (cal_slot32_of(code) << 16) | fb_id16_of(code); // (slot<<16)|fb_id
|
||||
} else if (f == OpFormat::SLOT_OFF) {
|
||||
d.imm32 = slot32_of(code);
|
||||
d.off_hi = off32_hi_of(code);
|
||||
} else if (f == OpFormat::RS_OFF) {
|
||||
d.off_hi = off32_hi_of(code);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 编码:字段视图 → 指令字节。
|
||||
*/
|
||||
size_t encode(const Decoded& d, uint8_t* out) {
|
||||
set_byte0(out, d.prefix, d.op);
|
||||
set_func(out, d.func);
|
||||
set_rd(out, d.rd);
|
||||
set_rs1(out, d.rs1);
|
||||
set_rs2(out, d.rs2);
|
||||
const OpFormat f = format_of(d.prefix, d.op);
|
||||
if (f == OpFormat::IMM || f == OpFormat::SLOT) {
|
||||
set_imm32(out, d.imm32);
|
||||
} else if (f == OpFormat::JMP) {
|
||||
set_off32(out, static_cast<int32_t>(d.imm32));
|
||||
} else if (f == OpFormat::JC) {
|
||||
set_off32_jc(out, static_cast<int32_t>(d.imm32));
|
||||
} else if (f == OpFormat::CALL) {
|
||||
set_fn_id32(out, d.imm32);
|
||||
} else if (f == OpFormat::CAL) {
|
||||
set_fb_id16(out, static_cast<uint16_t>(d.imm32 & 0xFFFF));
|
||||
set_cal_slot32(out, d.imm32);
|
||||
} else if (f == OpFormat::SLOT_OFF) {
|
||||
set_slot32(out, d.imm32);
|
||||
set_off32_hi(out, d.off_hi);
|
||||
} else if (f == OpFormat::RS_OFF) {
|
||||
set_off32_hi(out, d.off_hi);
|
||||
}
|
||||
return instr_len(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 反汇编一条指令为一行文本。
|
||||
*/
|
||||
void disasm(const uint8_t* code, size_t len, char* out, size_t cap) {
|
||||
if (cap == 0) {
|
||||
return;
|
||||
}
|
||||
out[0] = '\0';
|
||||
const Decoded d = decode(code, len);
|
||||
const int idx = find_op(d.prefix, d.op);
|
||||
if (idx < 0) {
|
||||
snprintf(out, cap, "??? 0x");
|
||||
size_t used = 6; // "??? 0x" 长度
|
||||
for (size_t i = 0; i < len && used + 2 < cap; ++i) {
|
||||
snprintf(out + used, cap - used, "%02x", static_cast<unsigned>(code[i]));
|
||||
used += 2;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const OpDef& od = kOps[idx];
|
||||
const std::string suffix =
|
||||
shows_type_suffix(od.func_mode) ? std::string(".") + type_suffix(d.func) : "";
|
||||
const OpFormat f = od.format;
|
||||
switch (f) {
|
||||
case OpFormat::RR:
|
||||
snprintf(out, cap, "%s%s r%u, r%u", od.name, suffix.c_str(),
|
||||
static_cast<unsigned>(d.rd), static_cast<unsigned>(d.rs1));
|
||||
break;
|
||||
case OpFormat::RRR:
|
||||
snprintf(out, cap, "%s%s r%u, r%u, r%u", od.name, suffix.c_str(),
|
||||
static_cast<unsigned>(d.rd), static_cast<unsigned>(d.rs1),
|
||||
static_cast<unsigned>(d.rs2));
|
||||
break;
|
||||
case OpFormat::IMM:
|
||||
snprintf(out, cap, "%s%s r%u, %u", od.name, suffix.c_str(),
|
||||
static_cast<unsigned>(d.rd), static_cast<unsigned>(d.imm32));
|
||||
break;
|
||||
case OpFormat::SLOT:
|
||||
snprintf(out, cap, "%s%s r%u, s%u", od.name, suffix.c_str(),
|
||||
static_cast<unsigned>(d.rd), static_cast<unsigned>(d.imm32));
|
||||
break;
|
||||
case OpFormat::JMP:
|
||||
snprintf(out, cap, "%s %+d", od.name, static_cast<int>(d.imm32));
|
||||
break;
|
||||
case OpFormat::JC:
|
||||
snprintf(out, cap, "%s r%u, %+d", od.name, static_cast<unsigned>(d.rd),
|
||||
static_cast<int>(d.imm32));
|
||||
break;
|
||||
case OpFormat::CALL:
|
||||
snprintf(out, cap, "%s %u", od.name, static_cast<unsigned>(d.imm32));
|
||||
break;
|
||||
case OpFormat::CAL: {
|
||||
const uint16_t fb = static_cast<uint16_t>(d.imm32 & 0xFFFF);
|
||||
const char* fbname = (fb < 15) ? kFbNames[fb] : "?";
|
||||
snprintf(out, cap, "CAL.%s s%u", fbname, static_cast<unsigned>(d.imm32 >> 16));
|
||||
break;
|
||||
}
|
||||
case OpFormat::SLOT_OFF:
|
||||
snprintf(out, cap, "%s%s r%u, s%u, %d", od.name, suffix.c_str(),
|
||||
static_cast<unsigned>(d.rd), static_cast<unsigned>(d.imm32),
|
||||
static_cast<int>(d.off_hi));
|
||||
break;
|
||||
case OpFormat::RS_OFF:
|
||||
snprintf(out, cap, "%s%s r%u, r%u, %d", od.name, suffix.c_str(),
|
||||
static_cast<unsigned>(d.rd), static_cast<unsigned>(d.rs1),
|
||||
static_cast<int>(d.off_hi));
|
||||
break;
|
||||
case OpFormat::IMM64:
|
||||
case OpFormat::NONE:
|
||||
case OpFormat::RESERVED:
|
||||
default:
|
||||
snprintf(out, cap, "%s", od.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace isa
|
||||
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* @file Op.cpp
|
||||
* @brief 操作码表与查询实现(V2)
|
||||
* @details 指令表与 compiler/machine.toml 的 72 条 op 行强校验一致;
|
||||
* uses_* 由形态直推(前缀分区表达,零查表)。
|
||||
* @author
|
||||
* @date 2026-08-19
|
||||
*/
|
||||
|
||||
#include "isa/Op.h"
|
||||
|
||||
namespace isa {
|
||||
|
||||
const char* kPrefixNames[12] = {
|
||||
"11", "10", "011", "010000", "010001", "010010",
|
||||
"010011", "010100", "010101", "0010", "0011", "0001",
|
||||
};
|
||||
|
||||
// 指令表(72 条;顺序与 machine.toml [[op]] 一致)
|
||||
const OpDef kOps[] = {
|
||||
// 4B RR(前缀 11)
|
||||
{"MOVE", "11", Op::MOVE, OpFormat::RR, FuncMode::Width},
|
||||
{"NOT", "11", Op::NOT, OpFormat::RR, FuncMode::Uint},
|
||||
{"NEG", "11", Op::NEG, OpFormat::RR, FuncMode::Full},
|
||||
{"ABS", "11", Op::ABS, OpFormat::RR, FuncMode::Full},
|
||||
{"INC", "11", Op::INC, OpFormat::RR, FuncMode::Full},
|
||||
{"DEC", "11", Op::DEC, OpFormat::RR, FuncMode::Full},
|
||||
{"SQRT", "11", Op::SQRT, OpFormat::RR, FuncMode::Float},
|
||||
{"SIN", "11", Op::SIN, OpFormat::RR, FuncMode::Float},
|
||||
{"COS", "11", Op::COS, OpFormat::RR, FuncMode::Float},
|
||||
{"TAN", "11", Op::TAN, OpFormat::RR, FuncMode::Float},
|
||||
{"ASIN", "11", Op::ASIN, OpFormat::RR, FuncMode::Float},
|
||||
{"ACOS", "11", Op::ACOS, OpFormat::RR, FuncMode::Float},
|
||||
{"ATAN", "11", Op::ATAN, OpFormat::RR, FuncMode::Float},
|
||||
{"LN", "11", Op::LN, OpFormat::RR, FuncMode::Float},
|
||||
{"LOG", "11", Op::LOG, OpFormat::RR, FuncMode::Float},
|
||||
{"EXP", "11", Op::EXP, OpFormat::RR, FuncMode::Float},
|
||||
{"ROUND", "11", Op::ROUND, OpFormat::RR, FuncMode::Float},
|
||||
{"TRUNC", "11", Op::TRUNC, OpFormat::RR, FuncMode::Float},
|
||||
{"FLOOR", "11", Op::FLOOR, OpFormat::RR, FuncMode::Float},
|
||||
{"CEIL", "11", Op::CEIL, OpFormat::RR, FuncMode::Float},
|
||||
{"CONV", "11", Op::CONV, OpFormat::RR, FuncMode::Dst},
|
||||
// 4B NONE(前缀 10)
|
||||
{"NOP", "10", Op::NOP, OpFormat::NONE, FuncMode::None},
|
||||
{"RET", "10", Op::RET, OpFormat::NONE, FuncMode::None},
|
||||
// 8B RRR(前缀 011)
|
||||
{"ADD", "011", Op::ADD, OpFormat::RRR, FuncMode::Full},
|
||||
{"SUB", "011", Op::SUB, OpFormat::RRR, FuncMode::Full},
|
||||
{"MUL", "011", Op::MUL, OpFormat::RRR, FuncMode::Full},
|
||||
{"DIV", "011", Op::DIV, OpFormat::RRR, FuncMode::Full},
|
||||
{"MOD", "011", Op::MOD, OpFormat::RRR, FuncMode::Full},
|
||||
{"EXPT", "011", Op::EXPT, OpFormat::RRR, FuncMode::Full},
|
||||
{"AND", "011", Op::AND, OpFormat::RRR, FuncMode::Uint},
|
||||
{"OR", "011", Op::OR, OpFormat::RRR, FuncMode::Uint},
|
||||
{"XOR", "011", Op::XOR, OpFormat::RRR, FuncMode::Uint},
|
||||
{"NAND", "011", Op::NAND, OpFormat::RRR, FuncMode::Uint},
|
||||
{"NOR", "011", Op::NOR, OpFormat::RRR, FuncMode::Uint},
|
||||
{"XNOR", "011", Op::XNOR, OpFormat::RRR, FuncMode::Uint},
|
||||
{"SHL", "011", Op::SHL, OpFormat::RRR, FuncMode::Uint},
|
||||
{"SHR", "011", Op::SHR, OpFormat::RRR, FuncMode::Full},
|
||||
{"ROL", "011", Op::ROL, OpFormat::RRR, FuncMode::Uint},
|
||||
{"ROR", "011", Op::ROR, OpFormat::RRR, FuncMode::Uint},
|
||||
{"CMP_EQ", "011", Op::CMP_EQ, OpFormat::RRR, FuncMode::Full},
|
||||
{"CMP_NE", "011", Op::CMP_NE, OpFormat::RRR, FuncMode::Full},
|
||||
{"CMP_LT", "011", Op::CMP_LT, OpFormat::RRR, FuncMode::Full},
|
||||
{"CMP_LE", "011", Op::CMP_LE, OpFormat::RRR, FuncMode::Full},
|
||||
{"CMP_GT", "011", Op::CMP_GT, OpFormat::RRR, FuncMode::Full},
|
||||
{"CMP_GE", "011", Op::CMP_GE, OpFormat::RRR, FuncMode::Full},
|
||||
{"GETBIT", "011", Op::GETBIT, OpFormat::RRR, FuncMode::None},
|
||||
{"SETBIT", "011", Op::SETBIT, OpFormat::RRR, FuncMode::None},
|
||||
{"CLRBIT", "011", Op::CLRBIT, OpFormat::RRR, FuncMode::None},
|
||||
{"TOGBIT", "011", Op::TOGBIT, OpFormat::RRR, FuncMode::None},
|
||||
// 8B 010 区
|
||||
{"LOADK", "010000", Op::LOADK, OpFormat::IMM, FuncMode::Full},
|
||||
{"LOAD", "010001", Op::LOAD, OpFormat::SLOT, FuncMode::Full},
|
||||
{"STORE", "010001", Op::STORE, OpFormat::SLOT, FuncMode::Full},
|
||||
{"JMP", "010010", Op::JMP, OpFormat::JMP, FuncMode::None},
|
||||
{"JT", "010011", Op::JT, OpFormat::JC, FuncMode::None},
|
||||
{"JF", "010011", Op::JF, OpFormat::JC, FuncMode::None},
|
||||
{"CALL", "010100", Op::CALL, OpFormat::CALL, FuncMode::None},
|
||||
{"CAL", "010101", Op::CAL, OpFormat::CAL, FuncMode::None},
|
||||
// 16B
|
||||
{"LOAD_OFF", "0010", Op::LOAD_OFF, OpFormat::SLOT_OFF, FuncMode::Full},
|
||||
{"STORE_OFF", "0010", Op::STORE_OFF, OpFormat::SLOT_OFF, FuncMode::Full},
|
||||
{"STR_LEN", "0010", Op::STR_LEN, OpFormat::SLOT_OFF, FuncMode::None},
|
||||
{"STR_CMP", "0010", Op::STR_CMP, OpFormat::SLOT_OFF, FuncMode::None},
|
||||
{"STR_EQ", "0010", Op::STR_EQ, OpFormat::SLOT_OFF, FuncMode::None},
|
||||
{"STR_MID", "0010", Op::STR_MID, OpFormat::SLOT_OFF, FuncMode::None},
|
||||
{"STR_LEFT", "0010", Op::STR_LEFT, OpFormat::SLOT_OFF, FuncMode::None},
|
||||
{"STR_RIGHT", "0010", Op::STR_RIGHT, OpFormat::SLOT_OFF, FuncMode::None},
|
||||
{"LOAD_PTR", "0010", Op::LOAD_PTR, OpFormat::RS_OFF, FuncMode::Full},
|
||||
{"STORE_PTR", "0010", Op::STORE_PTR, OpFormat::RS_OFF, FuncMode::Full},
|
||||
{"LOADK64", "0011", Op::LOADK64, OpFormat::IMM64, FuncMode::Full},
|
||||
// 32B 预留
|
||||
{"JTBL", "0001", Op::JTBL, OpFormat::RESERVED, FuncMode::None},
|
||||
{"STR_CONCAT", "0001", Op::STR_CONCAT, OpFormat::RESERVED, FuncMode::None},
|
||||
{"STR_FIND", "0001", Op::STR_FIND, OpFormat::RESERVED, FuncMode::None},
|
||||
{"STR_REPLACE", "0001", Op::STR_REPLACE, OpFormat::RESERVED, FuncMode::None},
|
||||
{"ARR_LOAD", "0001", Op::ARR_LOAD, OpFormat::RESERVED, FuncMode::None},
|
||||
{"ARR_STORE", "0001", Op::ARR_STORE, OpFormat::RESERVED, FuncMode::None},
|
||||
};
|
||||
|
||||
int find_op(const char* prefix, uint8_t op) {
|
||||
for (int i = 0; i < kOpCount; ++i) {
|
||||
const char* p = kOps[i].prefix;
|
||||
const char* q = prefix;
|
||||
bool eq = true;
|
||||
while (*p && *q) {
|
||||
if (*p != *q) { eq = false; break; }
|
||||
++p; ++q;
|
||||
}
|
||||
if (eq && *p == *q && static_cast<uint8_t>(kOps[i].op) == op) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int find_op_by_name(const char* name) {
|
||||
for (int i = 0; i < kOpCount; ++i) {
|
||||
const char* p = kOps[i].name;
|
||||
const char* q = name;
|
||||
while (*p && *q && *p == *q) { ++p; ++q; }
|
||||
if (*p == '\0' && *q == '\0') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* mnemonic(const char* prefix, uint8_t op) {
|
||||
const int i = find_op(prefix, op);
|
||||
return i >= 0 ? kOps[i].name : "???";
|
||||
}
|
||||
|
||||
OpFormat format_of(const char* prefix, uint8_t op) {
|
||||
const int i = find_op(prefix, op);
|
||||
return i >= 0 ? kOps[i].format : OpFormat::RESERVED;
|
||||
}
|
||||
|
||||
FuncMode func_mode_of(const char* prefix, uint8_t op) {
|
||||
const int i = find_op(prefix, op);
|
||||
return i >= 0 ? kOps[i].func_mode : FuncMode::None;
|
||||
}
|
||||
|
||||
bool uses_rd(OpFormat f) {
|
||||
return f == OpFormat::RR || f == OpFormat::RRR || f == OpFormat::IMM ||
|
||||
f == OpFormat::SLOT || f == OpFormat::JC || f == OpFormat::SLOT_OFF ||
|
||||
f == OpFormat::RS_OFF || f == OpFormat::IMM64;
|
||||
}
|
||||
|
||||
bool uses_rs1(OpFormat f) {
|
||||
return f == OpFormat::RR || f == OpFormat::RRR || f == OpFormat::RS_OFF;
|
||||
}
|
||||
|
||||
bool uses_rs2(OpFormat f) {
|
||||
return f == OpFormat::RRR;
|
||||
}
|
||||
|
||||
const char* kFbNames[15] = {
|
||||
"TON", "TOF", "TP", "CTU", "DCTU", "CTD", "DCTD", "CTUD",
|
||||
"DCTUD", "R_TRIG", "F_TRIG", "SR", "RS", "PWM", "RTC",
|
||||
};
|
||||
}
|
||||
+123
-3
@@ -1,3 +1,123 @@
|
||||
# 自动化用例:先放一个必过的空测试,验证 CTest 接线
|
||||
add_test(NAME host_version
|
||||
COMMAND host)
|
||||
# 自动化用例
|
||||
add_test(NAME stcompiler_version
|
||||
COMMAND STCompiler)
|
||||
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)
|
||||
|
||||
target_link_libraries(isa_test PRIVATE isa)
|
||||
|
||||
add_test(NAME isa_roundtrip
|
||||
COMMAND isa_test)
|
||||
|
||||
# compiler 库测试:toml schema、文件集合、工程哈希(REPO_ROOT 注入源目录绝对路径)
|
||||
add_executable(compiler_test
|
||||
./src/compiler_test.cpp)
|
||||
|
||||
target_link_libraries(compiler_test PRIVATE compiler)
|
||||
target_compile_definitions(compiler_test PRIVATE
|
||||
REPO_ROOT="${CMAKE_SOURCE_DIR}")
|
||||
|
||||
add_test(NAME compiler_toml
|
||||
COMMAND compiler_test)
|
||||
|
||||
# 词法测试:token 流 + 负例(REPO_ROOT 注入源目录绝对路径)
|
||||
add_executable(lexer_test
|
||||
./src/lexer_test.cpp)
|
||||
|
||||
target_link_libraries(lexer_test PRIVATE compiler)
|
||||
target_compile_definitions(lexer_test PRIVATE
|
||||
REPO_ROOT="${CMAKE_SOURCE_DIR}")
|
||||
|
||||
add_test(NAME lexer_tokens
|
||||
COMMAND lexer_test)
|
||||
|
||||
# 语法测试:line1 AST 结构 + 负例(REPO_ROOT 注入源目录绝对路径)
|
||||
add_executable(parser_test
|
||||
./src/parser_test.cpp)
|
||||
|
||||
target_link_libraries(parser_test PRIVATE compiler)
|
||||
target_compile_definitions(parser_test PRIVATE
|
||||
REPO_ROOT="${CMAKE_SOURCE_DIR}")
|
||||
|
||||
add_test(NAME parser_syntax
|
||||
COMMAND parser_test)
|
||||
|
||||
# 链接测试:用例 09/10/11/12/16/19 + line1 正例(REPO_ROOT 注入源目录绝对路径)
|
||||
add_executable(linker_test
|
||||
./src/linker_test.cpp)
|
||||
|
||||
target_link_libraries(linker_test PRIVATE compiler)
|
||||
target_compile_definitions(linker_test PRIVATE
|
||||
REPO_ROOT="${CMAKE_SOURCE_DIR}")
|
||||
|
||||
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)
|
||||
|
||||
# 寄存器码测试(V2):用例产物结构断言(用 compiler::Codec,去 isa 依赖)
|
||||
add_executable(codegen_test
|
||||
./src/codegen_test.cpp)
|
||||
|
||||
target_link_libraries(codegen_test PRIVATE compiler)
|
||||
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)
|
||||
|
||||
+11
-1
@@ -2,7 +2,17 @@
|
||||
|
||||
自动化用例,由顶层 `enable_testing()` 接入 CTest。
|
||||
|
||||
可以 `add_test` 调用 `host`,或直接链接 `compiler` + `vm`。样例工程数据按「一份 toml + 若干 `.st`」分子目录存放,不要做成 library。
|
||||
可以 `add_test` 调 `STCompiler` / `BytecodeExecutor`(编译→执行管线),或直接链接 `compiler` + `vm` 做单元断言。样例工程数据按「一份 toml + 若干 `.st`」分子目录存放,不要做成 library。
|
||||
|
||||
## 用例目录(`cases/`)
|
||||
|
||||
每个用例一个子目录:`project.toml` + `.st` + `EXPECTED.md`(期望:通过 / 报哪类错)。清单与编号见 [`Doc/初步计划.md`](../Doc/初步计划.md) 12.2 节,共 20 个:
|
||||
|
||||
- 正例:01 空 MAIN、02 BOOL、03 短路、04 IF、05 WHILE、07 算术、08 TIME、09 GVL+EXTERNAL、13 FUNCTION、15 FB、17 TON、18 TOF/CTU、20 line1
|
||||
- 负例(编译错误/链接失败):10 非 GVL 写全局、11 同名全局、12 io.var 未声明、14 FUNCTION 写全局、16 未声明实例、19 循环调用
|
||||
- 运行时故障:06 cycle_limit 打满
|
||||
|
||||
12.2 完成标准:目录和期望表齐,此时大部分还编不过,属正常。实现阶段逐个点亮。
|
||||
|
||||
## 第一版要冻的
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# 期望:编译通过
|
||||
|
||||
- 映像:MAIN(fn_id=0)字节码为一条 `RET`,`nregs=0`
|
||||
- 运行任意周期数无故障
|
||||
@@ -0,0 +1,2 @@
|
||||
PROGRAM MAIN
|
||||
END_PROGRAM
|
||||
@@ -0,0 +1,8 @@
|
||||
[project]
|
||||
name = "case01"
|
||||
entry = "program MAIN"
|
||||
cycle_limit = 1000
|
||||
dt_ms = 10
|
||||
|
||||
[files]
|
||||
st = ["main.st"]
|
||||
@@ -0,0 +1,4 @@
|
||||
# 期望:编译通过
|
||||
|
||||
- 执行后 `a` = TRUE、`b` = FALSE
|
||||
- `TRUE` / `FALSE` 是关键字,不是标识符
|
||||
@@ -0,0 +1,8 @@
|
||||
PROGRAM MAIN
|
||||
VAR
|
||||
a : BOOL;
|
||||
b : BOOL;
|
||||
END_VAR
|
||||
a := TRUE;
|
||||
b := FALSE;
|
||||
END_PROGRAM
|
||||
@@ -0,0 +1,8 @@
|
||||
[project]
|
||||
name = "case02"
|
||||
entry = "program MAIN"
|
||||
cycle_limit = 1000
|
||||
dt_ms = 10
|
||||
|
||||
[files]
|
||||
st = ["main.st"]
|
||||
@@ -0,0 +1,4 @@
|
||||
# 期望:编译通过
|
||||
|
||||
- 反汇编中 `AND` / `OR` 必须编成条件跳转(`JT` / `JF`),不得先算两侧再组合
|
||||
- v1 表达式无调用、无副作用;「右侧有副作用时不执行」以编译产物含条件跳转为验收标准
|
||||
@@ -0,0 +1,9 @@
|
||||
PROGRAM MAIN
|
||||
VAR
|
||||
a : BOOL;
|
||||
b : BOOL;
|
||||
x : BOOL;
|
||||
END_VAR
|
||||
x := a AND b;
|
||||
x := a OR b;
|
||||
END_PROGRAM
|
||||
@@ -0,0 +1,8 @@
|
||||
[project]
|
||||
name = "case03"
|
||||
entry = "program MAIN"
|
||||
cycle_limit = 1000
|
||||
dt_ms = 10
|
||||
|
||||
[files]
|
||||
st = ["main.st"]
|
||||
@@ -0,0 +1,4 @@
|
||||
# 期望:编译通过
|
||||
|
||||
- `sel` = 0 → `out` = 10;`sel` = 1 → `out` = 20;其他 → `out` = 30
|
||||
- 必须编成比较 + 条件跳转,不逐分支执行
|
||||
@@ -0,0 +1,13 @@
|
||||
PROGRAM MAIN
|
||||
VAR
|
||||
sel : INT;
|
||||
out : INT;
|
||||
END_VAR
|
||||
IF sel = 0 THEN
|
||||
out := 10;
|
||||
ELSIF sel = 1 THEN
|
||||
out := 20;
|
||||
ELSE
|
||||
out := 30;
|
||||
END_IF
|
||||
END_PROGRAM
|
||||
@@ -0,0 +1,8 @@
|
||||
[project]
|
||||
name = "case04"
|
||||
entry = "program MAIN"
|
||||
cycle_limit = 1000
|
||||
dt_ms = 10
|
||||
|
||||
[files]
|
||||
st = ["main.st"]
|
||||
@@ -0,0 +1,3 @@
|
||||
# 期望:编译通过
|
||||
|
||||
- 循环结束后 `n` = 10(正常终止,不触发 cycle_limit)
|
||||
@@ -0,0 +1,9 @@
|
||||
PROGRAM MAIN
|
||||
VAR
|
||||
n : INT;
|
||||
END_VAR
|
||||
n := 0;
|
||||
WHILE n < 10 DO
|
||||
n := n + 1;
|
||||
END_WHILE
|
||||
END_PROGRAM
|
||||
@@ -0,0 +1,8 @@
|
||||
[project]
|
||||
name = "case05"
|
||||
entry = "program MAIN"
|
||||
cycle_limit = 1000
|
||||
dt_ms = 10
|
||||
|
||||
[files]
|
||||
st = ["main.st"]
|
||||
@@ -0,0 +1,4 @@
|
||||
# 期望:运行时故障
|
||||
|
||||
- 单周期指令数超 `cycle_limit = 10`,VM 报故障并停止
|
||||
- 编译本身通过;WHILE 不做终止证明
|
||||
@@ -0,0 +1,4 @@
|
||||
PROGRAM MAIN
|
||||
WHILE TRUE DO
|
||||
END_WHILE
|
||||
END_PROGRAM
|
||||
@@ -0,0 +1,8 @@
|
||||
[project]
|
||||
name = "case06"
|
||||
entry = "program MAIN"
|
||||
cycle_limit = 10
|
||||
dt_ms = 10
|
||||
|
||||
[files]
|
||||
st = ["main.st"]
|
||||
@@ -0,0 +1,4 @@
|
||||
# 期望:编译通过
|
||||
|
||||
- `a` = 42、`b` = 10、`c` = 32、`eq` = TRUE
|
||||
- 溢出饱和:`30000 + 30000` = 32767、`-30000 - 30000` = -32768(由 isa 饱和函数保证)
|
||||
@@ -0,0 +1,10 @@
|
||||
PROGRAM MAIN
|
||||
VAR
|
||||
a, b, c : INT;
|
||||
eq : BOOL;
|
||||
END_VAR
|
||||
a := 7 * 6;
|
||||
b := a / 4;
|
||||
c := a - b;
|
||||
eq := c > 30;
|
||||
END_PROGRAM
|
||||
@@ -0,0 +1,8 @@
|
||||
[project]
|
||||
name = "case07"
|
||||
entry = "program MAIN"
|
||||
cycle_limit = 1000
|
||||
dt_ms = 10
|
||||
|
||||
[files]
|
||||
st = ["main.st"]
|
||||
@@ -0,0 +1,4 @@
|
||||
# 期望:编译通过
|
||||
|
||||
- `t1` = 10 毫秒、`t2` = 1250 毫秒(内部一律毫秒)
|
||||
- `TIME` 与 `INT` 不混用;不支持 `T#` 以外的 TIME 语法
|
||||
@@ -0,0 +1,7 @@
|
||||
PROGRAM MAIN
|
||||
VAR
|
||||
t1, t2 : TIME;
|
||||
END_VAR
|
||||
t1 := T#10ms;
|
||||
t2 := T#1s250ms;
|
||||
END_PROGRAM
|
||||
@@ -0,0 +1,8 @@
|
||||
[project]
|
||||
name = "case08"
|
||||
entry = "program MAIN"
|
||||
cycle_limit = 1000
|
||||
dt_ms = 10
|
||||
|
||||
[files]
|
||||
st = ["main.st"]
|
||||
@@ -0,0 +1,4 @@
|
||||
# 期望:编译通过
|
||||
|
||||
- 符号表里 `G1` 的 global 与 external 指向**同一槽**,不是新变量
|
||||
- 执行后 `x` = 5(初值来自 GVL)
|
||||
@@ -0,0 +1,3 @@
|
||||
VAR_GLOBAL
|
||||
G1 : INT := 5;
|
||||
END_VAR
|
||||
@@ -0,0 +1,9 @@
|
||||
PROGRAM MAIN
|
||||
VAR_EXTERNAL
|
||||
G1 : INT;
|
||||
END_VAR
|
||||
VAR
|
||||
x : INT;
|
||||
END_VAR
|
||||
x := G1;
|
||||
END_PROGRAM
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user