diff --git a/Doc/compiler/STCompiler使用说明.md b/Doc/compiler/STCompiler使用说明.md index 4a1c268..04974f0 100644 --- a/Doc/compiler/STCompiler使用说明.md +++ b/Doc/compiler/STCompiler使用说明.md @@ -1,13 +1,13 @@ # STCompiler 使用说明 -把 ST 工程(`project.toml` + 若干 `.st`)编译成单一映像 `.stb`。执行由 `BytecodeExecutor`(12.9/12.10 实现)负责。 +把 ST 工程(`project.toml` + 若干 `.st`)编译成单一映像 `.stb`。执行由 `BytecodeExecutor` 负责。 ## 构建 ```bash cmake --preset gcc-debug # 配置 cmake --build --preset gcc-debug # 编译 -ctest --test-dir build/gcc-debug # 测试(当前 9 个用例) +ctest --test-dir build/gcc-debug # 测试(14/14 全绿) ``` 可执行文件:`build/gcc-debug/compiler/STCompiler` @@ -35,29 +35,39 @@ STCompiler .stb --disasm --machine ```bash $ STCompiler line1.stb --disasm --machine machine.toml -image: line1.stb (288 bytes, 2 functions, 4 globals, entry fn 1) - dt_ms=10 cycle_limit=100000 hash=0xc3fe4ae74ad45900 model=STATOR1 sha=ok +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=1 - 0x0080 RET - fn 1: nregs=13, offset=4, len=17 - 0x0084 LOAD_I r8, 1 - 0x0088 STORE_GLOBAL r8, 4 + 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 ... -data (56 bytes): +slots: + [0] addr=0 +data (7 bytes): 0x0000: ... ``` 说明: -- 指令偏移是**文件绝对字节偏移**,可直接对照 `xxd line1.stb` -- 映像里**没有符号名**(函数表只有 fn_id,数据段无名字映射),因此按 `fn 0` / `fn 1`、槽号显示 +- 指令偏移是**文件绝对字节偏移**,可直接对照 `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` 拼成(如 `"STATOR1"`),写进头偏移 72 +- **型号标识[32]**:`[meta] name + version` 拼成(如 `"STATOR2"`),写进头(V2 头 128B,`kOffModelId`=32) - **SHA-256 文件尾[32]**:对文件尾之前全部内容计算;compiler 写侧与 vm 读侧各实现一份 - 执行器读取时型号不匹配 / SHA 不符 → 直接拒绝(详见 `Doc/isa/指令与映像.md` 12.13 修订) @@ -73,10 +83,10 @@ files: hash: 0xc3fe4ae74ad45900 $ STCompiler examples/line1/project.toml -o line1.stb --machine compiler/machine.toml -compiled: line1.stb (288 bytes, 2 functions, 4 globals) +compiled: line1.stb (399 bytes, 2 functions, 4 globals) ``` -产物:`line1.stb`(288 字节:头 104 含型号标识 + 各段 + SHA-256 文件尾)+ `line1.runtime.toml`(sidecar)。指令见 `Doc/isa/指令与映像.md`,结构拆解见 `Doc/isa/stb文件格式.md`。 +产物:`line1.stb`(399 字节:头 128B 含型号标识 + 各段 + SHA-256 文件尾)+ `line1.runtime.toml`(sidecar)。指令见 `Doc/isa/指令与映像.md`,结构拆解见 `Doc/isa/stb文件格式.md`。 ## 工程文件(project.toml) @@ -115,17 +125,17 @@ bit = 2 **POU**:`PROGRAM` / `FUNCTION` / `FUNCTION_BLOCK`(含对应 `END_*`)。 -**变量段**:`VAR` / `VAR_INPUT` / `VAR_OUTPUT` / `VAR_GLOBAL`(仅 `gvl.file` 顶层)/ `VAR_EXTERNAL`。 +**变量段**:`VAR` / `VAR_INPUT` / `VAR_OUTPUT` / `VAR_GLOBAL`(仅 `gvl.file` 顶层)/ `VAR_EXTERNAL` / `VAR_TEMP`(每次调用重新初始化,不跨周期持久;FB 内不占实例字段)。 -**类型**:`BOOL` / `INT` / `TIME`;内建 FB 类型 `TON` / `TOF` / `TP` / `CTU` / `CTD` / `CTUD` / `R_TRIG` / `F_TRIG`(关键字,不可作变量名)。 +**类型**: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)、`NOT`、`AND`/`OR`(**短路**)、比较 `= <> < <= > >=`(不连锁)、算术 `+ - * /`、一元负号、函数调用 `Add(3, 4)`。 +**表达式**:字面量(`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 字段赋值、`REAL`、字符串、用户类型套娃、隐式类型转换、函数循环调用(链接层拒绝)、`FUNCTION` 写全局(类型检查层拒绝,含经 `VAR_EXTERNAL`)。 +**限制(v1 明确不做)**:指针 / `REF` / `CLASS` / `ANY` / `VAR_IN_OUT`(语法层直接拒绝)、链式比较、FB 字段赋值、字符串、用户类型套娃、**隐式类型转换**(类型检查层拒绝,字面量按目标类型适配)、函数循环调用(链接层拒绝)、`FUNCTION` 写全局(类型检查层拒绝,含经 `VAR_EXTERNAL`)。 ## 编译管线与错误类别 @@ -149,10 +159,10 @@ project.toml + .st - `examples/line1/`:最小闭环(MAIN + MotorStarter FB + I/O + 全局),给人看 - `tests/cases/01~20/`:20 个用例(每份 `project.toml` + `.st` + `EXPECTED.md`),编译结果按表 -- `tests/`:CTest(`compiler_toml` / `lexer_tokens` / `parser_syntax` / `linker_links` / `typecheck_types` / `codegen_slice1` / `machine_config` / `vm_cycles` / `cases_all` / `cli_*`) +- `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`(含型号标识 + SHA-256);6 个负例按期望拒绝 -- ✅ 执行:`BytecodeExecutor` 加载 `.stb` 跑扫描周期(型号/SHA 校验、回放、单步) +- ✅ 可编译:14 个正例用例全部产出 `.stb`(V2 头 128B + 型号标识 + SHA-256);6 个负例按期望拒绝 +- ✅ 执行:`BytecodeExecutor` 加载 `.stb` 跑扫描周期(型号/SHA 校验、回放、单步),V2 槽表两级采样 - ✅ 配置驱动:指令 opcode / 类型元数据 / FB 布局来自 `compiler/machine.toml`(`--machine`) diff --git a/Doc/compiler/寄存器码.md b/Doc/compiler/寄存器码.md index 687f402..eaf008c 100644 --- a/Doc/compiler/寄存器码.md +++ b/Doc/compiler/寄存器码.md @@ -1,33 +1,33 @@ -# 寄存器码(12.8) +# 寄存器码(V2) -compiler 模块的代码生成。输入:`Project` + `Unit`(AST)+ `LinkResult`;输出:`.stb` 映像字节(`std::vector`)。全工程定案见 [`初步计划.md`](../初步计划.md) 12.8,指令/映像布局见 [`指令与映像.md`](../isa/指令与映像.md)。 +compiler 模块的代码生成。输入:`Project` + `Unit`(AST)+ `LinkResult`;输出:`.stb` 映像字节(`std::vector`)。全工程定案见 [`初步计划.md`](../初步计划.md) 12.8,指令/映像布局见 [`指令与映像.md`](../isa/指令与映像.md),槽表/值段格式见 [`stb文件格式.md`](../isa/stb文件格式.md)。 ## 做 / 不做 **做** - 寄存器分配:局部/变量固定寄存器,表达式临时值往后编号,`nregs` 写进函数头 -- 跳转先留洞、后回填;`AND` / `OR` **必须编成跳转**(短路),禁止两边都算 -- 常量表(`LOADK`)、全局/实例槽(`LOAD_GLOBAL` / `STORE_GLOBAL` 等) -- `CALL fn_id` 有界帧;用户 FB 调用**内联展开**;内建 `CAL_TON` / `CAL_TOF` / `CAL_CTU` -- 拼装映像:函数表 / 常量表 / 数据段 / 头(`cycle_limit`、`dt_ms`、工程哈希、入口) +- 跳转先留洞、后回填(字节单位,相对下一条);`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、生命周期分析、优化、异常表、闭包 -- 函数指针、间接跳转、`REAL` +- 函数指针、间接跳转、动态实例 -## 存储布局(v1 定案) +## 存储布局(V2 定案) | 数据 | 放哪 | 说明 | |---|---|---| | PROGRAM / FUNCTION 标量变量与临时 | **寄存器**(函数帧) | 每周期帧保留由 VM 负责(12.9) | -| FB 实例(任意 POU 内声明) | **数据区实例块**(绝对地址) | 状态跨周期持久 | -| 全局(含 I/Q/M) | 数据区(声明序) | 12.6 已定槽号 | +| 全局(含 I/Q/M)与 FB 实例 | **值区**(紧凑变宽,1/2/4/8 对齐) | 槽表条目(8B:addr:u32 + 预留)指到值区偏移;状态跨周期持久 | -- **数据区** = 全局块(声明序,**每槽 8 字节定宽**,见 [`指令与映像.md`](../isa/指令与映像.md))→ FB 实例块(按实例声明序,每字段一槽) -- **指令 slot = 槽号**(u16,0..65535);数据偏移 = 槽号 × 8;符号表 `address` 即指令 slot,无映射 -- 字段访问 `fb.Q`:`LOAD_GLOBAL rd, <实例基槽+字段序号>`(编译期算死);I/Q/M 用 `LOAD_I` / `STORE_Q` / `LOAD_M` / `STORE_M` 同槽号 +- **槽表两级**:指令操作数 = **槽号**(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 按此实现) @@ -37,7 +37,7 @@ compiler 模块的代码生成。输入:`Project` + `Unit`(AST)+ `LinkResu | 结果寄存器 | **r0**(FUNCTION 结果写入处) | | 参数寄存器 | **r1..r7**(最多 7 个输入,按声明序) | | 变量 / 临时 | **全部从 r8 起**(调用约定区不受覆盖) | -| `CALL` | VM 复制当前帧 **r0..r7 → 新帧 r0..r7** | +| `CALL` | VM 复制当前帧 **r0..r7 → 新帧 r0..r7**(全宽) | | `RET` | VM 复制当前帧 **r0..r7 → 调用方帧 r0..r7**(r0=结果,r1..r7 原样返回) | | 函数输入 | 函数体内**只读**(写输入 → codegen error) | @@ -53,9 +53,9 @@ compiler 模块的代码生成。输入:`Project` + `Unit`(AST)+ `LinkResu ## 表达式与语句的指令模式 ```text -字面量 LOADK rd, const_id -变量读 LOAD_GLOBAL/LOAD_I/LOAD_M rd, slot (或帧内 MOVE) -字段读 LOAD_GLOBAL rd, <基址+偏移> +字面量 LOADK rd, const_id (func = 语言类型;REAL/LREAL 位模式按目标类型装载) +变量读 LOAD rd, slot (帧内直接 MOVE) +字段读 LOAD_OFF rd, <实例槽>, <偏移> 函数调用 <实参求值 → 参数寄存器> CALL fn_id MOVE rd, r0 NOT NOT rd, t @@ -65,7 +65,7 @@ AND/OR 短路(必须跳转): L_end: 比较 CMP_xx rd, t1, t2 算术 ADD/SUB/MUL/DIV rd, t1, t2 -负号 SUB rd, r0, t (0 - t,r0 恒 0) +负号 NEG rd, t IF JF t, L_else JMP L_end @@ -73,34 +73,44 @@ IF JF t, L_else L_end: WHILE L_loop: JF t, L_end JMP L_loop L_end: -FB 调用(用户,内联展开): - <每个实参求值 → 临时> STORE_GLOBAL <实例基址+字段偏移>, 临时 - <内联体:读输入字段、算输出、写输出字段> -内建 实参同左 → CAL_TON/CAL_TOF/CAL_CTU <实例偏移> +FB 调用(用户,共享体): + <每个实参求值 → 临时> STORE_OFF <实例槽>, <字段偏移>, 临时 + LOADK r1, <实例值区地址> CALL + (FB 函数体内:字段访问 = LOAD_PTR/STORE_PTR rd, r1, <字段偏移>) +内建 实参同左 → CAL , <实例槽> ``` -- 常量统一进常量表(BOOL / INT / TIME),`const_id` = 首次出现序 -- 空条件寄存器约定:`r0` 恒 0(`enc_jmp` 用),`SUB rd, r0, t` 实现取负 +- 常量统一进常量表(8B 原始值,位模式去重;BOOL / 整数 / 时间 / 浮点 / 日期),`const_id` = 首次出现序;`LOADK` 按 func 解释位模式 +- 指令 func 位段 = 语言类型对应(`func_of_name`:宽度 × 符号 + F32/F64) -## 用户 FB 调用内联(v1 定案) +## 用户 FB 共享体(函数化,2026-08-26 实施) -FB 体共享会与「绝对字段地址」冲突(不同实例基址不同),v1 选择**调用点内联展开**: +FB 体共享会与「绝对字段地址」冲突(不同实例基址不同),V1 内联;V2 改为**共享体函数化**: -- 调用点:实参写入该实例字段 → 内联该 FB 的函数体(字段地址按本实例基址算死)→ 字段读取自然可用 -- 代价:字节码随调用点数膨胀;v1 规模可接受;以后可升级为「基址寄存器寻址」指令 -- FB 无递归、无动态实例,内联安全 +- 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 → { tag, value } -数据段:全局 + 实例块(见上) -头:cycle_limit / dt_ms / 工程哈希(12.3)/ entry=MAIN fn_id / n_globals / n_i=n_q=n_m=0 +常量表:const_id → { value:u64 }(无 tag) +字节码:各函数指令连续 +槽表:每条目 8B(addr:u32 + 预留:u32) +值段:全局 + FB 实例紧凑变宽 +尾部:SHA-256(覆盖头部到值段末尾) ``` -- 用 `isa` 的 `enc_*` 编码指令、`ImageView` 校验回读 -- 工程哈希用 12.3 的 `compute_project_hash` +- 用 `isa` 的 `enc_*` 编码指令(变长 4/8/16/32B,前缀分区) +- 工程哈希用 12.3 的 `compute_project_hash`(FNV-1a 64) +- `--dump-map` 可打印变量/槽映射(对照源码 ↔ 反汇编) ## 错误 @@ -109,27 +119,14 @@ FB 体共享会与「绝对字段地址」冲突(不同实例基址不同) ```text codegen error: register overflow (>256) in function 'f' codegen error: constant table overflow -codegen error: slot out of range +codegen error: no storage for 'x' +codegen error: no instance 't' in 'MAIN' +codegen error: recursive CALL/FB nesting too deep ``` ## 完成标准 -1. `line1` 能写出映像(函数字节码 + 全局/FB 槽 + 工程哈希),`ImageView` 校验通过 -2. 跳转回填无悬空(disasm 抽查) +1. `line1` 能写出映像(函数字节码 + 槽表/值段 + 工程哈希 + SHA-256),`StbView` 校验通过 +2. 跳转回填无悬空(`--disasm` 抽查) 3. 用例 1~9、13、15、17、18、20 编译出映像;负例(10/11/12/14/16/19)仍在前层被拒 -4. 全部构建 + `ctest` 无回归 - ---- - -## 执行计划(8 片,每片验证编译产物) - -1. **`Codegen.h`(新建)+ `Codegen.cpp` 骨架**:帧/槽/常量表/指令缓冲/回填基础设施;`MOVE` `LOADK` `RET` → 用例 1、2 出映像 -2. **全局与 I/Q/M**:`LOAD_I` / `STORE_Q` / `LOAD_M` / `STORE_M` / `LOAD_GLOBAL` / `STORE_GLOBAL`(用例 9) -3. **`NOT` + 短路 `AND` / `OR`**(用例 3,断言产物含跳转) -4. **`CMP_*` + `JMP` / `JT` / `JF` → `IF`**(用例 4) -5. **`WHILE`**(用例 5;`cycle_limit` 进映像头) -6. **`ADD/SUB/MUL/DIV` + TIME 常量**(用例 7、8) -7. **`CALL fn_id` + 有界帧**(用例 13) -8. **FB 实例块 + 字段偏移;内联用户 FB;`CAL_TON` / `CAL_TOF` / `CAL_CTU`**(用例 15、17、18、20 line1 全链路) - -每片配 `codegen_test` 断言(nregs / 指令序列 disasm / 回填 / 常量表 / 数据区);最终 `ctest` 9/9。 +4. 全部构建 + `ctest` 无回归(14/14) diff --git a/Doc/compiler/用例反汇编对照.md b/Doc/compiler/用例反汇编对照.md new file mode 100644 index 0000000..e44b3c9 --- /dev/null +++ b/Doc/compiler/用例反汇编对照.md @@ -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)精确 diff --git a/Doc/compiler/符号表与链接.md b/Doc/compiler/符号表与链接.md index 90eceeb..fad3401 100644 --- a/Doc/compiler/符号表与链接.md +++ b/Doc/compiler/符号表与链接.md @@ -12,18 +12,19 @@ compiler 模块的符号收集与链接。输入:`Project`(toml)+ 各 `.st - `FUNCTION` 标无状态;`FUNCTION_BLOCK` 必须先有实例才能调用 - `CALL` 目标解析成 `fn_id`(立即数),无函数指针 - 校验:同名全局、未声明 `io.var`、重复 POU 名、未声明 FB 实例、函数循环调用 → 失败 -- FB 实例布局:按类型算字段清单(输入/输出/内部/内建 8 个 FB 固定字段(12.11 扩充)) +- FB 实例布局:按类型算字段清单(输入/输出/内部;内建 FB 15 条字段来自 machine.toml `[[fb]]` 表,编译期按 1/2/4/8 对齐计算偏移) **不做(第一版)** - 寄存器分配(12.8)、类型检查(12.7) - 多 GVL 文件、I/Q/M 与全局分槽(v1 **单一数据区**,见下) -## 数据区定址(v1 定案) +## 数据区定址(V2 定案) -- **单一数据区**:全部全局(含 I/Q/M)按 `globals.st` 声明顺序编槽号 0..n-1 -- 映像头 `n_globals` = 声明总数;`n_i` / `n_q` / `n_m` = 0(第一版不拆,见 [`初步计划.md`](../初步计划.md) 5.1) -- `LOAD_I` / `STORE_Q` / `LOAD_GLOBAL` / `STORE_GLOBAL` 操作码不同,槽号指向同一数据区 +- **槽表两级**:全部全局(含 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),不创造变量 ## 符号表一行 @@ -70,20 +71,27 @@ struct Symbol { ## FB 实例布局 - 用户 FB:字段 = 全部 `VAR_INPUT` + `VAR_OUTPUT` + `VAR`(内部),按声明段序排 -- 内建(冻结,isa 规格;12.11 扩为 8 个): +- 内建 FB 15 条(`compiler/machine.toml` `[[fb]]` 表驱动,fb_id 0..14): -| FB | 字段(名 → 类型) | -|---|---| -| TON | in BOOL, pt TIME, q BOOL, et TIME | -| TOF | in BOOL, pt TIME, q BOOL, et TIME | -| TP | in BOOL, pt TIME, q BOOL, et TIME | -| CTU | cu BOOL, r BOOL, pv INT, q BOOL, cv INT | -| CTD | cd BOOL, ld BOOL, pv INT, q BOOL, cv INT | -| CTUD | cu BOOL, cd BOOL, r BOOL, lu BOOL, pv INT, qu BOOL, qd BOOL, cv INT | -| R_TRIG | clk BOOL, q BOOL | -| F_TRIG | clk BOOL, q BOOL | +| 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 | -- 实例布局 = 类型字段表的一份拷贝;字段偏移 = 字段序号 × 8(8 字节定宽槽) +- 实例布局 = 类型字段表的一份拷贝(`VAR_TEMP` 不是字段,不持久,编译期映射为帧寄存器 + 每次调用初值装载);字段偏移编译期按 1/2/4/8 对齐计算(字段类型决定宽度),实例 = 槽表 1 条 ## 错误 diff --git a/Doc/compiler/编译器优化.md b/Doc/compiler/编译器优化.md new file mode 100644 index 0000000..fcb490f --- /dev/null +++ b/Doc/compiler/编译器优化.md @@ -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);禁隐式转换 + 字面量适配;进制前缀定案 diff --git a/Doc/compiler/阶段3-语言层V2.md b/Doc/compiler/阶段3-语言层V2.md new file mode 100644 index 0000000..c6a23e1 --- /dev/null +++ b/Doc/compiler/阶段3-语言层V2.md @@ -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) diff --git a/Doc/executor/执行器入口.md b/Doc/executor/执行器入口.md index bad9343..02b9de4 100644 --- a/Doc/executor/执行器入口.md +++ b/Doc/executor/执行器入口.md @@ -7,7 +7,7 @@ BytecodeExecutor 模块:可执行入口。CMake 目标:`BytecodeExecutor`( ## 用法 ```text -BytecodeExecutor .stb [--cycles N] [--replay ] [--step] +BytecodeExecutor .stb [--cycles N] [--replay ] [--step] [--sidecar ] ``` - `--cycles N`:跑 N 个周期(缺省 10) @@ -15,12 +15,17 @@ BytecodeExecutor .stb [--cycles N] [--replay ] [--step] - `--step`:单步——采样 I 后逐指令打印(pc/fn/反汇编 + 执行后非零寄存器),跑 1 个周期,最后打印 I/Q - 每周期打印 `cycle N: I=[...] Q=[...]`(按 sidecar 绑定顺序);故障打印 `FAULT(n)` 并退出码 1 +### sidecar 定位(当前 + 计划) + +- **当前(已实现)**:sidecar 路径按「同名替换扩展名」推导——`.stb` → `.runtime.toml`。依据:sidecar 是编译产物的配对文件,同名约束由 STCompiler 写侧强制,约定即可。 +- **已实现**:`--sidecar ` 显式指定 I/O 绑定文件。适用场景:同一程序多套接线配置并存(厂内/现场不同 sidecar)、接线文件与 `.stb` 不同目录。缺省行为**不变**(未指定时回退到同名推导),向后兼容。 + ## 职责 -- 加载 `.stb` 映像与 `.runtime.toml` sidecar(I/O 绑定 var → 槽号 → channel/bit) +- 加载 `.stb` 映像与 `.runtime.toml` sidecar(I/O 绑定 var → 槽号 → channel/bit;V2 槽号为槽表条目下标) - 读取时**校验型号标识与 SHA-256**:型号不匹配 / 文件被篡改 → 直接报错拒绝(执行器只接受与自身内建型号一致的映像) -- 按 sidecar 采样 I、写回 Q(不创造变量) -- `dt_ms` / `cycle_limit` 从映像头取,推进内置 FB(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG) +- 按 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` 回放 ## 边界 diff --git a/Doc/isa/stb文件格式.md b/Doc/isa/stb文件格式.md index 3f59a55..a0c0827 100644 --- a/Doc/isa/stb文件格式.md +++ b/Doc/isa/stb文件格式.md @@ -1,127 +1,134 @@ -# .stb 映像文件结构 +# .stb 映像文件结构(V2,STATOR2) -编译产物 `.stb` 的**面向使用者**的结构说明。规范以 [`指令与映像.md`](指令与映像.md) 为准,本文用 `line1.stb`(288 字节,12.13 修订后)做真实拆解。 +编译产物 `.stb` 的**面向使用者**的结构说明。规范以 [`指令与映像.md`](指令与映像.md) 为准,本文给结构与示例。 +真实 `line1.stb` 字节拆解在**阶段 5**(`line1` 推演重算,C5)补充;本文示例为概念性布局。 ## 概览 -- 魔数 `STSC`,版本 `1`,**全部整数小端** -- 文件 = 头 + 五个段,段序:**常量表 → 函数表 → 字节码 → FB 布局 → 数据** -- 段偏移记在头里;`offset_const ≤ offset_funcs ≤ offset_code ≤ offset_fb ≤ offset_data ≤ 文件长度` +- 魔数 `STSC`,**版本 `2`**,型号标识 `STATOR2`,全部整数小端 +- 文件 = 头 + 七个段,段序:**常量表 → 函数表 → 字节码 → 槽表段 → 值段 → 元数据段 → SHA-256 尾** +- 段偏移记在头里,单调:`offset_const ≤ offset_funcs ≤ offset_code ≤ offset_slots ≤ offset_values ≤ 文件长度` ``` ┌──────────────────────────────────────────────────┐ -│ 头(104 字节,偏移 0,含型号标识[32]) │ +│ 头(128 字节,偏移 0,含型号标识[32]) │ ├──────────────────────────────────────────────────┤ -│ 常量表段 offset_const .. offset_funcs │ +│ 常量表段 offset_const .. offset_funcs │ ├──────────────────────────────────────────────────┤ -│ 函数表段 offset_funcs .. offset_code │ +│ 函数表段 offset_funcs .. offset_code │ ├──────────────────────────────────────────────────┤ -│ 字节码段 offset_code .. offset_fb │ +│ 字节码段 offset_code .. offset_slots(16 对齐)│ ├──────────────────────────────────────────────────┤ -│ FB 布局段 offset_fb .. offset_data(v1 为空)│ +│ 槽表段 offset_slots .. offset_values │ ├──────────────────────────────────────────────────┤ -│ 数据段 offset_data .. offset_data+len │ +│ 值段 offset_values .. offset_meta(或文件尾)│ +├──────────────────────────────────────────────────┤ +│ 元数据段 offset_meta (v1 = 0,空段) │ ├──────────────────────────────────────────────────┤ │ SHA-256 文件尾(32 字节,对文件尾之前全部内容) │ └──────────────────────────────────────────────────┘ ``` -## 头(104 字节) +## 头(128 字节,16 对齐) -| 偏移 | 宽 | 字段 | line1 实际值 | +| 偏移 | 宽 | 字段 | 说明 | |---|---|---|---| -| 0 | 4 | 魔数 `STSC` | `53 53 54 43` | -| 4 | 4 | 版本 `1` | 1 | -| 8 | 4 | `cycle_limit`(每周期指令上限) | 100000 | -| 12 | 4 | `dt_ms`(本周期 Δt) | 10 | -| 16 | 8 | 工程哈希(FNV-1a 64) | `0xc3fe4ae74ad45900` | -| 24 | 4 | 入口 `entry_fn_id` | 1(MAIN) | -| 28 | 4 | 全局槽数 `n_globals` | 4 | -| 32 | 4 | I 槽数 `n_i` | 0(v1 不拆) | -| 36 | 4 | Q 槽数 `n_q` | 0 | -| 40 | 4 | M 槽数 `n_m` | 0 | -| 44 | 4 | 常量数 `n_consts` | 0 | -| 48 | 4 | 函数数 `n_funcs` | 2 | -| 52 | 4 | `offset_const` | 104(`0x68`) | -| 56 | 4 | `offset_funcs` | 104 | -| 60 | 4 | `offset_code` | 128(`0x80`) | -| 64 | 4 | `offset_fb` | 200(`0xC8`) | -| 68 | 4 | `offset_data` | 200 | -| 72 | 32 | **型号标识**(定长 ASCII 含版本,补 `'\0'`) | `"STATOR1"`(`0x48..0x67`) | - -> 12.13 修订:型号标识由 `machine.toml [meta] name + version` 拼成(如 `STATOR + 1` → `"STATOR1"`); -> 执行器读取时型号不匹配 → 直接报错。 +| 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` 起) -一行 12 字节:`[tag:u32][value:u64]`;`tag`:0=BOOL、1=INT、2=TIME。 +一行 **8 字节**:`[value:u64]`(原始位模式,**无 tag**——类型由 LOADK 指令的 func 位段解释)。 -line1 无字面量常量 → 段为空(`offset_const == offset_funcs`)。 +- 按位模式去重(同值多类型复用:`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` 指向的行 - -line1(2 行): - -| fn_id | nregs | code_offset | code_len | 内容 | -|---|---|---|---|---| -| 0 | 8 | 0 | 1 | `MotorStarter` 占位(内联展开,仅 RET) | -| 1 | 13 | 4 | 17 | `MAIN` | +- **行下标即 `fn_id`**;`code_offset` 相对字节码段起点(**字节**);`code_len` 为**字节数**(变长指令,字节单位) +- 入口 = 头 `entry_fn_id` 指向的行 +- `nregs` = 本函数帧寄存器数(帧分配 = nregs×8 + 帧头 12B;max_stack 由编译期按调用图算) ## 字节码段(`offset_code` 起) -指令 4 字节一条:`[op:8 | rd:8 | a:8 | b:8]`(小端 u32)。 +**变长指令 4/8/16/32B**,段起点 16 对齐。指令字: -- `rd` 目的寄存器;`a|b` 视操作码为寄存器、槽号(u16)或跳转偏移(有符号 int16,**相对下一条指令**:目标 = 当前 + 1 + off) -- `CALL` 的 `fn_id` = 函数表行下标 -- 调用约定:结果 r0、参数 r1..r7、变量与临时 r8 起 +```text +字节 0 字节 1 +[长度前缀+形态分区 | op] [func:4 | 空:4] +``` -line1 `MAIN` 前 5 条(`0x84` 起;`0x80` 是 `fn 0` 的占位 `RET`,slot 为 8 字节定宽槽号): +- 形态(前缀直推):`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 全宽 -| 偏移 | 字节 | 反汇编 | 含义 | -|---|---|---|---| -| 0x84 | `12 08 01 00` | `LOAD_I r8, 1` | I0_0(io.input 绑定 → LOAD_I,槽 1) | -| 0x88 | `17 08 04 00` | `STORE_GLOBAL r8, 4` | starter.start(槽 4)← I0_0 | -| 0x8C | `16 09 02 00` | `LOAD_GLOBAL r9, 2` | I0_1(槽 2) | -| 0x90 | `17 09 05 00` | `STORE_GLOBAL r9, 5` | starter.stop(槽 5)← I0_1 | -| 0x94 | `16 08 04 00` | `LOAD_GLOBAL r8, 4` | 内联体:读 starter.start(槽 4) | +## 槽表段(`offset_slots` 起) -## FB 布局段(`offset_fb` 起) +一行 **8 字节**:`[addr:u32][预留:u32]`(addr = 值段内字节偏移,指向变量/FB 实例起始)。 -v1 为空(用户 FB 内联展开,字段偏移已算进数据段;内建 8 个 FB 布局冻结在编译器中(12.11 扩充))。行格式约定:`[field_count:u32][field_count × (tag:u32, offset:u32)]`,offset 相对实例基址。 +- **行下标即槽号**(slot u32);`n_slots` 行 +- 槽序:全局块(声明序,`n_globals` 个)→ FB 实例块(POU 收集序、实例声明序) -## 数据段(`offset_data` 起) +## 值段(`offset_values` 起) -**每槽 8 字节定宽**(12.9 方案 a):`BOOL` 用低 1 字节、`INT` 用低 2 字节(小端)、`TIME` 全 8 字节。指令 `slot` 操作数即**槽号**,数据偏移 = 槽号 × 8。 +**变宽紧凑布局**(对齐 1/2/4/8B,编译器保证;段起点 16 对齐): -槽序:**全局块**(`n_globals` 个,声明序)→ **FB 实例块**(POU 收集序、实例声明序,每字段一槽)。 +- 变量按类型占宽:BOOL 1B、INT 2B、DINT/REAL 4B、TIME/LREAL 8B…(func 宽度表) +- 变量初值、FB 实例字段初值(0)写于此;运行时拷入工作副本,文件只读 +- 字段访问:`LOAD/STORE rd, slot, off32`——addr = 槽表[slot].addr + off(结构体/FB 共享体统一) -line1 数据段(56 字节 = 7 槽 × 8B,`0xC8` 起): +FB 实例布局(字段偏移/大小,对齐规则)见 [`指令集清单.md`](指令集清单.md) §8.1,例: -| 槽号 | 偏移 | 内容 | +| FB | 布局(偏移) | 大小 | |---|---|---| -| 0 | 0xC8 | EmergencyStop(BOOL,初值 0) | -| 1 | 0xD0 | I0_0(BOOL) | -| 2 | 0xD8 | I0_1(BOOL) | -| 3 | 0xE0 | Q0_0(BOOL) | -| 4 | 0xE8 | starter.start(BOOL) | -| 5 | 0xF0 | starter.stop(BOOL) | -| 6 | 0xF8 | starter.q(BOOL) | +| 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:**对文件尾之前全部内容计算**(`0x100..0x11F`)。执行器必须校验通过才运行;任一字节被篡改 → 直接拒绝。compiler 写侧与 vm 读侧**各自实现**一份 SHA-256。 +文件尾追加 32 字节 SHA-256:**对文件尾之前全部内容计算**。compiler 写侧与 vm 读侧**各自实现**一份。执行器校验不通过 → 直接拒绝。 + +## 读取校验链(vm 读侧) + +1. **结构校验**:魔数/版本/头大小/段偏移单调且在 SHA 前/常量表·函数表尺寸一致/代码段 16 对齐/入口 fn_id 范围 +2. **SHA-256**:完整性(防篡改) +3. **型号匹配**:头内 32 字节与 `"STATOR2"` 补零逐字节比对 + +任一不符 → `Machine::create` 拒绝,不创建机器。 + +## 与 sidecar / 执行器的关系 + +- `.stb` + `.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`:`codegen_slice1`(写侧 model/sha 自检)、`vm_cycles`(读侧型号/SHA 拒绝)、`machine_config` -- `STCompiler x.stb --disasm --machine machine.toml`:显示 `model=` 与 `sha=ok|BAD` -- 手工检查:`xxd line1.stb` 对照上表逐段核对 - -## 与执行器的关系 - -`BytecodeExecutor` 只读本格式:头(`cycle_limit`/`dt_ms`/型号标识)、函数表(帧 `nregs` + 代码定位)、数据段(全局 + FB 实例状态)。读取时**型号匹配 + SHA-256 校验**,任一不符 → 报错拒绝。I/O 绑定**不在**映像里,在 sidecar `.runtime.toml`(var → 槽号 → channel/bit)。 +- `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 推演重算) diff --git a/Doc/isa/指令与映像.md b/Doc/isa/指令与映像.md index 354fee2..2452e11 100644 --- a/Doc/isa/指令与映像.md +++ b/Doc/isa/指令与映像.md @@ -1,10 +1,14 @@ -# 指令与映像 +# 指令与映像(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` 写 `.stb`、`vm` 读 `.stb`,两边**各自实现**读写;`isa` 归执行器侧只管指令定义。指令 opcode / 类型 / FB 布局的登记见 [`指令配置.md`](指令配置.md)(`compiler/machine.toml`,与代码强校验)。 - -全工程定案见 [`初步计划.md`](../初步计划.md)。**类型、指令编码、映像布局以本文为准**,不要另写一份互相打架的表。头文件实现本文,不另当规范。 +`compiler` 写 `.stb`、`vm` 读 `.stb`,两边**各自实现**读写;`isa` 归执行器侧只管指令定义。 +指令 opcode / 类型 / FB 布局的登记见 [`指令配置.md`](指令配置.md)(`compiler/machine.toml`,与代码强校验)。 ```text STCompiler → compiler (编码查 machine.toml;自带 .stb 写) @@ -15,13 +19,13 @@ CMake 目标:`isa`(`STATIC`),无依赖;`vm` PUBLIC 链 `isa`。公开 --- -## 做 / 不做 +## 1. 做 / 不做 **做** -- 操作码与 32 位指令布局 -- 映像头与段(函数字节码、全局 / FB 槽、工程哈希) -- `BOOL` / `INT` / `TIME` 宽度与溢出规则 +- 操作码(前缀分区)与**变长指令**布局(4/8/16/32B) +- 映像头 128B 与段(常量表 / 函数表 / 字节码 / 槽表段 / 值段 / 元数据段) +- 19 种 IEC 类型 → 10 种 func 的宽度/符号表、饱和与除法边界 - 编解码、工程哈希、一条指令的文本转储(反汇编,给 STCompiler `--disasm` 与测试打印) **不做** @@ -34,209 +38,306 @@ CMake 目标:`isa`(`STATIC`),无依赖;`vm` PUBLIC 链 `isa`。公开 --- -## 特性 +## 2. 特性总表(V2) -| 项 | 第一版 | +| 项 | V2 定案 | |---|---| -| `BOOL` | `uint8_t`,只允许 0/1 | -| `INT` | `int16_t`(IEC `INT`) | -| `TIME` | `int64_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),无函数指针 | - -### 调用约定(冻结,compiler 12.8 与 VM 12.9 共同遵守) - -- 结果寄存器 **r0**;参数寄存器 **r1..r7**(最多 7 个输入,按声明序) -- 变量与表达式临时**全部从 r8 起**(调用约定区 r0..r7 不受用户数据占用) -- `CALL`:VM 复制当前帧 r0..r7 到新帧 r0..r7 -- `RET`:VM 复制当前帧 r0..r7 回调用方帧 r0..r7(r0 = 结果;r1..r7 原样返回) -- 函数输入在函数体内只读(compiler 拒绝写入) -| 映像魔数 | `STSC`,版本 `1` | +| 映像 | 魔数 `STSC`、**version = 2**、型号 `STATOR2` | | 工程哈希 | 源文件路径排序后对内容做 FNV-1a 64(非密码学) | -| I/O 绑定 | **不进映像**;编译期把 `var` 收成槽号,channel/bit 由 STCompiler 写进 sidecar(`.runtime.toml`),BytecodeExecutor 读 sidecar 采样 | -| 不做 | `REAL`、隐式宽化 | +| 完整性 | 文件尾 SHA-256[32];型号标识[32]@72 | +| I/O 绑定 | **不进映像**;sidecar(`.runtime.toml`)绑定 var→槽号→channel/bit,粒度不变 | +| 内建 FB | 15 条(CAL + fb_id 16 位);字段布局见 [`指令集清单.md`](指令集清单.md) §8.1 | --- -## 类型 - -C++20 定宽: - -- `BOOL`:`uint8_t`,0/1 -- `INT`:`int16_t`;加减乘除溢出饱和 -- `TIME`:`int64_t` 毫秒,有符号 - -`compiler` 与 `vm` 都按同一规则,不再另选溢出策略。 - -`TIME` 选 `int64_t` 的理由(冻结,不再反复问): - -- 有符号:`TOF` 剩余时间、两个时间点相减会出负值,免去无符号边界处理 -- 64 位:为将来 `DATE_AND_TIME`(毫秒时间戳)与 `TON` / `TOF` / `TP` 的 64 位定时器预留;毫秒量程约 2.9 亿年,扫描周期场景永不溢出 -- 代价:槽位 8 字节对齐。**槽位规则**(12.9 方案 a):每槽 8 字节定宽,`BOOL` 用低 1 字节、`INT` 用低 2 字节(小端)、`TIME` 全 8 字节;槽号 × 8 = 数据偏移,无 padding。 - ---- - -## 指令 - -定宽 32 bit: +## 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(指令条数),**相对下一条指令**:目标下标 = 当前下标 + 1 + off -- `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_TP / CAL_CTU / CAL_CTD / CAL_CTUD / CAL_R_TRIG / CAL_F_TRIG -CALL fn_id -RET +槽表段(静态引用表,8B/条) 值段(初值,紧凑变宽) +{ addr:u32, 预留:u32 } ──地址──▶ BOOL 1B / INT 2B / DINT·REAL 4B / TIME·LREAL 8B +槽 0..n_slots-1 结构体 = 连续字段块(v1 预留) ``` -> 12.11 修订:内置功能块扩为 8 个(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG), -> `CAL_*` 操作码**连续占 24..31**(原 CALL=27、RET=28 后移为 32、33)。 -> 操作码数值即编码,本表为准。 - -局部 / `VAR` = 固定寄存器;表达式临时值往后编号;`nregs` 写进函数头。 -`I`/`Q`/`M` 走映像指令,不是通用寄存器。 -FB 实例固定布局,字段偏移编译期算死。 - -### 反汇编格式(disasm) - -`disasm` 输出一条指令的一行文本,格式冻结: - -| 形态 | 格式 | 示例 | -|---|---|---| -| 二寄存器 | `MOVE rd, rs` / `NOT rd, rs` | `MOVE r1, r2` | -| 三寄存器 | `OP rd, ra, rb`(逻辑 / 算术 / 比较) | `ADD r1, r2, r3` | -| 常量 | `LOADK rd, imm` | `LOADK r0, 3` | -| 无条件跳转 | `JMP off` | `JMP +4` | -| 条件跳转 | `JT rd, off` / `JF rd, off` | `JT r0, -1` | -| I/Q/M/全局 | `LOAD_I rd, slot` 等;STORE 同形 `STORE_Q rs, slot` | `LOAD_I r0, 2` | -| FB 内置 | `CAL_TON instance` 等 | `CAL_TON 0` | -| 调用 | `CALL fn_id` | `CALL 1` | -| 返回 | `RET` | `RET` | -| 未知操作码 | `??? 0x` | — | - -跳转偏移带符号、恒显正负号(`%+d`);寄存器一律 `r`;常量/槽号/`fn_id` 无符号十进制。 +- **访问**:`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`。全部整数小端。文件 = 头 + 五个段 + **文件尾校验**,段序:常量表 → 函数表 → 字节码 → FB 布局 → 数据。偏移记在头里,段必须连续、偏移单调不减,末段终点不超过文件长度(文件尾在数据段之后)。 +### 8.1 头(128 字节,16 对齐) -> 12.13 修订:头新增 **型号标识[32]**(原头 72 字节 → 104 字节,段偏移基准同步后移); -> 文件尾新增 **SHA-256[32]**(对文件尾之前全部内容计算,compiler 写侧 / vm 读侧各自实现)。 -> 执行器读取时:型号标识与自身支持型号不匹配 → 报错;SHA-256 不匹配 → 报错(损坏/篡改)。 - -### 头(104 字节) - -| 偏移 | 宽 | 字段 | +| 偏移 | 字段 | 说明 | |---|---|---| -| 0 | 4 | 魔数 `STSC` | -| 4 | 4 | 版本 `1` | -| 8 | 4 | `cycle_limit` | -| 12 | 4 | `dt_ms` | -| 16 | 8 | 工程哈希(FNV-1a 64) | -| 24 | 4 | 入口 `entry_fn_id` | -| 28 | 4 | 全局槽数 `n_globals` | -| 32 | 4 | I 槽数 `n_i` | -| 36 | 4 | Q 槽数 `n_q` | -| 40 | 4 | M 槽数 `n_m` | -| 44 | 4 | 常量数 `n_consts` | -| 48 | 4 | 函数数 `n_funcs` | -| 52 | 4 | `offset_const` | -| 56 | 4 | `offset_funcs` | -| 60 | 4 | `offset_code` | -| 64 | 4 | `offset_fb` | -| 68 | 4 | `offset_data` | -| 72 | 32 | **型号标识**(定长 ASCII,含版本,如 `"STATOR" + "1"` 拼 32 字节补 `'\0'`) | +| 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 字节 | -### 文件尾(SHA-256) +### 8.2 段序 -数据段之后追加 **32 字节 SHA-256**:对文件尾之前全部内容计算。执行器必须校验通过才运行;compiler 写入。 - -### 常量表一行(12 字节) - -`[tag:u32][value:u64]`;`tag`:0=BOOL、1=INT、2=TIME。`value` 按 tag 解释,未用高位为 0。 - -### 函数表一行(12 字节) - -`[nregs:u32][code_offset:u32][code_len:u32]`;行下标即 `fn_id`,`code_offset` 相对字节码段起点,`code_len` 为指令条数。 - -### 字节码段 - -指令按 `u32` 连续排;`CALL` 的 `fn_id` 是函数表行下标。 - -### FB 布局段 - -第一版为空;每类型一行 `[field_count:u32][field_count × (tag:u32, offset:u32)]`,offset 相对实例基址,12.8 落地 codegen 时冻精确表。 - -### 数据段 - -槽初值按序排:全局(`n_globals`,v1 不拆 I/Q/M)→ FB 实例块(每字段一槽)。**每槽 8 字节定宽**(12.9 方案 a):`BOOL` 用低 1 字节、`INT` 用低 2 字节(小端)、`TIME` 全 8 字节。指令 `slot` 操作数即**槽号**,数据偏移 = 槽号 × 8;符号表 `address` 与指令 slot 一致,无映射。 - -工程哈希:收录源文件路径排序后对内容做 **FNV-1a 64**,只保证回放槽位一致,不是密码学哈希。FNV-1a 64:basis `0xcbf29ce484222325`,prime `0x100000001b3`。 - -`[[io.input]]` / `[[io.output]]` 不进映像。 - ---- - -## 映像文件(`.stb`)与 sidecar - -编译产物是单文件 `.stb`:映像头 + 各段按本文布局连续排,魔数 `STSC`、版本 `1`、小端。`STCompiler` 写,`BytecodeExecutor` 读。 - -- `dt_ms` / `cycle_limit` 在映像头,执行器只认映像,不重复配置。 -- I/O 绑定不进映像:`STCompiler` 另写 sidecar `.runtime.toml`,`BytecodeExecutor` 读 sidecar 做采样与写回,不创造变量。 - -sidecar 格式(TOML 子集,冻结): - -```toml -[[io.input]] # 或 [[io.output]] -var = "I0_0" # 已在 GVL 声明的名字 -slot = 1 # 全局槽号(编译期已解析) -channel = 0 -bit = 0 +```text +头(128) → 常量表(8B/条) → 函数表(12B/行) → 字节码(16 对齐) → +槽表段(8B/条) → 值段 → 元数据段(v1 空) → SHA-256[32] ``` -每行一个绑定;`var` 顺序不要求与映像槽序一致(`slot` 为准)。 +### 8.3 段细节 + +- **常量表行(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 + +- `.stb`(映像)+ `.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.h # 宽度、饱和、槽值表示 - Op.h # 操作码枚举 + 助记符 - Instr.h # 32-bit 打包/拆字段(inline) - Image.h # 头、段、函数表/常量表/FB 布局 - Encode.h # 编解码、哈希、反汇编一行 + Types.h # 宽度/符号/饱和、除零边界 + Op.h # 前缀分区 + 操作码 + 形态 + Instr.h # 变长 pack/拆字段(inline) + Encode.h # encode / decode / disasm src/ Encode.cpp - Image.cpp ``` ```cmake @@ -248,12 +349,8 @@ target_include_directories(isa PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) --- -## 实现顺序(对齐初步计划 12.0~12.1) +## 12. 实现约定 -1. `Types.h`:三类型 + `sat_add` 等饱和函数(inline 即可) -2. `Op.h` + `Instr.h`:枚举与 32-bit 编解码 -3. `Encode.cpp`:`encode` / `decode` / `disasm` 一条指令 -4. `Image.h` + `Image.cpp`:头布局、FNV-1a、读写缓冲(先能写空映像 + 一条 `RET`) -5. 测试随后在 `tests/` 加编解码往返;本阶段以头文件和库能链为准 - -不在本模块做:VM `switch`、toml、关键字、符号表。 +- machine.toml(V2 schema)与代码**强校验**:前缀分区/op/func 互锁(见指令配置.md,阶段 1 重写) +- 操作码有变:先改 machine.toml + 本目录 + 测试,再动两端 +- 错误类别:`isa` 层无错误前缀(编解码纯函数);vm/compiler 各自带前缀 diff --git a/Doc/isa/指令编码.md b/Doc/isa/指令编码.md new file mode 100644 index 0000000..9fb3b05 --- /dev/null +++ b/Doc/isa/指令编码.md @@ -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 diff --git a/Doc/isa/指令配置.md b/Doc/isa/指令配置.md index 88baef7..87c279a 100644 --- a/Doc/isa/指令配置.md +++ b/Doc/isa/指令配置.md @@ -1,176 +1,189 @@ -# 机器定义文件(machine.toml) +# 机器定义文件(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++)+ 强校验 +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 自实现) - │ + 型号匹配校验 + SHA-256 校验 + │ + 型号匹配校验(STATOR2)+ SHA-256 校验 ▼ isa(归执行器侧,只管指令定义) ``` | 模块 | 职责 | |---|---| -| `compiler` | 配置驱动编译;自带 `.stb` 写实现;`--disasm` 用配置参数名/format 自格式化 | -| `vm` | 内建全指令集(执行 switch 在 vm);自带 `.stb` 读实现 + 型号/SHA-256 校验;无屏蔽、不读配置 | -| `isa` | **归执行器侧**(vm 依赖),**只管指令定义**:Op 枚举/Instr/Encode/OpFormat/OpClass/类型/饱和/disasm。指令的具体执行实现在 vm | -| `machine.toml` | 编译器唯一知识来源 | +| `compiler` | 配置驱动编译;自带 `.stb` 写实现;`--disasm` 用配置参数名/形态自格式化 | +| `vm` | 内建全指令集(执行 switch 在 vm);自带 `.stb` 读实现 + 型号/SHA-256 校验;不读配置 | +| `isa` | 归执行器侧(vm 依赖),只管指令定义:前缀分区/Op/Instr/Encode/形态/类型/饱和/disasm | +| `machine.toml` | 编译器唯一知识来源(V2 schema,本文) | -## 决策清单(定案) +## 决策清单(V2 定案) -1. 指令两大分类:**PLAIN**(无实例,26 条)/ **INSTANCE**(有实例,8 条 `CAL_*`) -2. 三层分类:`class`(操作对象)→ `format`(操作数形态)→ `参数名`(语义) -3. isa 归执行器侧、只管指令定义;执行 switch 留在 vm -4. 配置改 TOML、改名 machine.toml,只给编译器;vm 不需要配置 -5. 一致性靠 `.stb`:**型号标识[32]**(定长字符串含版本)+ **SHA-256 文件尾**(compiler/vm 各实现一份)+ 强校验 -6. 类型:**基元内置**(编译器内建 bit/int8..uint64/float32/float64,元数据在代码);配置类型行 = `name + base + range? + tag`(别名);**值域约束进配置**(如 BOOL range=[0,1]) -7. 字符串:**方案 A**(v1 不做;"组合类型"(含定长字符串 STRING(n))为未来扩展,需时再扩 .stb 格式) -8. `.stb` 读写:compiler 写、vm 读,**各自实现**(无共享 image 模块) -9. 冻结哈希测试更新(接受) +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 预留位已留) -## 指令三层分类 - -```text -class(操作对象) → format(操作数形态) → 参数名(操作数语义) - PLAIN/INSTANCE RR/RRR/IMM/SLOT/ rd/rs/const_id/ - JMP/JC/CALL/CAL/NONE slot/off/fn_id/instance -``` - -| 大类 | 操作对象 | 现有指令 | -|---|---|---| -| **PLAIN**(无实例) | 寄存器 / 立即数 / 槽号 / 分支 / 调用 | MOVE/LOADK/NOT/AND/OR/算术/CMP_* / JMP/JT/JF / LOAD_*/STORE_* / CALL/RET(26 条) | -| **INSTANCE**(有实例) | 数据区实例块(内建 FB) | CAL_TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG(8 条) | - -format 不可省:同是"第二个操作数是数字",`LOADK`/`LOAD_I`/`CALL` 渲染相同但语义不同(常量 id / 槽号 / fn_id,由参数名表达);"数字 vs 寄存器 vs 偏移"的渲染差异(`r%d` / `%u` / `%+d`)只有 format 能表达。 - -## 配置文件格式(TOML) +## 配置文件格式(TOML V2) **全属性必填,不可省略**: ```toml [meta] name = "STATOR" # 型号名(参与 .stb 型号标识) -version = 1 # 指令集版本(参与 .stb 型号标识) +version = 2 # 格式版本(型号标识 = "STATOR2";V1 = 1) -# ---- 类型:编译器内建基元(bit/int8/int16/int32/int64/uint8/uint16/uint32/uint64/float32/float64) -# 配置类型 = 基元别名(+ 值域约束 + .stb 契约 tag) +# ---- 类型: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" -range = [0, 1] # 值域约束(语言语义数据化;缺省 = 基元全范围) -tag = 0 # 常量表契约(强校验) +kind = "bool" +func = 0 [[type]] -name = "INT" -base = "int16" -tag = 1 +name = "BYTE" +base = "uint8" +kind = "bit" +func = 0 [[type]] -name = "TIME" -base = "int64" -tag = 2 +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" -opcode = 0 -class = "plain" # plain / instance -format = "RR" +prefix = "11" +op = 0 +fmt = "RR" +func = "width" params = ["rd", "rs"] enabled = true [[op]] -name = "CAL_TON" -opcode = 24 -class = "instance" -format = "CAL" -params = ["instance"] +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" -opcode = 33 -class = "plain" -format = "NONE" +prefix = "10" +op = 1 +fmt = "NONE" +func = "none" params = [] enabled = true -# ---- 内建 FB(布局登记;cal_opcode 与 op 行强校验一致) +# ---- 内建 FB(15 条;fb_id 唯一 0..14;字段类型命中 type 表;布局偏移编译期按 1/2/4/8 对齐计算) [[fb]] -name = "ton" -opcode = 24 +name = "TON" +fb_id = 0 fields = [["in", "BOOL"], ["pt", "TIME"], ["q", "BOOL"], ["et", "TIME"]] [[fb]] -name = "ctud" -opcode = 29 -fields = [["cu", "BOOL"], ["cd", "BOOL"], ["r", "BOOL"], ["lu", "BOOL"], - ["pv", "INT"], ["qu", "BOOL"], ["qd", "BOOL"], ["cv", "INT"]] +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 互锁) -- **基元内置**:编译器内建 `bit/int8/int16/int32/int64/uint8/uint16/uint32/uint64/float32/float64`(宽度/有无符号/是否浮点元数据在代码,C 语义) -- **别名**:配置类型行只写 `name + base + range? + tag`——宽度/符号/浮点从 base 继承 -- **值域约束**:`range`(如 BOOL [0,1])供编译器做常量/赋值检查;缺省 = 基元全范围 -- **tag 契约**:`tag`(0/1/2)与 `.stb` 常量表格式强校验(防 type 表改序破坏旧 .stb) -- **字符串**:v1 不做(方案 A);组合类型(含 `STRING(n)`)为未来扩展,文档预留,需时再扩 `.stb` 格式 -- 半数据化边界:**类型定义数据化**(配置),**运算/类型检查语义代码化**(如 AND 只吃 BOOL 是语言规则,在 compiler 代码;饱和算术在 vm/isa) +| 类型 | 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 无 | -## .stb 加固(格式冻结更新) +- **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# -见 [`指令与映像.md`](指令与映像.md):头新增 **型号标识[32]**(定长 ASCII 字符串,含版本,不足补 `'\0'`);文件尾新增 **SHA-256[32]**(对前面全部内容计算,compiler 写侧 / vm 读侧各自实现)。执行器读取时: +## 强校验清单(加载时,任一不符 → 报错退出) -```text -型号标识与自身支持型号不匹配 → 直接报错 -SHA-256 不匹配 → 报错(文件损坏/篡改) -``` - -## 强校验清单(加载/读取时,任一不符 → 报错退出) - -1. TOML 语法与字段完整性(全属性必填) -2. `type.base` 命中内建基元;`range` 合法(min ≤ max、类型相符) -3. `tag` 与 `.stb` 常量表契约一致(0/1/2) -4. `op.opcode` 唯一、0..255;`class` 合法;`format` 合法;**类别-格式互锁**(INSTANCE ↔ CAL) -5. `fb.opcode` 与 op 行的 CAL_* 一致;fb 字段类型命中配置类型表 -6. 型号标识(.stb 侧) +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 表非空 ## 加东西的流程 -```text -加普通指令:machine.toml 加 [[op]] 一行 + vm 一个语义 case → 重建 vm -加内建 FB :machine.toml 加 [[fb]] 一行 + vm 一个语义 case → 重建 vm(compiler 免重建) -加语言类型:machine.toml 加 [[type]] 一行(别名+tag)+ 类型检查/VM 语义代码 → 重建 -改型号版本:machine.toml [meta] version +1 → 旧 .stb 被旧/新执行器按型号校验决定是否接受 -``` +| 加什么 | 改配置 | 改代码 | +|---|---|---| +| 新指令(RR/RRR 等已有形态) | op 表加一行(分区内空闲 op) | 编译器发射 + vm 执行 switch | +| 新形态(fmt 11..15) | — | 指令编码 + 两端实现(预留位) | +| 新类型(标量) | type 表加行 + func 空位 | 编译器类型层 + vm func 表 | +| 新内建 FB | fb 表加行 | vm do_cal 分派 + 编译器实例布局 | +| 字符串/数组/跳转表 | — | 32B 组(0001)实现(N2/N4,延后) | -## 执行计划(分支 dev_ops_config) +## 与 V1 schema 的差异 -``` -0. 文档定案(本文 + 指令与映像.md 格式冻结更新 + stb文件格式.md 修订注记) ← 当前 -1. ✅ OpFormat/OpClass 显式化 + disasm 表驱动(已完成,35dabf4) -2. machine.toml 落地:meta + type 表 + op 34 条 + fb 8 条 -3. compiler 配置驱动改造:toml++ 加载 + 强校验;编码 opcode / 类型元数据 / - FB 布局来自配置(compiler 不再依赖 isa) -4. .stb 写侧:compiler 实现 型号标识[32] + SHA-256 文件尾 -5. .stb 读侧:vm 自实现读 + 型号匹配 + SHA-256 校验(isa 的 Image 拆出) -6. CLI:STCompiler --machine (编译路径必填) -7. 测试:machine 解析/校验负例、型号不匹配报错、SHA-256 篡改检测、 - 冻结哈希更新、全量回归 -8. 文档 + 提交 -``` - -## 实现前确认项(4 项,未定案) - -1. 配置文件名与位置:建议 `compiler/machine.toml`(只归编译器用) -2. 型号标识生成规则:建议 `[meta] name + version` 拼成 32 字节(如 `"STATOR" + "1"` → 补 `'\0'`);vm 内建"支持型号列表"比对 -3. compiler 内部 `TypeKind`(Bool/Int/Time):保留为语言语义枚举(类型检查规则),元数据从配置 base 查 -4. 依赖方向确认:`compiler` 不再依赖 `isa`(`compiler → toml++` 自有;`STCompiler → compiler`;`vm → isa`) +| 项 | 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) | diff --git a/Doc/isa/指令集优化.md b/Doc/isa/指令集优化.md new file mode 100644 index 0000000..3577718 --- /dev/null +++ b/Doc/isa/指令集优化.md @@ -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 后实现;`预留` = 仅留编码/未来。 diff --git a/Doc/isa/指令集清单.md b/Doc/isa/指令集清单.md new file mode 100644 index 0000000..d9c3880 --- /dev/null +++ b/Doc/isa/指令集清单.md @@ -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` 区)。 diff --git a/Doc/isa/阶段1规格总览.md b/Doc/isa/阶段1规格总览.md new file mode 100644 index 0000000..eb6585e --- /dev/null +++ b/Doc/isa/阶段1规格总览.md @@ -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 映射) | diff --git a/Doc/isa/阶段4-执行侧V2.md b/Doc/isa/阶段4-执行侧V2.md new file mode 100644 index 0000000..81da1d3 --- /dev/null +++ b/Doc/isa/阶段4-执行侧V2.md @@ -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)——用户决定时机 | diff --git a/Doc/vm/扫描周期.md b/Doc/vm/扫描周期.md index 33c0bd1..277fc57 100644 --- a/Doc/vm/扫描周期.md +++ b/Doc/vm/扫描周期.md @@ -1,24 +1,24 @@ # 扫描周期 -vm 模块:寄存器虚拟机。CMake 目标:`vm`(`STATIC`),只依赖 [`指令与映像.md`](../isa/指令与映像.md)。 +vm 模块:寄存器虚拟机(V2:变长指令 / 槽表两级 / 栈方案 C)。CMake 目标:`vm`(`STATIC`),只依赖 [`指令与映像.md`](../isa/指令与映像.md)。 运行时是 **一台 VM、一个入口、一份映像**。不链接 `compiler`。扫描周期与确定性见 [`初步计划.md`](../初步计划.md) 第 2、8 节。 ## 扫描周期 ```text -1. 按 sidecar 的 io 绑定,采样 → I / 输入全局 +1. 按 sidecar 的 io 绑定,采样 → I / 输入全局(executor 写值区槽) 2. 从 PROGRAM MAIN 的 pc=0 执行到 RET -3. 写回 Q / 输出全局 -4. 用本周期 Δt 推进内置 FB(不用 wall clock) +3. 写回 Q / 输出全局(executor 读值区槽) +4. 用本周期 Δt(映像头 dt_ms)推进内置 FB(不用 wall clock);RTC 用累计周期数 ``` ## 边界 -- 只认 `.stb` 映像(`vm` 自带读实现);读取时**型号标识匹配 + SHA-256 校验**,不符/篡改 → 拒绝 -- 类型与溢出按 [`指令与映像.md`](../isa/指令与映像.md)(`INT` 饱和);指令定义归 `isa` -- 局部 / `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 谁给、回放怎么喂,属于 `BytecodeExecutor`(读 `.stb` + sidecar) @@ -32,19 +32,21 @@ TUI 是执行器(`BytecodeExecutor`)的界面,用途是方便观察 VM 运 - **只读轮询**:TUI 通过快照轮询,绝不写 VM 状态;"继续 / 单步"是 executor 侧命令,不改变确定性。 - **快照 POD 化**:快照保持简单数据(将来跨进程 TUI 可序列化)。 -建议的 API 形态(12.9 实现时冻结签名,本节只定方向): +已落地的观察面(`Machine` 公共接口): ```cpp -struct Snapshot { - uint32_t pc; // 当前指令下标(相对函数字节码段) - uint32_t fn_id; // 当前函数 - uint32_t cycle_count; // 本周期已执行指令数 - uint32_t call_depth; // 调用栈深度 - // 寄存器 / I / Q / M / 全局 / FB 字段 的只读视图 -}; - -bool step(); // 精确执行一条指令,停在指令边界 -const Snapshot& snapshot() const; +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、映像很小,快照拷贝成本可忽略。 @@ -52,71 +54,17 @@ const Snapshot& snapshot() const; --- -## 12.9 执行计划 +## 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)。 - -### 步骤 0:数据段改 8 字节定宽槽(方案 a,前置) - -VM 需要无歧义地访问数据区,当前变宽布局(BOOL 1B / INT 2B / TIME 8B 对齐)无法从槽号推断宽度。定案:**每槽 8 字节定宽**,指令 slot 语义 = **槽号**(数据偏移 = slot × 8)。 - -- 文档修订:`Doc/isa/指令与映像.md`(数据段规则 + slot 语义)、`Doc/compiler/寄存器码.md`(存储布局,`global_offset` 映射取消)、`Doc/isa/stb文件格式.md`(line1 数据段 16B → 56B) -- `Codegen.cpp` 简化:`width_of`/`align_up` 删除、`layout_data`/`layout_fb_instances` 按槽号布局、指令 slot = 符号表槽号 -- `codegen_test` 断言更新(slot 从字节偏移变槽号、数据区字节数变化) - -### 步骤 1:`Machine.h`(已落地 `vm/include/vm/Machine.h`) - -```cpp -namespace vm { - enum class Fault { None, CycleLimit, StackOverflow, BadOp, BadSlot, BadConst }; - - class Machine { - public: - static bool create(const std::vector& image, Machine* out, std::string* err); - Fault run_cycle(); // MAIN pc=0 → RET(MAIN 帧跨周期保留) - // 可观察性(v1 预留,见上节): - bool step(); // 执行一条指令,停在指令边界 - uint32_t pc() / fn_id() / cycle_count() / call_depth(); - uint8_t* data(); // 数据区(I 采样 / Q 读回由外部做) - int64_t reg(uint8_t i); // 当前帧寄存器 - }; -} -``` - -- `Fault` 枚举 + 帧结构 `{ fn_id, regs[nregs], ret_pc, ret_fn_id }`;调用栈深度上限 64 -- 只依赖 `isa`(指令定义)+ 自带 `vm::Image`(.stb 读) - -### 步骤 2:`Machine.cpp` 译码 switch(已落地) - -- 取指:函数表 → `code_offset`(字节)→ 读 u32 → 拆 `op/rd/a/b` -- 标量:`MOVE` / `LOADK`(常量表 tag 定值表示)/ `NOT` / `AND` / `OR`(布尔)/ `ADD/SUB/MUL/DIV`(`sat_*`)/ `CMP_xx`(全宽比较) -- 跳转:`JMP` / `JT` / `JF`(`pc += off`,相对下一条语义) -- 数据区:`LOAD_I/LOAD_M/LOAD_GLOBAL`、`STORE_Q/STORE_M/STORE_GLOBAL`(同一实现,slot × 8) - -### 步骤 3:CALL / RET 帧栈 + 故障(已落地) - -- `CALL fn_id`:压新帧(复制 r0..r7)→ pc=0;`RET`:复制 r0..r7 回调用方 → 弹栈(MAIN 的 RET = 周期结束) -- `cycle_limit` 超限(用例 6)、栈深 > 64、`BadOp` / `BadSlot` / `BadConst` → 周期中止返回故障 - -### 步骤 4:定时器与边沿(已落地) - -- 执行时用映像 `dt_ms` 推进(每周期一次调用 = 推进一次),不读系统时钟 -- TON:in 真 → et += dt(到 pt 停)、q = et ≥ pt;in 假 → et = 0 -- TOF:in 真 → q=1、et=0;掉电 → et += dt、et ≥ pt → q=0 -- CTU:cu 上升沿(**上次 cu 存 VM 侧 vector**,按实例基槽索引)→ cv+1;r → 复位;q = cv ≥ pv - -### 步骤 5:可观察性基础(已落地) - -- `step()` / `pc` / `fn_id` / `cycle_count` / `call_depth` / `reg` 落基础实现;`snapshot()` 留 12.11 补全 - -### 步骤 6:`vm_test`(已落地) - -- 手工拼小映像(`isa` 编码函数):赋值、跳转、CALL/RET 帧复制、cycle_limit -- 编译器产物全链路:用例 01/02/04/05/07/09/13/15/17/18/20 跑周期断言寄存器与数据区 -- 用例 06:`cycle_limit` 打满 → `CycleLimit` 故障 -- TON:dt=10、pt=30 → 第 3 周期 q=1;CTU:上升沿计数 -- **确定性**:同一 I 序列跑两遍,数据区完全一致 - -### 步骤 7:验证 + 提交(已落地,ctest 14/14) - -- `cmake --build` + `ctest`(新增 `vm_cycles` 后 10/10 全绿);20 用例扫描:14 正例执行通过 diff --git a/Doc/vm/指令执行.md b/Doc/vm/指令执行.md index f6ade00..d09ae1c 100644 --- a/Doc/vm/指令执行.md +++ b/Doc/vm/指令执行.md @@ -1,128 +1,139 @@ -# 指令执行流程(12.9) +# 指令执行流程(V2) `vm` 模块的指令执行语义:取指、译码、执行、故障。规范以 [`指令与映像.md`](../isa/指令与映像.md) 为准,本文描述**执行器怎么跑一条指令**。执行模型见 [`初步计划.md`](../初步计划.md) 第 2 节。 ## 执行模型 ```text -加载(Machine::create):解析 .stb(vm 自读实现)→ 型号匹配(内建 STATOR1)→ SHA-256 校验 +加载(Machine::create):解析 .stb(vm 自读实现)→ 型号匹配(内建 STATOR2)→ SHA-256 校验 任一不符 → 直接拒绝(不创建机器) + 值区(data_)取映像值段的写副本(周期内 FB 字段/全局可变) + 边沿缓冲 edge_prev_(每槽 2 字节,跨周期保留) + 栈区预分配 max_stack 字节(栈方案 C,编译期算出) 每个扫描周期(run_cycle): - 1. pc = 0,从入口函数(MAIN)开始逐条执行 + 1. cycle_count = 0;pc = 0,从入口函数(MAIN)逐条执行 2. 执行到 MAIN 的 RET → 周期结束 - 3. (I 采样 / Q 写回由 executor 按 sidecar 做,VM 只管数据区) - 4. 定时器在 CAL_* 指令执行时用映像 dt_ms 推进 + 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_bytes[code_offset/4 + pc]` 读 u32(小端) -- **译码**:拆 `op / rd / a / b` 四字段,`a|b` 按操作码解释为寄存器对、槽号(u16)、或跳转偏移(int16) +- **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 -struct Frame { - uint32_t fn_id; // 当前函数 - std::vector regs; // 寄存器文件,大小 = 函数头 nregs - uint32_t ret_pc; // CALL 后应恢复的 pc(仅非 MAIN 帧有意义) - uint32_t ret_fn_id; // 返回目标函数(调用方) -}; +// 栈区(stack_,预分配 max_stack 字节): +// [MAIN 帧][帧2][帧3]... 从低地址起连续压栈 +// 每帧 = 12B 帧头 + nregs×8 寄存器文件 +// 帧头:hdr[0]=fn_id hdr[1]=ret_pc(字节) hdr[2]=ret_fn_id ``` -- 调用栈 `std::vector`,**深度上限 64**;超限 → `StackOverflow` 故障 +- 调用栈深度**上限 64**(`frames_.size() >= 64` 时 CALL → `StackOverflow`);同时 `sp_ + 帧大小 > max_stack` → `StackOverflow`(双检查) - **MAIN 帧跨周期保留**(PROGRAM 变量状态持久);周期结束只清调用栈(保留栈底 MAIN 帧) - 帧寄存器初值 0 -### CALL / RET(调用约定,冻结于 12.8) +### CALL / RET(调用约定) ```text CALL fn_id: 1. 新帧:regs 大小 = callee 的 nregs,全部 0 - 2. 复制当前帧 r0..r7 → 新帧 r0..r7(实参已在调用点 MOVE 进 r1..r7) - 3. 新帧 ret_pc = 当前 pc + 1(CALL 的下一条)、ret_fn_id = 当前函数 + 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(栈深 0)→ 周期结束 + 1. 若当前是 MAIN(栈深 1)→ 周期结束(ended_ = true) 2. 否则:复制当前帧 r0..r7 → 调用方帧 r0..r7(r0 = 结果,r1..r7 原样返回) 3. 弹栈,pc = ret_pc,fn_id = ret_fn_id ``` -## 值表示(寄存器 / 槽统一约定) +## 值表示(func 位段驱动) -| 类型 | 表示 | 说明 | -|---|---|---| -| BOOL | `0` / `1` | 逻辑指令产出、字面量 TRUE=1 | -| INT | `int16_t` 符号扩展 | `sat_*` 返回 int16,赋给 int64 寄存器自动符号扩展 | -| TIME | 全 64 位 | 毫秒 | +| 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 比较等价于各类型自身比较**(INT 符号扩展后 -5 < 3 正确)。类型语义由编译期类型检查保证,VM 不查类型。 +- 寄存器(int64)统一承载:窄类型符号扩展/清 0 后,**全宽 int64 比较等价于各类型自身比较**;类型语义由编译期类型检查保证,VM 只按 func 解释 +- TIME/DATE/TOD/DT 走 func 6/7(64 位整数值) -## 数据区访问(8 字节定宽槽,方案 a) +## 数据区访问(槽表两级) ```text -槽号 slot(指令操作数)→ 数据偏移 = slot × 8 -LOAD_* rd, slot → rd = 读 data[slot*8 .. slot*8+8)(8 字节原样) -STORE_* slot, rs → 写 data[slot*8 .. slot*8+8) = rs(8 字节原样) +槽号 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 .. ](基址寻址) ``` -`LOAD_I` / `LOAD_M` / `LOAD_GLOBAL` / `STORE_Q` / `STORE_M` / `STORE_GLOBAL` 执行逻辑完全相同(v1 单一数据区,操作码只作语义标签)。 +槽表条目数 = `n_slots`;槽号越界(`slot >= n_slots`)或地址 + 宽度越出值段 → `BadSlot`。 ## 指令执行表 | 指令 | 流程 | |---|---| -| `MOVE rd, rs` | `reg[rd] = reg[rs]` | -| `LOADK rd, cid` | 查常量表 `const_entry(cid)`:BOOL → 0/1;INT → `(int16_t)value` 符号扩展;TIME → 全宽 | -| `NOT rd, rs` | `reg[rd] = (reg[rs] == 0) ? 1 : 0` | -| `AND/OR rd, ra, rb` | `reg[rd] = (reg[ra] != 0 && reg[rb] != 0) ? 1 : 0`(或 OR) | -| `ADD/SUB/MUL/DIV rd, ra, rb` | `sat_add((int16)ra, (int16)rb)` 等,结果符号扩展入 rd | -| `CMP_xx rd, ra, rb` | 全宽 int64 比较 → `1` / `0` | -| `JMP off` | `pc += off`(见跳转语义) | -| `JT rd, off` / `JF rd, off` | `reg[rd] != 0`(JT)/ `== 0`(JF)时 `pc += off`,否则正常 +1 | -| `LOAD_* rd, slot` | 见数据区访问 | -| `STORE_* slot, rs` | 见数据区访问 | -| `CAL_TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG slot` | 见定时器与边沿推进 | +| `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 -指令执行后 pc 先 +1(指向下一条),跳转指令再 pc += off -⇒ 目标下标 = 当前 + 1 + off(与编译端 patch_jump 一致) +整数(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 条) -- 每条指令执行后 `cycle_count++`;超过映像头 `cycle_limit` → `CycleLimit` 故障(周期中止) -- `JMP` / `JT` / `JF` / `CALL` 只算 1 条(cycle_limit 以"执行的指令数"计) +`CAL fb_id, slot`:`slot` 是实例**槽号**(槽表条目指向实例基址);字段偏移**编译期对齐计算**(1/2/4/8),字段类型按 machine.toml `[[fb]]` 表。每次执行 = 推进一次,Δt = 映像头 `dt_ms`(不读系统时钟)。边沿检测的"上次输入"存 VM 侧 `edge_prev_`(每槽 2 字节),跨周期保留: -## 定时器与边沿推进(CAL_TON / CAL_TOF / CAL_TP / CAL_CTU / CAL_CTD / CAL_CTUD / CAL_R_TRIG / CAL_F_TRIG) - -`CAL_* slot` 的 `slot` 是实例**基槽号**;字段偏移 = 字段序号 × 8(布局冻结:TON/TOF/TP = in/pt/q/et;CTU = cu/r/pv/q/cv;CTD = cd/ld/pv/q/cv;CTUD = cu/cd/r/lu/pv/qu/qd/cv;R_TRIG/F_TRIG = clk/q)。每次执行 = 推进一次,Δt = 映像头 `dt_ms`(不读系统时钟)。边沿检测的"上次输入"存 VM 侧 `edge_prev_`(每实例基槽 2 字节),跨周期保留: - -```text -CAL_TON: - in 为真:et += dt(et ≥ pt 时停在 pt);q = (et ≥ pt) ? 1 : 0 - in 为假:et = 0;q = 0 - -CAL_TOF: - in 为真:q = 1;et = 0 - in 掉电:et += dt;et ≥ pt 时 q = 0 - -CAL_TP(脉冲): - in 上升沿启动 PT 时长脉冲(期间 in 变化不影响) - 计时中:et += dt;et ≥ pt → q = 0、et 归零(脉冲结束);否则 q = 1 - 非计时:et = 0;q = 0 - -CAL_CTU:cu 上升沿 → cv += 1;r → cv = 0;q = (cv ≥ pv) ? 1 : 0 -CAL_CTD:cd 上升沿 → cv -= 1;ld → cv = pv;q = (cv ≤ 0) ? 1 : 0 -CAL_CTUD:cu 上升沿 → cv += 1;cd 上升沿 → cv -= 1;r → cv = 0;lu → cv = pv; - qu = (cv ≥ pv) ? 1 : 0;qd = (cv ≤ 0) ? 1 : 0 -CAL_R_TRIG:q = clk 且上次 clk 假(上升沿) -CAL_F_TRIG:q = !clk 且上次 clk 真(下降沿) -``` +| 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 | ## 故障 @@ -130,38 +141,38 @@ CAL_F_TRIG:q = !clk 且上次 clk 真(下降沿) |---|---|---| | `None` | — | 正常完成 | | `CycleLimit` | 周期指令数 > `cycle_limit`(用例 6) | 中止,返回故障 | -| `StackOverflow` | 调用栈深度 > 64 | 中止 | -| `BadOp` | op ≥ 29 | 中止 | -| `BadSlot` | slot × 8 + 8 > 数据区长度 | 中止 | -| `BadConst` | const_id ≥ n_consts | 中止 | +| `StackOverflow` | 调用栈深度 > 64 或 `sp_ + 帧大小 > max_stack` | 中止 | +| `BadOp` | pc 越界 / 指令截断 / 前缀 RESERVED / 寄存器越界 / CAL fb_id 未知 | 中止 | +| `BadSlot` | 槽号 ≥ n_slots,或地址 + 宽度越出值段 | 中止 | +| `BadConst` | LOADK const_id ≥ n_consts | 中止 | -## 示例:line1 MAIN 逐指令推演 +## 示例:line1 MAIN 逐指令推演(V2) -映像:`dt_ms=10`,`cycle_limit=100000`,数据区 56 字节(7 槽 × 8B,方案 a)。 +映像(`--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..6 全 0(初值 0) +初始:MAIN 帧 regs[13] 全 0;槽 0..4 全 0(初值 0) 假定 executor 已采样:槽 0 = EmergencyStop = 0、槽 1 = I0_0 = 1、槽 2 = I0_1 = 0 -pc 指令 执行后 -── ───────────────────── ────────────────────────────── -0 LOAD_I r8, 1 r8 = data[8..16) 低字节 = 1(I0_0) -1 STORE_GLOBAL r8, 4 槽 4(starter.start)= 1 -2 LOAD_GLOBAL r9, 2 r9 = 0(I0_1) -3 STORE_GLOBAL r9, 5 槽 5(starter.stop)= 0 -4 LOAD_GLOBAL r8, 8 内联体:r8 = 槽 4 = 1(start) -5 JF r8, +3 r8≠0 不跳;pc = 6 -6 LOAD_GLOBAL r10, 9 r10 = 槽 5 = 0(stop) -7 NOT r9, r10 r9 = 1 -8 MOVE r8, r9 r8 = 1 -9 JF r8, +3 r8≠0 不跳;pc = 10 -10 LOAD_I r12, 0 r12 = 槽 0 = 0(EmergencyStop) -11 NOT r11, r12 r11 = 1 -12 MOVE r8, r11 r8 = 1 -13 STORE_GLOBAL r8, 10 槽 6(starter.q)= 1 -14 LOAD_GLOBAL r8, 10 r8 = 槽 6 = 1 -15 STORE_Q r8, 3 槽 3(Q0_0)= 1 -16 RET 周期结束;executor 读槽 3 → 硬件 +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` 真值表一致。 +结果:`Q0_0 = 1`(start=1、stop=0、急停=0 → 电机启动)——与 `EXPECTED.md` 真值表一致(vm_test 94 checks + executor 端到端验证)。 diff --git a/Doc/全量验证表.md b/Doc/全量验证表.md new file mode 100644 index 0000000..3379440 --- /dev/null +++ b/Doc/全量验证表.md @@ -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,全部绿。** diff --git a/Doc/索引.md b/Doc/索引.md index 5b30e71..38af499 100644 --- a/Doc/索引.md +++ b/Doc/索引.md @@ -9,6 +9,11 @@ doc/ 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 @@ -16,6 +21,9 @@ doc/ compiler/符号表与链接.md compiler/类型检查.md compiler/寄存器码.md + compiler/编译器优化.md + compiler/阶段3-语言层V2.md + compiler/用例反汇编对照.md vm/扫描周期.md vm/指令执行.md executor/执行器入口.md @@ -27,6 +35,11 @@ doc/ | [`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 字面量 | @@ -34,6 +47,9 @@ doc/ | [`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 边界 | | [`vm/指令执行.md`](vm/指令执行.md) | 12.9 指令执行流程:帧/调用栈、指令表、定时器、故障 | | [`executor/执行器入口.md`](executor/执行器入口.md) | 可执行入口:加载 `.stb` + sidecar,跑扫描周期 | diff --git a/compiler/include/compiler/Codec.h b/compiler/include/compiler/Codec.h index 3a38dfc..f09b296 100644 --- a/compiler/include/compiler/Codec.h +++ b/compiler/include/compiler/Codec.h @@ -1,59 +1,127 @@ /** * @file Codec.h - * @brief 指令字编解码 + 配置驱动反汇编(编译器侧) + * @brief 指令字编解码 + 配置驱动反汇编(编译器侧,V2) * @author * @date 2026-08-21 * - * @details 编译器不依赖 isa:指令字布局([op:8|rd:8|a:8|b:8] 小端)与 - * 反汇编输出(按 machine.toml 的 format/参数名)全部由本模块 + MachineConfig 提供, - * 保证与执行器(isa)产生的字节一致。 + * @details 编译器不依赖 isa:变长指令字(前缀分区编码,4/8/16/32B)与 + * 反汇编输出(按 machine.toml 的 prefix/op/func/fmt)全部由本模块 + MachineConfig + * 提供,保证与执行器(isa V2)产生的字节一致。位号见 Doc/isa/指令编码.md。 */ #pragma once #include #include +#include #include "compiler/MachineConfig.h" namespace compiler { - /// 指令字:`[ op:8 | rd:8 | a:8 | b:8 ]`,小端 u32。 - typedef uint32_t Instr; + /// 指令字最大长度(字节)。 + static constexpr size_t kMaxInstrLen = 16; /** - * @brief 打包:opcode + 三个 8 位字段拼成一条指令字。 - * @param opcode 操作码(低 8 位) - * @param rd 目标寄存器 / 条件寄存器(第 8..15 位) - * @param a 源寄存器 / 立即数低 8 位 / 偏移低 8 位(第 16..23 位) - * @param b 源寄存器 / 立即数高 8 位 / 偏移高 8 位(第 24..31 位) - * @return 打包后的指令字(小端 u32) - * @details 布局与执行器 isa 一致,保证编译器产出的字节可被执行。 + * @brief 前缀定长:读字节 0 判指令长度。 + * @param code 指令字节(至少 1 字节) + * @return 4/8/16/32(前缀 `1`/`01`/`001`/`0001`) */ - Instr pack(uint8_t opcode, uint8_t rd, uint8_t a, uint8_t b); - - /// @brief 取操作码(低 8 位)。 - uint8_t op_of(Instr w); - /// @brief 取 rd 字段(第 8..15 位)。 - uint8_t rd_of(Instr w); - /// @brief 取 a 字段(第 16..23 位)。 - uint8_t a_of(Instr w); - /// @brief 取 b 字段(第 24..31 位)。 - uint8_t b_of(Instr w); - /// @brief a|b 拼 16 位无符号数(const_id / slot / fn_id)。 - uint16_t imm16_of(Instr w); - /// @brief a|b 为有符号相对偏移(单位:指令条数,相对下一条指令)。 - int16_t off16_of(Instr w); + size_t instr_len(const uint8_t* code); /** - * @brief 配置驱动反汇编:按 machine.toml 的 format 输出一行文本。 - * @param cfg 机器配置(opcode 名 / format / 参数名来源) - * @param w 指令字 + * @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 一致(如 `MOVE r1, r2`、 - * `LOADK r0, 3`、`JMP +4`、`JT r0, -1`、`CALL 1`、`CAL_TON 0`、`RET`); - * 未知操作码输出 `??? 0x`。 + * @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`。 */ - void disasm(const MachineConfig& cfg, Instr w, char* out, size_t cap); + void disasm(const MachineConfig& cfg, const uint8_t* code, size_t len, + char* out, size_t cap); } diff --git a/compiler/include/compiler/Codegen.h b/compiler/include/compiler/Codegen.h index 1a52a78..6eab3f0 100644 --- a/compiler/include/compiler/Codegen.h +++ b/compiler/include/compiler/Codegen.h @@ -11,6 +11,7 @@ #pragma once #include +#include #include #include @@ -21,6 +22,18 @@ namespace compiler { + /** + * @brief 变量/槽映射(--dump-map 用;验证源码 ↔ 反汇编对照)。 + */ + struct CodegenMap { + /// 每函数:{ 函数名, { 局部变量名 → "寄存器号" } } + std::vector>> funcs; + /// 全局:{ 变量名 → "slot=.. off=.. type=.." } + std::map globals; + /// 实例:{ "POU名/实例名" → "slot=.. off=.. fb=.. size=.. 字段:名@off.." } + std::map instances; + }; + /** * @brief 编译工程为 .stb 映像字节(在链接 + 类型检查成功后调用)。 * @param proj 工程定义(cycle_limit / dt_ms / 哈希) @@ -34,5 +47,6 @@ namespace compiler { */ bool codegen_project(const Project& proj, const std::vector& units, const LinkResult& link, const MachineConfig& cfg, - std::vector* image, std::string* err); + std::vector* image, std::string* err, + CodegenMap* map = nullptr); } diff --git a/compiler/include/compiler/Lexer.h b/compiler/include/compiler/Lexer.h index 371e085..7246bfb 100644 --- a/compiler/include/compiler/Lexer.h +++ b/compiler/include/compiler/Lexer.h @@ -35,10 +35,27 @@ namespace compiler { VAR_OUTPUT, ///< 输出变量段起始 VAR_GLOBAL, ///< 全局变量段起始 VAR_EXTERNAL, ///< 外部变量段起始 + VAR_TEMP, ///< 临时变量段起始(VAR_TEMP) END_VAR, ///< 变量段结束 BOOL, ///< 布尔类型 - INT, ///< 整数类型 - TIME, ///< 时间类型 + 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 关键字 @@ -52,18 +69,14 @@ namespace compiler { NOT, ///< 逻辑非(前缀) TRUE, ///< 布尔真字面量 FALSE, ///< 布尔假字面量 - TON, ///< 内建 FB 类型名 - TOF, ///< 内建 FB 类型名 - TP, ///< 内建 FB 类型名 - CTU, ///< 内建 FB 类型名 - CTD, ///< 内建 FB 类型名 - CTUD, ///< 内建 FB 类型名 - R_TRIG, ///< 内建 FB 类型名 - F_TRIG, ///< 内建 FB 类型名 // 字面量 - IDENT, ///< 标识符(text = 小写名) - INT_LIT, ///< 整数字面量(int_value) + 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, ///< =(等于) @@ -92,7 +105,8 @@ namespace compiler { struct Token { Tok type; ///< token 类型 std::string text; ///< 标识符/关键字的小写文本;字面量原文 - int64_t int_value; ///< INT_LIT / TIME_LIT(毫秒)的字面量值 + 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 起) diff --git a/compiler/include/compiler/Linker.h b/compiler/include/compiler/Linker.h index 65338f1..5ac99ca 100644 --- a/compiler/include/compiler/Linker.h +++ b/compiler/include/compiler/Linker.h @@ -19,6 +19,7 @@ #include #include +#include "compiler/MachineConfig.h" #include "compiler/Parser.h" #include "compiler/Project.h" @@ -51,11 +52,11 @@ namespace compiler { }; /** - * @brief FB 类型字段(实例布局用;v1 字段类型仅 BOOL/INT/TIME)。 + * @brief FB 类型字段(实例布局用;V2 字段类型为标量类型名小写)。 */ struct FbField { std::string name; ///< 字段名 - TypeKind type = TypeKind::Bool; ///< 字段类型(仅标量) + std::string type_name; ///< 字段类型名(小写,如 "bool"/"time") }; /** @@ -108,7 +109,7 @@ namespace compiler { * @return true 成功;false 失败(err 已写) */ bool link_project(const Project& proj, const std::vector& units, - LinkResult* out, std::string* err); + const MachineConfig& cfg, LinkResult* out, std::string* err); /** * @brief 读取并解析一个 .st 文件成 SourceUnit。 diff --git a/compiler/include/compiler/MachineConfig.h b/compiler/include/compiler/MachineConfig.h index 5bf8fce..c07da57 100644 --- a/compiler/include/compiler/MachineConfig.h +++ b/compiler/include/compiler/MachineConfig.h @@ -1,13 +1,16 @@ /** * @file MachineConfig.h - * @brief 机器定义(machine.toml)加载与强校验 + * @brief 机器定义(machine.toml V2)加载与强校验 * @author * @date 2026-08-21 * - * @details 见 Doc/isa/指令配置.md: + * @details 见 Doc/isa/指令配置.md(V2 schema): * - 配置只给编译器(toml++ 运行时加载);vm 不读配置(内建全指令集) - * - 强校验 5 项:字段必填 / type.base 命中内建基元·range 合法·tag 契约 / - * op opcode 唯一·类别-格式互锁 / fb.opcode 与 op 一致·字段类型命中 type 表 + * - 强校验 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,元数据在代码) */ @@ -31,30 +34,31 @@ namespace compiler { }; /** - * @brief 配置类型 = 基元别名(+ 值域约束 + .stb 契约 tag)。 - * @details 对应 machine.toml [[type]] 行:base 必为内建基元名;range 可选且 - * min ≤ max;tag 与 .stb 常量表契约一致(0=BOOL、1=INT、2=TIME,唯一)。 + * @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; ///< 基元名(命中内建基元表) - bool has_range = false; ///< 是否有值域约束 - int64_t range_min = 0; ///< 值域下界(含) - int64_t range_max = 0; ///< 值域上界(含) - uint32_t tag = 0; ///< .stb 常量表契约 tag + 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 配置指令。 - * @details 对应 machine.toml [[op]] 行:opcode 唯一且 0..255;class 与 format - * 互锁(INSTANCE ↔ CAL);params 数量与 format 期望一致。 + * @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 一致) - uint32_t opcode = 0; ///< 操作码(0..255,表内唯一) - bool is_instance = false; ///< class:instance / plain - std::string format; ///< 操作数形态:RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE - std::vector params; ///< 参数名(数量与 format 一致) + 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 params; ///< 参数名(数量与 fmt 一致) bool enabled = false; ///< 是否启用 }; @@ -63,34 +67,37 @@ namespace compiler { */ struct ConfigFbField { std::string name; ///< 字段名(如 in/pt/q/et/cu/cv) - std::string type; ///< 配置类型名(BOOL/INT/TIME) + std::string type; ///< 配置类型名(BOOL/INT/TIME/...) }; /** - * @brief 内建功能块(FB)布局登记。 - * @details 对应 machine.toml [[fb]] 行;opcode 必须命中 instance 类 op 行。 + * @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 opcode = 0; ///< 对应 op 行 opcode(CAL_*) + std::string name; ///< FB 名(大写,如 TON/CTU) + uint32_t fb_id = 0; ///< FB 类型 id(0..14) std::vector fields; ///< 字段列表(名称唯一,类型命中 type 表) }; /** - * @brief 机器定义:加载 machine.toml 并做强校验。 + * @brief 机器定义:加载 machine.toml 并做强校验(V2)。 * @details 编译器指令集 / 类型的唯一事实来源;校验失败时 ok() 为 false, * err 前缀 "machine error"。vm 不读配置(内建全指令集)。 */ class MachineConfig { public: /** - * @brief 加载 + 强校验。 + * @brief 加载 + 强校验(V2)。 * @param path machine.toml 路径 * @param err 错误输出;可为 nullptr(静默) * @return true 成功;false(err 已写,前缀 "machine error") - * @details 强校验项:字段必填 / type.base 命中内建基元·range 合法·tag 契约 / - * op opcode 唯一·类别-格式互锁 / fb.opcode 与 op 一致·字段类型命中 - * type 表(清单见文件头)。 + * @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); @@ -98,7 +105,7 @@ namespace compiler { bool ok() const { return ok_; } /// @brief 型号名(meta.name,参与 .stb 型号标识)。 const std::string& model_name() const { return model_name_; } - /// @brief 指令集版本(meta.version,参与 .stb 型号标识)。 + /// @brief 格式版本(meta.version,= 2,参与 .stb 型号标识)。 uint32_t version() const { return version_; } /// @brief 配置类型表([[type]])。 @@ -110,7 +117,7 @@ namespace compiler { /** * @brief 按配置类型名查类型行。 - * @param name 配置类型名 + * @param name 配置类型名(如 "INT") * @return 命中指针;未找到返回 nullptr */ const ConfigType* find_type(const std::string& name) const; @@ -123,26 +130,35 @@ namespace compiler { const ConfigOp* find_op(const std::string& name) const; /** - * @brief 按 opcode 查指令行。 - * @param opcode 操作码(0..255) + * @brief 按 (prefix, op) 键查指令行。 + * @param prefix 形态前缀(如 "11"/"010001") + * @param op 分区内编号 * @return 命中指针;未找到返回 nullptr */ - const ConfigOp* find_op_by_code(uint32_t opcode) const; + const ConfigOp* find_op_by_key(const std::string& prefix, uint32_t op) const; /** - * @brief 查询某 opcode 是否启用。 - * @param opcode 操作码 - * @return 指令存在且 enabled 为 true;未知 opcode 返回 false + * @brief 查询某 (prefix, op) 是否启用。 + * @param prefix 形态前缀 + * @param op 分区内编号 + * @return 指令存在且 enabled 为 true;未知返回 false */ - bool op_enabled(uint32_t opcode) const; + bool op_enabled(const std::string& prefix, uint32_t op) const; /** * @brief 按名称查内建 FB。 - * @param name FB 名(小写,如 "ton") + * @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& prims(); @@ -156,7 +172,7 @@ namespace compiler { private: bool ok_ = false; ///< 加载成功标志 std::string model_name_; ///< 型号名(meta.name) - uint32_t version_ = 0; ///< 指令集版本(meta.version) + uint32_t version_ = 0; ///< 格式版本(meta.version) std::vector types_; ///< 配置类型表 std::vector ops_; ///< 配置指令表 std::vector fbs_; ///< 内建 FB 表 diff --git a/compiler/include/compiler/Parser.h b/compiler/include/compiler/Parser.h index 24b7d8c..a0027d6 100644 --- a/compiler/include/compiler/Parser.h +++ b/compiler/include/compiler/Parser.h @@ -25,14 +25,14 @@ namespace compiler { * FbBuiltin=内建 FB 类型(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG); * FbUser=用户 FB 类型(存在性由 12.6 链接层校验)。 */ - enum class TypeKind { Bool, Int, Time, FbBuiltin, FbUser }; + enum class TypeKind { Scalar, FbBuiltin, FbUser }; /** * @brief 类型引用:种类 + (用户 FB 的)类型名。 */ struct TypeRef { - TypeKind kind = TypeKind::Bool; ///< 类型种类 - std::string name; ///< FbUser 时为用户 FB 类型名(小写) + TypeKind kind = TypeKind::Scalar; ///< 类型种类(Scalar=标量 19 型 / FbBuiltin / FbUser) + std::string name; ///< 类型名(小写:标量 = machine.toml type 键;FB = fb 表名或用户声明名) }; // ---- 变量段 ---- @@ -53,9 +53,9 @@ namespace compiler { /** * @brief 变量段种类。 * @details 枚举值:Local=VAR、Input=VAR_INPUT、Output=VAR_OUTPUT、 - * Global=VAR_GLOBAL、External=VAR_EXTERNAL。 + * Global=VAR_GLOBAL、External=VAR_EXTERNAL、Temp=VAR_TEMP。 */ - enum class VarSection { Local, Input, Output, Global, External }; + enum class VarSection { Local, Input, Output, Global, External, Temp }; /** * @brief 一段 VAR_* … END_VAR:段种类 + 声明列表。 @@ -75,8 +75,12 @@ namespace compiler { */ enum class ExprKind { LitBool, ///< 布尔字面量(int_value 0/1) - LitInt, ///< 整数字面量(int_value) + 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) @@ -104,7 +108,8 @@ namespace compiler { 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 毫秒) + int64_t int_value = 0; ///< 字面量值(BOOL 0/1、INT、TIME ms、DATE 天、TOD ms、DT) + double double_value = 0; ///< LitReal 字面量值 std::unique_ptr lhs; ///< 二目左操作数 std::unique_ptr rhs; ///< 二目右操作数 std::unique_ptr operand; ///< Not / Neg 的操作数 diff --git a/compiler/include/compiler/Stb.h b/compiler/include/compiler/Stb.h index 0143493..5ccec45 100644 --- a/compiler/include/compiler/Stb.h +++ b/compiler/include/compiler/Stb.h @@ -1,12 +1,13 @@ /** * @file Stb.h - * @brief 编译器自带的 .stb 映像规范(写侧)+ 只读视图 + FNV-1a + * @brief 编译器自带的 .stb 映像规范(写侧,V2)+ 只读视图 + FNV-1a * @author * @date 2026-08-21 * - * @details 执行器(vm)各有自己的读写实现;格式契约见 Doc/isa/指令与映像.md。 - * 12.13 修订:头 104 = 原 72 + 型号标识[32] @72,文件尾 SHA-256[32]。 - * 常量表 tag 契约:0=BOOL、1=INT、2=TIME;工程哈希用 FNV-1a 64。 + * @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 @@ -20,17 +21,19 @@ namespace compiler { - // ---- .stb 格式常量(契约;12.13 修订:头 104 = 原 72 + 型号标识[32],文件尾 SHA-256[32])---- + // ---- .stb 格式常量(V2 契约;头 128 = 原 72 字段 + 型号标识[32]@72 + 新增 24)---- /// 映像魔数:"STSC" 小端。 static const uint32_t kMagic = 0x43545353u; - /// 映像格式版本。 - static const uint32_t kVersion = 1; - /// 映像头字节数(原 72 字段 + 型号标识[32] @72)。 - static const size_t kHeaderSize = 104; - /// 常量表一行字节数(tag:4 + value:8)。 - static const size_t kConstEntrySize = 12; + /// 映像格式版本(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')。 @@ -40,13 +43,54 @@ namespace compiler { /// 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 常量表一行。 - * @details tag 契约:0=BOOL、1=INT、2=TIME。 + * @brief 常量表一行(8B 原始值,无 tag——LOADK 按 func 位段解释)。 */ struct ConstEntry { - uint32_t tag = 0; ///< 类型 tag(0=BOOL、1=INT、2=TIME) - uint64_t value = 0; ///< 常量值 + uint64_t value = 0; ///< 原始位模式 }; /** @@ -74,7 +118,7 @@ namespace compiler { void sha256(const uint8_t* data, size_t len, uint8_t out[kSha256Size]); /** - * @brief 型号标识:name + version 拼 32 字节 ASCII(如 "STATOR1")。 + * @brief 型号标识:name + version 拼 32 字节 ASCII(如 "STATOR2")。 * @param name 型号名 * @param version 版本号 * @param out 32 字节输出缓冲 @@ -83,17 +127,17 @@ namespace compiler { void fill_model_id(const std::string& name, uint32_t version, char out[kModelIdSize]); /** - * @brief .stb 映像只读视图(--disasm 用)。 + * @brief .stb 映像只读视图(--disasm 用,V2)。 * @details from() 时校验魔数/版本/段边界,之后各访问器只读; * 失败时 ok() == false,error() 取原因。 */ class StbView { public: - /// @brief 函数表一行(nregs / code_offset / code_len)。 + /// @brief 函数表一行(nregs / code_offset / code_len,均字节)。 struct FuncRow { uint32_t nregs = 0; ///< 帧寄存器数(含变量与临时) - uint32_t code_offset = 0; ///< 字节码段内偏移(4 字节对齐) - uint32_t code_len = 0; ///< 指令条数 + uint32_t code_offset = 0; ///< 字节码段内偏移(字节) + uint32_t code_len = 0; ///< 字节码长度(字节,变长指令) }; /// 从原始字节构造(不拷贝,调用方保证生命周期)。 @@ -120,14 +164,26 @@ namespace compiler { 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(get_le32_(56)); } /// 字节码段偏移(头 @60)。 uint32_t offset_code() const { return offset_code_; } - /// 数据段(FB 区)偏移(头 @64;即字节码段终点)。 - uint32_t offset_fb() const { return offset_fb_; } - /// 数据段偏移(头 @68)。 - uint32_t offset_data() const { return offset_data_; } + /// 槽表段偏移(头 @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_; } - /// 读常量表一行;越界时返回全 0。 + /// 读常量表一行(8B 原始值);越界时返回全 0。 ConstEntry const_entry(size_t i) const; /// 读函数表一行;越界时返回全 0。 FuncRow func_row(size_t i) const; @@ -135,39 +191,52 @@ namespace compiler { 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)。 + /// 值段字节数(不含文件尾 SHA-256)。 size_t data_len() const; /// 型号标识字符串(头 72..103,截断到首个 '\0')。 - std::string model_id() 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; // 文件尾 32 字节 SHA-256 校验 + bool sha_ok() const; private: /// 私有构造(只能经 from() 创建)。 StbView() : buf_(0), len_(0) {} /// 常量表偏移(from 已校验的段起点)。 - uint32_t offs_of_const() const; // offset_const(from 已校验段起点) + uint32_t offs_of_const() const; + /// 读头内 u32(小端;内部辅助)。 + uint32_t get_le32_(size_t off) const { + return static_cast(buf_[off]) + | (static_cast(buf_[off + 1]) << 8) + | (static_cast(buf_[off + 2]) << 16) + | (static_cast(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_fb_ = 0; ///< 数据段(FB 区)偏移 - uint32_t offset_data_ = 0; ///< 数据段偏移 + 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; ///< 栈区字节数 }; /** diff --git a/compiler/include/compiler/TypeInfo.h b/compiler/include/compiler/TypeInfo.h index f71d912..3a44b29 100644 --- a/compiler/include/compiler/TypeInfo.h +++ b/compiler/include/compiler/TypeInfo.h @@ -5,7 +5,7 @@ * @date 2026-08-21 * * @details 类型定义数据化(配置),运算/类型检查语义代码化: - * - 元数据(宽度/符号/浮点/base/range/tag)全部来自 machine.toml + 内建基元表 + * - 元数据(宽度/符号/浮点/kind/func)全部来自 machine.toml + 内建基元表 * - 查询大小写不敏感(配置名大写,Linker type_name 小写) */ @@ -19,12 +19,12 @@ namespace compiler { /** - * @brief 语言类型元数据。 + * @brief 语言类型元数据(V2)。 * @details 由配置类型行 + 内建基元合成;config 或 prim 未命中时 * 对应访问器取安全默认值,operator bool 为 false。 */ struct TypeMeta { - const ConfigType* config = nullptr; ///< 配置类型行(别名 + range + tag) + const ConfigType* config = nullptr; ///< 配置类型行(别名 + kind + func) const PrimType* prim = nullptr; ///< 内建基元(宽度/符号/浮点) /// @brief 宽度(字节);无基元返回 0。 @@ -33,18 +33,12 @@ namespace compiler { bool is_signed() const { return prim ? prim->is_signed : false; } /// @brief 是否浮点;无基元返回 false。 bool is_float() const { return prim ? prim->is_float : false; } - /// @brief .stb 契约 tag;无配置返回 0。 - uint32_t tag() const { return config ? config->tag : 0; } - /// @brief 是否有值域约束;无配置返回 false。 - bool has_range() const { return config ? config->has_range : false; } - /** - * @brief 值是否落在配置值域内。 - * @param v 待查值 - * @return 无配置或无 range 时为 true;否则 min ≤ v ≤ max - */ - bool value_in_range(int64_t v) const { - return !config || !config->has_range || - (v >= config->range_min && v <= config->range_max); + /// @brief 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 都已命中)。 diff --git a/compiler/include/compiler/Typecheck.h b/compiler/include/compiler/Typecheck.h index c8dfca4..b688024 100644 --- a/compiler/include/compiler/Typecheck.h +++ b/compiler/include/compiler/Typecheck.h @@ -15,19 +15,23 @@ #include #include "compiler/Linker.h" +#include "compiler/MachineConfig.h" #include "compiler/Parser.h" #include "compiler/Project.h" namespace compiler { /** - * @brief 表达式求值类型(v1 三型,无隐式宽化)。 - * @details 各成员含义: - * - Bool:布尔量(逻辑运算 / 条件表达式) - * - Int:整数(算术运算) - * - Time:时间量(仅比较,无算术);INT 与 TIME 不混用 + * @brief 表达式求值类型(V2:19 标量 + 未定型)。 + * @details 无隐式转换(赋值/算术/比较严格同型);字面量未定型(Unresolved), + * 由消费点(赋值目标/实参/条件/另一操作数)按上下文定型 + 值域检查; + * BOOL 编译器强制 0/1。 */ - enum class TType { Bool, Int, Time }; + enum class TType { + Bool, Byte, Word, Dword, Lword, Sint, Int, Dint, Lint, + Usint, Uint, Udint, Ulint, Time, Real, Lreal, Date, Tod, Dt, + Unresolved, ///< 字面量未定型(待消费点定型) + }; /** * @brief 对工程做类型检查(在链接成功后调用)。 @@ -39,5 +43,5 @@ namespace compiler { * @return true 全部通过;false 失败(err 已写) */ bool check_project(const Project& proj, const std::vector& units, - const LinkResult& link, std::string* err); + const LinkResult& link, const MachineConfig& cfg, std::string* err); } diff --git a/compiler/machine.toml b/compiler/machine.toml index 1145146..a490b9f 100644 --- a/compiler/machine.toml +++ b/compiler/machine.toml @@ -1,342 +1,886 @@ -# 机器定义:指令集与类型的唯一事实来源(只给编译器) -# 格式见 Doc/isa/指令配置.md;全属性必填,与代码强校验一致。 +# 机器定义:指令集与类型的唯一事实来源(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 = 1 # 指令集版本(参与 .stb 型号标识[32]) +version = 2 # 格式版本(型号标识 = "STATOR2";V1 = 1) -# ---- 类型:编译器内建基元(bit/int8/int16/int32/int64/uint8/uint16/uint32/uint64/float32/float64) -# 配置类型 = 基元别名(+ 值域约束 + .stb 契约 tag) +# ---- 类型: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" -range = [0, 1] # 值域约束:只允许 0/1 -tag = 0 +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" -tag = 1 +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" -tag = 2 +kind = "time" +func = 7 -# ---- 指令(全属性必填;class: plain/instance;format: RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE) +[[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" -opcode = 0 -class = "plain" -format = "RR" +prefix = "11" +op = 0 +fmt = "RR" +func = "width" params = ["rd", "rs"] enabled = true -[[op]] -name = "LOADK" -opcode = 1 -class = "plain" -format = "IMM" -params = ["rd", "const_id"] -enabled = true - [[op]] name = "NOT" -opcode = 2 -class = "plain" -format = "RR" +prefix = "11" +op = 1 +fmt = "RR" +func = "uint" params = ["rd", "rs"] enabled = true [[op]] -name = "AND" -opcode = 3 -class = "plain" -format = "RRR" -params = ["rd", "ra", "rb"] +name = "NEG" +prefix = "11" +op = 2 +fmt = "RR" +func = "full" +params = ["rd", "rs"] enabled = true [[op]] -name = "OR" -opcode = 4 -class = "plain" -format = "RRR" -params = ["rd", "ra", "rb"] +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" -opcode = 5 -class = "plain" -format = "RRR" +prefix = "011" +op = 0 +fmt = "RRR" +func = "full" params = ["rd", "ra", "rb"] enabled = true [[op]] name = "SUB" -opcode = 6 -class = "plain" -format = "RRR" +prefix = "011" +op = 1 +fmt = "RRR" +func = "full" params = ["rd", "ra", "rb"] enabled = true [[op]] name = "MUL" -opcode = 7 -class = "plain" -format = "RRR" +prefix = "011" +op = 2 +fmt = "RRR" +func = "full" params = ["rd", "ra", "rb"] enabled = true [[op]] name = "DIV" -opcode = 8 -class = "plain" -format = "RRR" +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" -opcode = 9 -class = "plain" -format = "RRR" +prefix = "011" +op = 16 +fmt = "RRR" +func = "full" params = ["rd", "ra", "rb"] enabled = true [[op]] name = "CMP_NE" -opcode = 10 -class = "plain" -format = "RRR" +prefix = "011" +op = 17 +fmt = "RRR" +func = "full" params = ["rd", "ra", "rb"] enabled = true [[op]] name = "CMP_LT" -opcode = 11 -class = "plain" -format = "RRR" +prefix = "011" +op = 18 +fmt = "RRR" +func = "full" params = ["rd", "ra", "rb"] enabled = true [[op]] name = "CMP_LE" -opcode = 12 -class = "plain" -format = "RRR" +prefix = "011" +op = 19 +fmt = "RRR" +func = "full" params = ["rd", "ra", "rb"] enabled = true [[op]] name = "CMP_GT" -opcode = 13 -class = "plain" -format = "RRR" +prefix = "011" +op = 20 +fmt = "RRR" +func = "full" params = ["rd", "ra", "rb"] enabled = true [[op]] name = "CMP_GE" -opcode = 14 -class = "plain" -format = "RRR" +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" -opcode = 15 -class = "plain" -format = "JMP" +prefix = "010010" +op = 0 +fmt = "JMP" +func = "none" params = ["off"] enabled = true [[op]] name = "JT" -opcode = 16 -class = "plain" -format = "JC" -params = ["r", "off"] +prefix = "010011" +op = 0 +fmt = "JC" +func = "none" +params = ["rd", "off"] enabled = true [[op]] name = "JF" -opcode = 17 -class = "plain" -format = "JC" -params = ["r", "off"] -enabled = true - -[[op]] -name = "LOAD_I" -opcode = 18 -class = "plain" -format = "SLOT" -params = ["rd", "slot"] -enabled = true - -[[op]] -name = "STORE_Q" -opcode = 19 -class = "plain" -format = "SLOT" -params = ["rs", "slot"] -enabled = true - -[[op]] -name = "LOAD_M" -opcode = 20 -class = "plain" -format = "SLOT" -params = ["rd", "slot"] -enabled = true - -[[op]] -name = "STORE_M" -opcode = 21 -class = "plain" -format = "SLOT" -params = ["rs", "slot"] -enabled = true - -[[op]] -name = "LOAD_GLOBAL" -opcode = 22 -class = "plain" -format = "SLOT" -params = ["rd", "slot"] -enabled = true - -[[op]] -name = "STORE_GLOBAL" -opcode = 23 -class = "plain" -format = "SLOT" -params = ["rs", "slot"] -enabled = true - -[[op]] -name = "CAL_TON" -opcode = 24 -class = "instance" -format = "CAL" -params = ["instance"] -enabled = true - -[[op]] -name = "CAL_TOF" -opcode = 25 -class = "instance" -format = "CAL" -params = ["instance"] -enabled = true - -[[op]] -name = "CAL_TP" -opcode = 26 -class = "instance" -format = "CAL" -params = ["instance"] -enabled = true - -[[op]] -name = "CAL_CTU" -opcode = 27 -class = "instance" -format = "CAL" -params = ["instance"] -enabled = true - -[[op]] -name = "CAL_CTD" -opcode = 28 -class = "instance" -format = "CAL" -params = ["instance"] -enabled = true - -[[op]] -name = "CAL_CTUD" -opcode = 29 -class = "instance" -format = "CAL" -params = ["instance"] -enabled = true - -[[op]] -name = "CAL_R_TRIG" -opcode = 30 -class = "instance" -format = "CAL" -params = ["instance"] -enabled = true - -[[op]] -name = "CAL_F_TRIG" -opcode = 31 -class = "instance" -format = "CAL" -params = ["instance"] +prefix = "010011" +op = 1 +fmt = "JC" +func = "none" +params = ["rd", "off"] enabled = true [[op]] name = "CALL" -opcode = 32 -class = "plain" -format = "CALL" +prefix = "010100" +op = 0 +fmt = "CALL" +func = "none" params = ["fn_id"] enabled = true [[op]] -name = "RET" -opcode = 33 -class = "plain" -format = "NONE" +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 -# ---- 内建 FB(布局登记;opcode 与 op 行强校验一致;字段类型命中 type 表) +[[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" -opcode = 24 +name = "TON" +fb_id = 0 fields = [["in", "BOOL"], ["pt", "TIME"], ["q", "BOOL"], ["et", "TIME"]] [[fb]] -name = "tof" -opcode = 25 +name = "TOF" +fb_id = 1 fields = [["in", "BOOL"], ["pt", "TIME"], ["q", "BOOL"], ["et", "TIME"]] [[fb]] -name = "tp" -opcode = 26 +name = "TP" +fb_id = 2 fields = [["in", "BOOL"], ["pt", "TIME"], ["q", "BOOL"], ["et", "TIME"]] [[fb]] -name = "ctu" -opcode = 27 +name = "CTU" +fb_id = 3 fields = [["cu", "BOOL"], ["r", "BOOL"], ["pv", "INT"], ["q", "BOOL"], ["cv", "INT"]] [[fb]] -name = "ctd" -opcode = 28 +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 = "ctud" -opcode = 29 +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 = "r_trig" -opcode = 30 +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" -opcode = 31 +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"]] diff --git a/compiler/src/Codec.cpp b/compiler/src/Codec.cpp index 96f7c65..6d862b1 100644 --- a/compiler/src/Codec.cpp +++ b/compiler/src/Codec.cpp @@ -1,11 +1,12 @@ /** * @file Codec.cpp - * @brief 指令字编解码 + 配置驱动反汇编(编译器侧) + * @brief 指令字编解码 + 配置驱动反汇编(编译器侧,V2) * @author * @date 2026-08-21 * - * @details pack / op_of / imm16_of / off16_of 与执行器 isa 字节布局一致; - * disasm 由 MachineConfig 驱动(opcode 名 / format / 参数名来自配置)。 + * @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" @@ -14,84 +15,334 @@ namespace compiler { -/** - * @brief 打包:四个字段拼成一条指令字。 - * @param opcode 操作码(低 8 位) - * @param rd 目标寄存器 / 条件寄存器(第 8..15 位) - * @param a 源寄存器 / 立即数低 8 位 / 偏移低 8 位(第 16..23 位) - * @param b 源寄存器 / 立即数高 8 位 / 偏移高 8 位(第 24..31 位) - * @return 打包后的指令字(小端 u32) - */ -Instr pack(uint8_t opcode, uint8_t rd, uint8_t a, uint8_t b) { - return static_cast(opcode) - | (static_cast(rd) << 8) - | (static_cast(a) << 16) - | (static_cast(b) << 24); -} +namespace { -/// @brief 取操作码(低 8 位)。 -uint8_t op_of(Instr w) { return static_cast(w & 0xFFu); } -/// @brief 取 rd 字段(第 8..15 位)。 -uint8_t rd_of(Instr w) { return static_cast((w >> 8) & 0xFFu); } -/// @brief 取 a 字段(第 16..23 位)。 -uint8_t a_of(Instr w) { return static_cast((w >> 16) & 0xFFu); } -/// @brief 取 b 字段(第 24..31 位)。 -uint8_t b_of(Instr w) { return static_cast((w >> 24) & 0xFFu); } + /** + * @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 a|b 拼成 16 位无符号数。 - * @param w 指令字 - * @return 小端拼出的 16 位值(const_id / slot / fn_id) + * @brief 前缀定长:读字节 0 判指令长度。 + * @param code 指令字节(至少 1 字节) + * @return 4/8/16/32(前缀 `1`/`01`/`001`/`0001`) */ -uint16_t imm16_of(Instr w) { - return static_cast(a_of(w) | (static_cast(b_of(w)) << 8)); +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 a|b 为有符号相对偏移。 - * @param w 指令字 - * @return 偏移量(单位:指令条数,相对下一条指令) + * @brief 推导形态前缀字符串。 + * @param code 指令字节 + * @return "11"/"10"/"011"/"010000".."010101"/"0010"/"0011"/"0001" */ -int16_t off16_of(Instr w) { return static_cast(imm16_of(w)); } +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 配置驱动反汇编:按 machine.toml 的 format 输出一行文本。 - * @param cfg 机器配置(opcode 名 / format / 参数名来源) - * @param w 指令字 + * @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((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(code[3]) + | (static_cast(code[4]) << 8) + | (static_cast(code[5]) << 16) + | (static_cast(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(static_cast(code[2]) + | (static_cast(code[3]) << 8) + | (static_cast(code[4]) << 16) + | (static_cast(code[5]) << 24)); +} + +/** + * @brief 取 off32(55..24,JC 形态,小端 4 字节,有符号)。 + * @param code 指令字节 + * @return 字节偏移(相对下一条) + */ +int32_t off32_jc_of(const uint8_t* code) { + return static_cast(static_cast(code[3]) + | (static_cast(code[4]) << 8) + | (static_cast(code[5]) << 16) + | (static_cast(code[6]) << 24)); +} + +uint32_t fn_id32_of(const uint8_t* code) { + return static_cast(code[2]) + | (static_cast(code[3]) << 8) + | (static_cast(code[4]) << 16) + | (static_cast(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(code[2] | (static_cast(code[3]) << 8)); +} + +/** + * @brief 取 CAL 的实例槽号(63..32)。 + * @param code 指令字节 + * @return 槽号 + */ +uint32_t cal_slot32_of(const uint8_t* code) { + return static_cast(code[4]) + | (static_cast(code[5]) << 8) + | (static_cast(code[6]) << 16) + | (static_cast(code[7]) << 24); +} + +/** + * @brief 取 16B 形态的 off32(87..56)。 + * @param code 指令字节 + * @return 字段偏移(字节) + */ +int32_t off32_hi_of(const uint8_t* code) { + return static_cast(static_cast(code[7]) + | (static_cast(code[8]) << 8) + | (static_cast(code[9]) << 16) + | (static_cast(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(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(0xC0 | (op & 0x3F)); // bit7..6 = 11 + } else if (prefix == "10") { + out[0] = static_cast(0x80 | (op & 0x3F)); // bit7..6 = 10 + } else if (prefix == "011") { + out[0] = static_cast(0x60 | (op & 0x1F)); // bit7..5 = 011 + } else if (prefix.size() == 6 && prefix.compare(0, 3, "010") == 0) { + const uint32_t shape = static_cast(prefix[3] - '0') * 4 + + static_cast(prefix[4] - '0') * 2 + + static_cast(prefix[5] - '0'); + out[0] = static_cast(0x40 | (shape << 2) | (op & 0x03)); + } else if (prefix == "0010") { + out[0] = static_cast(0x20 | (op & 0x0F)); + } else if (prefix == "0011") { + out[0] = static_cast(0x30 | (op & 0x0F)); + } else { // "0001" + out[0] = static_cast(0x10 | (op & 0x0F)); + } +} + +void set_func(uint8_t* out, uint8_t func) { + out[1] = static_cast((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(v & 0xFF); + out[4] = static_cast((v >> 8) & 0xFF); + out[5] = static_cast((v >> 16) & 0xFF); + out[6] = static_cast((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(v); + out[2] = static_cast(u & 0xFF); + out[3] = static_cast((u >> 8) & 0xFF); + out[4] = static_cast((u >> 16) & 0xFF); + out[5] = static_cast((u >> 24) & 0xFF); +} + +void set_off32_jc(uint8_t* out, int32_t v) { + const uint32_t u = static_cast(v); + out[3] = static_cast(u & 0xFF); + out[4] = static_cast((u >> 8) & 0xFF); + out[5] = static_cast((u >> 16) & 0xFF); + out[6] = static_cast((u >> 24) & 0xFF); +} +void set_fn_id32(uint8_t* out, uint32_t v) { set_off32(out, static_cast(v)); } +void set_fb_id16(uint8_t* out, uint16_t v) { + out[2] = static_cast(v & 0xFF); + out[3] = static_cast((v >> 8) & 0xFF); +} +void set_cal_slot32(uint8_t* out, uint32_t v) { + out[4] = static_cast(v & 0xFF); + out[5] = static_cast((v >> 8) & 0xFF); + out[6] = static_cast((v >> 16) & 0xFF); + out[7] = static_cast((v >> 24) & 0xFF); +} +void set_off32_hi(uint8_t* out, int32_t v) { + const uint32_t u = static_cast(v); + out[7] = static_cast(u & 0xFF); + out[8] = static_cast((u >> 8) & 0xFF); + out[9] = static_cast((u >> 16) & 0xFF); + out[10] = static_cast((u >> 24) & 0xFF); +} +void set_imm64(uint8_t* out, uint64_t v) { + for (int i = 0; i < 8; ++i) { + out[3 + i] = static_cast((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 - * @details 未知操作码输出 `??? 0x%08x`;format 分支 RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE - * 输出文本与 Doc/isa/指令与映像.md 一致。 */ -void disasm(const MachineConfig& cfg, Instr w, char* out, size_t cap) { +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 ConfigOp* op = cfg.find_op_by_code(op_of(w)); + 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%08x", static_cast(w)); + 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(code[i])); + used += 2; + } return; } - const uint8_t rd = rd_of(w); - const std::string& f = op->format; + 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 r%u, r%u", op->name.c_str(), static_cast(rd), - static_cast(a_of(w))); + snprintf(out, cap, "%s%s r%u, r%u", op->name.c_str(), suffix.c_str(), + static_cast(rd_of(code)), static_cast(rs1_of(code))); } else if (f == "RRR") { - snprintf(out, cap, "%s r%u, r%u, r%u", op->name.c_str(), static_cast(rd), - static_cast(a_of(w)), static_cast(b_of(w))); - } else if (f == "IMM" || f == "SLOT") { - snprintf(out, cap, "%s r%u, %u", op->name.c_str(), static_cast(rd), - static_cast(imm16_of(w))); + snprintf(out, cap, "%s%s r%u, r%u, r%u", op->name.c_str(), suffix.c_str(), + static_cast(rd_of(code)), static_cast(rs1_of(code)), + static_cast(rs2_of(code))); + } else if (f == "IMM") { + snprintf(out, cap, "%s%s r%u, %u", op->name.c_str(), suffix.c_str(), + static_cast(rd_of(code)), static_cast(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(rd_of(code)), static_cast(slot32_of(code))); } else if (f == "JMP") { - snprintf(out, cap, "%s %+d", op->name.c_str(), static_cast(off16_of(w))); + snprintf(out, cap, "%s %+d", op->name.c_str(), static_cast(off32_of(code))); } else if (f == "JC") { - snprintf(out, cap, "%s r%u, %+d", op->name.c_str(), static_cast(rd), - static_cast(off16_of(w))); - } else if (f == "CALL" || f == "CAL") { - snprintf(out, cap, "%s %u", op->name.c_str(), static_cast(imm16_of(w))); - } else { // NONE + snprintf(out, cap, "%s r%u, %+d", op->name.c_str(), + static_cast(rd_of(code)), static_cast(off32_jc_of(code))); + } else if (f == "CALL") { + snprintf(out, cap, "%s %u", op->name.c_str(), static_cast(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(cal_slot32_of(code))); + } else { + snprintf(out, cap, "CAL ?%u s%u", static_cast(fb_id16_of(code)), + static_cast(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(rd_of(code)), static_cast(slot32_of(code)), + static_cast(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(rd_of(code)), static_cast(rs1_of(code)), + static_cast(off32_hi_of(code))); + } else { // NONE / 预留 snprintf(out, cap, "%s", op->name.c_str()); } } diff --git a/compiler/src/Codegen.cpp b/compiler/src/Codegen.cpp index 721a6d2..54742fd 100644 --- a/compiler/src/Codegen.cpp +++ b/compiler/src/Codegen.cpp @@ -1,40 +1,36 @@ /** * @file Codegen.cpp - * @brief 寄存器码生成(配置驱动;compiler 不依赖 isa) + * @brief 寄存器码生成(V2:配置驱动 + 变长指令;compiler 不依赖 isa) * @author * @date 2026-08-21 * - * @details 设计说明(详见 Doc/compiler/寄存器码.md 与 Doc/isa/指令配置.md): - * - 指令 opcode / 类型 tag / FB 布局全部来自 machine.toml(MachineConfig) - * - 帧/字面量/MOVE/RET;全局数据区;短路 AND/OR;CMP 与 IF;WHILE 与四则; - * FUNCTION 与 CALL(调用约定 r0/r1..r7/r8+) - * - FB 实例 → 数据区实例块(跨周期持久);字段槽号 = 实例基槽 + 字段序号 - * - 用户 FB 调用点内联展开;内建 FB → CAL_* <实例基槽> - * - 数据段每槽 8 字节定宽,指令 slot = 槽号 + * @details 设计说明(详见 Doc/compiler/寄存器码.md 与 Doc/isa/指令与映像.md V2): + * - 指令 prefix/op/func 全部来自 machine.toml(MachineConfig);发射经 Codec 位号 + * - 数据模型:槽表(8B 引用 {addr,预留})→ 值段(变宽紧凑,1/2/4/8 对齐) + * - 常量表 8B 原始值(无 tag),LOADK 按 func 解释,位模式去重 + * - 跳转回填字节单位(off32 相对下一条:目标 = 当前指令起点 + 指令长 + off) + * - 用户 FB 调用点内联展开(字段访问 = LOAD_OFF/STORE_OFF 实例槽 + 字段偏移) + * - 内建 FB → CAL fb_id, 实例槽(fb_id 查 [[fb]] 表) + * - max_stack:MAIN + FUNCTION 调用图 DAG 最坏路径 Σ(12 + nregs×8);递归 = 编译期报错 * * 函数清单: - * - Builder::Builder (构造)存工程/源文件/链接结果/机器配置/输出,收集 io 绑定 - * - Builder::run 布局数据区 → 逐 POU 建函数 → 拼映像 - * - Builder::fail / opc 错误 / 按名查配置 opcode - * - E_rr / E_rrr / E_imm / E_slot / E_jmp / E_jc / E_call / E_ret 指令发射(配置 opcode) - * - find_pou / find_fn_id 按名查 POU AST / fn_id - * - build_function 编译一个 POU(帧约定分配;FB 占位) - * - begin_stmt / alloc_temp 临时寄存器管理 - * - compile_stmt 语句编译(赋值 / IF / WHILE / FB 调用) - * - compile_if / compile_while / compile_fb_call / compile_fb_inline - * - store_target / compile_expr 左值存储 / 表达式编译 - * - arith_name / cmp_name / load_name / store_name 操作码名选择 - * - patch_jump 回填跳转偏移(相对下一条) - * - layout_data / layout_fb_instances / instance_of / init_of - * - const_id 取常量表 id(tag 来自配置类型表) - * - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + 数据段 - * - codegen_project 对外入口 + * - Builder::Builder 构造;run 布局 → 逐 POU 建函数 → 拼映像 + * - emit / emit0 指令发射(查 ConfigOp,Codec 位号写字节) + * - E_rr/E_rrr/E_imm/E_slot/E_jmp/E_jc/E_call/E_cal/E_ret/E_slot_off + * - func_of_name 语言类型名 → func 位段值 + * - compile_stmt/if/while/fb_call/fb_inline/store_target/compile_expr(V1 语义保留) + * - patch_jump 字节化回填 + * - layout_slots_values 槽表 + 值段布局(全局 + FB 实例,对齐 1/2/4/8) + * - compute_max_stack 调用图 DAG 最长路径 + 环检测 + * - assemble_image V2 头 128B + 段序拼装 */ #include "compiler/Codegen.h" #include #include +#include +#include #include #include #include @@ -48,28 +44,18 @@ namespace compiler { namespace { /** - * @brief 代码生成器(寄存器码)。 - * @details 流程:布局数据区 → 布局 FB 实例 → 逐 POU 建函数 → 拼映像。 + * @brief 代码生成器(V2 寄存器码)。 + * @details 流程:布局槽表/值段 → 逐 POU 建函数 → 计算 max_stack → 拼映像。 * 寄存器分配:r0 结果、r1..r7 参数(调用约定区,输入只读)、r8+ 变量/临时; - * 跳转偏移相对下一条(目标 = 当前 + 1 + off)。失败统一经 fail() 写 - * err(前缀 "codegen error")。 + * 跳转偏移字节单位相对下一条。失败统一经 fail() 写 err(前缀 "codegen error")。 */ class Builder { public: - /** - * @brief 构造生成器 - * @param proj 工程定义(cycle_limit / dt_ms / 哈希用) - * @param units 全部源文件的 AST - * @param link 链接结果(POU 顺序 / 符号) - * @param cfg 机器定义(machine.toml 强校验后;指令 opcode / 类型 tag / FB 布局) - * @param image 输出映像字节 - * @param err 错误输出;可为 nullptr(静默) - */ Builder(const Project& proj, const std::vector& units, const LinkResult& link, const MachineConfig& cfg, - std::vector* image, std::string* err) - : proj_(proj), units_(units), link_(link), cfg_(cfg), image_(image), err_(err) { - // io 绑定分类(名已折小写;不创造变量,只影响操作码选择) + std::vector* image, std::string* err, CodegenMap* map) + : proj_(proj), units_(units), link_(link), cfg_(cfg), image_(image), err_(err), + map_(map) { for (const IoBinding& b : proj_.io) { std::string key = b.var; for (char& ch : key) { @@ -83,15 +69,8 @@ namespace { } } - /** - * @brief 布局数据区 → 布局 FB 实例 → 逐 POU 建函数 → 拼映像 - * @return true 成功;false(err 已写,前缀 "codegen error") - */ bool run() { - if (!layout_data()) { - return false; - } - if (!layout_fb_instances()) { + if (!layout_slots_values()) { return false; } for (const LinkResult::PouScope& sc : link_.scopes) { @@ -107,15 +86,13 @@ namespace { } funcs_.push_back(std::move(f)); } + if (!compute_max_stack()) { + return false; + } return assemble_image(); } private: - /** - * @brief 记录错误并返回失败。 - * @param msg 错误消息(自动加前缀 "codegen error: ") - * @return 恒 false(便于 return fail(...) 连写) - */ bool fail(const std::string& msg) { if (err_) { *err_ = "codegen error: " + msg; @@ -124,55 +101,139 @@ namespace { } /** - * @brief 按名查配置 opcode(MachineConfig 强校验保证存在) + * @brief 查指令行(MachineConfig 强校验保证存在)。 * @param name 指令名(大写,如 "ADD") - * @return opcode(找不到返回 0,不应发生) + * @return 配置行指针(找不到返回 nullptr,不应发生) */ - uint8_t opc(const char* name) const { - const ConfigOp* op = cfg_.find_op(name); - return op ? static_cast(op->opcode) : 0; + const ConfigOp* opc(const char* name) const { + return cfg_.find_op(name); } - // ---- 指令发射(配置 opcode)---- - /// 双寄存器指令(rd, rs)。 - Instr E_rr(const char* name, uint8_t rd, uint8_t rs) { - return pack(opc(name), rd, rs, 0); - } - /// 三寄存器指令(rd, ra, rb)。 - Instr E_rrr(const char* name, uint8_t rd, uint8_t ra, uint8_t rb) { - return pack(opc(name), rd, ra, rb); - } - /// 立即数指令(imm 拆低/高字节入字段)。 - Instr E_imm(const char* name, uint8_t rd, uint16_t imm) { - return pack(opc(name), rd, static_cast(imm & 0xFFu), - static_cast((imm >> 8) & 0xFFu)); - } - /// 槽号指令(slot 复用 imm 字段,u16)。 - Instr E_slot(const char* name, uint8_t rd, uint16_t slot) { - return E_imm(name, rd, slot); - } - /// 无条件跳转(off 相对下一条指令)。 - Instr E_jmp(int16_t off) { - const uint16_t u = static_cast(off); - return pack(opc("JMP"), 0, static_cast(u & 0xFFu), - static_cast((u >> 8) & 0xFFu)); - } - /// 条件跳转(寄存器 r 为真则跳;off 相对下一条指令)。 - Instr E_jc(const char* name, uint8_t r, int16_t off) { - const uint16_t u = static_cast(off); - return pack(opc(name), r, static_cast(u & 0xFFu), - static_cast((u >> 8) & 0xFFu)); - } - /// CALL 指令(fn_id 为函数表下标)。 - Instr E_call(uint16_t fn_id) { return E_imm("CALL", 0, fn_id); } - /// RET 指令。 - Instr E_ret() { return pack(opc("RET"), 0, 0, 0); } + // ---- 指令发射(V2:Codec 位号写字节)---- /** - * @brief 按名查 POU AST。 - * @param name POU 名 - * @return 找到返回指针;否则 nullptr + * @brief 单个函数的编译状态(V2)。 */ + struct FuncCtx { + std::string name; ///< 函数名 + std::string pou_name; ///< 所属 POU 名(实例查找键) + std::vector code; ///< 字节码(变长指令,字节流) + std::map regs; ///< 变量名 → 帧寄存器 + std::map reg_func; ///< 变量名 → func(类型) + bool is_fb = false; ///< 本函数是 FUNCTION_BLOCK(共享体,r1=实例指针) + std::map fb_field_off; ///< 本 FB 类型字段名 → 偏移 + std::map fb_field_func; ///< 本 FB 类型字段名 → func + uint8_t nlocals = 0; ///< 变量区终点 = 临时寄存器起始 + uint8_t nregs = 0; ///< 寄存器峰值 + uint8_t temp_used = 0; ///< 本语句已用临时数 + bool is_function = false; ///< FUNCTION(结果 r0 / 输入只读) + std::string result_name; ///< FUNCTION 名(结果寄存器映射) + std::vector calls; ///< 本函数 CALL 的 fn_id 列表(max_stack 用) + }; + + + /// 发射一条指令:追加 instr_len 字节到函数码流,返回起始字节偏移。 + size_t emit(FuncCtx& f, const ConfigOp* op) { + const size_t start = f.code.size(); + f.code.resize(start + kMaxInstrLen, 0); + set_byte0(f.code.data() + start, op->prefix, op->op); + if (op->func_mode == "none" || op->func_mode == "width" || + op->func_mode == "uint") { + set_func(f.code.data() + start, 0); + } + f.code.resize(start + instr_len(f.code.data() + start)); + return start; + } + + /// 双寄存器指令(rd, rs)。 + size_t E_rr(FuncCtx& f, const char* name, uint8_t rd, uint8_t rs) { + const size_t s = emit(f, opc(name)); + set_rd(f.code.data() + s, rd); + set_rs1(f.code.data() + s, rs); + return s; + } + /// 三寄存器指令(rd, ra, rb)。 + size_t E_rrr(FuncCtx& f, const char* name, uint8_t rd, uint8_t ra, uint8_t rb) { + const size_t s = emit(f, opc(name)); + set_rd(f.code.data() + s, rd); + set_rs1(f.code.data() + s, ra); + set_rs2(f.code.data() + s, rb); + return s; + } + /// 立即数指令(rd, imm32)。 + size_t E_imm(FuncCtx& f, const char* name, uint8_t rd, uint32_t imm) { + const size_t s = emit(f, opc(name)); + set_rd(f.code.data() + s, rd); + set_imm32(f.code.data() + s, imm); + return s; + } + /// 槽号指令(rd, slot32)。 + size_t E_slot(FuncCtx& f, const char* name, uint8_t rd, uint32_t slot) { + const size_t s = emit(f, opc(name)); + set_rd(f.code.data() + s, rd); + set_slot32(f.code.data() + s, slot); + return s; + } + /// 无条件跳转(off 字节相对下一条)。 + size_t E_jmp(FuncCtx& f, int32_t off) { + const size_t s = emit(f, opc("JMP")); + set_off32(f.code.data() + s, off); + return s; + } + /// 条件跳转(r 为真则跳;off 字节相对下一条,JC 形态 off@55..24)。 + size_t E_jc(FuncCtx& f, const char* name, uint8_t r, int32_t off) { + const size_t s = emit(f, opc(name)); + set_rd(f.code.data() + s, r); + set_off32_jc(f.code.data() + s, off); + return s; + } + /// CALL 指令(fn_id32)。 + size_t E_call(FuncCtx& f, uint32_t fn_id) { + const size_t s = emit(f, opc("CALL")); + set_fn_id32(f.code.data() + s, fn_id); + return s; + } + /// RET 指令。 + size_t E_ret(FuncCtx& f) { + const size_t s = emit(f, opc("RET")); + return s; + } + /// 字段访问(实例槽 + 字段偏移,16B SLOT+off)。 + size_t E_slot_off(FuncCtx& f, const char* name, uint8_t rd, uint32_t slot, + int32_t off) { + const size_t s = emit(f, opc(name)); + set_rd(f.code.data() + s, rd); + set_slot32(f.code.data() + s, slot); + set_off32_hi(f.code.data() + s, off); + return s; + } + /// 基址寻址字段访问(rd, 基址寄存器rs, 偏移;16B RS+off)。 + size_t E_rs_off(FuncCtx& f, const char* name, uint8_t rd, uint8_t rs, + int32_t off) { + const size_t s = emit(f, opc(name)); + set_rd(f.code.data() + s, rd); + set_rs1(f.code.data() + s, rs); + set_off32_hi(f.code.data() + s, off); + return s; + } + /// 内建 FB 调用(fb_id16 + 实例槽32)。 + size_t E_cal(FuncCtx& f, uint16_t fb_id, uint32_t slot) { + const size_t s = emit(f, opc("CAL")); + set_fb_id16(f.code.data() + s, fb_id); + set_cal_slot32(f.code.data() + s, slot); + return s; + } + + /// 语言类型名 → func 位段值(大小写不敏感:符号表存小写,type 表存大写)。 + uint8_t func_of_name(const std::string& type_name) const { + std::string key = type_name; + for (char& ch : key) { + ch = static_cast(std::toupper(static_cast(ch))); + } + const ConfigType* t = cfg_.find_type(key); + return t ? static_cast(t->func) : 0; + } + const POU* find_pou(const std::string& name) const { for (const SourceUnit& u : units_) { for (const POU& p : u.ast.pous) { @@ -184,42 +245,19 @@ namespace { return nullptr; } - // 每函数的编译态 /** - * @brief 单个函数的编译状态。 - * @details 寄存器分配(调用约定):结果 r0、输入 r1..r7、变量与临时 r8+。 - */ - struct FuncCtx { - std::string name; ///< 函数名(函数表行用) - std::string pou_name; ///< 所属 POU 名(实例查找键) - std::vector code; ///< 字节码 - std::map regs; ///< 变量名 → 帧寄存器 - uint8_t nlocals = 0; ///< 变量区终点 = 临时寄存器起始 - uint8_t nregs = 0; ///< 寄存器峰值(变量 + 临时) - uint8_t temp_used = 0; ///< 本语句已用临时数(语句结束清零) - bool is_function = false; ///< FUNCTION(结果 r0 / 输入只读) - std::string result_name; ///< FUNCTION 名(结果寄存器映射) - }; - - // FB 实例:字段名 → 数据区槽号 - /** - * @brief FB 实例布局。 - * @details 字段槽号 = 实例基槽 + 字段序号(跨周期持久)。 + * @brief FB 实例布局(V2:槽表条目 + 值区偏移 + 字段偏移)。 */ struct InstFields { - std::map field_addr; ///< 字段名 → 数据区槽号 - uint32_t base = 0; ///< 实例基槽 - std::string type_name; ///< 实例的 FB 类型名(内建 / 用户) + std::map field_off; ///< 字段名 → 值区内偏移 + std::map field_func; ///< 字段名 → func(类型) + uint32_t slot = 0; ///< 实例槽号(槽表条目) + uint32_t addr = 0; ///< 实例基址(值区偏移,LOADK r1 用) + std::string type_name; ///< 实例的 FB 类型名 }; - /// 语句开始:清零临时寄存器计数。 void begin_stmt(FuncCtx& f) { f.temp_used = 0; } - /** - * @brief 分配一个临时寄存器。 - * @param f 函数编译态 - * @return 临时寄存器号(nlocals + 已用数;随用抬高 nregs 峰值) - */ uint8_t alloc_temp(FuncCtx& f) { const uint8_t r = f.nlocals + f.temp_used; ++f.temp_used; @@ -229,35 +267,127 @@ namespace { return r; } - /** - * @brief 编译一个 POU - * @details 支持 PROGRAM / FUNCTION / FUNCTION_BLOCK: - * FB 不生成业务字节码(调用点内联),只出空函数占位保持 fn_id 一致; - * 帧约定(见 Doc/compiler/寄存器码.md):结果 r0、输入 r1..r7(只读)、 - * 变量与临时全部从 r8 起 - */ + /// 变量名 → 语言类型名(局部声明;标量 = d.type.name 小写)。 + std::string type_name_of_var(const POU& pou, const std::string& name) const { + for (const VarBlock& b : pou.blocks) { + for (const VarDecl& d : b.vars) { + if (d.name == name) { + return d.type.kind == TypeKind::Scalar ? d.type.name : "int"; + } + } + } + return "int"; + } + + /// VAR_TEMP 初值装载:对 temp_map 中每个变量 emit LOADK(声明初值或 0)。 + /// temp_map 键为变量名、值为 (寄存器, func);调用点开头执行 → 每次调用重新初始化。 + void emit_temp_init(FuncCtx& f, const POU& pou, + const std::map& map, + const std::map& func_map) { + for (const VarBlock& b : pou.blocks) { + if (b.section != VarSection::Temp) { + continue; + } + for (const VarDecl& d : b.vars) { + const auto it = map.find(d.name); + if (it == map.end()) { + continue; + } + const uint8_t func = func_map.count(d.name) + ? func_map.at(d.name) + : func_of_name("INT"); + int64_t v = d.has_init ? d.init_value : 0; + if (d.has_init && d.type.name == "bool") { + v = d.init_value ? 1 : 0; + } + const size_t s = E_imm(f, "LOADK", it->second, const_id(v)); + set_func(f.code.data() + s, func); + } + } + } + + /// FB 类型字段偏移(类型级,对齐规则与 layout_slots_values 一致)。 + void fb_type_field_off(const FbLayout& lay, + std::map* off, + std::map* func) { + int32_t cur = 0; + for (const FbField& fd : lay.fields) { + const TypeMeta tm = type_meta(cfg_, fd.type_name); + cur = static_cast(align_up(static_cast(cur), tm.width())); + (*off)[fd.name] = cur; + (*func)[fd.name] = static_cast(tm.func()); + cur += static_cast(tm.width()); + } + } + bool build_function(const POU& pou, FuncCtx* f) { if (pou.kind == PouKind::FunctionBlock) { + // FB 共享体:编译为独立函数,r1 = 实例指针(值区地址,约定) + // 字段(输入/输出/内部 VAR)在实例存储 → LOAD_PTR/STORE_PTR r1+off; + // 局部寄存器 = VAR_TEMP + 表达式临时(r8 起),帧每周期重建 → 天然重置 + f->is_fb = true; f->nlocals = 8; f->nregs = 8; - f->code.push_back(E_ret()); + const auto fl = link_.fb_types.find(pou.name); + if (fl == link_.fb_types.end()) { + return fail("no FB type layout for '" + pou.name + "'"); + } + fb_type_field_off(fl->second, &f->fb_field_off, &f->fb_field_func); + uint8_t r = 8; + for (const VarBlock& b : pou.blocks) { + if (b.section != VarSection::Temp) { + continue; + } + for (const VarDecl& d : b.vars) { + if (d.type.kind != TypeKind::Scalar) { + continue; + } + f->regs[d.name] = r; + f->reg_func[d.name] = func_of_name(type_name_of_var(pou, d.name)); + ++r; + } + } + f->nlocals = r; + f->nregs = r; + if (map_ != nullptr) { + std::map vars; + for (const auto& kv : f->regs) { + std::string tn = "int"; + const auto tf = f->reg_func.find(kv.first); + if (tf != f->reg_func.end()) { + tn = std::to_string(tf->second); + } + vars[kv.first] = "r" + std::to_string(kv.second) + " (func " + tn + ")"; + } + map_->funcs.emplace_back(f->name, vars); + } + emit_temp_init(*f, pou, f->regs, f->reg_func); + for (const Stmt& st : pou.body) { + begin_stmt(*f); + if (!compile_stmt(*f, st)) { + return false; + } + } + E_ret(*f); return true; } f->is_function = pou.kind == PouKind::Function; f->result_name = pou.name; if (f->is_function) { - f->regs[pou.name] = 0; // 结果寄存器 r0 + f->regs[pou.name] = 0; + f->reg_func[pou.name] = func_of_name("INT"); uint8_t idx = 1; for (const VarBlock& b : pou.blocks) { if (b.section != VarSection::Input) { continue; } for (const VarDecl& d : b.vars) { - f->regs[d.name] = idx++; // 输入 r1..r7 + f->regs[d.name] = idx++; + f->reg_func[d.name] = func_of_name(type_name_of_var(pou, d.name)); } } } - uint8_t r = 8; // 变量/临时基址(调用约定区 r0..r7 不占用) + uint8_t r = 8; for (const VarBlock& b : pou.blocks) { if (b.section == VarSection::External || b.section == VarSection::Global) { continue; @@ -269,30 +399,36 @@ namespace { if (d.type.kind == TypeKind::FbUser || d.type.kind == TypeKind::FbBuiltin) { continue; } - f->regs[d.name] = r++; + f->regs[d.name] = r; + f->reg_func[d.name] = func_of_name(type_name_of_var(pou, d.name)); + ++r; } } f->nlocals = r; f->nregs = r; + if (map_ != nullptr) { + std::map vars; + for (const auto& kv : f->regs) { + std::string tn = "int"; + const auto tf = f->reg_func.find(kv.first); + if (tf != f->reg_func.end()) { + tn = std::to_string(tf->second); + } + vars[kv.first] = "r" + std::to_string(kv.second) + " (func " + tn + ")"; + } + map_->funcs.emplace_back(f->name, vars); + } + emit_temp_init(*f, pou, f->regs, f->reg_func); for (const Stmt& st : pou.body) { begin_stmt(*f); if (!compile_stmt(*f, st)) { return false; } } - f->code.push_back(E_ret()); + E_ret(*f); return true; } - /** - * @brief 编译一条语句。 - * @param f 函数编译态 - * @param st 语句 AST - * @return true 成功;false(err 已写) - * @details 支持 IF / WHILE / FB 调用 / 赋值;其他语句报 - * "statement not supported"。内联 FB 体内左值可能是实例字段 - * (走 STORE_GLOBAL);写 FUNCTION 输入报 "cannot write function input"。 - */ bool compile_stmt(FuncCtx& f, const Stmt& st) { if (st.kind == StmtKind::If) { return compile_if(f, st); @@ -306,15 +442,21 @@ namespace { if (st.kind != StmtKind::Assign) { return fail("statement not supported"); } - // 内联 FB 体内:左值可能是实例字段(STORE_GLOBAL) - if (inline_fields_) { - const auto fit = inline_fields_->field_addr.find(st.target); - if (fit != inline_fields_->field_addr.end()) { + if (f.is_fb) { + // FB 共享体:本实例字段(裸名)→ STORE_PTR r1+off + const auto fit = f.fb_field_off.find(st.target); + if (fit != f.fb_field_off.end()) { const uint8_t t = alloc_temp(f); - if (!compile_expr(f, *st.value, t)) { + const auto ff_ = f.fb_field_func.find(st.target); + if (!compile_expr(f, *st.value, t, + ff_ != f.fb_field_func.end() ? ff_->second : 0)) { return false; } - f.code.push_back(E_slot("STORE_GLOBAL", t, fit->second)); + const size_t so = E_rs_off(f, "STORE_PTR", t, 1, fit->second); + const auto ff = f.fb_field_func.find(st.target); + if (ff != f.fb_field_func.end()) { + set_func(f.code.data() + so, ff->second); + } return true; } } @@ -325,51 +467,64 @@ namespace { if (f.is_function && it->second >= 1 && it->second <= 7) { return fail("cannot write function input '" + st.target + "'"); } - return compile_expr(f, *st.value, it->second); + const auto tf_ = f.reg_func.find(st.target); + return compile_expr(f, *st.value, it->second, + tf_ != f.reg_func.end() ? tf_->second : 0); } - /** - * @brief 编译 FB 调用语句。 - * @param f 函数编译态 - * @param st FB 调用语句 AST - * @return true 成功;false(err 已写) - * @details 实参逐项编译后 STORE_GLOBAL 写实例字段;内建 FB - * 发 CAL_ <实例基槽>;用户 FB 调用点内联展开。 - * 错误:"no instance" / "unknown input" / "no FB type"。 - */ bool compile_fb_call(FuncCtx& f, const Stmt& st) { const InstFields* inst = instance_of(f.pou_name, st.instance); if (inst == nullptr) { return fail("no instance '" + st.instance + "' in '" + f.pou_name + "'"); } for (const FbArg& a : st.args) { - const auto it = inst->field_addr.find(a.name); - if (it == inst->field_addr.end()) { + const auto it = inst->field_off.find(a.name); + if (it == inst->field_off.end()) { return fail("unknown input '" + a.name + "' for '" + st.instance + "'"); } const uint8_t t = alloc_temp(f); - if (!compile_expr(f, *a.value, t)) { + const auto ff_in = inst->field_func.find(a.name); + if (!compile_expr(f, *a.value, t, + ff_in != inst->field_func.end() ? ff_in->second : 0)) { return false; } - f.code.push_back(E_slot("STORE_GLOBAL", t, it->second)); + const size_t so = E_slot_off(f, "STORE_OFF", t, inst->slot, it->second); + const auto ff = inst->field_func.find(a.name); + if (ff != inst->field_func.end()) { + set_func(f.code.data() + so, ff->second); + } } - // 内建 FB:配置里必须有对应 op 行(强校验保证)→ 直接查配置 opcode - const std::string& tn = inst->type_name; - if (cfg_.find_op("CAL_" + uppercase_of(tn)) != nullptr && - (tn == "ton" || tn == "tof" || tn == "tp" || tn == "ctu" || - tn == "ctd" || tn == "ctud" || tn == "r_trig" || tn == "f_trig")) { - const std::string opname = "CAL_" + uppercase_of(tn); - f.code.push_back(E_slot(opname.c_str(), 0, inst->base)); + // 内建 FB:fb 表查 id + const ConfigFb* fb = cfg_.find_fb(uppercase_of(inst->type_name)); + if (fb != nullptr) { + E_cal(f, static_cast(fb->fb_id), inst->slot); return true; } - const POU* fb = find_pou(inst->type_name); - if (fb == nullptr) { + const POU* user_fb = find_pou(inst->type_name); + if (user_fb == nullptr) { return fail("no FB type '" + inst->type_name + "'"); } - return compile_fb_inline(f, *fb, *inst); + // 用户 FB → CALL 共享体:r1 = 实例指针(值区地址)→ CALL + const int fb_fn = find_fn_id(user_fb->name); + if (fb_fn < 0) { + return fail("no fn_id for '" + user_fb->name + "'"); + } + f.calls.push_back(static_cast(fb_fn)); + uint8_t save_r = 0; + if (f.is_fb) { + // 嵌套(FB 函数体内再调 FB):保存本层 r1,调用后恢复 + save_r = alloc_temp(f); + E_rr(f, "MOVE", save_r, 1); + } + const size_t lk = E_imm(f, "LOADK", 1, const_id(inst->addr)); + set_func(f.code.data() + lk, func_of_name("UDINT")); + E_call(f, static_cast(fb_fn)); + if (f.is_fb) { + E_rr(f, "MOVE", 1, save_r); + } + return true; } - /// 转大写(内建 FB 名 → 操作码名)。 static std::string uppercase_of(const std::string& s) { std::string out = s; for (char& ch : out) { @@ -378,65 +533,25 @@ namespace { return out; } - /** - * @brief 用户 FB 调用点内联展开。 - * @param f 函数编译态 - * @param fb 用户 FB 的 POU AST - * @param inst 实例布局 - * @return true 成功;false(err 已写) - * @details 临时置 inline_fields_ 使体内变量引用改查实例字段 - * (编译结束后恢复)。 - */ - bool compile_fb_inline(FuncCtx& f, const POU& fb, const InstFields& inst) { - const InstFields* saved = inline_fields_; - inline_fields_ = &inst; - for (const Stmt& s : fb.body) { - begin_stmt(f); - if (!compile_stmt(f, s)) { - return false; - } - } - inline_fields_ = saved; - return true; - } - - /** - * @brief 编译 WHILE 循环。 - * @param f 函数编译态 - * @param st WHILE 语句 AST - * @return true 成功;false(err 已写) - * @details 结构:条件 → JF 跳出 → 体 → JMP 回条件;偏移经 - * patch_jump 回填(相对下一条)。 - */ bool compile_while(FuncCtx& f, const Stmt& st) { const size_t loop = f.code.size(); const uint8_t t = alloc_temp(f); if (!compile_expr(f, *st.cond, t)) { return false; } - const size_t jf_idx = f.code.size(); - f.code.push_back(E_jc("JF", t, 0)); + const size_t jf_idx = E_jc(f, "JF", t, 0); for (const Stmt& s : st.body) { begin_stmt(f); if (!compile_stmt(f, s)) { return false; } } - const size_t jmp_idx = f.code.size(); - f.code.push_back(E_jmp(0)); + const size_t jmp_idx = E_jmp(f, 0); patch_jump(f, jmp_idx, loop); patch_jump(f, jf_idx, f.code.size()); return true; } - /** - * @brief 编译 IF / ELSIF / ELSE。 - * @param f 函数编译态 - * @param st IF 语句 AST - * @return true 成功;false(err 已写) - * @details 每条分支:条件 → JF 跳下一条 → 体;分支间 JMP 跳 - * 公共结束点;偏移经 patch_jump 回填。 - */ bool compile_if(FuncCtx& f, const Stmt& st) { std::vector*>> branches; branches.push_back({st.cond.get(), &st.body}); @@ -451,8 +566,7 @@ namespace { if (!compile_expr(f, *branches[i].first, t)) { return false; } - const size_t jf_idx = f.code.size(); - f.code.push_back(E_jc("JF", t, 0)); + const size_t jf_idx = E_jc(f, "JF", t, 0); for (const Stmt& s : *branches[i].second) { begin_stmt(f); if (!compile_stmt(f, s)) { @@ -460,9 +574,7 @@ namespace { } } if (i + 1 < branches.size() || has_else) { - const size_t jmp_idx = f.code.size(); - f.code.push_back(E_jmp(0)); - end_jmps.push_back(jmp_idx); + end_jmps.push_back(E_jmp(f, 0)); } patch_jump(f, jf_idx, f.code.size()); } @@ -481,17 +593,6 @@ namespace { return true; } - /** - * @brief 编译左值存储(全局槽)。 - * @param f 函数编译态 - * @param target 目标名 - * @param value 表达式 - * @return true 成功;false(err 已写) - * @details 目标槽号在 link_.global_index 中查;I/O 输入禁止写 - * ("cannot write to input");存储操作码经 store_name 选择 - * (I/O 输出 STORE_Q,其余 STORE_GLOBAL)。 - * 错误:"no storage for ..." / "cannot write to input ..."。 - */ bool store_target(FuncCtx& f, const std::string& target, const Expr& value) { const auto git = link_.global_index.find(target); if (git == link_.global_index.end()) { @@ -501,51 +602,73 @@ namespace { return fail("cannot write to input '" + target + "'"); } const uint8_t tmp = alloc_temp(f); - if (!compile_expr(f, value, tmp)) { + const compiler::Symbol& sym = link_.globals[git->second]; + if (!compile_expr(f, value, tmp, func_of_name(sym.type_name))) { return false; } - f.code.push_back(E_slot(store_name(target), tmp, static_cast(git->second))); + const size_t st = E_slot(f, "STORE", tmp, git->second); + set_func(f.code.data() + st, func_of_global(target)); return true; } - /** - * @brief 编译表达式到目标寄存器。 - * @param f 函数编译态 - * @param e 表达式 AST - * @param rd 目标寄存器号 - * @return true 成功;false(err 已写) - * @details 支持:字面量(LOADK,tag 来自配置类型表)、变量/字段引用、 - * 四则、比较(cmp_name)、NOT、短路 AND/OR(JF/JT)、函数调用 - * (实参 MOVE 到 r1..r7 后 CALL,结果 MOVE 回 rd)。 - * 错误:"no register or slot" / "no instance" / "unknown field" / - * "too many arguments (max 7)" / "no fn_id" / "expression not supported"。 - */ - bool compile_expr(FuncCtx& f, const Expr& e, uint8_t rd) { + bool compile_expr(FuncCtx& f, const Expr& e, uint8_t rd, + uint8_t dst_func = 0) { if (e.kind == ExprKind::LitBool || e.kind == ExprKind::LitInt || - e.kind == ExprKind::LitTime) { - const char* type_name = e.kind == ExprKind::LitBool ? "BOOL" - : e.kind == ExprKind::LitInt ? "INT" - : "TIME"; - f.code.push_back(E_imm("LOADK", rd, const_id(type_name, e.int_value))); + e.kind == ExprKind::LitTime || e.kind == ExprKind::LitReal || + e.kind == ExprKind::LitDate || e.kind == ExprKind::LitTod || + e.kind == ExprKind::LitDt) { + const char* type_name = "INT"; + int64_t value = e.int_value; + switch (e.kind) { + case ExprKind::LitBool: type_name = "BOOL"; break; + case ExprKind::LitInt: type_name = "INT"; break; + case ExprKind::LitTime: type_name = "TIME"; break; + case ExprKind::LitReal: { + // 按目标 func 装载:LREAL(F64)位模式,否则 REAL(F32) + if (dst_func == func_of_name("LREAL")) { + type_name = "LREAL"; + std::memcpy(&value, &e.double_value, sizeof(double)); + } else { + type_name = "REAL"; + float fv = static_cast(e.double_value); + std::memcpy(&value, &fv, sizeof fv); + } + break; + } + case ExprKind::LitDate: type_name = "DATE"; break; + case ExprKind::LitTod: type_name = "TOD"; break; + case ExprKind::LitDt: type_name = "DT"; break; + default: break; + } + const size_t s = E_imm(f, "LOADK", rd, const_id(value)); + set_func(f.code.data() + s, func_of_name(type_name)); return true; } if (e.kind == ExprKind::VarRef) { - if (inline_fields_) { - const auto fit = inline_fields_->field_addr.find(e.name); - if (fit != inline_fields_->field_addr.end()) { - f.code.push_back(E_slot("LOAD_GLOBAL", rd, fit->second)); + if (f.is_fb) { + // FB 共享体:本实例字段(裸名)→ LOAD_PTR r1+off + const auto fit = f.fb_field_off.find(e.name); + if (fit != f.fb_field_off.end()) { + const size_t lo = E_rs_off(f, "LOAD_PTR", rd, 1, fit->second); + const auto ff = f.fb_field_func.find(e.name); + if (ff != f.fb_field_func.end()) { + set_func(f.code.data() + lo, ff->second); + } return true; } } const auto sit = f.regs.find(e.name); if (sit != f.regs.end()) { - f.code.push_back(E_rr("MOVE", rd, sit->second)); + const size_t s = E_rr(f, "MOVE", rd, sit->second); + const auto tf = f.reg_func.find(e.name); + set_func(f.code.data() + s, + tf != f.reg_func.end() ? tf->second : func_of_name("INT")); return true; } const auto git = link_.global_index.find(e.name); if (git != link_.global_index.end()) { - f.code.push_back( - E_slot(load_name(e.name), rd, static_cast(git->second))); + const size_t s = E_slot(f, "LOAD", rd, git->second); + set_func(f.code.data() + s, func_of_global(e.name)); return true; } return fail("no register or slot for '" + e.name + "'"); @@ -555,11 +678,15 @@ namespace { if (inst == nullptr) { return fail("no instance '" + e.name + "' in '" + f.pou_name + "'"); } - const auto fit = inst->field_addr.find(e.field); - if (fit == inst->field_addr.end()) { + const auto fit = inst->field_off.find(e.field); + if (fit == inst->field_off.end()) { return fail("unknown field '" + e.field + "' for '" + e.name + "'"); } - f.code.push_back(E_slot("LOAD_GLOBAL", rd, fit->second)); + const size_t lo = E_slot_off(f, "LOAD_OFF", rd, inst->slot, fit->second); + const auto ff = inst->field_func.find(e.field); + if (ff != inst->field_func.end()) { + set_func(f.code.data() + lo, ff->second); + } return true; } if (e.kind == ExprKind::Add || e.kind == ExprKind::Sub || @@ -572,7 +699,8 @@ namespace { if (!compile_expr(f, *e.rhs, r)) { return false; } - f.code.push_back(E_rrr(arith_name(e.kind), rd, l, r)); + const size_t s = E_rrr(f, arith_name(e.kind), rd, l, r); + set_func(f.code.data() + s, expr_func(f, *e.lhs, *e.rhs)); return true; } if (e.kind == ExprKind::Cmp) { @@ -584,7 +712,8 @@ namespace { if (!compile_expr(f, *e.rhs, r)) { return false; } - f.code.push_back(E_rrr(cmp_name(e.op), rd, l, r)); + const size_t s = E_rrr(f, cmp_name(e.op), rd, l, r); + set_func(f.code.data() + s, expr_func(f, *e.lhs, *e.rhs)); return true; } if (e.kind == ExprKind::Not) { @@ -592,7 +721,16 @@ namespace { if (!compile_expr(f, *e.operand, t)) { return false; } - f.code.push_back(E_rr("NOT", rd, t)); + E_rr(f, "NOT", rd, t); + return true; + } + if (e.kind == ExprKind::Neg) { + const uint8_t t = alloc_temp(f); + if (!compile_expr(f, *e.operand, t)) { + return false; + } + const size_t s = E_rr(f, "NEG", rd, t); + set_func(f.code.data() + s, expr_func(f, *e.operand, *e.operand)); return true; } if (e.kind == ExprKind::And || e.kind == ExprKind::Or) { @@ -600,13 +738,12 @@ namespace { return false; } const char* jname = (e.kind == ExprKind::And) ? "JF" : "JT"; - const size_t jmp_idx = f.code.size(); - f.code.push_back(E_jc(jname, rd, 0)); + const size_t jmp_idx = E_jc(f, jname, rd, 0); const uint8_t t = alloc_temp(f); if (!compile_expr(f, *e.rhs, t)) { return false; } - f.code.push_back(E_rr("MOVE", rd, t)); + E_rr(f, "MOVE", rd, t); patch_jump(f, jmp_idx, f.code.size()); return true; } @@ -626,21 +763,62 @@ namespace { if (fn_id < 0) { return fail("no fn_id for '" + e.name + "'"); } - for (size_t i = 0; i < args.size(); ++i) { - f.code.push_back(E_rr("MOVE", static_cast(1 + i), args[i])); + uint8_t save_r = 0; + if (f.is_fb) { + // FB 函数内调 FUNCTION:实参区 r1..r7 会覆盖 r1(实例指针)→ 保存恢复 + save_r = alloc_temp(f); + E_rr(f, "MOVE", save_r, 1); } - f.code.push_back(E_call(static_cast(fn_id))); - f.code.push_back(E_rr("MOVE", rd, 0)); + for (size_t i = 0; i < args.size(); ++i) { + E_rr(f, "MOVE", static_cast(1 + i), args[i]); + } + f.calls.push_back(static_cast(fn_id)); + E_call(f, static_cast(fn_id)); + if (f.is_fb) { + E_rr(f, "MOVE", 1, save_r); + } + E_rr(f, "MOVE", rd, 0); return true; } return fail("expression not supported"); } - /** - * @brief 按名查 fn_id(函数表下标)。 - * @param name 函数名 - * @return 找到返回下标;否则 -1 - */ + /// 表达式类型 func(算术/比较用):VarRef 取 reg_func/全局; + /// 嵌套表达式左右递归;字面量按装载类型(LitInt→INT、LitReal→REAL 等)。 + uint8_t expr_func(const FuncCtx& f, const Expr& l, const Expr& r) const { + if (l.kind == ExprKind::VarRef) { + const auto tf = f.reg_func.find(l.name); + if (tf != f.reg_func.end()) { + return tf->second; + } + return func_of_global(l.name); + } + if (l.kind == ExprKind::LitBool || l.kind == ExprKind::LitInt || + l.kind == ExprKind::LitTime || l.kind == ExprKind::LitReal || + l.kind == ExprKind::LitDate || l.kind == ExprKind::LitTod || + l.kind == ExprKind::LitDt) { + // 左是字面量 → 取右操作数(Typecheck 已定型同型) + if (r.kind == ExprKind::VarRef) { + const auto tf = f.reg_func.find(r.name); + if (tf != f.reg_func.end()) { + return tf->second; + } + return func_of_global(r.name); + } + } + return func_of_name("INT"); + } + + /// 全局变量 → func(查链接符号类型)。 + uint8_t func_of_global(const std::string& name) const { + for (const Symbol& s : link_.globals) { + if (s.name == name) { + return func_of_name(s.type_name); + } + } + return func_of_name("INT"); + } + int find_fn_id(const std::string& name) const { for (size_t i = 0; i < link_.scopes.size(); ++i) { if (link_.scopes[i].name == name) { @@ -651,24 +829,25 @@ namespace { } /** - * @brief 回填跳转偏移。 + * @brief 回填跳转偏移(字节单位)。 * @param f 函数编译态 - * @param idx 跳转指令下标 - * @param target_idx 目标指令下标 - * @details 偏移相对下一条:目标 = 当前 + 1 + off(off 为 - * int16,重写指令的低 16 位)。 + * @param idx 跳转指令起始字节偏移 + * @param target_off 目标字节偏移 + * @details off 相对下一条:目标 = 当前起点 + 指令长 + off。 */ - void patch_jump(FuncCtx& f, size_t idx, size_t target_idx) { - const int16_t off = static_cast( - static_cast(target_idx) - (static_cast(idx) + 1)); - const Instr w = f.code[idx]; - const uint16_t u = static_cast(off); - f.code[idx] = pack(op_of(w), rd_of(w), static_cast(u & 0xFFu), - static_cast((u >> 8) & 0xFFu)); + void patch_jump(FuncCtx& f, size_t idx, size_t target_off) { + const int32_t off = static_cast( + static_cast(target_off) - + (static_cast(idx) + static_cast(instr_len(f.code.data() + idx)))); + // 按前缀选偏移字段:JMP(010010)off@47..16;JT/JF(010011)off@55..24 + const std::string prefix = prefix_of(f.code.data() + idx); + if (prefix == "010010") { + set_off32(f.code.data() + idx, off); + } else { + set_off32_jc(f.code.data() + idx, off); + } } - // ---- 操作码名(MachineConfig 已强校验存在)---- - /// 四则运算操作码名(Add→"ADD" 等;未知回退 "ADD")。 static const char* arith_name(ExprKind k) { switch (k) { case ExprKind::Add: return "ADD"; @@ -678,7 +857,7 @@ namespace { default: return "ADD"; } } - /// 比较操作码名(Eq→"CMP_EQ" 等;未知回退 "CMP_EQ")。 + static const char* cmp_name(BinOp op) { switch (op) { case BinOp::Eq: return "CMP_EQ"; @@ -690,67 +869,39 @@ namespace { default: return "CMP_EQ"; } } - /// 加载操作码选择:I/O 输入走 LOAD_I,其余 LOAD_GLOBAL。 - const char* load_name(const std::string& name) const { - return io_input_.count(name) ? "LOAD_I" : "LOAD_GLOBAL"; - } - /// 存储操作码选择:I/O 输出走 STORE_Q,其余 STORE_GLOBAL。 - const char* store_name(const std::string& name) const { - return io_output_.count(name) ? "STORE_Q" : "STORE_GLOBAL"; - } + + // ---- V2 布局:槽表 → 值段 ---- /** - * @brief 布局全局数据区。 - * @return true 成功;false(err 已写) - * @details 每槽 8 字节定宽小端,槽号 × 8 定位;初值按类型元数据 - * 写(宽度 2 写 u16、8 写 u64、否则写 0/1;别名先经 type_meta 解析)。 - * 错误:"data area exceeds slot range" / "no type in machine.toml" / - * "float initializer not supported yet"。 + * @brief 布局槽表与值段。 + * @details 槽表条目:全局(声明序,每变量 1 条)+ FB 实例(每实例 1 条, + * 指向值区块起点)。值段:全局按类型 1/2/4/8 对齐 + 实例字段按 + * 字段类型对齐连续排。初值写值段(实例字段 0)。 */ - bool layout_data() { + bool layout_slots_values() { + uint32_t cur_addr = 0; // 值段当前偏移 for (const Symbol& s : link_.globals) { - if (s.address > 0xFFFF) { - return fail("data area exceeds slot range"); - } - bool has_init = false; - int64_t init = 0; - init_of(s.name, &has_init, &init); - const int64_t v = has_init ? init : 0; - const size_t off = static_cast(s.address) * 8; - data_.resize(off + 8, 0); - // 初值按类型元数据写(配置别名 → 基元宽度/浮点) const TypeMeta tm = type_meta(cfg_, s.type_name); if (!tm) { return fail("no type in machine.toml for '" + s.type_name + "'"); } - if (tm.is_float()) { - return fail("float initializer not supported yet"); - } - if (tm.width() == 2) { - const uint16_t iv = static_cast(v); - data_[off] = static_cast(iv & 0xFFu); - data_[off + 1] = static_cast((iv >> 8) & 0xFFu); - } else if (tm.width() == 8) { - const uint64_t tv = static_cast(v); - for (int i = 0; i < 8; ++i) { - data_[off + i] = static_cast((tv >> (8 * i)) & 0xFFu); - } - } else { - data_[off] = v ? 1 : 0; + cur_addr = align_up(cur_addr, tm.width()); + slots_.push_back(cur_addr); + values_.resize(cur_addr + tm.width(), 0); + bool has_init = false; + int64_t init = 0; + init_of(s.name, &has_init, &init); + const int64_t v = has_init ? init : 0; + write_value(values_, cur_addr, v, tm.width()); + global_slot_[s.name] = static_cast(slots_.size() - 1); + if (map_ != nullptr) { + map_->globals[s.name] = "slot=" + std::to_string(slots_.size() - 1) + + " off=" + std::to_string(cur_addr) + + " type=" + s.type_name + + " func=" + std::to_string(tm.func()); } + cur_addr += tm.width(); } - return true; - } - - /** - * @brief 布局 FB 实例块(全局区之后顺序追加)。 - * @return true 成功;false(err 已写) - * @details 实例 key 为 "POU名/实例名";字段槽号 = 基槽 + 字段序号。 - * 错误:"no layout for instance ..."。 - */ - bool layout_fb_instances() { - uint32_t cur = static_cast(data_.size() / 8); - bool any = false; for (const LinkResult::PouScope& sc : link_.scopes) { for (const Symbol& s : sc.syms) { if (s.kind != SymbolKind::FbInstance) { @@ -761,35 +912,67 @@ namespace { return fail("no layout for instance '" + s.name + "'"); } InstFields inst; - inst.base = cur; + inst.slot = static_cast(slots_.size()); inst.type_name = s.type_name; - for (size_t i = 0; i < lay->second.fields.size(); ++i) { - inst.field_addr[lay->second.fields[i].name] = cur + i; + int32_t off = 0; + for (const FbField& fd : lay->second.fields) { + const TypeMeta tm = type_meta(cfg_, fd.type_name); + if (!tm) { + return fail("no type for fb field '" + fd.type_name + "'"); + } + off = static_cast(align_up(off, tm.width())); + inst.field_off[fd.name] = off; + inst.field_func[fd.name] = static_cast(tm.func()); + off += static_cast(tm.width()); } - cur += static_cast(lay->second.fields.size()); + // 实例块对齐到 max(字段对齐) + uint32_t max_align = 1; + for (const FbField& fd : lay->second.fields) { + const TypeMeta tm = type_meta(cfg_, fd.type_name); + max_align = max_align > tm.width() ? max_align : tm.width(); + } + cur_addr = align_up(cur_addr, max_align); + inst.addr = cur_addr; + slots_.push_back(cur_addr); + values_.resize(cur_addr + static_cast(off), 0); instances_[sc.name + "/" + s.name] = inst; - any = true; + if (map_ != nullptr) { + std::string desc = "slot=" + std::to_string(inst.slot) + + " off=" + std::to_string(cur_addr) + + " fb=" + inst.type_name + + " size=" + std::to_string(off) + " "; + for (const auto& fo : inst.field_off) { + desc += fo.first + "@" + std::to_string(fo.second) + " "; + } + map_->instances[sc.name + "/" + s.name] = desc; + } + cur_addr += static_cast(off); } } - if (any) { - data_.resize(static_cast(cur) * 8, 0); - } return true; } - /// 按 "POU名/实例名" 查实例布局;找不到返回 nullptr。 + static uint32_t align_up(uint32_t v, uint32_t a) { + if (a <= 1) { + return v; + } + return (v + a - 1) / a * a; + } + + /// 按宽度写小端值(1/2/4/8 字节)。 + static void write_value(std::vector& buf, size_t off, int64_t v, + uint32_t width) { + for (uint32_t i = 0; i < width; ++i) { + buf[off + i] = static_cast((static_cast(v) >> (8 * i)) & 0xFFu); + } + } + const InstFields* instance_of(const std::string& pou, const std::string& name) const { const auto it = instances_.find(pou + "/" + name); return it == instances_.end() ? nullptr : &it->second; } - /** - * @brief 查全局变量初值(源文件 globals 段)。 - * @param name 变量名 - * @param has_init 输出:是否有初值 - * @param init 输出:初值 - */ void init_of(const std::string& name, bool* has_init, int64_t* init) const { for (const SourceUnit& u : units_) { for (const VarBlock& b : u.ast.globals) { @@ -806,57 +989,111 @@ namespace { *init = 0; } - /** - * @brief 取常量表 id(无则追加) - * @param type_name 语言类型名("BOOL"/"INT"/"TIME")→ 配置 tag(契约) - * @param value 常量值 - * @return const_id(u16) - */ - uint16_t const_id(const char* type_name, int64_t value) { - const TypeMeta tm = type_meta(cfg_, type_name); - const uint32_t tag = tm ? tm.tag() : 0; + /// 常量表:8B 原始值位模式去重(无 tag;LOADK 按 func 解释)。 + uint16_t const_id(int64_t value) { + const uint64_t v = static_cast(value); for (size_t i = 0; i < consts_.size(); ++i) { - if (consts_[i].tag == tag && consts_[i].value == static_cast(value)) { + if (consts_[i] == v) { return static_cast(i); } } - consts_.push_back({tag, static_cast(value)}); + consts_.push_back(v); return static_cast(consts_.size() - 1); } /** - * @brief 拼装最终映像。 - * @return true 成功;false(err 已写) - * @details 布局:头(104) + 常量表 + 函数表 + 字节码 + 数据段 + - * SHA-256 文件尾;型号标识 @72;头内各段偏移(52..68)与函数表 - * code_offset 小端写入;SHA-256 对文件尾之前全部内容计算。 + * @brief max_stack:MAIN + FUNCTION 调用图 DAG 最坏路径 Σ(12 + nregs×8)。 + * @return true 成功;false(调用图有环 → "recursive call") */ + bool compute_max_stack() { + const size_t n = funcs_.size(); + // 邻接表:fn_id → 被调 fn_id 列表(权重 = 帧需求) + std::vector> edges(n); + for (size_t i = 0; i < n; ++i) { + for (const uint32_t c : funcs_[i].calls) { + if (c < n) { + edges[i].push_back(c); + } + } + } + // 环检测(DFS 三色)——防御(链接层已拒递归) + std::vector color(n, 0); + std::vector in_stack(n, false); + std::function dfs_cycle = [&](size_t u) -> bool { + color[u] = 1; + in_stack[u] = true; + for (const uint32_t v : edges[u]) { + if (color[v] == 0) { + if (dfs_cycle(v)) return true; + } else if (in_stack[v]) { + return true; + } + } + in_stack[u] = false; + return false; + }; + for (size_t i = 0; i < n; ++i) { + if (color[i] == 0 && dfs_cycle(i)) { + return fail("recursive call detected"); + } + } + // 最长路径(按字节权重)——DAG 上记忆化 + std::vector memo(n, -1); + std::function best = [&](size_t u) -> int64_t { + if (memo[u] >= 0) { + return memo[u]; + } + int64_t b = 0; + for (const uint32_t v : edges[u]) { + const int64_t cand = 12 + static_cast(funcs_[v].nregs) * 8 + + best(v); + if (cand > b) { + b = cand; + } + } + memo[u] = b; + return b; + }; + int64_t total = 0; + for (size_t i = 0; i < n; ++i) { + const int64_t need = 12 + static_cast(funcs_[i].nregs) * 8 + best(i); + if (need > total) { + total = need; + } + } + max_stack_ = static_cast(total); + return true; + } + bool assemble_image() { + // 段布局(V2 头 128B + 常量表 8B + 函数表 12B + 字节码 + 槽表 + 值段 + SHA) const uint32_t off_const = static_cast(kHeaderSize); - const uint32_t off_funcs = off_const + - static_cast(consts_.size()) * static_cast(kConstEntrySize); - uint32_t off_code = off_funcs + - static_cast(funcs_.size()) * static_cast(kFuncRowSize); + const uint32_t off_funcs = + off_const + static_cast(consts_.size()) * kConstEntrySize; + uint32_t off_code = + off_funcs + static_cast(funcs_.size()) * kFuncRowSize; uint32_t code_total = 0; for (const FuncCtx& f : funcs_) { - code_total += static_cast(f.code.size()) * 4; + code_total += static_cast(f.code.size()); } - const uint32_t off_data = off_code + code_total; - const uint32_t off_end = off_data + static_cast(data_.size()) + + const uint32_t off_slots = align_up(off_code + code_total, 16); + const uint32_t off_values = + off_slots + static_cast(slots_.size()) * kSlotRowSize; + const uint32_t off_end = off_values + static_cast(values_.size()) + static_cast(kSha256Size); std::vector& b = *image_; b.assign(off_end, 0); - put_le32(b, 0, kMagic); - put_le32(b, 4, kVersion); - put_le32(b, 8, proj_.cycle_limit); - put_le32(b, 12, proj_.dt_ms); + put_le32(b, kOffMagic, kMagic); + put_le32(b, kOffVersion, kVersion); + put_le32(b, kOffCycleLimit, proj_.cycle_limit); + put_le32(b, kOffDtMs, proj_.dt_ms); uint64_t hash = kFnvBasis; if (!compute_project_hash(proj_, &hash, err_)) { return false; } - put_le64(b, 16, hash); + put_le64(b, kOffProjectHash, hash); uint32_t entry = 0; for (size_t i = 0; i < funcs_.size(); ++i) { @@ -864,56 +1101,60 @@ namespace { entry = static_cast(i); } } - put_le32(b, 24, entry); - put_le32(b, 28, static_cast(link_.globals.size())); - put_le32(b, 32, 0); - put_le32(b, 36, 0); - put_le32(b, 40, 0); - put_le32(b, 44, static_cast(consts_.size())); - put_le32(b, 48, static_cast(funcs_.size())); - put_le32(b, 52, off_const); - put_le32(b, 56, off_funcs); - put_le32(b, 60, off_code); - put_le32(b, 64, off_data); - put_le32(b, 68, off_data); + put_le32(b, kOffEntryFnId, entry); + put_le32(b, kOffNGlobals, static_cast(link_.globals.size())); + put_le32(b, kOffNI, 0); + put_le32(b, kOffNQ, 0); + put_le32(b, kOffNM, 0); + put_le32(b, kOffNConsts, static_cast(consts_.size())); + put_le32(b, kOffNFuncs, static_cast(funcs_.size())); + put_le32(b, kOffConst, off_const); + put_le32(b, kOffFuncs, off_funcs); + put_le32(b, kOffCode, off_code); + put_le32(b, kOffSlots, off_slots); + put_le32(b, kOffValues, off_values); + put_le32(b, kOffNSlots, static_cast(slots_.size())); + put_le32(b, kOffValuesSize, static_cast(values_.size())); + put_le32(b, kOffMeta, 0); + put_le32(b, kOffMaxStack, max_stack_); - // 型号标识[32](偏移 72;12.13 修订) char mid[kModelIdSize]; fill_model_id(cfg_.model_name(), cfg_.version(), mid); for (size_t i = 0; i < kModelIdSize; ++i) { - b[72 + i] = static_cast(mid[i]); + b[kOffModelId + i] = static_cast(mid[i]); } + // 常量表(8B 原始值) for (size_t i = 0; i < consts_.size(); ++i) { - const size_t o = off_const + i * kConstEntrySize; - put_le32(b, o, consts_[i].tag); - put_le64(b, o + 4, consts_[i].value); + put_le64(b, off_const + i * kConstEntrySize, consts_[i]); } - uint32_t c = off_code; - for (size_t i = 0; i < funcs_.size(); ++i) { - const FuncCtx& f = funcs_[i]; - const size_t o = off_funcs + i * kFuncRowSize; - put_le32(b, o, f.nregs); - put_le32(b, o + 8, static_cast(f.code.size())); - for (const Instr in : f.code) { - put_le32(b, c, in); - c += 4; - } - } + // 函数表(nregs/code_offset/code_len 字节)+ 字节码 uint32_t acc = 0; for (size_t i = 0; i < funcs_.size(); ++i) { const size_t o = off_funcs + i * kFuncRowSize; - put_le32(b, o + 4, acc); - acc += static_cast(funcs_[i].code.size()) * 4; + put_le32(b, o + kFuncNregsOff, funcs_[i].nregs); + put_le32(b, o + kFuncCodeOff, acc); + put_le32(b, o + kFuncLenOff, static_cast(funcs_[i].code.size())); + for (size_t j = 0; j < funcs_[i].code.size(); ++j) { + b[off_code + acc + j] = funcs_[i].code[j]; + } + acc += static_cast(funcs_[i].code.size()); } - for (size_t i = 0; i < data_.size(); ++i) { - b[off_data + i] = data_[i]; + // 槽表(addr:u32 + 预留:u32) + for (size_t i = 0; i < slots_.size(); ++i) { + put_le32(b, off_slots + i * kSlotRowSize + kSlotAddrOff, slots_[i]); + put_le32(b, off_slots + i * kSlotRowSize + kSlotPadOff, 0); } - // 文件尾 SHA-256(对文件尾之前全部内容计算) - const size_t content_len = off_data + data_.size(); + // 值段 + for (size_t i = 0; i < values_.size(); ++i) { + b[off_values + i] = values_[i]; + } + + // SHA-256 文件尾(对文件尾之前全部内容) + const size_t content_len = off_values + values_.size(); uint8_t digest[kSha256Size]; sha256(&b[0], content_len, digest); for (size_t i = 0; i < kSha256Size; ++i) { @@ -922,14 +1163,12 @@ namespace { return true; } - /// 小端写 32 位。 static void put_le32(std::vector& b, size_t off, uint32_t v) { b[off + 0] = static_cast(v & 0xFFu); b[off + 1] = static_cast((v >> 8) & 0xFFu); b[off + 2] = static_cast((v >> 16) & 0xFFu); b[off + 3] = static_cast((v >> 24) & 0xFFu); } - /// 小端写 64 位。 static void put_le64(std::vector& b, size_t off, uint64_t v) { for (int i = 0; i < 8; ++i) { b[off + i] = static_cast((v >> (8 * i)) & 0xFFu); @@ -937,37 +1176,40 @@ namespace { } // ---- 成员 ---- - const Project& proj_; ///< 工程定义(cycle_limit / dt_ms / 哈希) - const std::vector& units_; ///< 全部源文件 AST - const LinkResult& link_; ///< 链接结果(POU 顺序 / 全局符号 / FB 布局) - const MachineConfig& cfg_; ///< 机器定义(opcode / tag / FB 布局) - std::vector* image_; ///< 输出映像字节 - std::string* err_; ///< 错误输出(可为 nullptr) - std::vector funcs_; ///< 已编译函数 - std::vector consts_; ///< 常量表(tag + value) - std::map io_input_; ///< I/O 输入变量名集合(小写;只影响操作码选择) - std::map io_output_; ///< I/O 输出变量名集合(小写;只影响操作码选择) - std::vector data_; ///< 全局数据区字节(每槽 8 字节定宽) + const Project& proj_; + const std::vector& units_; + const LinkResult& link_; + const MachineConfig& cfg_; + std::vector* image_; + std::string* err_; + std::vector funcs_; + std::vector consts_; ///< 常量表(8B 原始值,位模式去重) + std::map io_input_; + std::map io_output_; + std::vector slots_; ///< 槽表 addr 列表 + std::vector values_; ///< 值段字节 + std::map global_slot_; ///< 全局名 → 槽号 std::map instances_; ///< "POU名/实例名" → 实例布局 - const InstFields* inline_fields_ = nullptr; ///< 内联 FB 字段表(非空 = 正在内联 FB 体) + uint32_t max_stack_ = 0; ///< 栈区字节数 + CodegenMap* map_ = nullptr; ///< 变量/槽映射输出(可空) }; } // namespace /** - * @brief 编译工程为 .stb 映像字节(对外入口)。 + * @brief 编译工程为 .stb 映像字节(对外入口,V2)。 * @param proj 工程定义 * @param units 全部源文件的 AST * @param link 链接结果 - * @param cfg 机器定义(machine.toml) + * @param cfg 机器定义(machine.toml V2) * @param image 输出映像字节 * @param err 错误输出;可为 nullptr * @return true 成功;false(err 前缀 "codegen error") */ bool codegen_project(const Project& proj, const std::vector& units, const LinkResult& link, const MachineConfig& cfg, - std::vector* image, std::string* err) { - Builder b(proj, units, link, cfg, image, err); + std::vector* image, std::string* err, CodegenMap* map) { + Builder b(proj, units, link, cfg, image, err, map); return b.run(); } diff --git a/compiler/src/Lexer.cpp b/compiler/src/Lexer.cpp index 98e3ee9..d96e682 100644 --- a/compiler/src/Lexer.cpp +++ b/compiler/src/Lexer.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -59,11 +60,28 @@ namespace { {"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}, @@ -80,15 +98,7 @@ namespace { // 字面量 {"true", Tok::TRUE}, {"false", Tok::FALSE}, - // 内置 FB 类型名(12.11 扩充为 8 个) - {"ton", Tok::TON}, - {"tof", Tok::TOF}, - {"tp", Tok::TP}, - {"ctu", Tok::CTU}, - {"ctd", Tok::CTD}, - {"ctud", Tok::CTUD}, - {"r_trig", Tok::R_TRIG}, - {"f_trig", Tok::F_TRIG}, + // FB 类型名不再设关键字(V2:IDENT + Typecheck 查 machine.toml [[fb]] 表) }; /** @@ -261,9 +271,25 @@ namespace { tok_col_ = col_; const char c = cur(); - // TIME 字面量:T#(大小写不敏感)优先于标识符 - if ((c == 't' || c == 'T') && pos_ + 1 < src_.size() && src_[pos_ + 1] == '#') { - return lex_time(); + // 日期/TIME 字面量前缀:D# / T# / TOD# / DT#(大小写不敏感,最长匹配) + // 字母序列后跟 '#' 才进入;"ton"、"timer" 等普通词走标识符分支。 + if (std::isalpha(static_cast(c))) { + const uint32_t wstart = pos_; + size_t j = pos_; + while (j < src_.size() && + std::isalpha(static_cast(src_[j]))) { + ++j; + } + if (j < src_.size() && src_[j] == '#') { + std::string w = src_.substr(wstart, j - wstart); + for (char& ch : w) { + ch = static_cast(std::tolower(static_cast(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(内部形), @@ -288,14 +314,50 @@ namespace { return true; } - // 整数:十进制 [0-9]+,int64_t 存储; - // 溢出检查在累加前做(v 上限 INT64_MAX)。 + // 数字:进制整数(2#/8#/16#)→ 浮点(. / e)→ 十进制整数 if (std::isdigit(static_cast(c))) { const uint32_t start = pos_; while (!at_end() && std::isdigit(static_cast(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(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(src_[pos_ + 1]))) { + is_float = true; + advance(); // . + while (!at_end() && std::isdigit(static_cast(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(cur()))) { + return fail("bad float literal"); + } + while (!at_end() && std::isdigit(static_cast(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(ch - '0'); @@ -434,6 +496,180 @@ namespace { 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(ch - '0'); + } else if (ch >= 'a' && ch <= 'f') { + d = static_cast(ch - 'a' + 10); + } else if (ch >= 'A' && ch <= 'F') { + d = static_cast(ch - 'A' + 10); + } else { + break; + } + if (d >= base) { + return fail("bad base literal digit"); + } + if (v > (static_cast(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(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(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(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(m))) return false; + *out_days = days_from_civil(y, static_cast(m), static_cast(d)); + return true; + } + + /// 读一个无符号整数段(消费数字)。 + bool read_uint(int64_t* out) { + if (at_end() || !std::isdigit(static_cast(cur()))) { + return false; + } + int64_t v = 0; + while (!at_end() && std::isdigit(static_cast(cur()))) { + v = v * 10 + static_cast(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(h * 3600000 + m * 60000 + s * 1000); + const int64_t dt = (static_cast(days) << 32) | tod; + push(Tok::DT_LIT, src_.substr(start, pos_ - start), dt); + return true; + } + // ---- 状态 ---- const std::string& src_; ///< 源文本(外部所有,不拷贝) const std::string& sf_; ///< 源文件名(报错用) diff --git a/compiler/src/Linker.cpp b/compiler/src/Linker.cpp index 54a0a30..12fcb4d 100644 --- a/compiler/src/Linker.cpp +++ b/compiler/src/Linker.cpp @@ -77,60 +77,47 @@ namespace { * @return "bool" / "int" / "time" / FB 类型名(小写);未知返回 "?" */ std::string type_str(const TypeRef& t) { - switch (t.kind) { - case TypeKind::Bool: return "bool"; - case TypeKind::Int: return "int"; - case TypeKind::Time: return "time"; - case TypeKind::FbBuiltin: - case TypeKind::FbUser: return t.name; + 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(std::tolower(static_cast(ch))); } - return "?"; + return out; } /** - * @brief 是否为标量类型(BOOL / INT / TIME) - * @param k 类型种类 - * @return true 标量(FB 字段只允许标量,见 collect_fb_types) - */ - bool is_scalar(TypeKind k) { - return k == TypeKind::Bool || k == TypeKind::Int || k == TypeKind::Time; - } - - /// 内建 FB 布局(冻结,见 Doc/compiler/符号表与链接.md;12.11 扩为 8 个) - const FbLayout kTonLayout{"ton", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time}, - {"q", TypeKind::Bool}, {"et", TypeKind::Time}}}; - const FbLayout kTofLayout{"tof", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time}, - {"q", TypeKind::Bool}, {"et", TypeKind::Time}}}; - const FbLayout kTpLayout{"tp", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time}, - {"q", TypeKind::Bool}, {"et", TypeKind::Time}}}; - const FbLayout kCtuLayout{"ctu", {{"cu", TypeKind::Bool}, {"r", TypeKind::Bool}, - {"pv", TypeKind::Int}, {"q", TypeKind::Bool}, - {"cv", TypeKind::Int}}}; - const FbLayout kCtdLayout{"ctd", {{"cd", TypeKind::Bool}, {"ld", TypeKind::Bool}, - {"pv", TypeKind::Int}, {"q", TypeKind::Bool}, - {"cv", TypeKind::Int}}}; - const FbLayout kCtudLayout{"ctud", {{"cu", TypeKind::Bool}, {"cd", TypeKind::Bool}, - {"r", TypeKind::Bool}, {"lu", TypeKind::Bool}, - {"pv", TypeKind::Int}, {"qu", TypeKind::Bool}, - {"qd", TypeKind::Bool}, {"cv", TypeKind::Int}}}; - const FbLayout kRTrigLayout{"r_trig", {{"clk", TypeKind::Bool}, {"q", TypeKind::Bool}}}; - const FbLayout kFTrigLayout{"f_trig", {{"clk", TypeKind::Bool}, {"q", TypeKind::Bool}}}; - - /** - * @brief 内建 FB 名 → 冻结布局 + * @brief 内建 FB 名 → 布局(查 machine.toml [[fb]] 表,V2 配置驱动)。 * @param name FB 类型名(小写) - * @return 布局指针(ton/tof/tp/ctu/ctd/ctud/r_trig/f_trig);未知返回 nullptr + * @return 布局指针(缓存);未知返回 nullptr */ - const FbLayout* builtin_layout(const std::string& name) { - if (name == "ton") return &kTonLayout; - if (name == "tof") return &kTofLayout; - if (name == "tp") return &kTpLayout; - if (name == "ctu") return &kCtuLayout; - if (name == "ctd") return &kCtdLayout; - if (name == "ctud") return &kCtudLayout; - if (name == "r_trig") return &kRTrigLayout; - if (name == "f_trig") return &kFTrigLayout; - return nullptr; + const FbLayout* builtin_layout(const MachineConfig& cfg, const std::string& name) { + static std::map cache; + auto it = cache.find(name); + if (it != cache.end()) { + return &it->second; + } + std::string up = name; + for (char& ch : up) { + ch = static_cast(std::toupper(static_cast(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; } /** @@ -148,8 +135,8 @@ namespace { * @param err 错误输出;可为 nullptr(静默) */ Linker(const Project& proj, const std::vector& units, - LinkResult* out, std::string* err) - : proj_(proj), units_(units), out_(out), err_(err) { + 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); @@ -227,6 +214,11 @@ namespace { 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 + "'", @@ -279,21 +271,24 @@ namespace { FbLayout lay; lay.type_name = p.name; for (const VarBlock& b : p.blocks) { - // 字段 = input / output / 内部 var;external 与 global 不是字段 + // 字段 = input / output / 内部 var;external/global/temp 不是字段 + //(VAR_TEMP 不持久,每次调用重新初始化,走帧寄存器) if (b.section == VarSection::External || - b.section == VarSection::Global) { + b.section == VarSection::Global || + b.section == VarSection::Temp) { continue; } for (const VarDecl& d : b.vars) { - if (!is_scalar(d.type.kind)) { + // FB 字段必须是标量类型(FB 实例字段不允许) + if (d.type.kind != TypeKind::Scalar) { return fail(u.path, - "FB field type must be BOOL/INT/TIME ('" + - d.name + "')", + "FB field type must be scalar ('" + d.name + + "')", d.line, d.col); } FbField f; f.name = d.name; - f.type = d.type.kind; + f.type_name = d.type.name; // 标量类型名(小写) lay.fields.push_back(f); } } @@ -343,9 +338,10 @@ namespace { /** * @brief 建一个 POU 的局部符号 - * @details VAR/INPUT/OUTPUT → 对应种类;VAR_EXTERNAL → 查全局表接同一槽 - * (类型必须一致);FB 类型 → FbInstance + 布局(内建取冻结表, - * 用户类型查 fb_types);POU 内 VAR_GLOBAL → 拒绝 + * @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 被填充) @@ -399,8 +395,13 @@ namespace { sc.syms.push_back(s); if (is_fb) { FbLayout lay; - if (d.type.kind == TypeKind::FbBuiltin) { - const FbLayout* bl = builtin_layout(d.type.name); + // 内建判定:查 machine.toml [[fb]] 表(V2 配置驱动) + std::string up = d.type.name; + for (char& ch : up) { + ch = static_cast(std::toupper(static_cast(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); @@ -693,6 +694,7 @@ namespace { // ---- 成员 ---- const Project& proj_; ///< 工程定义(toml) const std::vector& units_; ///< 全部源文件 AST + const MachineConfig& cfg_; ///< 机器定义(内建 FB 布局来源) LinkResult* out_; ///< 链接结果 std::string* err_; ///< 错误输出(可空) std::filesystem::path gvl_path_; ///< 规范化后的 gvl 路径 @@ -712,13 +714,13 @@ namespace { * @return true 成功;false 失败(err 前缀 "link error") */ bool link_project(const Project& proj, const std::vector& units, - LinkResult* out, std::string* err) { + 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, out, err); + Linker l(proj, units, cfg, out, err); return l.run(); } diff --git a/compiler/src/MachineConfig.cpp b/compiler/src/MachineConfig.cpp index 461dece..75df9fc 100644 --- a/compiler/src/MachineConfig.cpp +++ b/compiler/src/MachineConfig.cpp @@ -1,16 +1,17 @@ /** * @file MachineConfig.cpp - * @brief 机器定义(machine.toml)加载与强校验 + * @brief 机器定义(machine.toml V2)加载与强校验 * @author * @date 2026-08-21 * - * @details 校验项(见 Doc/isa/指令配置.md 强校验清单 1~5): - * 1. TOML 语法与字段完整性(全属性必填) - * 2. type.base 命中内建基元;range 合法(min ≤ max) - * 3. tag 与 .stb 常量表契约一致(BOOL=0、INT=1、TIME=2,唯一) - * 4. op.opcode 唯一、0..255;class/format 合法;类别-格式互锁(INSTANCE ↔ CAL); - * params 数量与 format 一致 - * 5. fb.opcode 与 op 行(instance 类)一致;字段类型命中配置类型表;字段名唯一 + * @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" @@ -73,31 +74,114 @@ namespace { } /** - * @brief format → 期望参数数量(解析时校验)。 - * @param format 操作数形态(RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE) - * @return 参数数量;未知 format 返回 -1 + * @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& format) { - if (format == "RR") return 2; - if (format == "RRR") return 3; - if (format == "IMM") return 2; - if (format == "SLOT") return 2; - if (format == "JMP") return 1; - if (format == "JC") return 2; - if (format == "CALL") return 1; - if (format == "CAL") return 1; - if (format == "NONE") return 0; + 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 format 是否已知(params_of 返回非负)。 + /// @brief fmt 是否已知(params_of 返回非负)。 bool is_known_format(const std::string& f) { return params_of(f) >= 0; } - /// @brief tag 是否在 .stb 常量表契约内(0=BOOL、1=INT、2=TIME)。 - bool is_known_tag(uint32_t tag) { - return tag <= 2; // .stb 常量表契约:0=BOOL 1=INT 2=TIME + /** + * @brief 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; } /** @@ -163,17 +247,17 @@ namespace { } // namespace /** - * @brief 加载 machine.toml 并做强校验。 + * @brief 加载 machine.toml(V2)并做强校验。 * @param path machine.toml 路径 * @param err 错误输出;可为 nullptr(静默) * @return true 成功;false(err 已写,前缀 "machine error") - * @details 校验顺序与文件头清单 1~5 一致: - * 1. TOML 语法与字段完整性(meta/type/op/fb 全属性必填) - * 2. type.base 命中内建基元;range 合法(min ≤ max) - * 3. tag 与 .stb 常量表契约一致(BOOL=0、INT=1、TIME=2,唯一) - * 4. op.opcode 唯一、0..255;class/format 合法;类别-格式互锁(INSTANCE ↔ CAL); - * params 数量与 format 一致 - * 5. fb.opcode 与 op 行(instance 类)一致;字段类型命中配置类型表;字段名唯一 + * @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; @@ -191,29 +275,38 @@ bool MachineConfig::load(const std::string& path, std::string* err) { return false; } - // ---- meta ---- + // ---- 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 < 0 || ver > 0xFFFFFFFFLL) { - fail(err, "invalid version in [meta]"); + if (ver != 2) { + fail(err, "version must be 2 (V2, STATOR2)"); return false; } version_ = static_cast(ver); - // ---- type ---- + // ---- 2. type(19 条全集)---- { const toml::array* arr = root["type"].as_array(); if (!arr || arr->empty()) { fail(err, "missing 'type' table"); return false; } - std::map tag_owner; // tag → 类型名 + 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 seen; for (const auto& el : *arr) { const toml::table* t = el.as_table(); if (!t) { @@ -223,59 +316,56 @@ bool MachineConfig::load(const std::string& path, std::string* err) { ConfigType ct; if (!req_string(*t, "type", "name", &ct.name, err)) return false; if (!req_string(*t, "type", "base", &ct.base, err)) return false; - if (find_prim(ct.base) == nullptr) { + 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(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; } - int64_t tag = 0; - if (!req_int(*t, "type", "tag", &tag, err)) return false; - if (tag < 0 || !is_known_tag(static_cast(tag))) { - fail(err, "type '" + ct.name + "': tag out of contract (0..2)"); + // 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; } - ct.tag = static_cast(tag); - if (tag_owner.count(ct.tag)) { - fail(err, "duplicate tag " + std::to_string(ct.tag)); - return false; - } - tag_owner[ct.tag] = ct.name; - if (t->contains("range")) { - const toml::array* r = (*t)["range"].as_array(); - if (!r || r->size() != 2) { - fail(err, "type '" + ct.name + "': range must be [min, max]"); - return false; - } - ct.has_range = true; - ct.range_min = (*r)[0].value_or(0); - ct.range_max = (*r)[1].value_or(0); - if (ct.range_min > ct.range_max) { - fail(err, "type '" + ct.name + "': range min > max"); - return false; - } - } + if (!func_matches_prim(ct.name, *prim, ct.func, err)) return false; types_.push_back(ct); } - // .stb 契约:BOOL/INT/TIME 必须存在且 tag 为 0/1/2 - const std::string need[3] = {"BOOL", "INT", "TIME"}; - for (uint32_t i = 0; i < 3; ++i) { - const auto it = tag_owner.find(i); - if (it == tag_owner.end() || it->second != need[i]) { - fail(err, std::string("type contract broken: tag ") + std::to_string(i) + - " must be " + need[i]); + // 19 条全集必填 + for (const char* need : kAllTypes) { + if (!seen.count(need)) { + fail(err, std::string("type contract broken: missing '") + need + "'"); return false; } } } - // ---- op ---- + // ---- 3. op ---- { const toml::array* arr = root["op"].as_array(); if (!arr || arr->empty()) { fail(err, "missing 'op' table"); return false; } - std::map code_owner; - std::map name_owner; + std::map key_owner; // "prefix/op" → 已存在 + std::map name_owner; // 助记符 → 已存在 for (const auto& el : *arr) { const toml::table* t = el.as_table(); if (!t) { @@ -284,51 +374,59 @@ bool MachineConfig::load(const std::string& path, std::string* err) { } ConfigOp op; if (!req_string(*t, "op", "name", &op.name, err)) return false; - int64_t code = 0; - if (!req_int(*t, "op", "opcode", &code, err)) return false; - if (code < 0 || code > 255) { - fail(err, "op '" + op.name + "': opcode out of range"); + 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.opcode = static_cast(code); - if (code_owner.count(op.opcode)) { - fail(err, "duplicate opcode " + std::to_string(op.opcode)); - return false; - } - code_owner[op.opcode] = op.name; + op.op = static_cast(opno); if (name_owner.count(op.name)) { fail(err, "duplicate op name '" + op.name + "'"); return false; } - name_owner[op.name] = op.opcode; - std::string cls; - if (!req_string(*t, "op", "class", &cls, err)) return false; - if (cls == "instance") { - op.is_instance = true; - } else if (cls == "plain") { - op.is_instance = false; - } else { - fail(err, "op '" + op.name + "': bad class '" + cls + "'"); + name_owner[op.name] = true; + if (!is_known_prefix(op.prefix)) { + fail(err, "op '" + op.name + "': bad prefix '" + op.prefix + "'"); return false; } - if (!req_string(*t, "op", "format", &op.format, err)) return false; - if (!is_known_format(op.format)) { - fail(err, "op '" + op.name + "': bad format '" + op.format + "'"); + // 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; } - // 类别-格式互锁:INSTANCE ↔ CAL - const bool is_cal = op.format == "CAL"; - if (op.is_instance != is_cal) { - fail(err, "op '" + op.name + "': class-format mismatch (instance <-> CAL)"); + 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.format); + 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"); @@ -338,7 +436,7 @@ bool MachineConfig::load(const std::string& path, std::string* err) { } if (static_cast(op.params.size()) != want) { fail(err, "op '" + op.name + "': params count " + - std::to_string(op.params.size()) + " != format expects " + + std::to_string(op.params.size()) + " != fmt expects " + std::to_string(want)); return false; } @@ -346,13 +444,15 @@ bool MachineConfig::load(const std::string& path, std::string* err) { } } - // ---- fb ---- + // ---- 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 id_owner; + std::map name_owner; for (const auto& el : *arr) { const toml::table* t = el.as_table(); if (!t) { @@ -361,16 +461,23 @@ bool MachineConfig::load(const std::string& path, std::string* err) { } ConfigFb fb; if (!req_string(*t, "fb", "name", &fb.name, err)) return false; - int64_t code = 0; - if (!req_int(*t, "fb", "opcode", &code, err)) return false; - fb.opcode = static_cast(code); - // opcode 必须命中 instance 类 op 行 - const ConfigOp* op = find_op_by_code(fb.opcode); - if (op == nullptr || !op->is_instance) { - fail(err, "fb '" + fb.name + "': opcode " + std::to_string(code) + - " must match an instance op"); + 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(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'"); @@ -403,6 +510,20 @@ bool MachineConfig::load(const std::string& path, std::string* err) { } 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; @@ -438,13 +559,15 @@ const ConfigOp* MachineConfig::find_op(const std::string& name) const { } /** - * @brief 按 opcode 查指令行。 - * @param opcode 操作码(0..255) + * @brief 按 (prefix, op) 键查指令行。 + * @param prefix 形态前缀(如 "11"/"010001") + * @param op 分区内编号 * @return 命中指针;未找到返回 nullptr */ -const ConfigOp* MachineConfig::find_op_by_code(uint32_t opcode) const { +const ConfigOp* MachineConfig::find_op_by_key(const std::string& prefix, + uint32_t op) const { for (const ConfigOp& o : ops_) { - if (o.opcode == opcode) { + if (o.prefix == prefix && o.op == op) { return &o; } } @@ -452,18 +575,19 @@ const ConfigOp* MachineConfig::find_op_by_code(uint32_t opcode) const { } /** - * @brief 查询某 opcode 是否启用。 - * @param opcode 操作码 - * @return 指令存在且 enabled 为 true;未知 opcode 返回 false + * @brief 查询某 (prefix, op) 是否启用。 + * @param prefix 形态前缀 + * @param op 分区内编号 + * @return 指令存在且 enabled 为 true;未知返回 false */ -bool MachineConfig::op_enabled(uint32_t opcode) const { - const ConfigOp* op = find_op_by_code(opcode); - return op != nullptr && op->enabled; +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") + * @param name FB 名(如 "TON") * @return 命中指针;未找到返回 nullptr */ const ConfigFb* MachineConfig::find_fb(const std::string& name) const { @@ -475,4 +599,18 @@ const ConfigFb* MachineConfig::find_fb(const std::string& name) const { 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 diff --git a/compiler/src/Parser.cpp b/compiler/src/Parser.cpp index 12d326a..4000056 100644 --- a/compiler/src/Parser.cpp +++ b/compiler/src/Parser.cpp @@ -64,7 +64,7 @@ namespace { * 用户标识符(详见 Doc/compiler/语法.md)。 */ const char* const kForbidden[] = { - "var_in_out", "var_temp", "ref", "class", "any", + "var_in_out", "ref", "class", "any", "pointer", "interface", "method", }; @@ -73,6 +73,36 @@ namespace { * @param word 小写单词 * @return true 命中(var_in_out / ref / class / any / pointer / interface / method) */ + /** + * @brief 类型关键字 → 小写类型名(machine.toml type 表键)。 + * @param tk 类型 Tok(BOOL..DT) + * @return 小写名(如 "dint");非类型 Tok 返回空串 + */ + std::string tok_name(Tok tk) { + switch (tk) { + case Tok::BOOL: return "bool"; + case Tok::BYTE: return "byte"; + case Tok::WORD: return "word"; + case Tok::DWORD: return "dword"; + case Tok::LWORD: return "lword"; + case Tok::SINT: return "sint"; + case Tok::INT: return "int"; + case Tok::DINT: return "dint"; + case Tok::LINT: return "lint"; + case Tok::USINT: return "usint"; + case Tok::UINT: return "uint"; + case Tok::UDINT: return "udint"; + case Tok::ULINT: return "ulint"; + case Tok::TIME: return "time"; + case Tok::REAL: return "real"; + case Tok::LREAL: return "lreal"; + case Tok::DATE: return "date"; + case Tok::TOD: return "tod"; + case Tok::DT: return "dt"; + default: return ""; + } + } + bool is_forbidden(const std::string& word) { for (const char* f : kForbidden) { if (word == f) { @@ -276,8 +306,8 @@ namespace { } /** - * @brief 当前 token 是否为五种变量段关键字之一 - * @return true 为 VAR / VAR_INPUT / VAR_OUTPUT / VAR_GLOBAL / VAR_EXTERNAL + * @brief 当前 token 是否为六种变量段关键字之一 + * @return true 为 VAR / VAR_INPUT / VAR_OUTPUT / VAR_GLOBAL / VAR_EXTERNAL / VAR_TEMP */ bool is_var_section() const { switch (type()) { @@ -286,6 +316,7 @@ namespace { case Tok::VAR_OUTPUT: case Tok::VAR_GLOBAL: case Tok::VAR_EXTERNAL: + case Tok::VAR_TEMP: return true; default: return false; @@ -328,6 +359,7 @@ namespace { case Tok::VAR_OUTPUT: b->section = VarSection::Output; break; case Tok::VAR_GLOBAL: b->section = VarSection::Global; break; case Tok::VAR_EXTERNAL: b->section = VarSection::External; break; + case Tok::VAR_TEMP: b->section = VarSection::Temp; break; default: return fail("expected variable section"); } @@ -425,70 +457,49 @@ namespace { e->int_value = cur().int_value; advance(); return true; + case Tok::FLOAT_LIT: + e->kind = ExprKind::LitReal; + e->double_value = cur().double_value; + advance(); + return true; + case Tok::DATE_LIT: + e->kind = ExprKind::LitDate; + e->int_value = cur().int_value; + advance(); + return true; + case Tok::TOD_LIT: + e->kind = ExprKind::LitTod; + e->int_value = cur().int_value; + advance(); + return true; + case Tok::DT_LIT: + e->kind = ExprKind::LitDt; + e->int_value = cur().int_value; + advance(); + return true; default: return fail("expected literal initializer"); } } /** - * @brief 解析类型名 - * @details 内建标量 BOOL/INT/TIME、内建 FB TON/TOF/CTU、 - * 或标识符(用户 FB 类型,存在性由 12.6 链接层校验) + * @brief 解析类型名(V2) + * @details 19 种 IEC 类型关键字(type_name 小写字符串)、 + * 或标识符(FB 类型名——内建查 machine.toml、用户查符号表,Typecheck 判定) * @param t 输出 TypeRef * @return true 成功;false 语法错误(err 已写) */ bool parse_type(TypeRef* t) { - switch (type()) { - case Tok::BOOL: - t->kind = TypeKind::Bool; - advance(); - return true; - case Tok::INT: - t->kind = TypeKind::Int; - advance(); - return true; - case Tok::TIME: - t->kind = TypeKind::Time; - advance(); - return true; - case Tok::TON: - t->kind = TypeKind::FbBuiltin; - t->name = "ton"; - advance(); - return true; - case Tok::TOF: - t->kind = TypeKind::FbBuiltin; - t->name = "tof"; - advance(); - return true; - case Tok::TP: - t->kind = TypeKind::FbBuiltin; - t->name = "tp"; - advance(); - return true; - case Tok::CTU: - t->kind = TypeKind::FbBuiltin; - t->name = "ctu"; - advance(); - return true; - case Tok::CTD: - t->kind = TypeKind::FbBuiltin; - t->name = "ctd"; - advance(); - return true; - case Tok::CTUD: - t->kind = TypeKind::FbBuiltin; - t->name = "ctud"; - advance(); - return true; - case Tok::R_TRIG: - t->kind = TypeKind::FbBuiltin; - t->name = "r_trig"; - advance(); - return true; - case Tok::F_TRIG: - t->kind = TypeKind::FbBuiltin; - t->name = "f_trig"; + // 类型关键字(小写名 = machine.toml type 表键) + const Tok tk = type(); + switch (tk) { + case Tok::BOOL: case Tok::BYTE: case Tok::WORD: case Tok::DWORD: + case Tok::LWORD: case Tok::SINT: case Tok::INT: case Tok::DINT: + case Tok::LINT: case Tok::USINT: case Tok::UINT: case Tok::UDINT: + case Tok::ULINT: case Tok::TIME: case Tok::REAL: case Tok::LREAL: + case Tok::DATE: case Tok::TOD: case Tok::DT: + t->kind = TypeKind::Scalar; + t->name = tok_name(tk); advance(); return true; case Tok::IDENT: { @@ -496,7 +507,7 @@ namespace { if (is_forbidden(word)) { return fail("'" + word + "' is not supported"); } - t->kind = TypeKind::FbUser; + t->kind = TypeKind::FbUser; // 内建或用户 FB(Linker/Typecheck 查表判定) t->name = word; advance(); return true; @@ -910,6 +921,26 @@ namespace { e->int_value = cur().int_value; advance(); return true; + case Tok::FLOAT_LIT: + e->kind = ExprKind::LitReal; + e->double_value = cur().double_value; + advance(); + return true; + case Tok::DATE_LIT: + e->kind = ExprKind::LitDate; + e->int_value = cur().int_value; + advance(); + return true; + case Tok::TOD_LIT: + e->kind = ExprKind::LitTod; + e->int_value = cur().int_value; + advance(); + return true; + case Tok::DT_LIT: + e->kind = ExprKind::LitDt; + e->int_value = cur().int_value; + advance(); + return true; case Tok::TRUE: e->kind = ExprKind::LitBool; e->int_value = 1; diff --git a/compiler/src/Stb.cpp b/compiler/src/Stb.cpp index 75a85e2..917ea4a 100644 --- a/compiler/src/Stb.cpp +++ b/compiler/src/Stb.cpp @@ -243,32 +243,36 @@ StbView StbView::from(const uint8_t* buf, size_t len) { v.err_ = "image too short"; return v; } - if (get_le32(buf + 0) != kMagic) { + if (get_le32(buf + kOffMagic) != kMagic) { v.err_ = "bad magic"; return v; } - if (get_le32(buf + 4) != kVersion) { + if (get_le32(buf + kOffVersion) != kVersion) { v.err_ = "bad version"; return v; } - v.cycle_limit_ = get_le32(buf + 8); - v.dt_ms_ = get_le32(buf + 12); - v.project_hash_ = get_le64(buf + 16); - v.entry_fn_id_ = get_le32(buf + 24); - v.n_globals_ = get_le32(buf + 28); - v.n_consts_ = get_le32(buf + 44); - v.n_funcs_ = get_le32(buf + 48); - v.offset_code_ = get_le32(buf + 60); - v.offset_fb_ = get_le32(buf + 64); - v.offset_data_ = get_le32(buf + 68); + 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); - // 段校验(12.13:文件尾 SHA-256[32] 在数据段之后) - if (len < static_cast(v.offset_data_) + kSha256Size) { + // 段校验(V2:值段之后是 SHA-256 文件尾;元数据段 v1 为 0 = 空段) + if (len < static_cast(v.offset_values_) + kSha256Size) { v.err_ = "missing sha256 tail"; return v; } - const uint64_t offs[5] = {get_le32(buf + 52), get_le32(buf + 56), v.offset_code_, - v.offset_fb_, v.offset_data_}; + 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"; @@ -287,8 +291,22 @@ StbView StbView::from(const uint8_t* buf, size_t len) { v.err_ = "function table size mismatch"; return v; } - if ((v.offset_fb_ - v.offset_code_) % 4 != 0) { - v.err_ = "code segment not 4-byte aligned"; + if (offs[4] - offs[3] != static_cast(v.n_slots_) * kSlotRowSize) { + v.err_ = "slot table size mismatch"; + return v; + } + // 值段大小:meta 段为 0(空段)时值段到 SHA 尾;否则到 meta 起点 + if (v.offset_meta_ != 0) { + if (static_cast(v.offset_meta_) - offs[4] != v.values_size_) { + v.err_ = "values segment size mismatch"; + return v; + } + } else if (static_cast(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) { @@ -310,8 +328,7 @@ ConstEntry StbView::const_entry(size_t i) const { ConstEntry e; if (ok_ && i < n_consts_) { const uint8_t* p = buf_ + offs_of_const() + i * kConstEntrySize; - e.tag = get_le32(p); - e.value = get_le64(p + 4); + e.value = get_le64(p + kConstValueOff); } return e; } @@ -327,9 +344,9 @@ StbView::FuncRow StbView::func_row(size_t i) const { FuncRow r; if (ok_ && i < n_funcs_) { const uint8_t* p = buf_ + get_le32(buf_ + 56) + i * kFuncRowSize; - r.nregs = get_le32(p + 0); - r.code_offset = get_le32(p + 4); - r.code_len = get_le32(p + 8); + r.nregs = get_le32(p + kFuncNregsOff); + r.code_offset = get_le32(p + kFuncCodeOff); + r.code_len = get_le32(p + kFuncLenOff); } return r; } @@ -341,25 +358,29 @@ const uint8_t* StbView::code_bytes() const { /// 字节码段字节数(未 ok() 时返回 0)。 size_t StbView::code_len() const { - return ok_ ? offset_fb_ - offset_code_ : 0; + return ok_ ? offset_slots_ - offset_code_ : 0; } /// 数据段起点(未 ok() 时返回 nullptr)。 const uint8_t* StbView::data_bytes() const { - return ok_ ? buf_ + offset_data_ : nullptr; + return ok_ ? buf_ + offset_values_ : nullptr; } /// 数据段字节数(不含文件尾 SHA-256;未 ok() 时返回 0)。 size_t StbView::data_len() const { - return ok_ ? (len_ - kSha256Size) - offset_data_ : 0; + 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(buf_ + 72), kModelIdSize); + std::string s(reinterpret_cast(buf_ + kOffModelId), kModelIdSize); const size_t z = s.find('\0'); if (z != std::string::npos) { s.resize(z); @@ -371,7 +392,7 @@ std::string StbView::model_id() const { bool StbView::model_matches(const std::string& name, uint32_t version) const { char want[kModelIdSize]; fill_model_id(name, version, want); - return std::memcmp(buf_ + 72, want, kModelIdSize) == 0; + return std::memcmp(buf_ + kOffModelId, want, kModelIdSize) == 0; } /// 文件尾 32 字节 SHA-256 校验(对文件尾之前全部内容重算;未 ok() 时返回 false)。 diff --git a/compiler/src/Typecheck.cpp b/compiler/src/Typecheck.cpp index 41cba80..beae691 100644 --- a/compiler/src/Typecheck.cpp +++ b/compiler/src/Typecheck.cpp @@ -1,86 +1,130 @@ /** * @file Typecheck.cpp - * @brief 类型检查 + * @brief 类型检查(V2) * @author * @date 2026-08-21 * - * @details 设计说明(详见 Doc/compiler/类型检查.md): + * @details 设计说明(详见 Doc/compiler/类型检查.md 与阶段3-语言层V2.md): * - 在链接成功后运行:符号/字段/函数存在性已由 12.6 保证,本阶段只查类型 - * - 表达式求值类型三型:BOOL / INT / TIME;无隐式宽化,INT 与 TIME 不混用 - * - 语句规则:赋值类型相等、FUNCTION 禁写全局(含 EXTERNAL,用例 14)、 - * FB 命名实参匹配字段类型、IF/WHILE 条件必须 BOOL - * - 错误:稳定前缀 "type error" + 文件(Expr/Stmt 无行列信息,只带文件) + * - 19 种 IEC 标量类型 + Unresolved(字面量未定型);**禁隐式转换** + * (赋值/算术/比较严格同型) + * - 字面量机制:整数字面量 / 浮点字面量未定型(Unresolved),由消费点 + * (赋值目标/实参/条件/另一操作数)按上下文定型 + **值域检查**(冲突报错); + * TRUE/FALSE(BOOL)、T#(TIME)、D#/TOD#/DT#(日期)字面量类型固定 + * - BOOL 编译器强制 0/1;IF/WHILE 条件必须 BOOL + * - 错误:稳定前缀 "type error" + 文件 * * 函数清单: - * - tname TType → 文本名(BOOL/INT/TIME) - * - to_type(TypeKind) TypeKind → TType(FB 类型返回 false) - * - to_type(string) 符号 type_name 字符串 → TType - * - Checker::Checker (构造)存工程/源文件/链接结果/错误输出 - * - Checker::run 逐 POU 逐语句检查 - * - Checker::fail 组装 "type error: (file)" 返回 false - * - find_scope 按 POU 名取作用域 - * - find_sym 在作用域里按名查符号 - * - find_global 按名查全局符号 - * - find_pou 按名查 POU AST(函数返回类型用) - * - check_stmt 语句规则分发(赋值/FB 调用/IF/WHILE) - * - assign_target_type 解析赋值左值类型(局部/全局/函数名),FUNCTION 禁写全局 - * - check_expr 表达式定类型(递归) - * - check_project 对外入口:逐 POU 检查 + * - tname / tname_lower TType ↔ 文本名(19 + Unresolved) + * - to_type(string) 符号 type_name 字符串 → TType + * - Checker::Checker (构造)存工程/源文件/链接结果/机器配置/错误输出 + * - Checker::run 逐 POU 逐语句检查 + * - fail 组装 "type error: (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 #include #include #include +#include "compiler/TypeInfo.h" + namespace compiler { namespace { /** - * @brief TType → 文本名(错误报文用) + * @brief TType → 文本名(错误报文用,大写)。 * @param t 求值类型 - * @return "BOOL" / "INT" / "TIME" + * @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 TypeKind → TType - * @param k 类型种类 - * @param out 输出求值类型 - * @return true 标量(BOOL/INT/TIME);FB 类型返回 false + * @brief TType → 小写类型名(machine.toml type 表键)。 + * @param t 求值类型 + * @return 如 "dint";Unresolved/未知返回空串 */ - bool to_type(TypeKind k, TType* out) { - switch (k) { - case TypeKind::Bool: *out = TType::Bool; return true; - case TypeKind::Int: *out = TType::Int; return true; - case TypeKind::Time: *out = TType::Time; return true; - default: return false; + 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 - * @param name "bool" / "int" / "time"(小写) + * @brief 符号 type_name 字符串 → TType(19 标量)。 + * @param name 小写类型名("bool".."dt") * @param out 输出求值类型 - * @return true 标量;FB 类型名返回 false + * @return true 标量;未知(FB 类型名等)返回 false */ bool to_type(const std::string& name, TType* out) { - if (name == "bool") { *out = TType::Bool; return true; } - if (name == "int") { *out = TType::Int; return true; } - if (name == "time") { *out = TType::Time; return true; } + for (int i = 0; i < 19; ++i) { + const TType t = static_cast(i); + if (name == tname_lower(t)) { + *out = t; + return true; + } + } return false; } /** - * @brief 类型检查器 + * @brief 类型检查器(V2) * * @details 链接结果只读;不修改 AST 与符号表。 */ @@ -88,14 +132,15 @@ namespace { public: /** * @brief 构造检查器 - * @param proj 工程定义(未用,保留接口对称;io 类型映射留待 12.8) + * @param proj 工程定义(未用,保留接口对称) * @param units 全部源文件的 AST * @param link 链接结果(符号/布局已解析) + * @param cfg 机器定义(类型元数据/值域来源) * @param err 错误输出;可为 nullptr(静默) */ Checker(const Project& proj, const std::vector& units, - const LinkResult& link, std::string* err) - : proj_(proj), units_(units), link_(link), err_(err) {} + const LinkResult& link, const MachineConfig& cfg, std::string* err) + : proj_(proj), units_(units), link_(link), cfg_(cfg), err_(err) {} /** * @brief 逐 POU 逐语句做类型检查 @@ -121,7 +166,6 @@ namespace { private: /** * @brief 组装 "type error: (file)" 写入 err - * @details Expr/Stmt 不携带行列,只报文件;err_ 为 nullptr 时静默 * @param file 出错文件 * @param msg 错误描述(不含前缀) * @return 恒 false(便于 return fail(...) 一行退出) @@ -133,11 +177,6 @@ namespace { return false; } - /** - * @brief 按 POU 名取作用域 - * @param name POU 名(小写) - * @return 作用域指针;未找到返回 nullptr - */ const LinkResult::PouScope* find_scope(const std::string& name) const { for (const LinkResult::PouScope& sc : link_.scopes) { if (sc.name == name) { @@ -147,12 +186,6 @@ namespace { return nullptr; } - /** - * @brief 在作用域里按名查符号 - * @param sc 作用域 - * @param name 符号名(小写) - * @return 符号指针;未找到返回 nullptr - */ const Symbol* find_sym(const LinkResult::PouScope& sc, const std::string& name) const { for (const Symbol& s : sc.syms) { @@ -163,11 +196,6 @@ namespace { return nullptr; } - /** - * @brief 按名查全局符号 - * @param name 全局名(小写) - * @return 符号指针;未找到返回 nullptr - */ const Symbol* find_global(const std::string& name) const { const auto it = link_.global_index.find(name); if (it == link_.global_index.end()) { @@ -176,11 +204,6 @@ namespace { return &link_.globals[it->second]; } - /** - * @brief 按名查 POU AST(函数返回类型用) - * @param name POU 名(小写) - * @return POU 指针;未找到返回 nullptr - */ const POU* find_pou(const std::string& name) const { for (const SourceUnit& u : units_) { for (const POU& p : u.ast.pous) { @@ -192,6 +215,129 @@ namespace { 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(tm.width()); + if (tm.is_signed()) { + const int64_t minv = + (w == 8) ? INT64_MIN : -(static_cast(1) << (8 * w - 1)); + const int64_t maxv = + (w == 8) ? INT64_MAX : (static_cast(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(1) << (8 * w)) - 1); + if (static_cast(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 出错文件 @@ -212,9 +358,8 @@ namespace { if (!check_expr(file, sc, *st.value, &rhs)) { return false; } - if (lhs != rhs) { - return fail(file, "type mismatch in assignment to '" + st.target + - "' (" + tname(lhs) + " vs " + tname(rhs) + ")"); + if (!resolve_to(file, *st.value, rhs, lhs)) { + return false; } return true; } @@ -228,7 +373,7 @@ namespace { bool found = false; for (const FbField& f : it->second.fields) { if (f.name == a.name) { - found = to_type(f.type, &want); + found = to_type(f.type_name, &want); break; } } @@ -240,9 +385,8 @@ namespace { if (!check_expr(file, sc, *a.value, &got)) { return false; } - if (got != want) { - return fail(file, "FB input '" + a.name + "' expects " + - tname(want)); + if (!resolve_to(file, *a.value, got, want)) { + return false; } } return true; @@ -277,11 +421,7 @@ namespace { } /** - * @brief 条件表达式必须为 BOOL(IF / WHILE 共用) - * @param file 出错文件 - * @param sc 当前作用域 - * @param e 条件表达式 - * @return true 合法;false(err 已写) + * @brief 条件表达式必须为 BOOL(IF / WHILE 共用;未定型按 BOOL 定型)。 */ bool check_cond(const std::string& file, const LinkResult::PouScope& sc, const Expr& e) { @@ -289,30 +429,23 @@ namespace { if (!check_expr(file, sc, e, &t)) { return false; } - if (t != TType::Bool) { - return fail(file, "condition must be BOOL, got " + std::string(tname(t))); + if (!resolve_to(file, e, t, TType::Bool)) { + return fail(file, "condition must be BOOL"); } return true; } /** - * @brief 解析赋值左值类型 + * @brief 解析赋值左值类型。 * @details 普通左值:局部/输入/输出/外部/全局 → 符号类型; - * FUNCTION 内函数名 → 返回类型;**FUNCTION 内左值是全局/外部 → 拒绝** - * (用例 14:FUNCTION 禁止写全局,含经 VAR_EXTERNAL) - * @param file 出错文件 - * @param sc 当前作用域 - * @param pou 当前 POU - * @param target 左值标识符 - * @param out 输出左值类型 - * @return true 合法;false(err 已写) + * 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.kind, out) || + return to_type(pou.result_type.name, out) || fail(file, "function result must be scalar"); } @@ -337,12 +470,7 @@ namespace { } /** - * @brief 表达式定类型(递归) - * @param file 出错文件 - * @param sc 当前作用域 - * @param e 表达式 AST - * @param out 输出求值类型 - * @return true 合法;false(err 已写) + * @brief 表达式定类型(递归;V2 字面量未定型机制)。 */ bool check_expr(const std::string& file, const LinkResult::PouScope& sc, const Expr& e, TType* out) { @@ -351,11 +479,21 @@ namespace { *out = TType::Bool; return true; case ExprKind::LitInt: - *out = TType::Int; + 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) { @@ -379,7 +517,7 @@ namespace { } for (const FbField& f : it->second.fields) { if (f.name == e.field) { - return to_type(f.type, out) || + return to_type(f.type_name, out) || fail(file, "field '" + e.field + "' has no scalar type"); } } @@ -390,7 +528,7 @@ namespace { if (!check_expr(file, sc, *e.operand, &t)) { return false; } - if (t != TType::Bool) { + if (!resolve_to(file, *e.operand, t, TType::Bool)) { return fail(file, "NOT operand must be BOOL"); } *out = TType::Bool; @@ -402,11 +540,27 @@ namespace { if (!check_expr(file, sc, *e.lhs, &l) || !check_expr(file, sc, *e.rhs, &r)) { return false; } - if (l != TType::Bool || r != TType::Bool) { - const char* op = (e.kind == ExprKind::And) ? "AND" : "OR"; - return fail(file, std::string(op) + " operands must be BOOL"); + 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; } - *out = TType::Bool; + // 同型定型;允许 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: { @@ -414,7 +568,14 @@ namespace { if (!check_expr(file, sc, *e.lhs, &l) || !check_expr(file, sc, *e.rhs, &r)) { return false; } - if (l != r) { + // 比较同型:一侧未定型 → 按另一侧定型;两侧未定型 → 结果 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)) + ")"); @@ -426,14 +587,26 @@ namespace { 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; } - if (l != TType::Int || r != TType::Int) { - return fail(file, "arithmetic operands must be INT (TIME has no arithmetic)"); + // 同型定型:一侧未定型 → 按另一侧 + 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)) + ")"); } - *out = TType::Int; + if (!arith_allowed(file, t, is_add_sub)) { + return false; + } + *out = t; return true; } case ExprKind::Neg: { @@ -441,10 +614,14 @@ namespace { if (!check_expr(file, sc, *e.operand, &t)) { return false; } - if (t != TType::Int) { - return fail(file, "unary minus operand must be INT"); + 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 = TType::Int; + *out = t; return true; } case ExprKind::Call: { @@ -452,13 +629,31 @@ namespace { if (f == nullptr) { return fail(file, "undeclared function '" + e.name + "'"); } - for (const auto& a : e.args) { - TType at; - if (!check_expr(file, sc, *a, &at)) { - return false; + // 实参类型 = 函数 VAR_INPUT 声明序 + std::vector 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); + } } } - if (!to_type(f->result_type.kind, out)) { + 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; @@ -471,23 +666,24 @@ namespace { const Project& proj_; ///< 工程定义(本阶段未用) const std::vector& units_; ///< 全部源文件 AST const LinkResult& link_; ///< 链接结果(只读) + const MachineConfig& cfg_; ///< 机器定义(类型元数据/值域) std::string* err_; ///< 错误输出(可空) }; } // namespace /** - * @brief 对工程做类型检查(对外入口,链接成功后调用) - * @details 逐 POU 逐语句检查;规则见 Doc/compiler/类型检查.md - * @param proj 工程定义(本阶段未用,保留接口对称) + * @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& units, - const LinkResult& link, std::string* err) { - Checker c(proj, units, link, err); + const LinkResult& link, const MachineConfig& cfg, std::string* err) { + Checker c(proj, units, link, cfg, err); return c.run(); } diff --git a/compiler/src/main.cpp b/compiler/src/main.cpp index c3575de..41a874f 100644 --- a/compiler/src/main.cpp +++ b/compiler/src/main.cpp @@ -34,6 +34,7 @@ namespace { " -o .stb 编译并写出映像文件\n" " --machine 机器定义(machine.toml,编译路径必填)\n" " --disasm 反汇编一个已编译的 .stb 映像(需要 --machine)\n" + " --dump-map 编译后打印变量/槽映射(对照源码↔反汇编用)\n" " --help 打印本帮助\n"); } @@ -59,19 +60,41 @@ namespace { std::fprintf(stderr, "error: %s\n", v.error().c_str()); return 1; } - std::printf("image: %s (%zu bytes, %u functions, %u globals, entry fn %u)\n", - path, bytes.size(), v.n_funcs(), v.n_globals(), v.entry_fn_id()); - std::printf(" dt_ms=%u cycle_limit=%u hash=0x%016llx model=%s sha=%s\n", - v.dt_ms(), v.cycle_limit(), - static_cast(v.project_hash()), - v.model_id().c_str(), v.sha_ok() ? "ok" : "BAD"); + std::printf("image: %s (%zu bytes)\n", path, bytes.size()); + std::printf("header:\n"); + std::printf(" magic = 0x%08x # 魔数 \"STSC\"\n", + static_cast(compiler::kMagic)); + std::printf(" version = %u # 格式版本(V2)\n", + static_cast(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(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); - const char* tag = c.tag == 0 ? "BOOL" : c.tag == 1 ? "INT" : "TIME"; - std::printf(" [%u] %s %llu\n", i, tag, + std::printf(" [%u] %llu\n", i, static_cast(c.value)); } } @@ -82,12 +105,26 @@ namespace { std::printf(" fn %u: nregs=%u, offset=%u, len=%u\n", i, r.nregs, r.code_offset, r.code_len); const uint8_t* base = v.code_bytes() + r.code_offset; - for (uint32_t j = 0; j < r.code_len; ++j) { + uint32_t pc = 0; + while (pc < r.code_len) { char buf[64]; - compiler::disasm(cfg, reinterpret_cast(base)[j], buf, - sizeof buf); + 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 + j * 4, buf); + v.offset_code() + r.code_offset + pc, buf); + pc += static_cast(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(v.slots_bytes()[i * 8]) + | (static_cast(v.slots_bytes()[i * 8 + 1]) << 8) + | (static_cast(v.slots_bytes()[i * 8 + 2]) << 16) + | (static_cast(v.slots_bytes()[i * 8 + 3]) << 24); + std::printf(" [%u] addr=%u\n", i, addr); } } @@ -131,11 +168,16 @@ int main(int argc, char** argv) { std::string machine_path; std::string out_path; bool disasm_mode = false; - for (int i = 2; i + 1 < argc; ++i) { - if (std::strcmp(argv[i], "--machine") == 0) { + 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]; - } else if (std::strcmp(argv[i], "-o") == 0) { + ++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) { @@ -208,19 +250,36 @@ int main(int argc, char** argv) { units.push_back(std::move(u)); } compiler::LinkResult link; - if (!compiler::link_project(proj, units, &link, &err)) { + 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, &err)) { + if (!compiler::check_project(proj, units, link, cfg, &err)) { std::fprintf(stderr, "error: %s\n", err.c_str()); return 1; } std::vector image; - if (!compiler::codegen_project(proj, units, link, cfg, &image, &err)) { + 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; diff --git a/executor/src/main.cpp b/executor/src/main.cpp index 938969f..777ca92 100644 --- a/executor/src/main.cpp +++ b/executor/src/main.cpp @@ -5,7 +5,7 @@ * @date 2026-08-21 * * @details 12.10:加载 .stb + sidecar,按扫描周期执行。 - * - `BytecodeExecutor .stb [--cycles N] [--replay ] [--step]` + * - `BytecodeExecutor .stb [--cycles N] [--replay ] [--step] [--sidecar ]` * - 只链 vm + isa,不链 compiler;dt_ms / cycle_limit 从映像头取 * - I 采样 / Q 写回按 sidecar(var → 槽号 → channel/bit),不创造变量 * - --replay:读录制文本(每行空格分隔的 0/1,按 io.input 绑定顺序), @@ -28,11 +28,12 @@ namespace { /// 打印命令行用法。 void usage() { - std::printf("usage: BytecodeExecutor .stb [--cycles N] [--replay ] [--step]\n" - " 加载 .stb + .runtime.toml,按扫描周期执行\n" + std::printf("usage: BytecodeExecutor .stb [--cycles N] [--replay ] [--step] [--sidecar ]\n" + " 加载 .stb + sidecar(缺省 .runtime.toml),按扫描周期执行\n" " --cycles N 跑 N 个周期(缺省 10)\n" " --replay 读录制的 I 序列(每行按 io.input 顺序的 0/1)\n" " --step 单步:逐指令打印 pc/fn/反汇编/寄存器(跑 1 个周期)\n" + " --sidecar 显式指定 I/O 绑定文件(缺省同名推导)\n" " --help 打印本帮助\n"); } @@ -129,7 +130,7 @@ namespace { * @return 低字节非零 → 1,否则 0 */ int slot_bool(vm::Machine& m, uint32_t slot) { - return m.data()[static_cast(slot) * 8] ? 1 : 0; + return m.data()[m.slot_addr(slot)] ? 1 : 0; } /** @@ -139,7 +140,7 @@ namespace { * @param v 0/1(非零归一化为 1) */ void put_bool(vm::Machine& m, uint32_t slot, int v) { - m.data()[static_cast(slot) * 8] = v ? 1 : 0; + m.data()[m.slot_addr(slot)] = v ? 1 : 0; } /** @@ -234,10 +235,12 @@ int main(int argc, char** argv) { } const std::string stb_path = argv[1]; - // 参数:--cycles N / --replay / --step(缺省 cycles=10、无回放、非单步) + // 参数:--cycles N / --replay / --step / --sidecar + //(缺省 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(std::stoul(argv[i + 1])); @@ -245,6 +248,8 @@ int main(int argc, char** argv) { 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]; } } @@ -259,25 +264,24 @@ int main(int argc, char** argv) { } bytes.assign(std::istreambuf_iterator(in), std::istreambuf_iterator()); } - // 结构校验(Image::from)+ 型号/SHA-256 校验(Machine::create) - const vm::Image img = vm::Image::from(bytes); - if (!img.ok()) { - std::fprintf(stderr, "error: %s\n", img.error().c_str()); - return 1; - } + // 信任链(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 路径推导:.stb → .runtime.toml - std::string sidecar_path = stb_path; - const size_t dot = sidecar_path.find_last_of('.'); - if (dot != std::string::npos) { - sidecar_path = sidecar_path.substr(0, dot); + // sidecar 路径:--sidecar 显式指定,否则同名推导 .stb → .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"; } - sidecar_path += ".runtime.toml"; std::vector bindings; if (!parse_sidecar(sidecar_path, &bindings, &err)) { std::fprintf(stderr, "error: %s\n", err.c_str()); @@ -306,7 +310,7 @@ int main(int argc, char** argv) { uint32_t guard = 0; while (machine.fault() == vm::Fault::None && !machine.ended()) { char buf[64]; - isa::disasm(machine.cur_instr(), buf, sizeof buf); + 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()) { diff --git a/isa/CMakeLists.txt b/isa/CMakeLists.txt index 9df22ef..aa32c1f 100644 --- a/isa/CMakeLists.txt +++ b/isa/CMakeLists.txt @@ -7,6 +7,7 @@ project(ISA DESCRIPTION "指令集架构") add_library(isa STATIC - ./src/Encode.cpp) + ./src/Encode.cpp + ./src/Op.cpp) target_include_directories(isa PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) \ No newline at end of file diff --git a/isa/include/isa/Encode.h b/isa/include/isa/Encode.h index b4dfcfa..a14d29b 100644 --- a/isa/include/isa/Encode.h +++ b/isa/include/isa/Encode.h @@ -1,7 +1,7 @@ /** * @file Encode.h - * @brief 指令编解码与反汇编 - * @details 给 STCompiler --disasm 与测试往返使用;文本格式见 Doc/isa/指令与映像.md。 + * @brief 指令编解码与反汇编(V2) + * @details 给 VM/测试/STCompiler --disasm 使用;文本格式见 Doc/isa/指令与映像.md。 * @author * @date 2026-08-19 */ @@ -15,40 +15,46 @@ #include "isa/Op.h" namespace isa { + /** - * @brief 解码后的字段视图。 - * @details 各字段是 8 位原始值,含义按操作数形态(OpFormat)解释, - * a|b 可用 imm16 / off16 组合成 16 位视图。 + * @brief 解码后的字段视图(通用字段;具体形态语义由调用方按 OpFormat 解释)。 */ struct Decoded { - Op op; ///< 操作码 - uint8_t rd; ///< 目标寄存器 / 条件寄存器 - uint8_t a; ///< 源寄存器 / 立即数低 8 位 / 偏移低 8 位 - uint8_t b; ///< 源寄存器 / 立即数高 8 位 / 偏移高 8 位 + 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 编码:字段视图打包成一条 32 位指令字。 - * @param d 解码后的字段视图 - * @return 打包后的指令字;满足 encode(decode(w)) == w + * @brief 解码:指令字节 → 字段视图。 + * @param code 指令字节 + * @param len 指令长度(instr_len 定) + * @return 字段视图(含 prefix/op/func/寄存器与 32 位数值字段) */ - Instr encode(Decoded d); + Decoded decode(const uint8_t* code, size_t len); /** - * @brief 解码:指令字拆成字段视图。 - * @param w 32 位指令字 - * @return 各字段视图 + * @brief 编码:字段视图 → 指令字节。 + * @param d 字段视图 + * @param out 输出缓冲(≥16 字节) + * @return 指令长度 + * @details 满足 decode(encode(d)) 字段一致(encode 填 prefix/op/func/rd/rs1/rs2/imm32/off_hi)。 */ - Decoded decode(Instr w); + size_t encode(const Decoded& d, uint8_t* out); /** * @brief 反汇编一条指令为一行文本。 - * @param w 指令字 + * @param code 指令字节 + * @param len 指令长度 * @param out 输出缓冲 * @param cap 缓冲容量(含 '\0');cap==0 时直接返回、不写 out - * @details 按操作数形态表驱动输出(如 `RET`、`MOVE r1, r2`、`LOADK r0, 3`、 - * `ADD r1, r2, r3`、`JMP +4`、`JT r0, -1`、`CALL 1`、`CAL_TON 0`); - * 未知操作码输出 `??? 0x`。 + * @details 按形态表驱动输出(类型后缀:full/float/dst 类显示 .U8/.I16/.F32 等; + * CAL 显示 fb 名如 `CAL.TON s5`);未知 (prefix,op) 输出 `??? 0x`。 */ - void disasm(Instr w, char* out, size_t cap); + void disasm(const uint8_t* code, size_t len, char* out, size_t cap); } diff --git a/isa/include/isa/Instr.h b/isa/include/isa/Instr.h index f9e4a6d..7a52ecb 100644 --- a/isa/include/isa/Instr.h +++ b/isa/include/isa/Instr.h @@ -1,8 +1,13 @@ /** * @file Instr.h - * @brief 32-bit 指令字打包 / 拆字段 - * @details 指令字布局:`[ op:8 | rd:8 | a:8 | b:8 ]`,整体是一个小端 uint32_t。 - * 字段含义按操作码形态解释,见 Doc/isa/指令与映像.md。 + * @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 */ @@ -14,162 +19,297 @@ #include "isa/Op.h" namespace isa { - /// 指令字:`[ op:8 | rd:8 | a:8 | b:8 ]`,小端 uint32_t。 - typedef uint32_t Instr; /** - * @brief 打包:四个字段拼成一条指令字。 - * @param op 操作码(低 8 位) - * @param rd 目标寄存器 / 条件寄存器(第 8..15 位) - * @param a 源寄存器 / 立即数低 8 位 / 偏移低 8 位(第 16..23 位) - * @param b 源寄存器 / 立即数高 8 位 / 偏移高 8 位(第 24..31 位) - * @return 打包后的指令字 + * @brief 前缀定长:读字节 0 判指令长度。 + * @param code 指令字节(至少 1 字节) + * @return 4/8/16/32 */ - inline Instr pack(Op op, uint8_t rd, uint8_t a, uint8_t b) { - return static_cast(static_cast(op)) - | (static_cast(rd) << 8) - | (static_cast(a) << 16) - | (static_cast(b) << 24); + 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 取操作码。 - * @param w 指令字 - * @return 低 8 位解释为 Op - */ - inline Op op(Instr w) { - return static_cast(w & 0xFFu); + /// @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 取目标寄存器字段。 - * @param w 指令字 - * @return rd(第 8..15 位) - */ - inline uint8_t rd(Instr w) { - return static_cast((w >> 8) & 0xFFu); + /// @brief 取 func 位段(字节 1 高 4 位)。 + inline uint8_t func_of(const uint8_t* code) { + return static_cast((code[1] >> 4) & 0x0F); } - /** - * @brief 取 a 字段。 - * @param w 指令字 - * @return a(第 16..23 位) - */ - inline uint8_t a(Instr w) { - return static_cast((w >> 16) & 0xFFu); + /// @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(code[3]) + | (static_cast(code[4]) << 8) + | (static_cast(code[5]) << 16) + | (static_cast(code[6]) << 24); } - /** - * @brief 取 b 字段。 - * @param w 指令字 - * @return b(第 24..31 位) - */ - inline uint8_t b(Instr w) { - return static_cast((w >> 24) & 0xFFu); + /// @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(static_cast(code[2]) + | (static_cast(code[3]) << 8) + | (static_cast(code[4]) << 16) + | (static_cast(code[5]) << 24)); } - /** - * @brief 组合视图:a|b 拼成 16 位无符号数。 - * @param w 指令字 - * @return 小端拼出的 16 位值,用作 const_id / fn_id / slot 号 - */ - inline uint16_t imm16(Instr w) { - return static_cast(a(w) | (static_cast(b(w)) << 8)); + /// @brief 取 off32(55..24 = 字节 3..6,JC 形态,有符号)。 + inline int32_t off32_jc_of(const uint8_t* code) { + return static_cast(static_cast(code[3]) + | (static_cast(code[4]) << 8) + | (static_cast(code[5]) << 16) + | (static_cast(code[6]) << 24)); } - /** - * @brief 组合视图:a|b 为有符号相对偏移。 - * @param w 指令字 - * @return 偏移量,单位是指令条数(跳转目标 = 下一条指令 + off) - */ - inline int16_t off16(Instr w) { - return static_cast(imm16(w)); + /// @brief 取 fn_id32(47..16 = 字节 2..5)。 + inline uint32_t fn_id32_of(const uint8_t* code) { + return static_cast(code[2]) + | (static_cast(code[3]) << 8) + | (static_cast(code[4]) << 16) + | (static_cast(code[5]) << 24); } - /** - * @brief 按 RR 形态编码。 - * @param op 操作码(MOVE / NOT) - * @param rd 目标寄存器 - * @param rs 源寄存器(放进 a) - * @return 指令字 - */ - inline Instr enc_rr(Op op, uint8_t rd, uint8_t rs) { - return pack(op, rd, rs, 0); + /// @brief 取 fb_id16(31..16 = 字节 2..3)。 + inline uint16_t fb_id16_of(const uint8_t* code) { + return static_cast(code[2] | (static_cast(code[3]) << 8)); } - /** - * @brief 按 RRR 形态编码。 - * @param op 操作码(AND/OR/ADD/.../CMP_xx) - * @param rd 目标寄存器 - * @param ra 第一源寄存器 - * @param rb 第二源寄存器 - * @return 指令字 - */ - inline Instr enc_rrr(Op op, uint8_t rd, uint8_t ra, uint8_t rb) { - return pack(op, rd, ra, rb); + /// @brief 取 CAL 实例槽号(63..32 = 字节 4..7)。 + inline uint32_t cal_slot32_of(const uint8_t* code) { + return static_cast(code[4]) + | (static_cast(code[5]) << 8) + | (static_cast(code[6]) << 16) + | (static_cast(code[7]) << 24); } - /** - * @brief 按 IMM 形态编码。 - * @param op 操作码(LOADK) - * @param rd 目标寄存器 - * @param imm 16 位立即数(const_id),拆成 a|b 存放 - * @return 指令字 - */ - inline Instr enc_imm(Op op, uint8_t rd, uint16_t imm) { - return pack(op, rd, static_cast(imm & 0xFFu), - static_cast((imm >> 8) & 0xFFu)); + /// @brief 取 SLOT_OFF 的 off32(87..56 = 字节 7..10,有符号)。 + inline int32_t off32_hi_of(const uint8_t* code) { + return static_cast(static_cast(code[7]) + | (static_cast(code[8]) << 8) + | (static_cast(code[9]) << 16) + | (static_cast(code[10]) << 24)); } - /** - * @brief 按 JMP 形态编码。 - * @param off 有符号相对偏移(条数),拆成 a|b 存放 - * @return 指令字 - */ - inline Instr enc_jmp(int16_t off) { - return pack(Op::JMP, 0, - static_cast(static_cast(off) & 0xFFu), - static_cast((static_cast(off) >> 8) & 0xFFu)); + /// @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(code[3 + i]) << (8 * i); + } + return v; } - /** - * @brief 按 JC 形态编码(条件跳转)。 - * @param op 操作码(JT / JF) - * @param r 条件寄存器(放进 rd) - * @param off 有符号相对偏移(条数),放进 a|b - * @return 指令字 - */ - inline Instr enc_jc(Op op, uint8_t r, int16_t off) { - return pack(op, r, - static_cast(static_cast(off) & 0xFFu), - static_cast((static_cast(off) >> 8) & 0xFFu)); + // ---- 发射器(写字节,调用方提供 ≥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((prefix[1] == '1' ? 0xC0 : 0x80) | (op & 0x3F)); + } else if (prefix[0] == '0') { + if (prefix[1] == '1' && prefix[2] == '1') { + out[0] = static_cast(0x60 | (op & 0x1F)); // 011 + } else if (prefix[1] == '1') { + // 010xxx:形态位 3 位 + 子 op 2 位 + const uint8_t shape = static_cast( + (prefix[3] - '0') * 4 + (prefix[4] - '0') * 2 + (prefix[5] - '0')); + out[0] = static_cast(0x40 | (shape << 2) | (op & 0x03)); + } else if (prefix[2] == '1') { + out[0] = static_cast(0x20 | (op & 0x0F)); // 0010/0011 + } else { + out[0] = static_cast(0x10 | (op & 0x0F)); // 0001 + } + } else { + out[0] = 0; + } } - /** - * @brief 按 SLOT 形态编码。 - * @param op 操作码(LOAD_I / STORE_Q / LOAD_M / STORE_M / LOAD_GLOBAL / STORE_GLOBAL / CAL_*) - * @param rd 目标寄存器(CAL_* 时置 0) - * @param slot 槽号 / 实例号,放进 a|b - * @return 指令字 - */ - inline Instr enc_slot(Op op, uint8_t rd, uint16_t slot) { - return enc_imm(op, rd, slot); + /// @brief 写 func(字节 1 高 4 位)。 + inline void set_func(uint8_t* out, uint8_t func) { + out[1] = static_cast((out[1] & 0x0F) | ((func & 0x0F) << 4)); } - /** - * @brief 按 CALL 形态编码。 - * @param fn_id 函数表下标,放进 a|b - * @return 指令字 - */ - inline Instr enc_call(uint16_t fn_id) { - return enc_imm(Op::CALL, 0, fn_id); + 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(v & 0xFF); + out[4] = static_cast((v >> 8) & 0xFF); + out[5] = static_cast((v >> 16) & 0xFF); + out[6] = static_cast((v >> 24) & 0xFF); } - /** - * @brief 按 NONE 形态编码(RET)。 - * @return 指令字 - */ - inline Instr enc_ret() { - return pack(Op::RET, 0, 0, 0); + 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(v); + out[2] = static_cast(u & 0xFF); + out[3] = static_cast((u >> 8) & 0xFF); + out[4] = static_cast((u >> 16) & 0xFF); + out[5] = static_cast((u >> 24) & 0xFF); + } + + inline void set_off32_jc(uint8_t* out, int32_t v) { // JC:字节 3..6 + const uint32_t u = static_cast(v); + out[3] = static_cast(u & 0xFF); + out[4] = static_cast((u >> 8) & 0xFF); + out[5] = static_cast((u >> 16) & 0xFF); + out[6] = static_cast((u >> 24) & 0xFF); + } + + inline void set_fn_id32(uint8_t* out, uint32_t v) { set_off32(out, static_cast(v)); } + + inline void set_fb_id16(uint8_t* out, uint16_t v) { + out[2] = static_cast(v & 0xFF); + out[3] = static_cast((v >> 8) & 0xFF); + } + + inline void set_cal_slot32(uint8_t* out, uint32_t v) { + out[4] = static_cast(v & 0xFF); + out[5] = static_cast((v >> 8) & 0xFF); + out[6] = static_cast((v >> 16) & 0xFF); + out[7] = static_cast((v >> 24) & 0xFF); + } + + inline void set_off32_hi(uint8_t* out, int32_t v) { // SLOT_OFF:字节 7..10 + const uint32_t u = static_cast(v); + out[7] = static_cast(u & 0xFF); + out[8] = static_cast((u >> 8) & 0xFF); + out[9] = static_cast((u >> 16) & 0xFF); + out[10] = static_cast((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((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(Op::RET)); + set_func(out, 0); + return instr_len(out); } } diff --git a/isa/include/isa/Op.h b/isa/include/isa/Op.h index a3684ac..606f182 100644 --- a/isa/include/isa/Op.h +++ b/isa/include/isa/Op.h @@ -1,8 +1,13 @@ /** * @file Op.h - * @brief 操作码枚举、操作数形态与编译期指令表 - * @details 操作码数值即 .stb 指令编码。执行器(vm)只依赖本表; - * 编译器侧的登记见 Doc/isa/指令配置.md(compiler/machine.toml,与之强校验)。 + * @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 */ @@ -12,154 +17,124 @@ #include namespace isa { + + /// 指令最大长度(字节)。 + static constexpr int kMaxInstrLen = 16; + /** - * @brief 操作码枚举。 - * @details 数值即编码:冻结后不得改顺序、不得插值,增加指令只能追加到末尾, - * 并同步 Doc/isa/指令与映像.md、compiler/machine.toml 与测试。 + * @brief 操作码(分区内独立编号:MOVE=0、ADD=0、LOADK=0 等不冲突,靠前缀区分)。 + * @details 每个分区的 op 从 0 起;查表用 (prefix, op) 键。 */ enum class Op : uint8_t { - MOVE = 0, // MOVE rd, rs - LOADK = 1, // LOADK rd, const_id - NOT = 2, // NOT rd, rs - AND = 3, // AND rd, ra, rb - OR = 4, // OR rd, ra, rb - ADD = 5, // ADD rd, ra, rb - SUB = 6, // SUB rd, ra, rb - MUL = 7, // MUL rd, ra, rb - DIV = 8, // DIV rd, ra, rb - CMP_EQ = 9, // CMP_EQ rd, ra, rb - CMP_NE = 10, // CMP_NE rd, ra, rb - CMP_LT = 11, // CMP_LT rd, ra, rb - CMP_LE = 12, // CMP_LE rd, ra, rb - CMP_GT = 13, // CMP_GT rd, ra, rb - CMP_GE = 14, // CMP_GE rd, ra, rb - JMP = 15, // JMP offset - JT = 16, // JT r, offset - JF = 17, // JF r, offset - LOAD_I = 18, // LOAD_I rd, slot - STORE_Q = 19, // STORE_Q rs, slot - LOAD_M = 20, // LOAD_M rd, slot - STORE_M = 21, // STORE_M rs, slot - LOAD_GLOBAL = 22, // LOAD_GLOBAL rd, slot - STORE_GLOBAL = 23, // STORE_GLOBAL rs, slot - // 内置功能块(8 个连续,12.11 扩充:TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG) - CAL_TON = 24, // CAL_TON instance_slot - CAL_TOF = 25, // CAL_TOF instance_slot - CAL_TP = 26, // CAL_TP instance_slot - CAL_CTU = 27, // CAL_CTU instance_slot - CAL_CTD = 28, // CAL_CTD instance_slot - CAL_CTUD = 29, // CAL_CTUD instance_slot - CAL_R_TRIG = 30, // CAL_R_TRIG instance_slot - CAL_F_TRIG = 31, // CAL_F_TRIG instance_slot - CALL = 32, // CALL fn_id - RET = 33, // RET + // 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, }; - /// 操作码总数(= 最大 opcode + 1,kOpDefs 的下标上界)。 - static const int kOpCount = 34; - /** - * @brief 操作数形态(三层分类第二层,见 Doc/isa/指令配置.md)。 - * @details 决定操作数 a|b 的解释方式与反汇编文本格式。 + * @brief 操作数形态(前缀分区的语义标签)。 */ enum class OpFormat : uint8_t { - RR, // 两寄存器:rd rs(MOVE / NOT) - RRR, // 三寄存器:rd ra rb(逻辑 / 算术 / 比较) - IMM, // 常量:rd const_id(LOADK) - SLOT, // 槽:rd slot(LOAD_* / STORE_*) - JMP, // 偏移:off - JC, // 条件跳转:r off(JT / JF) - CALL, // 调用:fn_id - CAL, // 实例:instance(CAL_*) - NONE, // 无操作数(RET) + 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 操作对象大类(三层分类第一层)。 + * @brief func 语义类别(与 machine.toml op 表一致)。 */ - enum class OpClass : uint8_t { - Plain, // 无实例:寄存器 / 立即数 / 槽 / 分支 / 调用 - Instance, // 有实例:操作数据区实例块(内建 FB) + 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 编译期指令表条目。 - * @details 一条指令的名称、操作数形态与大类;下标与 Op 数值一一对应。 + * @brief 指令定义(与 machine.toml [[op]] 行对应)。 */ struct OpDef { - const char* name; ///< 助记符(与 .stb / machine.toml 一致) - OpFormat format; ///< 操作数形态 - OpClass cls; ///< 操作对象大类 + const char* name; ///< 助记符(大写,与配置一致) + const char* prefix; ///< 形态前缀("11"/"010001" 等) + Op op; ///< 分区内编号 + OpFormat format; ///< 操作数形态 + FuncMode func_mode; ///< func 语义类别 }; - /** - * @brief 编译期指令表:下标与 Op 数值一一对应(machine.toml 强校验的基准)。 - * @details 表与 Op 枚举同步维护;查询入口见 mnemonic / format / op_class。 - */ - static const OpDef kOpDefs[kOpCount] = { - {"MOVE", OpFormat::RR, OpClass::Plain}, // 0 - {"LOADK", OpFormat::IMM, OpClass::Plain}, // 1 - {"NOT", OpFormat::RR, OpClass::Plain}, // 2 - {"AND", OpFormat::RRR, OpClass::Plain}, // 3 - {"OR", OpFormat::RRR, OpClass::Plain}, // 4 - {"ADD", OpFormat::RRR, OpClass::Plain}, // 5 - {"SUB", OpFormat::RRR, OpClass::Plain}, // 6 - {"MUL", OpFormat::RRR, OpClass::Plain}, // 7 - {"DIV", OpFormat::RRR, OpClass::Plain}, // 8 - {"CMP_EQ", OpFormat::RRR, OpClass::Plain}, // 9 - {"CMP_NE", OpFormat::RRR, OpClass::Plain}, // 10 - {"CMP_LT", OpFormat::RRR, OpClass::Plain}, // 11 - {"CMP_LE", OpFormat::RRR, OpClass::Plain}, // 12 - {"CMP_GT", OpFormat::RRR, OpClass::Plain}, // 13 - {"CMP_GE", OpFormat::RRR, OpClass::Plain}, // 14 - {"JMP", OpFormat::JMP, OpClass::Plain}, // 15 - {"JT", OpFormat::JC, OpClass::Plain}, // 16 - {"JF", OpFormat::JC, OpClass::Plain}, // 17 - {"LOAD_I", OpFormat::SLOT, OpClass::Plain}, // 18 - {"STORE_Q", OpFormat::SLOT, OpClass::Plain}, // 19 - {"LOAD_M", OpFormat::SLOT, OpClass::Plain}, // 20 - {"STORE_M", OpFormat::SLOT, OpClass::Plain}, // 21 - {"LOAD_GLOBAL", OpFormat::SLOT, OpClass::Plain}, // 22 - {"STORE_GLOBAL", OpFormat::SLOT, OpClass::Plain},// 23 - {"CAL_TON", OpFormat::CAL, OpClass::Instance}, // 24 - {"CAL_TOF", OpFormat::CAL, OpClass::Instance}, // 25 - {"CAL_TP", OpFormat::CAL, OpClass::Instance}, // 26 - {"CAL_CTU", OpFormat::CAL, OpClass::Instance}, // 27 - {"CAL_CTD", OpFormat::CAL, OpClass::Instance}, // 28 - {"CAL_CTUD", OpFormat::CAL, OpClass::Instance}, // 29 - {"CAL_R_TRIG", OpFormat::CAL, OpClass::Instance},// 30 - {"CAL_F_TRIG", OpFormat::CAL, OpClass::Instance},// 31 - {"CALL", OpFormat::CALL, OpClass::Plain}, // 32 - {"RET", OpFormat::NONE, OpClass::Plain}, // 33 - }; + /// 指令表(72 条,与 machine.toml 强校验一致)。 + extern const OpDef kOps[]; + + /// 指令表条数(= 72)。 + static const int kOpCount = 72; /** - * @brief 查助记符。 - * @param op 操作码 - * @return 对应助记符;越界返回哨兵 "???" + * @brief 按 (prefix, op) 查指令。 + * @param prefix 形态前缀 + * @param op 分区内编号 + * @return 命中下标;未找到返回 -1 */ - inline const char* mnemonic(Op op) { - const int idx = static_cast(op); - return (idx >= 0 && idx < kOpCount) ? kOpDefs[idx].name : "???"; - } + int find_op(const char* prefix, uint8_t op); /** - * @brief 查操作数形态。 - * @param op 操作码 - * @return 对应 OpFormat;越界返回 OpFormat::NONE + * @brief 按助记符查指令。 + * @param name 助记符(如 "MOVE") + * @return 命中下标;未找到返回 -1 */ - inline OpFormat format(Op op) { - const int idx = static_cast(op); - return (idx >= 0 && idx < kOpCount) ? kOpDefs[idx].format : OpFormat::NONE; - } + int find_op_by_name(const char* name); - /** - * @brief 查操作对象大类。 - * @param op 操作码 - * @return 对应 OpClass;越界返回 OpClass::Plain - */ - inline OpClass op_class(Op op) { - const int idx = static_cast(op); - return (idx >= 0 && idx < kOpCount) ? kOpDefs[idx].cls : OpClass::Plain; - } + /// @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]; } diff --git a/isa/include/isa/Types.h b/isa/include/isa/Types.h index 104e783..6fde318 100644 --- a/isa/include/isa/Types.h +++ b/isa/include/isa/Types.h @@ -1,8 +1,12 @@ /** * @file Types.h - * @brief 定宽类型与饱和算术 + * @brief 定宽类型、func 表与饱和算术(V2) * @details 虚拟机字长与类型契约的唯一来源。compiler 与 vm 共用同一套规则, - * 详见 Doc/isa/指令与映像.md。 + * 详见 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 */ @@ -14,93 +18,101 @@ namespace isa { namespace types { - /** - * @brief 布尔值:8 位,只允许 0/1(0=FALSE,1=TRUE)。 - */ + /// 布尔值:8 位,只允许 0/1(0=FALSE,1=TRUE)。 using BOOL = uint8_t; - /** - * @brief IEC INT:16 位有符号整数,溢出按饱和规则处理。 - */ + /// IEC INT:16 位有符号整数,溢出按饱和规则处理。 using INT = int16_t; - /** - * @brief 时间:64 位有符号整数,单位毫秒。 - * @details 取 64 位的理由见 Doc/isa/指令与映像.md:差值可负、 - * 为 DATE_AND_TIME 与 64 位定时器预留。槽位按 8 字节对齐。 - */ + /// 时间:64 位有符号整数,单位毫秒。 using TIME = int64_t; /** - * @brief 槽 / 常量表类型标记。 - * @details 数值冻结并写入 .stb 常量表:0=BOOL、1=INT、2=TIME(见映像规范), - * 不得改顺序、不得插值。 + * @brief func 位段 → 数据宽度(字节)。 + * @details 下标 0..9 有效(0..7 = 1/2/4/8B × 有/无符号,8=F32、9=F64); + * 10..15 预留返回 0。 */ - enum TypeTag : uint8_t { - Bool = 0, - Int = 1, - Time = 2, + 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 预留 }; - // 饱和算术:夹到 INT 最小/最大,编译期与 VM 共用同一规则。 + /** + * @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,超出 INT 范围时夹到最小/最大值 - * @details 中间量用 int32 计算,避免 int16 溢出未定义行为。 + * @return a+b,超出 T 范围时夹到最小/最大值 + * @details 中间量用 int64 计算,避免窄类型溢出未定义行为。 */ - inline INT sat_add(INT a, INT b) { - const int32_t sum = static_cast(a) + static_cast(b); - return static_cast(sum < std::numeric_limits::min() ? std::numeric_limits::min() - : sum > std::numeric_limits::max() ? std::numeric_limits::max() - : sum); + template + T sat_add(T a, T b) { + const __int128 sum = static_cast<__int128>(a) + static_cast<__int128>(b); + return static_cast(sum < std::numeric_limits::min() + ? std::numeric_limits::min() + : sum > std::numeric_limits::max() + ? std::numeric_limits::max() + : sum); } /** * @brief 饱和减法。 * @param a 被减数 * @param b 减数 - * @return a-b,超出 INT 范围时夹到最小/最大值 - * @details 中间量用 int32 计算,避免 int16 溢出未定义行为。 + * @return a-b,超出 T 范围时夹到最小/最大值 */ - inline INT sat_sub(INT a, INT b) { - const int32_t diff = static_cast(a) - static_cast(b); - return static_cast(diff < std::numeric_limits::min() ? std::numeric_limits::min() - : diff > std::numeric_limits::max() ? std::numeric_limits::max() - : diff); + template + T sat_sub(T a, T b) { + const __int128 diff = static_cast<__int128>(a) - static_cast<__int128>(b); + return static_cast(diff < std::numeric_limits::min() + ? std::numeric_limits::min() + : diff > std::numeric_limits::max() + ? std::numeric_limits::max() + : diff); } /** * @brief 饱和乘法。 * @param a 乘数 * @param b 乘数 - * @return a*b,超出 INT 范围时夹到最小/最大值 - * @details 中间量用 int32 计算,避免 int16 溢出未定义行为。 + * @return a*b,超出 T 范围时夹到最小/最大值 */ - inline INT sat_mul(INT a, INT b) { - const int32_t prod = static_cast(a) * static_cast(b); - return static_cast(prod < std::numeric_limits::min() ? std::numeric_limits::min() - : prod > std::numeric_limits::max() ? std::numeric_limits::max() - : prod); + template + T sat_mul(T a, T b) { + const __int128 prod = static_cast<__int128>(a) * static_cast<__int128>(b); + return static_cast(prod < std::numeric_limits::min() + ? std::numeric_limits::min() + : prod > std::numeric_limits::max() + ? std::numeric_limits::max() + : prod); } /** - * @brief 饱和除法。 + * @brief 饱和除法(除法 A 定案)。 * @param a 被除数 * @param b 除数 - * @return a/b;b==0 视为除以 1 返回 a;INT_MIN / -1 溢出饱和到 INT_MAX - * @details b==0 返回 a 是为避免未定义行为;除零的语义由 compiler/VM 层报错。 + * @return a/b(截断除法,向零);b==0 → 返回 a(结果保持); + * MIN/-1 → 饱和夹到 MAX */ - inline INT sat_div(INT a, INT b) { + template + T sat_div(T a, T b) { if (b == 0) { - return a; + return a; // 除零返回被除数(Fail-safe) } - if (a == std::numeric_limits::min() && b == -1) { - return std::numeric_limits::max(); + if (a == std::numeric_limits::min() && b == T(-1)) { + return std::numeric_limits::max(); // MIN/-1 溢出饱和 } - return static_cast(a / b); + return static_cast(a / b); } } } diff --git a/isa/src/Encode.cpp b/isa/src/Encode.cpp index db74a9a..9cae8bd 100644 --- a/isa/src/Encode.cpp +++ b/isa/src/Encode.cpp @@ -1,6 +1,6 @@ /** * @file Encode.cpp - * @brief 指令编解码与反汇编实现 + * @brief 指令编解码与反汇编实现(V2) * @author * @date 2026-08-19 */ @@ -8,83 +8,190 @@ #include "isa/Encode.h" #include +#include +#include namespace isa { -/** - * @brief 编码:字段视图打包成一条 32 位指令字。 - * @param d 解码后的字段视图 - * @return 打包后的指令字;满足 encode(decode(w)) == w - */ -Instr encode(Decoded d) { - return pack(d.op, d.rd, d.a, d.b); -} +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 解码:指令字拆成字段视图。 - * @param w 32 位指令字 - * @return 各字段视图 + * @brief 解码:指令字节 → 字段视图。 */ -Decoded decode(Instr w) { +Decoded decode(const uint8_t* code, size_t len) { Decoded d; - d.op = op(w); - d.rd = rd(w); - d.a = a(w); - d.b = b(w); + (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(off32_of(code)); + } else if (f == OpFormat::JC) { + d.imm32 = static_cast(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 反汇编一条指令为一行文本。 - * @param w 指令字 - * @param out 输出缓冲 - * @param cap 缓冲容量(含 '\0');cap==0 时直接返回、不写 out - * @details 按操作数形态表驱动输出,文本与 Doc/isa/指令与映像.md 一致; - * 未知操作码输出 `??? 0x`。 + * @brief 编码:字段视图 → 指令字节。 */ -void disasm(Instr w, char* out, size_t cap) { +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(d.imm32)); + } else if (f == OpFormat::JC) { + set_off32_jc(out, static_cast(d.imm32)); + } else if (f == OpFormat::CALL) { + set_fn_id32(out, d.imm32); + } else if (f == OpFormat::CAL) { + set_fb_id16(out, static_cast(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 Op o = op(w); - const int idx = static_cast(o); - if (idx < 0 || idx >= kOpCount) { - snprintf(out, cap, "??? 0x%08x", static_cast(w)); + 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(code[i])); + used += 2; + } return; } - const char* m = mnemonic(o); - - // 表驱动:按操作数形态输出(文本与 Doc/isa/指令与映像.md 一致) - switch (format(o)) { + 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 r%u, r%u", m, - static_cast(rd(w)), static_cast(a(w))); + snprintf(out, cap, "%s%s r%u, r%u", od.name, suffix.c_str(), + static_cast(d.rd), static_cast(d.rs1)); break; case OpFormat::RRR: - snprintf(out, cap, "%s r%u, r%u, r%u", m, - static_cast(rd(w)), static_cast(a(w)), - static_cast(b(w))); + snprintf(out, cap, "%s%s r%u, r%u, r%u", od.name, suffix.c_str(), + static_cast(d.rd), static_cast(d.rs1), + static_cast(d.rs2)); break; case OpFormat::IMM: + snprintf(out, cap, "%s%s r%u, %u", od.name, suffix.c_str(), + static_cast(d.rd), static_cast(d.imm32)); + break; case OpFormat::SLOT: - snprintf(out, cap, "%s r%u, %u", m, - static_cast(rd(w)), static_cast(imm16(w))); + snprintf(out, cap, "%s%s r%u, s%u", od.name, suffix.c_str(), + static_cast(d.rd), static_cast(d.imm32)); break; case OpFormat::JMP: - snprintf(out, cap, "%s %+d", m, static_cast(off16(w))); + snprintf(out, cap, "%s %+d", od.name, static_cast(d.imm32)); break; case OpFormat::JC: - snprintf(out, cap, "%s r%u, %+d", m, - static_cast(rd(w)), static_cast(off16(w))); + snprintf(out, cap, "%s r%u, %+d", od.name, static_cast(d.rd), + static_cast(d.imm32)); break; case OpFormat::CALL: - case OpFormat::CAL: - snprintf(out, cap, "%s %u", m, static_cast(imm16(w))); + snprintf(out, cap, "%s %u", od.name, static_cast(d.imm32)); break; + case OpFormat::CAL: { + const uint16_t fb = static_cast(d.imm32 & 0xFFFF); + const char* fbname = (fb < 15) ? kFbNames[fb] : "?"; + snprintf(out, cap, "CAL.%s s%u", fbname, static_cast(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(d.rd), static_cast(d.imm32), + static_cast(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(d.rd), static_cast(d.rs1), + static_cast(d.off_hi)); + break; + case OpFormat::IMM64: case OpFormat::NONE: - snprintf(out, cap, "%s", m); + case OpFormat::RESERVED: + default: + snprintf(out, cap, "%s", od.name); break; } } diff --git a/isa/src/Op.cpp b/isa/src/Op.cpp new file mode 100644 index 0000000..d7d2919 --- /dev/null +++ b/isa/src/Op.cpp @@ -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(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", + }; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e05b6e8..beafb38 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -78,11 +78,11 @@ target_compile_definitions(typecheck_test PRIVATE add_test(NAME typecheck_types COMMAND typecheck_test) -# 寄存器码测试(12.8 切片 1):用例 01/02 出映像(REPO_ROOT 注入源目录绝对路径) +# 寄存器码测试(V2):用例产物结构断言(用 compiler::Codec,去 isa 依赖) add_executable(codegen_test ./src/codegen_test.cpp) -target_link_libraries(codegen_test PRIVATE compiler isa) +target_link_libraries(codegen_test PRIVATE compiler) target_compile_definitions(codegen_test PRIVATE REPO_ROOT="${CMAKE_SOURCE_DIR}") diff --git a/tests/cases/21_var_temp/EXPECTED.md b/tests/cases/21_var_temp/EXPECTED.md new file mode 100644 index 0000000..600057e --- /dev/null +++ b/tests/cases/21_var_temp/EXPECTED.md @@ -0,0 +1,8 @@ +# 期望:编译通过;VAR_TEMP 每周期重置,VAR 跨周期持久 + +- FB Foo:`tmp`(VAR_TEMP 初值 100)每周期重置 → `tmp := tmp + 1` 恒为 101; + `persist`(VAR)跨周期累积 1、2、3… + `out = tmp * 100 + persist` = 10101、10102、10103… +- MAIN:`t`(VAR_TEMP 初值 5)每周期重置 → `t := t + 1` 恒为 6 +- 全局 `a = f.out + t`:周期 1/2/3 = 10107 / 10108 / 10109(差 1 仅来自 persist, + tmp 与 t 若不重置则偏差 100 / 1) diff --git a/tests/cases/21_var_temp/globals.st b/tests/cases/21_var_temp/globals.st new file mode 100644 index 0000000..1b42e52 --- /dev/null +++ b/tests/cases/21_var_temp/globals.st @@ -0,0 +1,4 @@ +(* globals.st *) +VAR_GLOBAL + a : INT; +END_VAR diff --git a/tests/cases/21_var_temp/main.st b/tests/cases/21_var_temp/main.st new file mode 100644 index 0000000..3147e21 --- /dev/null +++ b/tests/cases/21_var_temp/main.st @@ -0,0 +1,12 @@ +(* main.st *) +PROGRAM MAIN +VAR + f : Foo; +END_VAR +VAR_TEMP + t : INT := 5; +END_VAR + t := t + 1; + f(in := TRUE); + a := f.out + t; +END_PROGRAM diff --git a/tests/cases/21_var_temp/motor.st b/tests/cases/21_var_temp/motor.st new file mode 100644 index 0000000..b9dc507 --- /dev/null +++ b/tests/cases/21_var_temp/motor.st @@ -0,0 +1,18 @@ +(* motor.st *) +FUNCTION_BLOCK Foo +VAR_INPUT + in : BOOL; +END_VAR +VAR_OUTPUT + out : INT; +END_VAR +VAR + persist : INT; +END_VAR +VAR_TEMP + tmp : INT := 100; +END_VAR + tmp := tmp + 1; + persist := persist + 1; + out := tmp * 100 + persist; +END_FUNCTION_BLOCK diff --git a/tests/cases/21_var_temp/project.toml b/tests/cases/21_var_temp/project.toml new file mode 100644 index 0000000..e15d249 --- /dev/null +++ b/tests/cases/21_var_temp/project.toml @@ -0,0 +1,11 @@ +[project] +name = "case21" +entry = "program MAIN" +cycle_limit = 1000 +dt_ms = 10 + +[files] +st = ["globals.st", "motor.st", "main.st"] + +[gvl] +file = "globals.st" diff --git a/tests/cases/22_multi_instance/EXPECTED.md b/tests/cases/22_multi_instance/EXPECTED.md new file mode 100644 index 0000000..f114d4a --- /dev/null +++ b/tests/cases/22_multi_instance/EXPECTED.md @@ -0,0 +1,6 @@ +# 期望:编译通过;FB 共享体多实例隔离 + +- Counter 编译为**单一共享函数**(fn 0),MAIN 两次 `CALL 0`(c1/c2 各一次,c2 两次调用) +- 调用点 `LOADK r1` 装载**不同实例地址**(值区偏移),字段访问 `LOAD_PTR/STORE_PTR r1+off` +- `count` 是实例字段:c1 每周期 +1、c2 每周期 +2,互不干扰 +- 全局 `a = c1.q * 100 + c2.q`:周期 1/2/3 = 102 / 204 / 306 diff --git a/tests/cases/22_multi_instance/globals.st b/tests/cases/22_multi_instance/globals.st new file mode 100644 index 0000000..1b42e52 --- /dev/null +++ b/tests/cases/22_multi_instance/globals.st @@ -0,0 +1,4 @@ +(* globals.st *) +VAR_GLOBAL + a : INT; +END_VAR diff --git a/tests/cases/22_multi_instance/main.st b/tests/cases/22_multi_instance/main.st new file mode 100644 index 0000000..c1b2c76 --- /dev/null +++ b/tests/cases/22_multi_instance/main.st @@ -0,0 +1,27 @@ +(* main.st *) +FUNCTION_BLOCK Counter +VAR_INPUT + inc : BOOL; +END_VAR +VAR_OUTPUT + q : INT; +END_VAR +VAR + count : INT; +END_VAR + IF inc THEN + count := count + 1; + END_IF + q := count; +END_FUNCTION_BLOCK + +PROGRAM MAIN +VAR + c1 : Counter; + c2 : Counter; +END_VAR + c1(inc := TRUE); + c2(inc := TRUE); + c2(inc := TRUE); + a := c1.q * 100 + c2.q; +END_PROGRAM diff --git a/tests/cases/22_multi_instance/project.toml b/tests/cases/22_multi_instance/project.toml new file mode 100644 index 0000000..b78828d --- /dev/null +++ b/tests/cases/22_multi_instance/project.toml @@ -0,0 +1,11 @@ +[project] +name = "case22" +entry = "program MAIN" +cycle_limit = 1000 +dt_ms = 10 + +[files] +st = ["globals.st", "main.st"] + +[gvl] +file = "globals.st" diff --git a/tests/src/cases_test.cpp b/tests/src/cases_test.cpp index 2500ba6..40ab3dc 100644 --- a/tests/src/cases_test.cpp +++ b/tests/src/cases_test.cpp @@ -65,6 +65,8 @@ namespace { {"18_tof_ctu", Outcome::Ok, ""}, {"19_recursive_call", Outcome::CompileError, "recursive call"}, {"20_line1", Outcome::Ok, ""}, + {"21_var_temp", Outcome::Ok, ""}, + {"22_multi_instance", Outcome::Ok, ""}, }; bool run_case(const char* dir, Outcome want, const char* keyword) { @@ -94,8 +96,14 @@ namespace { } units.push_back(std::move(u)); } + compiler::MachineConfig cfg; + std::string cerr; + if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)) { + std::printf("FAIL %s machine: %s\n", dir, cerr.c_str()); + return false; + } LinkResult link; - if (!link_project(p, units, &link, &err)) { + if (!link_project(p, units, cfg, &link, &err)) { if (want == Outcome::CompileError) { if (err.find(keyword) == std::string::npos) { std::printf("FAIL %s: want '%s', got '%s'\n", dir, keyword, err.c_str()); @@ -107,7 +115,7 @@ namespace { std::printf("FAIL %s link: %s\n", dir, err.c_str()); return false; } - if (!check_project(p, units, link, &err)) { + if (!check_project(p, units, link, cfg, &err)) { if (want == Outcome::CompileError) { if (err.find(keyword) == std::string::npos) { std::printf("FAIL %s: want '%s', got '%s'\n", dir, keyword, err.c_str()); @@ -119,11 +127,6 @@ namespace { std::printf("FAIL %s type: %s\n", dir, err.c_str()); return false; } - MachineConfig cfg; - if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)) { - std::printf("FAIL %s machine load\n", dir); - return false; - } std::vector img; if (!codegen_project(p, units, link, cfg, &img, &err)) { if (want == Outcome::CompileError) { diff --git a/tests/src/codegen_test.cpp b/tests/src/codegen_test.cpp index 2146d57..2e2c5d6 100644 --- a/tests/src/codegen_test.cpp +++ b/tests/src/codegen_test.cpp @@ -1,22 +1,33 @@ /** * @file codegen_test.cpp - * @brief 寄存器码测试(12.8 切片 1):用例 01/02 出映像 + * @brief 寄存器码测试(V2):编译器产物结构断言(用 compiler::Codec,去 isa 依赖) * @author * @date 2026-08-21 + * + * @details 覆盖(L5 范围): + * - 头字段(version=2/model=STATOR2/n_globals/n_consts/n_funcs/n_slots/values_size/ + * offset_meta/max_stack)与段偏移一致性 + * - 指令序列(Codec disasm 文本:LOADK/CAL.TON/CALL/STORE_OFF/LOAD_OFF/JF 等) + * - 槽表(逐条 addr)、值段尺寸与初值 + * - 常量表 8B 无 tag(位模式) + * - 函数表(nregs/code_offset/code_len 字节) + * - 校验链(sha_ok/model_matches) */ #include -#include +#include #include #include #include +#include "compiler/Codec.h" #include "compiler/Codegen.h" #include "compiler/Linker.h" -#include "compiler/Project.h" -#include "compiler/Typecheck.h" -#include "isa/Encode.h" +#include "compiler/MachineConfig.h" +#include "compiler/Parser.h" #include "compiler/Stb.h" +#include "compiler/Typecheck.h" +#include "compiler/TypeInfo.h" #ifndef REPO_ROOT #define REPO_ROOT "." @@ -33,702 +44,252 @@ static int g_checks = 0; ++g_checks; \ } while (0) -// 从用例目录走完整编译管线(解析 → 链接 → 类型检查 → 代码生成) -static bool load_cfg(compiler::MachineConfig* cfg) { - std::string err; - if (!cfg->load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)) { - std::printf("FAIL load machine.toml: %s\n", err.c_str()); - return false; - } - return true; -} +namespace { -static bool compile_case(const char* dir, std::vector* image, std::string* err) { - using namespace compiler; - compiler::MachineConfig cfg; - if (!load_cfg(&cfg)) return false; - const std::string toml = std::string(REPO_ROOT) + "/tests/cases/" + dir + "/project.toml"; - Project p; - if (!parse_project(toml, &p, err)) { - return false; - } - std::vector units; - for (const std::string& f : compile_files(p)) { - SourceUnit u; - if (!load_unit(p.base_dir + "/" + f, &u, err)) { + /** + * @brief 编译一个用例目录为映像字节。 + * @param dir 用例目录名(tests/cases/) + * @param img 输出映像字节 + * @return true 成功;false(任一阶段失败,已打印) + */ + bool build_case(const char* dir, std::vector* img) { + using namespace compiler; + const std::string toml = + std::string(REPO_ROOT) + "/tests/cases/" + dir + "/project.toml"; + Project p; + std::string err; + if (!parse_project(toml, &p, &err)) { + std::printf("FAIL %s parse: %s\n", dir, err.c_str()); return false; } - units.push_back(std::move(u)); - } - LinkResult link; - if (!link_project(p, units, &link, err)) { - return false; - } - if (!check_project(p, units, link, err)) { - return false; - } - return codegen_project(p, units, link, cfg, image, err); -} - -// 从临时工程(globals.st + main.st + 可含 io 绑定)走完整管线 -static bool compile_src(const char* name, const char* io_extra, const char* globals_st, - const char* main_st, std::vector* image, - std::string* err) { - using namespace compiler; - compiler::MachineConfig cfg; - if (!load_cfg(&cfg)) return false; - const std::string dir = std::string(REPO_ROOT) + "/build/cg_tmp_" + name; - std::filesystem::remove_all(dir); - std::filesystem::create_directories(dir); - const std::string toml_path = dir + "/project.toml"; - std::FILE* f = std::fopen(toml_path.c_str(), "w"); - std::fprintf(f, "[project]\nname = \"cg\"\nentry = \"program MAIN\"\n" - "cycle_limit = 1000\ndt_ms = 10\n" - "[files]\nst = [\"globals.st\", \"main.st\"]\n" - "[gvl]\nfile = \"globals.st\"\n%s", io_extra); - std::fclose(f); - f = std::fopen((dir + "/globals.st").c_str(), "w"); - std::fputs(globals_st, f); - std::fclose(f); - f = std::fopen((dir + "/main.st").c_str(), "w"); - std::fputs(main_st, f); - std::fclose(f); - - Project p; - if (!parse_project(toml_path, &p, err)) { - return false; - } - std::vector units; - for (const std::string& file : compile_files(p)) { - SourceUnit u; - if (!load_unit(p.base_dir + "/" + file, &u, err)) { + std::vector units; + for (const std::string& f : compile_files(p)) { + SourceUnit u; + if (!load_unit(p.base_dir + "/" + f, &u, &err)) { + std::printf("FAIL %s load: %s\n", dir, err.c_str()); + return false; + } + units.push_back(std::move(u)); + } + MachineConfig cfg; + if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)) { + std::printf("FAIL %s machine: %s\n", dir, err.c_str()); return false; } - units.push_back(std::move(u)); + LinkResult link; + if (!link_project(p, units, cfg, &link, &err)) { + std::printf("FAIL %s link: %s\n", dir, err.c_str()); + return false; + } + if (!check_project(p, units, link, cfg, &err)) { + std::printf("FAIL %s type: %s\n", dir, err.c_str()); + return false; + } + if (!codegen_project(p, units, link, cfg, img, &err)) { + std::printf("FAIL %s codegen: %s\n", dir, err.c_str()); + return false; + } + return true; } - LinkResult link; - if (!link_project(p, units, &link, err)) { + + /** + * @brief 按函数 fn_id 生成 disasm 文本行列表。 + */ + std::vector disasm_lines(const compiler::MachineConfig& cfg, + const compiler::StbView& v, + uint32_t fn_id) { + std::vector lines; + const compiler::StbView::FuncRow r = v.func_row(fn_id); + const uint8_t* base = v.code_bytes() + r.code_offset; + uint32_t pc = 0; + while (pc < r.code_len) { + char buf[128]; + const size_t ilen = compiler::instr_len(base + pc); + compiler::disasm(cfg, base + pc, ilen, buf, sizeof buf); + lines.push_back(buf); + pc += static_cast(ilen); + } + return lines; + } + + /// 行列表是否包含指定前缀的指令(如 "LOADK.U8")。 + bool has_line(const std::vector& lines, const std::string& prefix) { + for (const std::string& l : lines) { + if (l.compare(0, prefix.size(), prefix) == 0) { + return true; + } + } return false; } - if (!check_project(p, units, link, err)) { - return false; + + /// 全局机器配置(静态单例,加载仓库 machine.toml)。 + const compiler::MachineConfig& cfg_global() { + static const compiler::MachineConfig* kCfg = [] { + auto* cfg = new compiler::MachineConfig(); + std::string err; + if (!cfg->load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)) { + std::printf("FAIL machine config: %s\n", err.c_str()); + std::exit(1); + } + return cfg; + }(); + return *kCfg; } - return codegen_project(p, units, link, cfg, image, err); -} -// ---- 1. 用例 01:空 MAIN + RET ---- +} // namespace -static bool test_case01() { +// ---- 1. 01_empty_main:最小映像(RET)---- + +static bool test_empty_main() { + using namespace compiler; std::vector img; - std::string err; - CHECK(compile_case("01_empty_main", &img, &err)); - - const compiler::StbView v = compiler::StbView::from(img); + CHECK(build_case("01_empty_main", &img)); + const StbView v = StbView::from(img); CHECK(v.ok()); - CHECK(v.cycle_limit() == 1000); - CHECK(v.dt_ms() == 10); + // 校验链 + CHECK(v.model_matches("STATOR", 2)); + CHECK(v.sha_ok()); + // 头字段 CHECK(v.entry_fn_id() == 0); CHECK(v.n_funcs() == 1); - CHECK(v.n_consts() == 0); CHECK(v.n_globals() == 0); - CHECK(v.project_hash() != compiler::kFnvBasis); - - const compiler::StbView::FuncRow r = v.func_row(0); - CHECK(r.nregs == 8); // 帧基址(调用约定区) - CHECK(r.code_len == 1); - - char buf[32]; - isa::disasm(v.code_bytes()[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "RET") == 0); + CHECK(v.n_consts() == 0); + CHECK(v.n_slots() == 0); + CHECK(v.values_size() == 0); + CHECK(v.offset_meta() == 0); + CHECK(v.max_stack() == 12 + 8 * 8); // MAIN 帧:12 帧头 + nregs(8)×8 + // 函数表 + const StbView::FuncRow r = v.func_row(0); + CHECK(r.nregs == 8); + CHECK(r.code_len == 4); // 仅 RET(4B) + // 指令序列 + const std::vector lines = disasm_lines(cfg_global(), v, 0); + CHECK(lines.size() == 1); + CHECK(lines[0] == "RET"); return true; } -// ---- 2. 用例 02:BOOL 赋值(TRUE / FALSE → LOADK)---- +// ---- 2. 02_bool_assign:常量表 + LOADK ---- -static bool test_case02() { +static bool test_bool_assign() { + using namespace compiler; std::vector img; - std::string err; - CHECK(compile_case("02_bool_assign", &img, &err)); - - const compiler::StbView v = compiler::StbView::from(img); + CHECK(build_case("02_bool_assign", &img)); + const StbView v = StbView::from(img); CHECK(v.ok()); - CHECK(v.n_funcs() == 1); - CHECK(v.n_consts() == 2); // TRUE 与 FALSE - - const compiler::StbView::FuncRow r = v.func_row(0); - CHECK(r.nregs == 10); // a, b(r8 起) - CHECK(r.code_len == 3); - - // 从字节码段取指令(按函数行 code_offset 定位) - const uint8_t* base = v.code_bytes(); - char buf[64]; - isa::disasm(reinterpret_cast(base)[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r8, 0") == 0); // a := TRUE(常量 0) - isa::disasm(reinterpret_cast(base)[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r9, 1") == 0); // b := FALSE(常量 1) - isa::disasm(reinterpret_cast(base)[2], buf, sizeof buf); - CHECK(std::strcmp(buf, "RET") == 0); - - // 常量表:0 = BOOL 1,1 = BOOL 0 - const compiler::ConstEntry c0 = v.const_entry(0); - CHECK(c0.tag == 0 && c0.value == 1); - const compiler::ConstEntry c1 = v.const_entry(1); - CHECK(c1.tag == 0 && c1.value == 0); + CHECK(v.n_consts() == 2); // 0 与 1 + const ConstEntry c0 = v.const_entry(0); + const ConstEntry c1 = v.const_entry(1); + CHECK((c0.value == 0 && c1.value == 1) || (c0.value == 1 && c1.value == 0)); + const std::vector lines = disasm_lines(cfg_global(), v, 0); + CHECK(has_line(lines, "LOADK.U8")); + CHECK(has_line(lines, "RET")); return true; } -// ---- 3. 用例 09:GVL + VAR_EXTERNAL(全局读取 LOAD_GLOBAL + 初值数据段)---- +// ---- 3. 17_ton:内建 FB(CAL.TON + 字段访问)---- -static bool test_case09() { +static bool test_ton() { + using namespace compiler; std::vector img; - std::string err; - CHECK(compile_case("09_gvl_external", &img, &err)); - - const compiler::StbView v = compiler::StbView::from(img); + CHECK(build_case("17_ton", &img)); + const StbView v = StbView::from(img); CHECK(v.ok()); - CHECK(v.n_globals() == 1); - CHECK(v.data_len() == 8); // 8 字节定宽槽(不含 SHA 尾) - CHECK(v.data_bytes()[0] == 5); // G1 初值 5(小端低位) - CHECK(v.data_bytes()[1] == 0); - - const compiler::StbView::FuncRow r = v.func_row(0); - CHECK(r.nregs == 9); // r8 起:仅局部 x - CHECK(r.code_len == 2); - - char buf[64]; - const uint8_t* base = v.code_bytes(); - isa::disasm(reinterpret_cast(base)[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 0") == 0); // x := G1(槽 0 偏移 0) - isa::disasm(reinterpret_cast(base)[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "RET") == 0); + CHECK(v.n_slots() == 1); // 1 个 TON 实例 + CHECK(v.values_size() == 32); // TON 实例 32B(in@0/pt@8/q@16/et@24) + CHECK(v.n_consts() == 2); // in 初值 0、pt 初值 30(T#30ms) + const std::vector lines = disasm_lines(cfg_global(), v, 0); + CHECK(has_line(lines, "STORE_OFF.U8")); + CHECK(has_line(lines, "STORE_OFF.I64")); // pt=TIME → I64 + CHECK(has_line(lines, "CAL.TON")); + CHECK(has_line(lines, "LOAD_OFF.U8")); + // 槽表:1 条 addr=0 + CHECK(v.slots_bytes()[0] == 0 && v.slots_bytes()[1] == 0); return true; } -// ---- 4. io 绑定:LOAD_I / STORE_Q ---- +// ---- 4. 13_function_call:CALL + max_stack ---- -static bool test_io_ops() { +static bool test_function_call() { + using namespace compiler; std::vector img; - std::string err; - const char* io_extra = - "[[io.input]]\nvar = \"I0_0\"\nchannel = 0\nbit = 0\n" - "[[io.output]]\nvar = \"Q0_0\"\nchannel = 0\nbit = 0\n"; - const char* globals_st = - "VAR_GLOBAL\n I0_0 : BOOL;\n Q0_0 : BOOL;\nEND_VAR\n"; - const char* main_st = - "PROGRAM MAIN\nVAR\n q : BOOL;\nEND_VAR\n" - " q := I0_0;\n" - " Q0_0 := q;\n" - "END_PROGRAM\n"; - CHECK(compile_src("io", io_extra, globals_st, main_st, &img, &err)); - - const compiler::StbView v = compiler::StbView::from(img); + CHECK(build_case("13_function_call", &img)); + const StbView v = StbView::from(img); CHECK(v.ok()); - CHECK(v.n_globals() == 2); - - const compiler::StbView::FuncRow r = v.func_row(0); - CHECK(r.nregs == 10); // r8 起:q + 1 临时 - - char buf[64]; - const uint8_t* base = v.code_bytes(); - isa::disasm(reinterpret_cast(base)[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOAD_I r8, 0") == 0); // q := I0_0(io.input → LOAD_I) - isa::disasm(reinterpret_cast(base)[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r9, r8") == 0); // 临时 ← q - isa::disasm(reinterpret_cast(base)[2], buf, sizeof buf); - CHECK(std::strcmp(buf, "STORE_Q r9, 1") == 0); // Q0_0 := 临时(io.output → STORE_Q) - isa::disasm(reinterpret_cast(base)[3], buf, sizeof buf); - CHECK(std::strcmp(buf, "RET") == 0); - return true; -} - -// ---- 5. 混合类型布局:BOOL@0 INT@2 TIME@8,初值入数据段 ---- - -static bool test_layout() { - std::vector img; - std::string err; - const char* globals_st = - "VAR_GLOBAL\n" - " B : BOOL;\n" - " I : INT := 300;\n" - " T : TIME := T#10ms;\n" - "END_VAR\n"; - const char* main_st = - "PROGRAM MAIN\nVAR\n x : INT;\nEND_VAR\n" - " x := I;\n" - "END_PROGRAM\n"; - CHECK(compile_src("layout", "", globals_st, main_st, &img, &err)); - - const compiler::StbView v = compiler::StbView::from(img); - CHECK(v.ok()); - CHECK(v.n_globals() == 3); - CHECK(v.data_len() == 24); // 3 槽 × 8 字节定宽 - CHECK(v.data_bytes()[0] == 0); // B 无初值 - CHECK(v.data_bytes()[8] == 0x2C && v.data_bytes()[9] == 0x01); // I = 300(槽 1) - CHECK(v.data_bytes()[16] == 10); // T = 10ms(槽 2) - - char buf[64]; - isa::disasm(reinterpret_cast(v.code_bytes())[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 1") == 0); // x := I(槽 1) - return true; -} - -// ---- 6. 写 io.input → codegen error ---- - -static bool test_write_input() { - std::vector img; - std::string err; - const char* io_extra = - "[[io.input]]\nvar = \"I0_0\"\nchannel = 0\nbit = 0\n"; - const char* globals_st = - "VAR_GLOBAL\n I0_0 : BOOL;\nEND_VAR\n"; - const char* main_st = - "PROGRAM MAIN\n I0_0 := TRUE;\nEND_PROGRAM\n"; - if (compile_src("winput", io_extra, globals_st, main_st, &img, &err)) { - std::printf("FAIL write_input: codegen passed\n"); - return false; - } - CHECK(err.find("codegen error") == 0); - CHECK(err.find("cannot write to input") != std::string::npos); - return true; -} - -// ---- 7. 用例 03:短路 AND / OR 必须编跳转 ---- - -static bool test_case03() { - std::vector img; - std::string err; - CHECK(compile_case("03_short_circuit", &img, &err)); - - const compiler::StbView v = compiler::StbView::from(img); - CHECK(v.ok()); - const compiler::StbView::FuncRow r = v.func_row(0); - CHECK(r.nregs == 12); // r8 起:a b x + 1 临时 - CHECK(r.code_len == 9); - - char buf[64]; - const uint8_t* base = v.code_bytes(); - const uint32_t* ins = reinterpret_cast(base); - // x := a AND b:MOVE r2,r0 / JF r2,+2 / MOVE r3,r1 / MOVE r2,r3 - isa::disasm(ins[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r10, r8") == 0); - isa::disasm(ins[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "JF r10, +2") == 0); - isa::disasm(ins[2], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r11, r9") == 0); - isa::disasm(ins[3], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r10, r11") == 0); - // x := a OR b:MOVE r2,r0 / JT r2,+2 / MOVE r3,r1 / MOVE r2,r3 - isa::disasm(ins[4], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r10, r8") == 0); - isa::disasm(ins[5], buf, sizeof buf); - CHECK(std::strcmp(buf, "JT r10, +2") == 0); - isa::disasm(ins[6], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r11, r9") == 0); - isa::disasm(ins[7], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r10, r11") == 0); - isa::disasm(ins[8], buf, sizeof buf); - CHECK(std::strcmp(buf, "RET") == 0); - return true; -} - -// ---- 8. NOT ---- - -static bool test_not() { - std::vector img; - std::string err; - const char* main_st = - "PROGRAM MAIN\nVAR\n a, x : BOOL;\nEND_VAR\n" - " x := NOT a;\n" - "END_PROGRAM\n"; - CHECK(compile_src("not", "", "", main_st, &img, &err)); - - const compiler::StbView v = compiler::StbView::from(img); - CHECK(v.ok()); - const compiler::StbView::FuncRow r = v.func_row(0); - CHECK(r.nregs == 11); // r8 起:a x + 1 临时 - CHECK(r.code_len == 3); - - char buf[64]; - const uint8_t* base = v.code_bytes(); - const uint32_t* ins = reinterpret_cast(base); - isa::disasm(ins[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r10, r8") == 0); // 临时 ← a - isa::disasm(ins[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "NOT r9, r10") == 0); // x := NOT 临时 - isa::disasm(ins[2], buf, sizeof buf); - CHECK(std::strcmp(buf, "RET") == 0); - return true; -} - -// ---- 9. 用例 04:IF / ELSIF / ELSE(CMP + JF + JMP 回填)---- - -static bool test_case04() { - std::vector img; - std::string err; - CHECK(compile_case("04_if_elsif_else", &img, &err)); - - const compiler::StbView v = compiler::StbView::from(img); - CHECK(v.ok()); - const compiler::StbView::FuncRow r = v.func_row(0); - CHECK(r.nregs == 13); // r8 起:sel out + 3 临时 - CHECK(r.code_len == 14); - - char buf[64]; - const uint8_t* base = v.code_bytes(); - const uint32_t* ins = reinterpret_cast(base); - // IF sel = 0 THEN:sel→r3、0→r4、CMP_EQ r2(常量序:0、10、1、20、30) - isa::disasm(ins[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r11, r8") == 0); - isa::disasm(ins[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r12, 0") == 0); - isa::disasm(ins[2], buf, sizeof buf); - CHECK(std::strcmp(buf, "CMP_EQ r10, r11, r12") == 0); - isa::disasm(ins[3], buf, sizeof buf); - CHECK(std::strcmp(buf, "JF r10, +2") == 0); // 假 → elsif([6]) - isa::disasm(ins[4], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r9, 1") == 0); // out := 10(常量 1) - isa::disasm(ins[5], buf, sizeof buf); - CHECK(std::strcmp(buf, "JMP +7") == 0); // → end([13]) - // ELSIF sel = 1 - isa::disasm(ins[6], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r11, r8") == 0); - isa::disasm(ins[7], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r12, 2") == 0); - isa::disasm(ins[8], buf, sizeof buf); - CHECK(std::strcmp(buf, "CMP_EQ r10, r11, r12") == 0); - isa::disasm(ins[9], buf, sizeof buf); - CHECK(std::strcmp(buf, "JF r10, +2") == 0); // 假 → else([12]) - isa::disasm(ins[10], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r9, 3") == 0); - isa::disasm(ins[11], buf, sizeof buf); - CHECK(std::strcmp(buf, "JMP +1") == 0); // → end([13]) - // ELSE - isa::disasm(ins[12], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r9, 4") == 0); - isa::disasm(ins[13], buf, sizeof buf); - CHECK(std::strcmp(buf, "RET") == 0); - return true; -} - -// ---- 10. 用例 05:WHILE 回环(JF 出口 + JMP 回填)---- - -static bool test_case05() { - std::vector img; - std::string err; - CHECK(compile_case("05_while_normal", &img, &err)); - - const compiler::StbView v = compiler::StbView::from(img); - CHECK(v.ok()); - const compiler::StbView::FuncRow r = v.func_row(0); - CHECK(r.nregs == 12); // r8 起:n + 3 临时(条件结果 r9 + 两操作数) - CHECK(r.code_len == 10); - - char buf[64]; - const uint8_t* base = v.code_bytes(); - const uint32_t* ins = reinterpret_cast(base); - isa::disasm(ins[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r8, 0") == 0); // n := 0 - // L_loop [1]:n < 10 → r9 - isa::disasm(ins[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r10, r8") == 0); - isa::disasm(ins[2], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r11, 1") == 0); // 常量 1 = 10 - isa::disasm(ins[3], buf, sizeof buf); - CHECK(std::strcmp(buf, "CMP_LT r9, r10, r11") == 0); - isa::disasm(ins[4], buf, sizeof buf); - CHECK(std::strcmp(buf, "JF r9, +4") == 0); // 假 → RET([9]) - // n := n + 1(体语句临时复用 r9/r10) - isa::disasm(ins[5], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r9, r8") == 0); - isa::disasm(ins[6], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r10, 2") == 0); // 常量 2 = 1 - isa::disasm(ins[7], buf, sizeof buf); - CHECK(std::strcmp(buf, "ADD r8, r9, r10") == 0); - isa::disasm(ins[8], buf, sizeof buf); - CHECK(std::strcmp(buf, "JMP -8") == 0); // 回 L_loop([1]) - isa::disasm(ins[9], buf, sizeof buf); - CHECK(std::strcmp(buf, "RET") == 0); - return true; -} - -// ---- 11. 用例 07:四则 + 比较 ---- - -static bool test_case07() { - std::vector img; - std::string err; - CHECK(compile_case("07_int_arith", &img, &err)); - - const compiler::StbView v = compiler::StbView::from(img); - CHECK(v.ok()); - const compiler::StbView::FuncRow r = v.func_row(0); - CHECK(r.nregs == 14); // r8 起:a b c eq + 2 临时 - - char buf[64]; - const uint8_t* base = v.code_bytes(); - const uint32_t* ins = reinterpret_cast(base); - bool saw_mul = false, saw_div = false, saw_sub = false, saw_gt = false; - for (uint32_t i = 0; i < r.code_len; ++i) { - isa::disasm(ins[i], buf, sizeof buf); - if (std::strstr(buf, "MUL r8, ")) saw_mul = true; - if (std::strstr(buf, "DIV r9, ")) saw_div = true; - if (std::strstr(buf, "SUB r10, ")) saw_sub = true; - if (std::strstr(buf, "CMP_GT r11, ")) saw_gt = true; - } - CHECK(saw_mul && saw_div && saw_sub && saw_gt); - return true; -} - -// ---- 12. 用例 08:TIME 字面量进常量表 ---- - -static bool test_case08() { - std::vector img; - std::string err; - CHECK(compile_case("08_time_literal", &img, &err)); - - const compiler::StbView v = compiler::StbView::from(img); - CHECK(v.ok()); - CHECK(v.n_consts() == 2); - const compiler::ConstEntry c0 = v.const_entry(0); - CHECK(c0.tag == 2 && c0.value == 10); // T#10ms - const compiler::ConstEntry c1 = v.const_entry(1); - CHECK(c1.tag == 2 && c1.value == 1250); // T#1s250ms - - const compiler::StbView::FuncRow r = v.func_row(0); - CHECK(r.nregs == 10); // r8 起 - char buf[64]; - const uint32_t* ins = reinterpret_cast(v.code_bytes()); - isa::disasm(ins[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r8, 0") == 0); - isa::disasm(ins[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r9, 1") == 0); - return true; -} - -// ---- 13. 用例 13:FUNCTION + CALL(调用约定 r0 结果 / r1.. 参数)---- - -static bool test_case13() { - std::vector img; - std::string err; - CHECK(compile_case("13_function_call", &img, &err)); - - const compiler::StbView v = compiler::StbView::from(img); - CHECK(v.ok()); - CHECK(v.n_funcs() == 2); - CHECK(v.entry_fn_id() == 1); // MAIN 是第二个 POU - - char buf[64]; - const uint8_t* base = v.code_bytes(); - // Add(fn_id 0):结果 r0、输入 r1(a) r2(b)、临时 r8/r9 - const compiler::StbView::FuncRow fa = v.func_row(0); - CHECK(fa.nregs == 10); - CHECK(fa.code_len == 4); - const uint32_t* ia = reinterpret_cast(base); - isa::disasm(ia[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r8, r1") == 0); // t ← a - isa::disasm(ia[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r9, r2") == 0); // t ← b - isa::disasm(ia[2], buf, sizeof buf); - CHECK(std::strcmp(buf, "ADD r0, r8, r9") == 0); // 结果 r0 - isa::disasm(ia[3], buf, sizeof buf); - CHECK(std::strcmp(buf, "RET") == 0); - - // MAIN(fn_id 1):x 在 r8,实参临时 r9/r10 - const compiler::StbView::FuncRow fm = v.func_row(1); - CHECK(fm.nregs == 11); - CHECK(fm.code_len == 7); - const uint32_t* im = reinterpret_cast(base) + fm.code_offset / 4; - isa::disasm(im[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r9, 0") == 0); // 3 - isa::disasm(im[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r10, 1") == 0); // 4 - isa::disasm(im[2], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r1, r9") == 0); // 参数 1 ← 3 - isa::disasm(im[3], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r2, r10") == 0); // 参数 2 ← 4 - isa::disasm(im[4], buf, sizeof buf); - CHECK(std::strcmp(buf, "CALL 0") == 0); - isa::disasm(im[5], buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r8, r0") == 0); // x ← 结果 - isa::disasm(im[6], buf, sizeof buf); - CHECK(std::strcmp(buf, "RET") == 0); - return true; -} - -// ---- 14. 用例 15:用户 FB 内联展开 ---- - -static bool test_case15() { - std::vector img; - std::string err; - CHECK(compile_case("15_fb_instance", &img, &err)); - - const compiler::StbView v = compiler::StbView::from(img); - CHECK(v.ok()); - CHECK(v.n_funcs() == 2); // FB 占位 + MAIN + CHECK(v.n_funcs() == 2); // FUNCTION + MAIN CHECK(v.entry_fn_id() == 1); - CHECK(v.data_len() == 24); // 实例 3 槽 × 8 字节定宽 - - const compiler::StbView::FuncRow fm = v.func_row(1); - CHECK(fm.nregs == 12); - CHECK(fm.code_len == 12); - const uint32_t* im = reinterpret_cast(v.code_bytes()) + fm.code_offset / 4; - char buf[64]; - // 实参写字段:start := TRUE / stop := FALSE - isa::disasm(im[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r9, 0") == 0); - isa::disasm(im[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 0") == 0); - isa::disasm(im[2], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r10, 1") == 0); - isa::disasm(im[3], buf, sizeof buf); - CHECK(std::strcmp(buf, "STORE_GLOBAL r10, 1") == 0); - // 内联体:Q := start AND NOT stop(短路跳转) - isa::disasm(im[4], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 0") == 0); - isa::disasm(im[5], buf, sizeof buf); - CHECK(std::strcmp(buf, "JF r9, +3") == 0); - isa::disasm(im[9], buf, sizeof buf); - CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 2") == 0); // Q 字段 - // q := starter.Q - isa::disasm(im[10], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 2") == 0); + const std::vector lines = disasm_lines(cfg_global(), v, 1); + CHECK(has_line(lines, "CALL")); + CHECK(has_line(lines, "MOVE r")); + // max_stack ≥ MAIN 帧 + 被调函数帧 + CHECK(v.max_stack() >= (12 + 11 * 8) + (12 + 10 * 8)); return true; } -// ---- 15. 用例 17:TON(CAL_TON + 字段偏移)---- - -static bool test_case17() { - std::vector img; - std::string err; - CHECK(compile_case("17_ton", &img, &err)); - - const compiler::StbView v = compiler::StbView::from(img); - CHECK(v.ok()); - CHECK(v.data_len() == 32); // in@0 pt@8 q@16 et@24 - const compiler::StbView::FuncRow r = v.func_row(0); - CHECK(r.nregs == 11); - const uint32_t* ins = reinterpret_cast(v.code_bytes()); - char buf[64]; - isa::disasm(ins[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r9, 0") == 0); - isa::disasm(ins[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 0") == 0); // in - isa::disasm(ins[2], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r10, 1") == 0); - isa::disasm(ins[3], buf, sizeof buf); - CHECK(std::strcmp(buf, "STORE_GLOBAL r10, 1") == 0); // pt(槽 1) - isa::disasm(ins[4], buf, sizeof buf); - CHECK(std::strcmp(buf, "CAL_TON 0") == 0); - isa::disasm(ins[5], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 2") == 0); // t.Q(槽 2) - return true; -} - -// ---- 16. 用例 18:TOF / CTU ---- - -static bool test_case18() { - std::vector img; - std::string err; - CHECK(compile_case("18_tof_ctu", &img, &err)); - - const compiler::StbView v = compiler::StbView::from(img); - CHECK(v.ok()); - CHECK(v.data_len() == 72); // tf 4 槽 + c 5 槽 = 9 槽 × 8 - const compiler::StbView::FuncRow r = v.func_row(0); - CHECK(r.nregs == 14); - const uint32_t* ins = reinterpret_cast(v.code_bytes()); - char buf[64]; - isa::disasm(ins[4], buf, sizeof buf); - CHECK(std::strcmp(buf, "CAL_TOF 0") == 0); - isa::disasm(ins[12], buf, sizeof buf); - CHECK(std::strcmp(buf, "CAL_CTU 4") == 0); - isa::disasm(ins[13], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 7") == 0); // c.Q(槽 7) - isa::disasm(ins[14], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOAD_GLOBAL r10, 8") == 0); // c.CV(槽 8) - return true; -} - -// ---- 17. line1 全链路(MAIN + Motor FB + I/O + 全局)---- +// ---- 5. 20_line1:全链路(全局 + FB 实例 + I/Q 语义)---- static bool test_line1() { - std::vector img; - std::string err; using namespace compiler; - const std::string toml = std::string(REPO_ROOT) + "/examples/line1/project.toml"; - Project p; - CHECK(parse_project(toml, &p, &err)); - std::vector units; - for (const std::string& f : compile_files(p)) { - SourceUnit u; - CHECK(load_unit(p.base_dir + "/" + f, &u, &err)); - units.push_back(std::move(u)); - } - LinkResult link; - CHECK(link_project(p, units, &link, &err)); - CHECK(check_project(p, units, link, &err)); - compiler::MachineConfig cfg; - CHECK(load_cfg(&cfg)); - CHECK(codegen_project(p, units, link, cfg, &img, &err)); - - const compiler::StbView v = compiler::StbView::from(img); + std::vector img; + CHECK(build_case("20_line1", &img)); + const StbView v = StbView::from(img); CHECK(v.ok()); - CHECK(v.n_globals() == 4); - CHECK(v.n_funcs() == 2); - CHECK(v.entry_fn_id() == 1); - CHECK(v.data_len() == 56); // 7 槽 × 8 字节定宽 + CHECK(v.n_globals() == 4); // EmergencyStop/I0_0/I0_1/Q0_0 + CHECK(v.n_slots() == 5); // 4 全局 + 1 实例(字段走偏移,实例占 1 条) + CHECK(v.values_size() == 7); // 全局 4×1B + starter 3×1B + CHECK(v.max_stack() >= (12 + 13 * 8) + (12 + 10 * 8)); // fn0 共享体 13 + MAIN 10 + // MAIN(fn 1):LOAD(I 读)、STORE_OFF(实参写)、LOADK r1 + CALL(共享体)、LOAD_OFF(输出读)、STORE(Q 写) + const std::vector lines = disasm_lines(cfg_global(), v, 1); + CHECK(has_line(lines, "LOAD.U8")); + CHECK(has_line(lines, "STORE_OFF.U8")); + CHECK(has_line(lines, "LOAD_OFF.U8")); + CHECK(has_line(lines, "STORE.U8")); + CHECK(has_line(lines, "LOADK.U32 r1")); + CHECK(has_line(lines, "CALL 0")); + // MotorStarter 共享体(fn 0):LOAD_PTR/STORE_PTR + 短路 JF + const std::vector fb = disasm_lines(cfg_global(), v, 0); + CHECK(has_line(fb, "LOAD_PTR.U8")); + CHECK(has_line(fb, "STORE_PTR.U8")); + CHECK(has_line(fb, "JF r")); + return true; +} - const compiler::StbView::FuncRow fm = v.func_row(1); - CHECK(fm.nregs == 13); - CHECK(fm.code_len == 17); - const uint32_t* im = reinterpret_cast(v.code_bytes()) + fm.code_offset / 4; - char buf[64]; - isa::disasm(im[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOAD_I r8, 1") == 0); // I0_0(io.input) - isa::disasm(im[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "STORE_GLOBAL r8, 4") == 0); // starter.start(槽 4) - isa::disasm(im[2], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 2") == 0); // I0_1 - isa::disasm(im[3], buf, sizeof buf); - CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 5") == 0); // starter.stop(槽 5) - isa::disasm(im[13], buf, sizeof buf); - CHECK(std::strcmp(buf, "STORE_GLOBAL r8, 6") == 0); // starter.Q(槽 6) - isa::disasm(im[14], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 6") == 0); // starter.Q 读回 - isa::disasm(im[15], buf, sizeof buf); - CHECK(std::strcmp(buf, "STORE_Q r8, 3") == 0); // Q0_0(io.output) - isa::disasm(im[16], buf, sizeof buf); - CHECK(std::strcmp(buf, "RET") == 0); +// ---- 6. 05_while_normal:跳转回填(字节单位)---- - // 12.13 写侧自检:型号标识 + SHA-256(compiler::StbView) - const compiler::StbView self = compiler::StbView::from(img); - CHECK(self.ok()); - CHECK(self.model_matches("STATOR", 1)); - CHECK(self.sha_ok()); - std::vector tampered = img; - tampered[100] ^= 0x01; // 篡改代码段一字节 - const compiler::StbView bad = compiler::StbView::from(tampered); - CHECK(bad.ok() && !bad.sha_ok()); // 结构可解析但 SHA 校验失败 +static bool test_while_jumps() { + using namespace compiler; + std::vector img; + CHECK(build_case("05_while_normal", &img)); + const StbView v = StbView::from(img); + CHECK(v.ok()); + const std::vector lines = disasm_lines(cfg_global(), v, 0); + CHECK(has_line(lines, "JF r")); + CHECK(has_line(lines, "JMP ")); + return true; +} + +// ---- 7. 09_gvl_external:全局变量 + 初值 ---- + +static bool test_gvl() { + using namespace compiler; + std::vector img; + CHECK(build_case("09_gvl_external", &img)); + const StbView v = StbView::from(img); + CHECK(v.ok()); + CHECK(v.n_globals() == 1); + CHECK(v.n_slots() == 1); + CHECK(v.values_size() == 2); // G1 : INT(2 字节,初值 5) return true; } int main() { - if (!test_case01()) return 1; - if (!test_case02()) return 1; - if (!test_case09()) return 1; - if (!test_io_ops()) return 1; - if (!test_layout()) return 1; - if (!test_write_input()) return 1; - if (!test_case03()) return 1; - if (!test_not()) return 1; - if (!test_case04()) return 1; - if (!test_case05()) return 1; - if (!test_case07()) return 1; - if (!test_case08()) return 1; - if (!test_case13()) return 1; - if (!test_case15()) return 1; - if (!test_case17()) return 1; - if (!test_case18()) return 1; + if (!test_empty_main()) return 1; + if (!test_bool_assign()) return 1; + if (!test_ton()) return 1; + if (!test_function_call()) return 1; if (!test_line1()) return 1; + if (!test_while_jumps()) return 1; + if (!test_gvl()) return 1; std::printf("codegen_test: %d checks passed\n", g_checks); return 0; } diff --git a/tests/src/compiler_test.cpp b/tests/src/compiler_test.cpp index d77fcb0..073c5ee 100644 --- a/tests/src/compiler_test.cpp +++ b/tests/src/compiler_test.cpp @@ -202,7 +202,7 @@ static bool test_all_cases() { } ++n; } - CHECK(n == 20); + CHECK(n == 22); return true; } diff --git a/tests/src/isa_test.cpp b/tests/src/isa_test.cpp index 5583c14..d272cc9 100644 --- a/tests/src/isa_test.cpp +++ b/tests/src/isa_test.cpp @@ -1,15 +1,12 @@ /** * @file isa_test.cpp - * @brief isa 合同测试:饱和、编解码、disasm、哈希、映像、sidecar + * @brief isa V2 测试:饱和模板 / func 表 / 前缀定长 / 位号往返 / 编解码 / disasm / uses_* * @author * @date 2026-08-19 */ -#include #include #include -#include -#include #include "isa/Encode.h" #include "isa/Instr.h" @@ -18,119 +15,262 @@ static int g_checks = 0; -#define CHECK(cond) \ - do { \ - if (!(cond)) { \ +#define CHECK(cond) \ + do { \ + if (!(cond)) { \ std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ - return false; \ - } \ - ++g_checks; \ + return false; \ + } \ + ++g_checks; \ } while (0) -// ---- 1. 饱和算术 ---- +// ---- 1. 饱和模板(各宽度 + 除法 A)---- static bool test_sat() { using namespace isa::types; - CHECK(sat_add(30000, 30000) == 32767); - CHECK(sat_add(-30000, -30000) == -32768); - CHECK(sat_add(10, 20) == 30); - CHECK(sat_sub(-30000, 30000) == -32768); - CHECK(sat_sub(5, 3) == 2); - CHECK(sat_mul(300, 200) == 32767); - CHECK(sat_mul(-300, 200) == -32768); - CHECK(sat_mul(6, 7) == 42); - CHECK(sat_div(10, 3) == 3); - CHECK(sat_div(-32768, -1) == 32767); // INT_MIN / -1 饱和 - CHECK(sat_div(7, 0) == 7); // 除零返回被除数 - CHECK(static_cast(TypeTag::Bool) == 0); - CHECK(static_cast(TypeTag::Int) == 1); - CHECK(static_cast(TypeTag::Time) == 2); + // int8 + CHECK(sat_add(100, 100) == 127); + CHECK(sat_add(-100, -100) == -128); + CHECK(sat_sub(-128, 1) == -128); + CHECK(sat_mul(20, 20) == 127); + CHECK(sat_div(-128, -1) == 127); // MIN/-1 饱和 + CHECK(sat_div(7, 0) == 7); // 除零返回被除数 + CHECK(sat_div(-7, 3) == -2); // 截断 + // int16 + CHECK(sat_add(30000, 30000) == 32767); + CHECK(sat_add(-30000, -30000) == -32768); + CHECK(sat_div(-32768, -1) == 32767); + CHECK(sat_div(10, 3) == 3); + CHECK(sat_div(7, 0) == 7); + // int32 / int64 边界 + CHECK(sat_add(2147483000, 1000) == 2147483647); + CHECK(sat_div(INT64_MIN, -1) == INT64_MAX); + CHECK(sat_add(INT64_MAX, 1) == INT64_MAX); return true; } -// ---- 2. 操作码与助记符 ---- +// ---- 2. func 表 ---- -static bool test_op() { - using namespace isa; - CHECK(static_cast(Op::MOVE) == 0); - CHECK(static_cast(Op::CAL_TON) == 24); - CHECK(static_cast(Op::CAL_F_TRIG) == 31); // 8 个 CAL_* 连续 24..31 - CHECK(static_cast(Op::CALL) == 32); - CHECK(static_cast(Op::RET) == 33); - CHECK(kOpCount == 34); - CHECK(std::strcmp(mnemonic(Op::CAL_CTUD), "CAL_CTUD") == 0); - CHECK(std::strcmp(mnemonic(Op::CAL_F_TRIG), "CAL_F_TRIG") == 0); - CHECK(std::strcmp(mnemonic(static_cast(255)), "???") == 0); +static bool test_func_table() { + using namespace isa::types; + CHECK(kFuncWidth[0] == 1 && !kFuncSigned[0]); // 1B 无 + CHECK(kFuncWidth[1] == 1 && kFuncSigned[1]); // 1B 有 + CHECK(kFuncWidth[3] == 2 && kFuncSigned[3]); // 2B 有(INT) + CHECK(kFuncWidth[4] == 4 && !kFuncSigned[4]); // 4B 无 + CHECK(kFuncWidth[6] == 8 && !kFuncSigned[6]); // 8B 无 + CHECK(kFuncWidth[7] == 8 && kFuncSigned[7]); // 8B 有 + CHECK(kFuncWidth[8] == 4); // F32 + CHECK(kFuncWidth[9] == 8); // F64 + CHECK(kFuncWidth[10] == 0); // 预留 return true; } -// ---- 3. 指令打包 / 拆字段 ---- +// ---- 3. 前缀定长 ---- -static bool test_instr() { +static bool test_instr_len() { using namespace isa; - const Instr w = pack(Op::ADD, 5, 3, 4); - CHECK(op(w) == Op::ADD); - CHECK(rd(w) == 5); - CHECK(a(w) == 3); - CHECK(b(w) == 4); - CHECK(imm16(enc_imm(Op::LOADK, 0, 0x1234)) == 0x1234); - CHECK(off16(enc_jmp(-1)) == -1); - CHECK(off16(enc_jmp(4)) == 4); - CHECK(rd(enc_jc(Op::JT, 2, -3)) == 2); - CHECK(off16(enc_jc(Op::JT, 2, -3)) == -3); - CHECK(imm16(enc_call(0xABCD)) == 0xABCD); - CHECK(imm16(enc_slot(Op::LOAD_I, 7, 300)) == 300); - CHECK(rd(enc_slot(Op::LOAD_I, 7, 300)) == 7); - CHECK(rd(enc_rr(Op::MOVE, 1, 2)) == 1); - CHECK(a(enc_rr(Op::MOVE, 1, 2)) == 2); - CHECK(op(enc_ret()) == Op::RET); + uint8_t buf[16] = {0}; + // 各前缀编码 → 长度 + set_byte0(buf, "11", 0); + CHECK(instr_len(buf) == 4); + set_byte0(buf, "10", 1); + CHECK(instr_len(buf) == 4); + set_byte0(buf, "011", 0); + CHECK(instr_len(buf) == 8); + set_byte0(buf, "010001", 0); + CHECK(instr_len(buf) == 8); + set_byte0(buf, "0010", 0); + CHECK(instr_len(buf) == 16); + set_byte0(buf, "0011", 0); + CHECK(instr_len(buf) == 16); + set_byte0(buf, "0001", 0); + CHECK(instr_len(buf) == 32); return true; } -// ---- 4. encode / decode / disasm ---- +// ---- 4. 位号往返 ---- -static bool test_encode() { +static bool test_fields() { using namespace isa; - const Instr w = enc_rrr(Op::ADD, 5, 3, 4); - CHECK(encode(decode(w)) == w); - CHECK(decode(w).op == Op::ADD); - CHECK(decode(w).rd == 5); - CHECK(decode(w).a == 3); - CHECK(decode(w).b == 4); + uint8_t buf[16] = {0}; + // RR(MOVE 11 区) + enc_rr(buf, "11", static_cast(isa::Op::MOVE), 0, 5, 3); + CHECK(op_of(buf) == static_cast(isa::Op::MOVE)); + CHECK(rd_of(buf) == 5); + CHECK(rs1_of(buf) == 3); + // RRR(ADD 011 区,func=3) + enc_rrr(buf, "011", static_cast(isa::Op::ADD), 3, 1, 2, 4); + CHECK(op_of(buf) == static_cast(isa::Op::ADD)); + CHECK(func_of(buf) == 3); + CHECK(rd_of(buf) == 1 && rs1_of(buf) == 2 && rs2_of(buf) == 4); + // SLOT(LOAD 010001 区) + enc_slot(buf, "010001", static_cast(isa::Op::LOAD), 0, 7, 300); + CHECK(rd_of(buf) == 7); + CHECK(slot32_of(buf) == 300); + // IMM(LOADK) + enc_imm(buf, "010000", static_cast(isa::Op::LOADK), 3, 0, 0x12345678); + CHECK(imm32_of(buf) == 0x12345678); + // JMP(010010) + enc_jmp(buf, -3); + CHECK(off32_of(buf) == -3); + // JC(010011,off@55..24 不覆盖 rd) + enc_jc(buf, static_cast(isa::Op::JT), 2, -3); + CHECK(rd_of(buf) == 2); + CHECK(off32_jc_of(buf) == -3); + // CALL(010100) + enc_call(buf, 7); + CHECK(fn_id32_of(buf) == 7); + // CAL(010101:fb_id16 + slot32) + enc_cal(buf, 3, 42); + CHECK(fb_id16_of(buf) == 3); + CHECK(cal_slot32_of(buf) == 42); + // SLOT_OFF(0010:off@87..56) + enc_slot_off(buf, static_cast(isa::Op::LOAD_OFF), 3, 1, 9, -12); + CHECK(slot32_of(buf) == 9); + CHECK(off32_hi_of(buf) == -12); + // RS_OFF(0010 op=8/9:rd@2、rs1@3、off@87..56) + enc_rs_off(buf, static_cast(isa::Op::LOAD_PTR), 3, 5, 1, -12); + CHECK(rd_of(buf) == 5); + CHECK(rs1_of(buf) == 1); + CHECK(off32_hi_of(buf) == -12); + // RET + enc_ret(buf); + CHECK(op_of(buf) == static_cast(isa::Op::RET)); + return true; +} - char buf[64]; - disasm(w, buf, sizeof buf); - CHECK(std::strcmp(buf, "ADD r5, r3, r4") == 0); - disasm(enc_rr(Op::MOVE, 1, 2), buf, sizeof buf); - CHECK(std::strcmp(buf, "MOVE r1, r2") == 0); - disasm(enc_imm(Op::LOADK, 0, 3), buf, sizeof buf); - CHECK(std::strcmp(buf, "LOADK r0, 3") == 0); - disasm(enc_jmp(4), buf, sizeof buf); - CHECK(std::strcmp(buf, "JMP +4") == 0); - disasm(enc_jmp(-1), buf, sizeof buf); - CHECK(std::strcmp(buf, "JMP -1") == 0); - disasm(enc_jc(Op::JT, 0, -1), buf, sizeof buf); - CHECK(std::strcmp(buf, "JT r0, -1") == 0); - disasm(enc_call(1), buf, sizeof buf); - CHECK(std::strcmp(buf, "CALL 1") == 0); - disasm(enc_slot(Op::LOAD_I, 0, 2), buf, sizeof buf); - CHECK(std::strcmp(buf, "LOAD_I r0, 2") == 0); - disasm(enc_slot(Op::STORE_Q, 4, 0), buf, sizeof buf); - CHECK(std::strcmp(buf, "STORE_Q r4, 0") == 0); - disasm(enc_ret(), buf, sizeof buf); - CHECK(std::strcmp(buf, "RET") == 0); - disasm(enc_imm(Op::CAL_TON, 0, 0), buf, sizeof buf); - CHECK(std::strcmp(buf, "CAL_TON 0") == 0); - disasm(0xFFu, buf, sizeof buf); // 未知操作码 - CHECK(std::strcmp(buf, "??? 0x000000ff") == 0); +// ---- 5. decode/encode 往返 ---- + +static bool test_roundtrip() { + using namespace isa; + uint8_t buf[16] = {0}; + enc_rrr(buf, "011", static_cast(isa::Op::ADD), 5, 3, 7, 9); + const isa::Decoded d = isa::decode(buf, 8); + CHECK(std::strcmp(d.prefix, "011") == 0); + CHECK(d.op == static_cast(isa::Op::ADD)); + CHECK(d.func == 5); + CHECK(d.rd == 3 && d.rs1 == 7 && d.rs2 == 9); + uint8_t buf2[16] = {0}; + const size_t len = isa::encode(d, buf2); + CHECK(len == 8); + CHECK(std::memcmp(buf, buf2, 8) == 0); + return true; +} + +// ---- 6. disasm 文本 ---- + +static bool test_disasm() { + using namespace isa; + uint8_t buf[16] = {0}; + char out[64]; + // 无类型:RET + enc_ret(buf); + isa::disasm(buf, 4, out, sizeof out); + CHECK(std::strcmp(out, "RET") == 0); + // RR:MOVE + enc_rr(buf, "11", static_cast(isa::Op::MOVE), 0, 1, 2); + isa::disasm(buf, 4, out, sizeof out); + CHECK(std::strcmp(out, "MOVE r1, r2") == 0); + // RRR 类型后缀:ADD.F32 + enc_rrr(buf, "011", static_cast(isa::Op::ADD), 8, 1, 2, 3); + isa::disasm(buf, 8, out, sizeof out); + CHECK(std::strcmp(out, "ADD.F32 r1, r2, r3") == 0); + // SLOT:LOAD.I16 + enc_slot(buf, "010001", static_cast(isa::Op::LOAD), 3, 5, 4); + isa::disasm(buf, 8, out, sizeof out); + CHECK(std::strcmp(out, "LOAD.I16 r5, s4") == 0); + // JMP + enc_jmp(buf, 12); + isa::disasm(buf, 8, out, sizeof out); + CHECK(std::strcmp(out, "JMP +12") == 0); + // JC + enc_jc(buf, static_cast(isa::Op::JT), 0, -1); + isa::disasm(buf, 8, out, sizeof out); + CHECK(std::strcmp(out, "JT r0, -1") == 0); + // CALL + enc_call(buf, 3); + isa::disasm(buf, 8, out, sizeof out); + CHECK(std::strcmp(out, "CALL 3") == 0); + // CAL:fb 名 + enc_cal(buf, 0, 5); + isa::disasm(buf, 8, out, sizeof out); + CHECK(std::strcmp(out, "CAL.TON s5") == 0); + enc_cal(buf, 4, 9); + isa::disasm(buf, 8, out, sizeof out); + CHECK(std::strcmp(out, "CAL.DCTU s9") == 0); + // SLOT_OFF + enc_slot_off(buf, static_cast(isa::Op::LOAD_OFF), 3, 2, 8, 16); + isa::disasm(buf, 16, out, sizeof out); + CHECK(std::strcmp(out, "LOAD_OFF.I16 r2, s8, 16") == 0); + // RS_OFF(LOAD_PTR/STORE_PTR) + enc_rs_off(buf, static_cast(isa::Op::LOAD_PTR), 3, 2, 1, 16); + isa::disasm(buf, 16, out, sizeof out); + CHECK(std::strcmp(out, "LOAD_PTR.I16 r2, r1, 16") == 0); + enc_rs_off(buf, static_cast(isa::Op::STORE_PTR), 9, 9, 1, -4); + isa::disasm(buf, 16, out, sizeof out); + CHECK(std::strcmp(out, "STORE_PTR.F64 r9, r1, -4") == 0); + // decode 回读(encode 路径) + { + isa::Decoded d = isa::decode(buf, 16); + isa::encode(d, buf); + isa::disasm(buf, 16, out, sizeof out); + CHECK(std::strcmp(out, "STORE_PTR.F64 r9, r1, -4") == 0); + } + // 未知(010110 预留区) + buf[0] = 0x58; + isa::disasm(buf, 8, out, sizeof out); + CHECK(std::strncmp(out, "??? 0x", 6) == 0); + return true; +} + +// ---- 7. uses_* 直推 ---- + +static bool test_uses() { + using namespace isa; + CHECK(uses_rd(OpFormat::RR) && uses_rs1(OpFormat::RR) && !uses_rs2(OpFormat::RR)); + CHECK(uses_rd(OpFormat::RRR) && uses_rs1(OpFormat::RRR) && uses_rs2(OpFormat::RRR)); + CHECK(uses_rd(OpFormat::IMM) && !uses_rs1(OpFormat::IMM)); + CHECK(uses_rd(OpFormat::SLOT) && !uses_rs1(OpFormat::SLOT)); + CHECK(uses_rd(OpFormat::JC) && !uses_rs1(OpFormat::JC)); + CHECK(!uses_rd(OpFormat::JMP) && !uses_rd(OpFormat::CALL)); + CHECK(!uses_rd(OpFormat::CAL) && !uses_rd(OpFormat::NONE)); + CHECK(uses_rd(OpFormat::SLOT_OFF) && uses_rd(OpFormat::IMM64)); + return true; +} + +// ---- 8. 指令表查询 ---- + +static bool test_op_table() { + using namespace isa; + CHECK(find_op("11", static_cast(Op::MOVE)) == 0); + CHECK(find_op("011", static_cast(Op::ADD)) >= 0); + CHECK(find_op("010001", static_cast(Op::LOAD)) >= 0); + CHECK(find_op("010101", static_cast(Op::CAL)) >= 0); + CHECK(find_op("99", 0) == -1); + CHECK(find_op_by_name("MOVE") == 0); + CHECK(find_op_by_name("ADD") >= 0); + CHECK(find_op_by_name("CAL") >= 0); + CHECK(find_op_by_name("NOPE") == -1); + CHECK(std::strcmp(mnemonic("11", static_cast(Op::MOVE)), "MOVE") == 0); + CHECK(std::strcmp(mnemonic("011", static_cast(Op::ADD)), "ADD") == 0); + CHECK(std::strcmp(mnemonic("99", 0), "???") == 0); + CHECK(format_of("010101", static_cast(Op::CAL)) == OpFormat::CAL); + CHECK(func_mode_of("11", static_cast(Op::MOVE)) == FuncMode::Width); + CHECK(func_mode_of("010001", static_cast(Op::LOAD)) == FuncMode::Full); + CHECK(std::strcmp(kFbNames[0], "TON") == 0); + CHECK(std::strcmp(kFbNames[14], "RTC") == 0); return true; } int main() { if (!test_sat()) return 1; - if (!test_op()) return 1; - if (!test_instr()) return 1; - if (!test_encode()) return 1; + if (!test_func_table()) return 1; + if (!test_instr_len()) return 1; + if (!test_fields()) return 1; + if (!test_roundtrip()) return 1; + if (!test_disasm()) return 1; + if (!test_uses()) return 1; + if (!test_op_table()) return 1; std::printf("isa_test: %d checks passed\n", g_checks); return 0; } diff --git a/tests/src/lexer_test.cpp b/tests/src/lexer_test.cpp index 2e0cca5..175ed92 100644 --- a/tests/src/lexer_test.cpp +++ b/tests/src/lexer_test.cpp @@ -197,7 +197,18 @@ static bool test_line1() { return true; } +static bool test_var_temp_kw() { + using namespace compiler; + std::vector ts; + std::string err; + CHECK(lex("t.st", "VAR_TEMP", &ts, &err)); + CHECK(!ts.empty()); + CHECK(ts[0].type == Tok::VAR_TEMP); + return true; +} + int main() { + if (!test_var_temp_kw()) return 1; if (!test_basic()) return 1; if (!test_case()) return 1; if (!test_symbols()) return 1; diff --git a/tests/src/linker_test.cpp b/tests/src/linker_test.cpp index b832389..8f8fd20 100644 --- a/tests/src/linker_test.cpp +++ b/tests/src/linker_test.cpp @@ -43,7 +43,12 @@ static bool link_case(const char* dir, compiler::LinkResult* out, std::string* e } units.push_back(std::move(u)); } - return link_project(p, units, out, err); + compiler::MachineConfig cfg; + std::string cerr; + if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)) { + return false; + } + return link_project(p, units, cfg, out, err); } static bool expect_link_err(const char* dir, const char* keyword) { @@ -65,6 +70,67 @@ static bool expect_link_err(const char* dir, const char* keyword) { return true; } +// ---- 0. VAR_TEMP:FB 布局排除 Temp、scope 符号为 Local ---- + +static bool test_var_temp() { + using namespace compiler; + const std::string toml = std::string(REPO_ROOT) + "/tests/cases/13_function_call/project.toml"; + Project p; + std::string err; + CHECK(parse_project(toml, &p, &err)); + SourceUnit u; + u.path = "main.st"; + CHECK(parse_pous("main.st", + "FUNCTION_BLOCK Foo\n" + "VAR_INPUT\n in : BOOL;\nEND_VAR\n" + "VAR_OUTPUT\n out : BOOL;\nEND_VAR\n" + "VAR\n state : BOOL;\nEND_VAR\n" + "VAR_TEMP\n tmp : INT;\nEND_VAR\n" + " out := tmp > 0;\n" + "END_FUNCTION_BLOCK\n" + "PROGRAM MAIN\nEND_PROGRAM", + &u.ast, &err)); + compiler::MachineConfig cfg; + std::string cerr; + CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)); + LinkResult r; + std::vector units; + units.push_back(std::move(u)); + CHECK(link_project(p, units, cfg, &r, &err)); + + // FB 布局:字段 = input/output/内部 var,VAR_TEMP 不是字段 + const auto it = r.fb_types.find("foo"); + CHECK(it != r.fb_types.end()); + CHECK(it->second.fields.size() == 3); + CHECK(it->second.fields[0].name == "in"); + CHECK(it->second.fields[1].name == "out"); + CHECK(it->second.fields[2].name == "state"); + for (const FbField& f : it->second.fields) { + CHECK(f.name != "tmp"); + } + + // scope 符号:VAR_TEMP → Local(在 Foo 作用域) + bool saw_tmp = false; + bool saw_main = false; + for (const auto& sc : r.scopes) { + if (sc.name == "foo") { + for (const Symbol& s : sc.syms) { + if (s.name == "tmp") { + saw_tmp = true; + CHECK(s.kind == SymbolKind::Local); + CHECK(s.type_name == "int"); + } + } + } + if (sc.name == "main") { + saw_main = true; + } + } + CHECK(saw_tmp); + CHECK(saw_main); + return true; +} + // ---- 1. 用例 09:GVL + VAR_EXTERNAL 同一槽 ---- static bool test_case09() { @@ -146,7 +212,10 @@ static bool test_line1() { CHECK(load_unit(p.base_dir + "/" + f, &u, &err)); units.push_back(std::move(u)); } - CHECK(link_project(p, units, &r, &err)); + compiler::MachineConfig cfg; + std::string cerr; + CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)); + CHECK(link_project(p, units, cfg, &r, &err)); // 全局槽序 = 声明顺序 CHECK(r.globals.size() == 4); @@ -162,7 +231,7 @@ static bool test_line1() { CHECK(it->second.fields[0].name == "start"); CHECK(it->second.fields[1].name == "stop"); CHECK(it->second.fields[2].name == "q"); - CHECK(it->second.fields[2].type == TypeKind::Bool); + CHECK(it->second.fields[2].type_name == "bool"); // 内建 TON 布局(用例 17 的 t) const FbLayout* ton = nullptr; @@ -204,20 +273,24 @@ static bool test_builtin() { CHECK(load_unit(p.base_dir + "/" + f, &u, &err)); units.push_back(std::move(u)); } - CHECK(link_project(p, units, &r, &err)); + compiler::MachineConfig cfg; + std::string cerr; + CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)); + CHECK(link_project(p, units, cfg, &r, &err)); CHECK(r.scopes.size() == 1); const LinkResult::PouScope& main = r.scopes[0]; const auto it = main.fb_instances.find("t"); CHECK(it != main.fb_instances.end()); CHECK(it->second.fields.size() == 4); - CHECK(it->second.fields[0].name == "in" && it->second.fields[0].type == TypeKind::Bool); - CHECK(it->second.fields[1].name == "pt" && it->second.fields[1].type == TypeKind::Time); - CHECK(it->second.fields[2].name == "q" && it->second.fields[2].type == TypeKind::Bool); - CHECK(it->second.fields[3].name == "et" && it->second.fields[3].type == TypeKind::Time); + CHECK(it->second.fields[0].name == "in" && it->second.fields[0].type_name == "bool"); + CHECK(it->second.fields[1].name == "pt" && it->second.fields[1].type_name == "time"); + CHECK(it->second.fields[2].name == "q" && it->second.fields[2].type_name == "bool"); + CHECK(it->second.fields[3].name == "et" && it->second.fields[3].type_name == "time"); return true; } int main() { + if (!test_var_temp()) return 1; if (!test_case09()) return 1; if (!test_neg_cases()) return 1; if (!test_case16()) return 1; diff --git a/tests/src/machine_test.cpp b/tests/src/machine_test.cpp index a17bfb9..1dab450 100644 --- a/tests/src/machine_test.cpp +++ b/tests/src/machine_test.cpp @@ -1,6 +1,6 @@ /** * @file machine_test.cpp - * @brief machine.toml 加载与强校验测试(阶段 A 步骤 3) + * @brief machine.toml V2 加载与强校验测试(阶段 2) * @author * @date 2026-08-21 */ @@ -31,16 +31,16 @@ static int g_checks = 0; // ---- 写临时 toml ---- -static std::string write_tmp(const char* name, const char* content) { +static std::string write_tmp(const char* name, const std::string& content) { const std::filesystem::path dir = std::filesystem::temp_directory_path(); const std::filesystem::path path = dir / name; std::FILE* f = std::fopen(path.string().c_str(), "w"); - std::fputs(content, f); + std::fputs(content.c_str(), f); std::fclose(f); return path.string(); } -static bool expect_load_err(const char* name, const char* content, const char* keyword) { +static bool expect_load_err(const char* name, const std::string& content, const char* keyword) { const std::string path = write_tmp(name, content); compiler::MachineConfig cfg; std::string err; @@ -64,16 +64,51 @@ static bool expect_load_err(const char* name, const char* content, const char* k return true; } -// 合法内容模板(可注入损坏) -static const char* kGood = - "[meta]\nname = \"STATOR\"\nversion = 1\n" - "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nrange = [0, 1]\ntag = 0\n" - "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" - "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" - "[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n" - "[[op]]\nname = \"CAL_TON\"\nopcode = 24\nclass = \"instance\"\nformat = \"CAL\"\nparams = [\"instance\"]\nenabled = true\n" - "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n" - "[[fb]]\nname = \"ton\"\nopcode = 24\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n"; +// 合法 type 模板(19 条全集,可注入损坏;缺 DT 时用于"缺全集"负例) +static const char* kTypesAll = + "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nkind = \"bool\"\nfunc = 0\n" + "[[type]]\nname = \"BYTE\"\nbase = \"uint8\"\nkind = \"bit\"\nfunc = 0\n" + "[[type]]\nname = \"WORD\"\nbase = \"uint16\"\nkind = \"bit\"\nfunc = 2\n" + "[[type]]\nname = \"DWORD\"\nbase = \"uint32\"\nkind = \"bit\"\nfunc = 4\n" + "[[type]]\nname = \"LWORD\"\nbase = \"uint64\"\nkind = \"bit\"\nfunc = 6\n" + "[[type]]\nname = \"SINT\"\nbase = \"int8\"\nkind = \"int\"\nfunc = 1\n" + "[[type]]\nname = \"INT\"\nbase = \"int16\"\nkind = \"int\"\nfunc = 3\n" + "[[type]]\nname = \"DINT\"\nbase = \"int32\"\nkind = \"int\"\nfunc = 5\n" + "[[type]]\nname = \"LINT\"\nbase = \"int64\"\nkind = \"int\"\nfunc = 7\n" + "[[type]]\nname = \"USINT\"\nbase = \"uint8\"\nkind = \"uint\"\nfunc = 0\n" + "[[type]]\nname = \"UINT\"\nbase = \"uint16\"\nkind = \"uint\"\nfunc = 2\n" + "[[type]]\nname = \"UDINT\"\nbase = \"uint32\"\nkind = \"uint\"\nfunc = 4\n" + "[[type]]\nname = \"ULINT\"\nbase = \"uint64\"\nkind = \"uint\"\nfunc = 6\n" + "[[type]]\nname = \"TIME\"\nbase = \"int64\"\nkind = \"time\"\nfunc = 7\n" + "[[type]]\nname = \"REAL\"\nbase = \"float32\"\nkind = \"float\"\nfunc = 8\n" + "[[type]]\nname = \"LREAL\"\nbase = \"float64\"\nkind = \"float\"\nfunc = 9\n" + "[[type]]\nname = \"DATE\"\nbase = \"uint32\"\nkind = \"date\"\nfunc = 4\n" + "[[type]]\nname = \"TOD\"\nbase = \"uint32\"\nkind = \"date\"\nfunc = 4\n" + "[[type]]\nname = \"DT\"\nbase = \"uint64\"\nkind = \"date\"\nfunc = 6\n"; + +// 合法 op 模板(含 CAL 与 RET,可注入损坏) +static const char* kOpsBasic = + "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n" + "[[op]]\nname = \"CAL\"\nprefix = \"010101\"\nop = 0\nfmt = \"CAL\"\nfunc = \"none\"\nparams = [\"fb_id\", \"slot\"]\nenabled = true\n" + "[[op]]\nname = \"RET\"\nprefix = \"10\"\nop = 1\nfmt = \"NONE\"\nfunc = \"none\"\nparams = []\nenabled = true\n"; + +// 合法 fb 模板(15 条全集,可注入损坏) +static const char* kFbsAll = + "[[fb]]\nname = \"TON\"\nfb_id = 0\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n" + "[[fb]]\nname = \"TOF\"\nfb_id = 1\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n" + "[[fb]]\nname = \"TP\"\nfb_id = 2\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n" + "[[fb]]\nname = \"CTU\"\nfb_id = 3\nfields = [[\"cu\", \"BOOL\"], [\"r\", \"BOOL\"], [\"pv\", \"INT\"], [\"q\", \"BOOL\"], [\"cv\", \"INT\"]]\n" + "[[fb]]\nname = \"DCTU\"\nfb_id = 4\nfields = [[\"cu\", \"BOOL\"], [\"r\", \"BOOL\"], [\"pv\", \"DINT\"], [\"q\", \"BOOL\"], [\"cv\", \"DINT\"]]\n" + "[[fb]]\nname = \"CTD\"\nfb_id = 5\nfields = [[\"cd\", \"BOOL\"], [\"ld\", \"BOOL\"], [\"pv\", \"INT\"], [\"q\", \"BOOL\"], [\"cv\", \"INT\"]]\n" + "[[fb]]\nname = \"DCTD\"\nfb_id = 6\nfields = [[\"cd\", \"BOOL\"], [\"ld\", \"BOOL\"], [\"pv\", \"DINT\"], [\"q\", \"BOOL\"], [\"cv\", \"DINT\"]]\n" + "[[fb]]\nname = \"CTUD\"\nfb_id = 7\nfields = [[\"cu\", \"BOOL\"], [\"cd\", \"BOOL\"], [\"r\", \"BOOL\"], [\"lu\", \"BOOL\"], [\"pv\", \"INT\"], [\"qu\", \"BOOL\"], [\"qd\", \"BOOL\"], [\"cv\", \"INT\"]]\n" + "[[fb]]\nname = \"DCTUD\"\nfb_id = 8\nfields = [[\"cu\", \"BOOL\"], [\"cd\", \"BOOL\"], [\"r\", \"BOOL\"], [\"lu\", \"BOOL\"], [\"pv\", \"DINT\"], [\"qu\", \"BOOL\"], [\"qd\", \"BOOL\"], [\"cv\", \"DINT\"]]\n" + "[[fb]]\nname = \"R_TRIG\"\nfb_id = 9\nfields = [[\"clk\", \"BOOL\"], [\"q\", \"BOOL\"]]\n" + "[[fb]]\nname = \"F_TRIG\"\nfb_id = 10\nfields = [[\"clk\", \"BOOL\"], [\"q\", \"BOOL\"]]\n" + "[[fb]]\nname = \"SR\"\nfb_id = 11\nfields = [[\"S1\", \"BOOL\"], [\"R\", \"BOOL\"], [\"Q1\", \"BOOL\"]]\n" + "[[fb]]\nname = \"RS\"\nfb_id = 12\nfields = [[\"SET\", \"BOOL\"], [\"RESET1\", \"BOOL\"], [\"Q1\", \"BOOL\"]]\n" + "[[fb]]\nname = \"PWM\"\nfb_id = 13\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"duty\", \"REAL\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n" + "[[fb]]\nname = \"RTC\"\nfb_id = 14\nfields = [[\"enable\", \"BOOL\"], [\"date\", \"DATE\"], [\"tod\", \"TOD\"]]\n"; // ---- 1. 正例:仓库 machine.toml ---- @@ -83,31 +118,45 @@ static bool test_positive() { CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)); CHECK(cfg.ok()); CHECK(cfg.model_name() == "STATOR"); - CHECK(cfg.version() == 1); + CHECK(cfg.version() == 2); - CHECK(cfg.types().size() == 3); + CHECK(cfg.types().size() == 19); const compiler::ConfigType* bt = cfg.find_type("BOOL"); - CHECK(bt != nullptr && bt->base == "uint8" && bt->has_range && bt->range_min == 0 && - bt->range_max == 1 && bt->tag == 0); - CHECK(cfg.find_type("INT") != nullptr && cfg.find_type("INT")->tag == 1); - CHECK(cfg.find_type("TIME") != nullptr && cfg.find_type("TIME")->tag == 2); - CHECK(cfg.find_type("REAL") == nullptr); + CHECK(bt != nullptr && bt->base == "uint8" && bt->kind == "bool" && bt->func == 0); + CHECK(cfg.find_type("INT") != nullptr && cfg.find_type("INT")->base == "int16" && + cfg.find_type("INT")->kind == "int" && cfg.find_type("INT")->func == 3); + CHECK(cfg.find_type("REAL") != nullptr && cfg.find_type("REAL")->func == 8); + CHECK(cfg.find_type("LREAL") != nullptr && cfg.find_type("LREAL")->func == 9); + CHECK(cfg.find_type("DATE") != nullptr && cfg.find_type("DATE")->func == 4); + CHECK(cfg.find_type("TOD") != nullptr && cfg.find_type("TOD")->func == 4); + CHECK(cfg.find_type("DT") != nullptr && cfg.find_type("DT")->func == 6); + CHECK(cfg.find_type("NOPE") == nullptr); - CHECK(cfg.ops().size() == 34); - const compiler::ConfigOp* add = cfg.find_op("ADD"); - CHECK(add != nullptr && add->opcode == 5 && add->format == "RRR" && !add->is_instance && - add->params.size() == 3); - const compiler::ConfigOp* ton = cfg.find_op("CAL_TON"); - CHECK(ton != nullptr && ton->opcode == 24 && ton->is_instance && ton->format == "CAL"); - CHECK(cfg.find_op("CAL_F_TRIG") != nullptr && cfg.find_op("CAL_F_TRIG")->opcode == 31); - CHECK(cfg.find_op_by_code(33) != nullptr && cfg.find_op_by_code(33)->name == "RET"); - CHECK(cfg.op_enabled(0) && cfg.op_enabled(24)); + CHECK(cfg.ops().size() == 74); + const compiler::ConfigOp* move = cfg.find_op("MOVE"); + CHECK(move != nullptr && move->prefix == "11" && move->op == 0 && move->fmt == "RR" && + move->func_mode == "width" && move->params.size() == 2); + const compiler::ConfigOp* load = cfg.find_op("LOAD"); + CHECK(load != nullptr && load->prefix == "010001" && load->fmt == "SLOT" && + load->func_mode == "full"); + const compiler::ConfigOp* cal = cfg.find_op("CAL"); + CHECK(cal != nullptr && cal->prefix == "010101" && cal->fmt == "CAL" && + cal->func_mode == "none" && cal->params.size() == 2); + CHECK(cfg.find_op_by_key("11", 0) != nullptr && + cfg.find_op_by_key("11", 0)->name == "MOVE"); + CHECK(cfg.find_op_by_key("010001", 1) != nullptr && + cfg.find_op_by_key("010001", 1)->name == "STORE"); + CHECK(cfg.op_enabled("11", 0) && cfg.op_enabled("010101", 0)); + CHECK(!cfg.op_enabled("11", 63)); CHECK(cfg.find_op("NOPE") == nullptr); - CHECK(cfg.fbs().size() == 8); - const compiler::ConfigFb* ctud = cfg.find_fb("ctud"); - CHECK(ctud != nullptr && ctud->opcode == 29 && ctud->fields.size() == 8); - CHECK(cfg.find_fb("r_trig") != nullptr && cfg.find_fb("r_trig")->fields.size() == 2); + CHECK(cfg.fbs().size() == 15); + const compiler::ConfigFb* ton = cfg.find_fb("TON"); + CHECK(ton != nullptr && ton->fb_id == 0 && ton->fields.size() == 4); + CHECK(cfg.find_fb("DCTU") != nullptr && cfg.find_fb("DCTU")->fb_id == 4 && + cfg.find_fb("DCTU")->fields[2].type == "DINT"); + CHECK(cfg.find_fb_by_id(14) != nullptr && cfg.find_fb_by_id(14)->name == "RTC"); + CHECK(cfg.find_fb_by_id(15) == nullptr); // 基元表 CHECK(compiler::MachineConfig::find_prim("int16") != nullptr); @@ -118,178 +167,187 @@ static bool test_positive() { return true; } -// ---- 2. 负例:9 类 ---- +// ---- 2. 负例:V2 校验项 ---- static bool test_negative() { - // 缺属性(enabled 缺失) - if (!expect_load_err( - "mc_bad1.toml", - "[meta]\nname = \"S\"\nversion = 1\n" - "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" - "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" - "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" - "[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\", \"rs\"]\n", - "missing field 'enabled'")) { + const std::string m = "[meta]\nname = \"STATOR\"\nversion = 2\n"; + // version != 2 + if (!expect_load_err("mc_v2_bad1.toml", + "[meta]\nname = \"STATOR\"\nversion = 1\n" + std::string(kTypesAll) + + kOpsBasic + kFbsAll, + "version must be 2")) { return false; } + // type 缺全集(去掉 DT) + { + std::string types = kTypesAll; + const std::string dt = "[[type]]\nname = \"DT\"\nbase = \"uint64\"\nkind = \"date\"\nfunc = 6\n"; + const size_t pos = types.find(dt); + types.erase(pos, dt.size()); + if (!expect_load_err("mc_v2_bad2.toml", m + types + kOpsBasic + kFbsAll, + "missing 'DT'")) { + return false; + } + } // base 未命中基元 - if (!expect_load_err( - "mc_bad2.toml", - "[meta]\nname = \"S\"\nversion = 1\n" - "[[type]]\nname = \"BOOL\"\nbase = \"bigint\"\ntag = 0\n" - "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" - "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" - "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n", - "unknown base")) { - return false; + { + std::string types = kTypesAll; + const std::string old = "[[type]]\nname = \"BYTE\"\nbase = \"uint8\"\nkind = \"bit\"\nfunc = 0\n"; + const std::string bad = "[[type]]\nname = \"BYTE\"\nbase = \"bigint\"\nkind = \"bit\"\nfunc = 0\n"; + types.replace(types.find(old), old.size(), bad); + if (!expect_load_err("mc_v2_bad3.toml", m + types + kOpsBasic + kFbsAll, + "unknown base")) { + return false; + } } - // range min > max - if (!expect_load_err( - "mc_bad3.toml", - "[meta]\nname = \"S\"\nversion = 1\n" - "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nrange = [1, 0]\ntag = 0\n" - "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" - "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" - "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n", - "range min > max")) { - return false; + // func 与 base 互锁破坏(INT func=0) + { + std::string types = kTypesAll; + const std::string old = "[[type]]\nname = \"INT\"\nbase = \"int16\"\nkind = \"int\"\nfunc = 3\n"; + const std::string bad = "[[type]]\nname = \"INT\"\nbase = \"int16\"\nkind = \"int\"\nfunc = 0\n"; + types.replace(types.find(old), old.size(), bad); + if (!expect_load_err("mc_v2_bad4.toml", m + types + kOpsBasic + kFbsAll, + "func 0 mismatches base")) { + return false; + } } - // tag 契约破坏(BOOL tag=5) - if (!expect_load_err( - "mc_bad4.toml", - "[meta]\nname = \"S\"\nversion = 1\n" - "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 5\n" - "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" - "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" - "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n", - "tag out of contract")) { - return false; + // kind 非法 + { + std::string types = kTypesAll; + const std::string old = "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nkind = \"bool\"\nfunc = 0\n"; + const std::string bad = "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nkind = \"weird\"\nfunc = 0\n"; + types.replace(types.find(old), old.size(), bad); + if (!expect_load_err("mc_v2_bad5.toml", m + types + kOpsBasic + kFbsAll, + "bad kind")) { + return false; + } } - // tag 契约破坏(INT 占 tag 0) - if (!expect_load_err( - "mc_bad5.toml", - "[meta]\nname = \"S\"\nversion = 1\n" - "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 1\n" - "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 0\n" - "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" - "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n", - "type contract broken")) { - return false; + // prefix 非法 + { + std::string ops = kOpsBasic; + const std::string old = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n"; + const std::string bad = "[[op]]\nname = \"MOVE\"\nprefix = \"99\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n"; + ops.replace(ops.find(old), old.size(), bad); + if (!expect_load_err("mc_v2_bad6.toml", m + kTypesAll + ops + kFbsAll, + "bad prefix")) { + return false; + } } - // opcode 重号 - if (!expect_load_err( - "mc_bad6.toml", - "[meta]\nname = \"S\"\nversion = 1\n" - "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" - "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" - "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" - "[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n" - "[[op]]\nname = \"NOPE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"a\", \"b\"]\nenabled = true\n", - "duplicate opcode")) { - return false; + // prefix-fmt 互锁破坏(11 + RRR) + { + std::string ops = kOpsBasic; + const std::string old = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n"; + const std::string bad = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RRR\"\nfunc = \"width\"\nparams = [\"rd\", \"ra\", \"rb\"]\nenabled = true\n"; + ops.replace(ops.find(old), old.size(), bad); + if (!expect_load_err("mc_v2_bad7.toml", m + kTypesAll + ops + kFbsAll, + "must pair with fmt")) { + return false; + } } - // 类别-格式互锁破坏(instance + RRR) - if (!expect_load_err( - "mc_bad7.toml", - "[meta]\nname = \"S\"\nversion = 1\n" - "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" - "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" - "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" - "[[op]]\nname = \"BAD\"\nopcode = 24\nclass = \"instance\"\nformat = \"RRR\"\nparams = [\"a\", \"b\", \"c\"]\nenabled = true\n", - "class-format mismatch")) { - return false; + // (prefix, op) 分区内重复 + { + std::string ops = kOpsBasic; + ops += "[[op]]\nname = \"DUP\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"a\", \"b\"]\nenabled = true\n"; + if (!expect_load_err("mc_v2_bad8.toml", m + kTypesAll + ops + kFbsAll, + "duplicate (prefix, op)")) { + return false; + } } - // params 数量与 format 不符 - if (!expect_load_err( - "mc_bad8.toml", - "[meta]\nname = \"S\"\nversion = 1\n" - "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" - "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" - "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" - "[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\"]\nenabled = true\n", - "params count")) { - return false; + // func 类别非法 + { + std::string ops = kOpsBasic; + const std::string old = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n"; + const std::string bad = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"magic\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n"; + ops.replace(ops.find(old), old.size(), bad); + if (!expect_load_err("mc_v2_bad9.toml", m + kTypesAll + ops + kFbsAll, + "bad func mode")) { + return false; + } } - // fb.opcode 不匹配 instance op - if (!expect_load_err( - "mc_bad9.toml", - "[meta]\nname = \"S\"\nversion = 1\n" - "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" - "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" - "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" - "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n" - "[[fb]]\nname = \"ton\"\nopcode = 33\nfields = [[\"in\", \"BOOL\"]]\n", - "must match an instance op")) { - return false; + // params 数量与 fmt 不符(CAL 应为 2) + { + std::string ops = kOpsBasic; + const std::string old = "[[op]]\nname = \"CAL\"\nprefix = \"010101\"\nop = 0\nfmt = \"CAL\"\nfunc = \"none\"\nparams = [\"fb_id\", \"slot\"]\nenabled = true\n"; + const std::string bad = "[[op]]\nname = \"CAL\"\nprefix = \"010101\"\nop = 0\nfmt = \"CAL\"\nfunc = \"none\"\nparams = [\"fb_id\"]\nenabled = true\n"; + ops.replace(ops.find(old), old.size(), bad); + if (!expect_load_err("mc_v2_bad10.toml", m + kTypesAll + ops + kFbsAll, + "params count")) { + return false; + } } - // fb 字段类型未命中 type 表 - if (!expect_load_err( - "mc_bad10.toml", - "[meta]\nname = \"S\"\nversion = 1\n" - "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" - "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" - "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" - "[[op]]\nname = \"CAL_TON\"\nopcode = 24\nclass = \"instance\"\nformat = \"CAL\"\nparams = [\"instance\"]\nenabled = true\n" - "[[fb]]\nname = \"ton\"\nopcode = 24\nfields = [[\"in\", \"REAL\"]]\n", - "unknown field type")) { - return false; + // fb 缺全集(去掉 RTC) + { + std::string fbs = kFbsAll; + const std::string rtc = "[[fb]]\nname = \"RTC\"\nfb_id = 14\nfields = [[\"enable\", \"BOOL\"], [\"date\", \"DATE\"], [\"tod\", \"TOD\"]]\n"; + fbs.erase(fbs.find(rtc), rtc.size()); + if (!expect_load_err("mc_v2_bad11.toml", m + kTypesAll + kOpsBasic + fbs, + "missing fb_id 14")) { + return false; + } + } + // fb 字段类型未知 + { + std::string fbs = kFbsAll; + const std::string old = "[[\"in\", \"BOOL\"], [\"pt\", \"TIME\"]"; + const std::string bad = "[[\"in\", \"NOPE\"], [\"pt\", \"TIME\"]"; + fbs.replace(fbs.find(old), old.size(), bad); + if (!expect_load_err("mc_v2_bad12.toml", m + kTypesAll + kOpsBasic + fbs, + "unknown field type")) { + return false; + } + } + // 缺 CAL(op-fb 互锁) + { + std::string ops = kOpsBasic; + const std::string cal = "[[op]]\nname = \"CAL\"\nprefix = \"010101\"\nop = 0\nfmt = \"CAL\"\nfunc = \"none\"\nparams = [\"fb_id\", \"slot\"]\nenabled = true\n"; + ops.erase(ops.find(cal), cal.size()); + if (!expect_load_err("mc_v2_bad13.toml", m + kTypesAll + ops + kFbsAll, + "op-fb interlock")) { + return false; + } } // 语法错误 - if (!expect_load_err("mc_bad11.toml", "[meta\nname = \"S\"\n", "parse error")) { + if (!expect_load_err("mc_v2_bad14.toml", "[meta\nname = \"S\"\n", "parse error")) { return false; } return true; } -// ---- 3. 合法最小配置正例 ---- - -static bool test_positive_min() { - const std::string path = write_tmp("mc_good.toml", kGood); - compiler::MachineConfig cfg; - std::string err; - CHECK(cfg.load(path, &err)); - CHECK(cfg.ops().size() == 3); - CHECK(cfg.fbs().size() == 1); - CHECK(cfg.find_fb("ton")->fields.size() == 4); - std::remove(path.c_str()); - return true; -} - -// ---- 4. 类型元数据(TypeInfo)---- +// ---- 3. 类型元数据(TypeInfo,V2)---- static bool test_type_meta() { compiler::MachineConfig cfg; std::string err; CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)); - // BOOL:base=uint8 → 1 字节、无符号、非浮点、tag 0、range [0,1] + // BOOL:1 字节、无符号、kind bool、func 0 const compiler::TypeMeta b = compiler::type_meta(cfg, "BOOL"); - CHECK(b && b.width() == 1 && !b.is_signed() && !b.is_float() && b.tag() == 0); - CHECK(b.has_range() && b.value_in_range(0) && b.value_in_range(1)); - CHECK(!b.value_in_range(2) && !b.value_in_range(-1)); + CHECK(b && b.width() == 1 && !b.is_signed() && !b.is_float() && b.func() == 0); + CHECK(b.kind() == "bool"); // 大小写不敏感(Linker type_name 是小写) const compiler::TypeMeta b2 = compiler::type_meta(cfg, "bool"); - CHECK(b2 && b2.tag() == 0 && b2.width() == 1); + CHECK(b2 && b2.func() == 0 && b2.width() == 1); - // INT:int16 → 2 字节、有符号、tag 1、无 range + // INT:2 字节、有符号、kind int、func 3 const compiler::TypeMeta i = compiler::type_meta(cfg, "int"); - CHECK(i && i.width() == 2 && i.is_signed() && !i.is_float() && i.tag() == 1); - CHECK(!i.has_range() || i.value_in_range(30000)); + CHECK(i && i.width() == 2 && i.is_signed() && !i.is_float() && i.func() == 3); + CHECK(i.kind() == "int"); - // TIME:int64 → 8 字节、有符号、tag 2 - const compiler::TypeMeta t = compiler::type_meta(cfg, "TIME"); - CHECK(t && t.width() == 8 && t.is_signed() && !t.is_float() && t.tag() == 2); + // REAL:4 字节、浮点、func 8;TOD:4 字节、func 4 + const compiler::TypeMeta r = compiler::type_meta(cfg, "REAL"); + CHECK(r && r.width() == 4 && r.is_float() && r.func() == 8); + const compiler::TypeMeta tod = compiler::type_meta(cfg, "TOD"); + CHECK(tod && tod.width() == 4 && tod.func() == 4); // 未定义类型 → 空 - CHECK(!compiler::type_meta(cfg, "REAL")); + CHECK(!compiler::type_meta(cfg, "NOPE")); CHECK(!compiler::type_meta(cfg, "")); return true; } -// ---- 5. SHA-256 已知向量 + 型号标识 ---- +// ---- 4. SHA-256 已知向量 + 型号标识 ---- static bool test_sha256() { - // 标准测试向量 { uint8_t out[compiler::kSha256Size]; compiler::sha256(nullptr, 0, out); @@ -309,10 +367,10 @@ static bool test_sha256() { 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad}; CHECK(std::memcmp(out, want, 32) == 0); } - // 型号标识:STATOR + 1 → "STATOR1" 补 '\0' + // 型号标识:STATOR + 2 → "STATOR2" 补 '\0' char mid[compiler::kModelIdSize]; - compiler::fill_model_id("STATOR", 1, mid); - CHECK(std::strncmp(mid, "STATOR1", 7) == 0); + compiler::fill_model_id("STATOR", 2, mid); + CHECK(std::strncmp(mid, "STATOR2", 7) == 0); CHECK(mid[7] == '\0'); CHECK(mid[31] == '\0'); return true; @@ -321,7 +379,6 @@ static bool test_sha256() { int main() { if (!test_positive()) return 1; if (!test_negative()) return 1; - if (!test_positive_min()) return 1; if (!test_type_meta()) return 1; if (!test_sha256()) return 1; std::printf("machine_test: %d checks passed\n", g_checks); diff --git a/tests/src/parser_test.cpp b/tests/src/parser_test.cpp index 9f8954f..612b752 100644 --- a/tests/src/parser_test.cpp +++ b/tests/src/parser_test.cpp @@ -38,7 +38,7 @@ static bool test_gvl_file() { CHECK(g.section == VarSection::Global); CHECK(g.vars.size() == 4); CHECK(g.vars[0].name == "emergencystop"); - CHECK(g.vars[0].type.kind == TypeKind::Bool); + CHECK(g.vars[0].type.kind == TypeKind::Scalar && g.vars[0].type.name == "bool"); CHECK(g.vars[0].has_init && g.vars[0].init_value == 0); // := FALSE CHECK(!g.vars[1].has_init); // I0_0 return true; @@ -145,7 +145,7 @@ static bool test_function() { const POU& f = u.pous[0]; CHECK(f.kind == PouKind::Function); CHECK(f.name == "add"); - CHECK(f.result_type.kind == TypeKind::Int); + CHECK(f.result_type.kind == TypeKind::Scalar && f.result_type.name == "int"); CHECK(f.body[0].kind == StmtKind::Assign); CHECK(f.body[0].value->kind == ExprKind::Add); @@ -192,7 +192,7 @@ static bool test_control() { CHECK(parse_pous("t.st", src, &u, &err)); const POU& p = u.pous[0]; CHECK(p.blocks[0].vars[2].name == "t"); - CHECK(p.blocks[0].vars[2].type.kind == TypeKind::FbBuiltin); + CHECK(p.blocks[0].vars[2].type.kind == TypeKind::FbUser); CHECK(p.blocks[0].vars[2].type.name == "ton"); CHECK(p.body.size() == 4); @@ -289,7 +289,38 @@ static bool test_negative() { return true; } +// ---- VAR_TEMP 段:解析为 VarSection::Temp ---- + +static bool test_var_temp() { + using namespace compiler; + Unit u; + std::string err; + CHECK(parse_pous("t.st", + "FUNCTION_BLOCK Foo\n" + "VAR_INPUT\n in : BOOL;\nEND_VAR\n" + "VAR_OUTPUT\n out : BOOL;\nEND_VAR\n" + "VAR\n state : BOOL;\nEND_VAR\n" + "VAR_TEMP\n tmp : INT;\nEND_VAR\n" + " out := tmp > 0;\n" + "END_FUNCTION_BLOCK", + &u, &err)); + CHECK(u.pous.size() == 1); + const POU& p = u.pous[0]; + CHECK(p.kind == PouKind::FunctionBlock); + CHECK(p.blocks.size() == 4); + CHECK(p.blocks[0].section == VarSection::Input); + CHECK(p.blocks[1].section == VarSection::Output); + CHECK(p.blocks[2].section == VarSection::Local); + CHECK(p.blocks[3].section == VarSection::Temp); + CHECK(p.blocks[3].vars.size() == 1); + CHECK(p.blocks[3].vars[0].name == "tmp"); + CHECK(p.blocks[3].vars[0].type.kind == TypeKind::Scalar); + CHECK(p.blocks[3].vars[0].type.name == "int"); + return true; +} + int main() { + if (!test_var_temp()) return 1; if (!test_gvl_file()) return 1; if (!test_fb()) return 1; if (!test_program()) return 1; diff --git a/tests/src/typecheck_test.cpp b/tests/src/typecheck_test.cpp index e680967..f622dd1 100644 --- a/tests/src/typecheck_test.cpp +++ b/tests/src/typecheck_test.cpp @@ -45,10 +45,15 @@ static bool check_case(const char* dir, compiler::LinkResult* r, std::string* er } units.push_back(std::move(u)); } - if (!link_project(p, units, r, err)) { + compiler::MachineConfig cfg; + std::string cerr; + if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)) { return false; } - return check_project(p, units, *r, err); + if (!link_project(p, units, cfg, r, err)) { + return false; + } + return check_project(p, units, *r, cfg, err); } // 从内联源码构造工程(写临时目录),加载、链接、类型检查 @@ -81,10 +86,15 @@ static bool check_src(const char* src_name, const char* st_content, units.push_back(std::move(u)); } LinkResult r; - if (!link_project(p, units, &r, err)) { + compiler::MachineConfig cfg; + std::string cerr; + if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)) { return false; } - return check_project(p, units, r, err); + if (!link_project(p, units, cfg, &r, err)) { + return false; + } + return check_project(p, units, r, cfg, err); } static bool expect_type_err(const char* src_name, const char* st_content, @@ -134,14 +144,14 @@ static bool test_negative() { if (!expect_type_err("assign_mismatch", "PROGRAM MAIN\nVAR\n b : BOOL;\nEND_VAR\n" " b := 5;\nEND_PROGRAM\n", - "type mismatch in assignment to 'b'")) { + "BOOL literal must be 0 or 1")) { return false; } // INT 与 TIME 混用算术 if (!expect_type_err("int_time", "PROGRAM MAIN\nVAR\n x : INT;\n t : TIME;\n y : INT;\nEND_VAR\n" " y := x + t;\nEND_PROGRAM\n", - "TIME has no arithmetic")) { + "arithmetic operands must have same type")) { return false; } // NOT 吃 INT @@ -163,7 +173,7 @@ static bool test_negative() { "PROGRAM MAIN\nVAR\n t : TON;\n q : BOOL;\nEND_VAR\n" " t(in := TRUE, pt := TRUE);\n" " q := t.Q;\nEND_PROGRAM\n", - "FB input 'pt' expects TIME")) { + "type mismatch: BOOL vs TIME")) { return false; } // IF 条件非 BOOL diff --git a/tests/src/vm_test.cpp b/tests/src/vm_test.cpp index d5df419..b1dd07e 100644 --- a/tests/src/vm_test.cpp +++ b/tests/src/vm_test.cpp @@ -37,17 +37,34 @@ static int g_checks = 0; // ---- 数据区槽访问 ---- +/// 槽宽 = 与下一槽 addr 的差(紧凑布局下按实际宽读写,避免覆盖相邻槽); +/// 最后一槽(下一槽越界返回 0)按值区剩余字节兜底。 +static uint32_t slot_width(const vm::Machine& m, int s) { + const uint32_t a = m.slot_addr(static_cast(s)); + const uint32_t b = m.slot_addr(static_cast(s + 1)); + if (b > a && b - a < 8) { + return b - a; + } + if (b == 0) { + const uint32_t rem = static_cast(m.data_len()) - a; + return rem < 8 ? rem : 8; + } + return 8; +} + static int64_t slot(const vm::Machine& m, int s) { + const uint32_t addr = m.slot_addr(static_cast(s)); int64_t v = 0; - for (int i = 0; i < 8; ++i) { - v |= static_cast(m.data()[s * 8 + i]) << (8 * i); + for (uint32_t i = 0; i < slot_width(m, s); ++i) { + v |= static_cast(m.data()[addr + i]) << (8 * i); } return v; } static void wslot(vm::Machine& m, int s, int64_t v) { - for (int i = 0; i < 8; ++i) { - m.data()[s * 8 + i] = static_cast((v >> (8 * i)) & 0xFF); + const uint32_t addr = m.slot_addr(static_cast(s)); + for (uint32_t i = 0; i < slot_width(m, s); ++i) { + m.data()[addr + i] = static_cast((v >> (8 * i)) & 0xFF); } } @@ -73,10 +90,10 @@ static bool make_machine(const char* dir, vm::Machine* m, std::string* err) { units.push_back(std::move(u)); } LinkResult link; - if (!link_project(p, units, &link, err)) { + if (!link_project(p, units, cfg, &link, err)) { return false; } - if (!check_project(p, units, link, err)) { + if (!check_project(p, units, link, cfg, err)) { return false; } std::vector img; @@ -86,114 +103,125 @@ static bool make_machine(const char* dir, vm::Machine* m, std::string* err) { return vm::Machine::create(img, m, err); } -// ---- 手工拼映像 ---- +// ---- 手工拼映像(V2)---- struct HFunc { - std::vector code; + std::vector code; ///< 指令字节流(isa::enc_* 追加) uint32_t nregs = 8; }; -static std::vector hand_image(const std::vector& consts, +/// 发射一条指令到 HFunc(isa::enc_* → 字节流追加)。 +#define EMIT(H, fn, ...) \ + do { \ + uint8_t b_[16]; \ + size_t l_ = fn(b_ __VA_OPT__(,) __VA_ARGS__); \ + H.code.insert(H.code.end(), b_, b_ + l_); \ + } while (0) + +/// 拼装 V2 .stb 字节(头 128B + 常量表 8B + 函数表 + 字节码 + 槽表 + 值段 + SHA)。 +static std::vector hand_image(const std::vector& consts, const std::vector& funcs, uint32_t entry = 0, uint32_t cycle_limit = 100000, - const std::vector& data = {}) { - size_t code_total = 0; + const std::vector& values = {}, + const std::vector& slots = {}, + uint32_t max_stack = 4096) { + using namespace compiler; + uint32_t off_const = static_cast(kHeaderSize); + uint32_t off_funcs = off_const + static_cast(consts.size()) * kConstEntrySize; + uint32_t off_code = off_funcs + static_cast(funcs.size()) * kFuncRowSize; + uint32_t code_total = 0; for (const HFunc& f : funcs) { - code_total += f.code.size() * 4; + code_total += static_cast(f.code.size()); } - const uint32_t off_const = 104; - const uint32_t off_funcs = off_const + static_cast(consts.size()) * 12; - const uint32_t off_code = off_funcs + static_cast(funcs.size()) * 12; - const uint32_t off_data = off_code + static_cast(code_total); - const uint32_t off_end = off_data + static_cast(data.size()) + 32; // SHA 尾占位 + uint32_t off_slots = off_code + code_total; + uint32_t off_values = off_slots + static_cast(slots.size()) * kSlotRowSize; + std::vector b(off_values + values.size() + kSha256Size, 0); - std::vector b(off_end, 0); auto put32 = [&](size_t o, uint32_t v) { - b[o + 0] = static_cast(v & 0xFFu); - b[o + 1] = static_cast((v >> 8) & 0xFFu); - b[o + 2] = static_cast((v >> 16) & 0xFFu); - b[o + 3] = static_cast((v >> 24) & 0xFFu); + b[o] = static_cast(v & 0xFF); + b[o + 1] = static_cast((v >> 8) & 0xFF); + b[o + 2] = static_cast((v >> 16) & 0xFF); + b[o + 3] = static_cast((v >> 24) & 0xFF); }; auto put64 = [&](size_t o, uint64_t v) { for (int i = 0; i < 8; ++i) { - b[o + i] = static_cast((v >> (8 * i)) & 0xFFu); + b[o + i] = static_cast((v >> (8 * i)) & 0xFF); } }; - put32(0, 0x43545353u); - put32(4, 1u); - put32(8, cycle_limit); - put32(12, 10); // dt_ms - put32(24, entry); - put32(44, static_cast(consts.size())); - put32(48, static_cast(funcs.size())); - put32(52, off_const); - put32(56, off_funcs); - put32(60, off_code); - put32(64, off_data); - put32(68, off_data); - + put32(kOffMagic, kMagic); + put32(kOffVersion, kVersion); + put32(kOffCycleLimit, cycle_limit); + put32(kOffDtMs, 10); + put32(kOffEntryFnId, entry); + put32(kOffNGlobals, static_cast(slots.size())); + put32(kOffNI, 0); + put32(kOffNQ, 0); + put32(kOffNM, 0); + put32(kOffNConsts, static_cast(consts.size())); + put32(kOffNFuncs, static_cast(funcs.size())); + put32(kOffConst, off_const); + put32(kOffFuncs, off_funcs); + put32(kOffCode, off_code); + put32(kOffSlots, off_slots); + put32(kOffValues, off_values); + put32(kOffNSlots, static_cast(slots.size())); + put32(kOffValuesSize, static_cast(values.size())); + put32(kOffMeta, 0); + put32(kOffMaxStack, max_stack); + char mid[kModelIdSize]; + fill_model_id("STATOR", 2, mid); + for (size_t i = 0; i < kModelIdSize; ++i) { + b[kOffModelId + i] = static_cast(mid[i]); + } for (size_t i = 0; i < consts.size(); ++i) { - const size_t o = off_const + i * 12; - put32(o, static_cast(consts[i].tag)); - put64(o + 4, consts[i].value); + put64(off_const + i * kConstEntrySize, consts[i]); } - size_t acc = 0; + uint32_t acc = 0; for (size_t i = 0; i < funcs.size(); ++i) { - const size_t o = off_funcs + i * 12; - put32(o, funcs[i].nregs); - put32(o + 4, static_cast(acc)); - put32(o + 8, static_cast(funcs[i].code.size())); - acc += funcs[i].code.size() * 4; - } - size_t c = off_code; - for (const HFunc& f : funcs) { - for (const isa::Instr in : f.code) { - put32(c, in); - c += 4; + const size_t o = off_funcs + i * kFuncRowSize; + put32(o + kFuncNregsOff, funcs[i].nregs); + put32(o + kFuncCodeOff, acc); + put32(o + kFuncLenOff, static_cast(funcs[i].code.size())); + for (size_t j = 0; j < funcs[i].code.size(); ++j) { + b[off_code + acc + j] = funcs[i].code[j]; } + acc += static_cast(funcs[i].code.size()); } - for (size_t i = 0; i < data.size(); ++i) { - b[off_data + i] = data[i]; + for (size_t i = 0; i < slots.size(); ++i) { + put32(off_slots + i * kSlotRowSize + kSlotAddrOff, slots[i]); + put32(off_slots + i * kSlotRowSize + kSlotPadOff, 0); } - // 型号标识(STATOR1)+ 真实 SHA-256 尾(用 compiler 实现填) - const char* mid = "STATOR1"; - for (size_t i = 0; i < 32; ++i) { - b[72 + i] = i < 7 ? static_cast(mid[i]) : 0; + for (size_t i = 0; i < values.size(); ++i) { + b[off_values + i] = values[i]; } - uint8_t digest[32]; - compiler::sha256(b.data(), b.size() - 32, digest); - for (int i = 0; i < 32; ++i) { - b[b.size() - 32 + i] = digest[i]; + const size_t content_len = off_values + values.size(); + uint8_t digest[kSha256Size]; + sha256(&b[0], content_len, digest); + for (size_t i = 0; i < kSha256Size; ++i) { + b[content_len + i] = digest[i]; } return b; } -// ---- 1. 手工映像:标量 + 跳转 + 数据区 ---- - static bool test_hand_scalar() { // r8 := 5;r9 := r8 + 3;槽 0 ← r9;槽 1 → r10;JT 跳过一条 - const std::vector consts = { - {1, 5}, - {1, 3}, - {1, 1}, - }; - const std::vector funcs = {{ - { - isa::enc_imm(isa::Op::LOADK, 8, 0), - isa::enc_rr(isa::Op::MOVE, 9, 8), - isa::enc_imm(isa::Op::LOADK, 11, 1), - isa::enc_rrr(isa::Op::ADD, 9, 9, 11), - isa::enc_slot(isa::Op::STORE_GLOBAL, 9, 0), - isa::enc_slot(isa::Op::LOAD_GLOBAL, 10, 0), - isa::enc_jc(isa::Op::JT, 9, 1), // r9≠0 → 跳过下一条 - isa::enc_imm(isa::Op::LOADK, 10, 2), - isa::enc_ret(), - }, - 12, - }}; - const std::vector img = hand_image(consts, funcs, 0, 100000, - std::vector(16, 0)); // 2 槽 + HFunc hf; + hf.nregs = 12; + EMIT(hf, isa::enc_imm, "010000", 0, 3, 8, 0); // LOADK r8, c0(5) + EMIT(hf, isa::enc_rr, "11", 0, 0, 9, 8); // MOVE r9, r8 + EMIT(hf, isa::enc_imm, "010000", 0, 3, 11, 1); // LOADK r11, c1(3) + EMIT(hf, isa::enc_rrr, "011", 0, 3, 9, 9, 11); // ADD.I16 r9, r9, r11 + EMIT(hf, isa::enc_slot, "010001", 1, 0, 9, 0); // STORE.U8 s0, r9 + EMIT(hf, isa::enc_slot, "010001", 0, 0, 10, 1); // LOAD.U8 r10, s1 + EMIT(hf, isa::enc_jc, static_cast(isa::Op::JT), 9, 8); // JT r9, +8 跳过 + EMIT(hf, isa::enc_imm, "010000", 0, 3, 10, 2); // LOADK r10, c2(1) + EMIT(hf, isa::enc_ret); + const std::vector funcs = {hf}; + const std::vector consts = {5, 3, 1}; + const std::vector slots = {0, 8}; // 槽 0/1 → 值区 0/8 + const std::vector img = + hand_image(consts, funcs, 0, 100000, std::vector(16, 0), slots); vm::Machine m; std::string err; CHECK(vm::Machine::create(img, &m, &err)); @@ -201,8 +229,7 @@ static bool test_hand_scalar() { CHECK(m.reg(8) == 5); CHECK(m.reg(9) == 8); // 5 + 3 CHECK(slot(m, 0) == 8); // 槽 0 收到 8 - CHECK(m.reg(10) == 8); // 槽 1 读回(JT 跳过覆盖) - CHECK(m.cycle_count() == 8); + CHECK(m.reg(10) == 0); // 槽 1 未写=0;JT r9≠0 跳过 LOADK(覆盖未发生) return true; } @@ -211,26 +238,19 @@ static bool test_hand_scalar() { static bool test_hand_call() { // fn0(入口):r8=5、r2=7、实参 r1←r8;CALL 1;结果 r0 → r8 // fn1:r0 = r1 + r2(r1/r2 来自调用约定区复制) - const std::vector consts = { - {1, 5}, - {1, 7}, - }; - const std::vector funcs = { - {{ - isa::enc_imm(isa::Op::LOADK, 8, 0), - isa::enc_imm(isa::Op::LOADK, 2, 1), - isa::enc_rr(isa::Op::MOVE, 1, 8), - isa::enc_call(1), - isa::enc_rr(isa::Op::MOVE, 8, 0), - isa::enc_ret(), - }, - 12}, - {{ - isa::enc_rrr(isa::Op::ADD, 0, 1, 2), - isa::enc_ret(), - }, - 10}, - }; + HFunc main_f, add_f; + main_f.nregs = 12; + add_f.nregs = 10; + EMIT(main_f, isa::enc_imm, "010000", 0, 3, 8, 0); // LOADK r8, c0(5) + EMIT(main_f, isa::enc_imm, "010000", 0, 3, 2, 1); // LOADK r2, c1(7) + EMIT(main_f, isa::enc_rr, "11", 0, 0, 1, 8); // MOVE r1, r8(实参) + EMIT(main_f, isa::enc_call, 1); + EMIT(main_f, isa::enc_rr, "11", 0, 0, 8, 0); // MOVE r8, r0(结果) + EMIT(main_f, isa::enc_ret); + EMIT(add_f, isa::enc_rrr, "011", 0, 3, 0, 1, 2); // ADD.I16 r0, r1, r2 + EMIT(add_f, isa::enc_ret); + const std::vector funcs = {main_f, add_f}; + const std::vector consts = {5, 7}; const std::vector img = hand_image(consts, funcs); vm::Machine m; std::string err; @@ -244,14 +264,14 @@ static bool test_hand_call() { // ---- 3. 手工映像:自调用 → StackOverflow ---- static bool test_stack_overflow() { - const std::vector funcs = {{ - { - isa::enc_call(0), - isa::enc_ret(), - }, - 8, - }}; - const std::vector img = hand_image({}, funcs); + // fn0 自 CALL → 深度 64 → StackOverflow(深度检查) + HFunc hf; + hf.nregs = 8; + EMIT(hf, isa::enc_call, 0); + EMIT(hf, isa::enc_ret); + const std::vector funcs = {hf}; + const std::vector img = hand_image({}, funcs, 0, 100000, {}, {}, + 12 + 64 * (12 + 8 * 8)); vm::Machine m; std::string err; CHECK(vm::Machine::create(img, &m, &err)); @@ -263,14 +283,13 @@ static bool test_stack_overflow() { // ---- 4. 手工映像:越界槽 → BadSlot ---- static bool test_bad_slot() { - const std::vector funcs = {{ - { - isa::enc_slot(isa::Op::STORE_GLOBAL, 8, 60000), // 数据区 0 字节 - isa::enc_ret(), - }, - 12, - }}; - const std::vector img = hand_image({}, funcs); + // LOAD 槽号 5 ≥ n_slots 0 → BadSlot(槽表为空) + HFunc hf; + hf.nregs = 12; + EMIT(hf, isa::enc_slot, "010001", 0, 0, 8, 5); + EMIT(hf, isa::enc_ret); + const std::vector funcs = {hf}; + const std::vector img = hand_image({}, funcs, 0, 100000, {}, {}); vm::Machine m; std::string err; CHECK(vm::Machine::create(img, &m, &err)); @@ -338,13 +357,32 @@ static bool test_cases_positive() { m.run_cycle(); m.run_cycle(); CHECK(m.run_cycle() == vm::Fault::None); - CHECK(slot(m, 2) == 1); // t.Q - CHECK(slot(m, 3) == 30); // t.ET + // TMR 实例 = 槽表 1 条 + 字段偏移:IN=0 PT=8 Q=16 ET=24 + CHECK(m.data()[m.slot_addr(0) + 16] == 1); // t.Q + CHECK(m.data()[m.slot_addr(0) + 24] == 30); // t.ET // 18 TOF/CTU:跑一个周期无故障 CHECK(make_machine("tests/cases/18_tof_ctu", &m, &err)); CHECK(m.run_cycle() == vm::Fault::None); + // 21 VAR_TEMP:tmp/t 每周期重置,persist 累积 → a = 10107/10108/10109 + CHECK(make_machine("tests/cases/21_var_temp", &m, &err)); + m.run_cycle(); + CHECK(slot(m, 0) == 10107); + m.run_cycle(); + CHECK(slot(m, 0) == 10108); + CHECK(m.run_cycle() == vm::Fault::None); + CHECK(slot(m, 0) == 10109); + + // 22 多实例共享体:Counter 单函数服务 c1/c2,a = 102/204/306 + CHECK(make_machine("tests/cases/22_multi_instance", &m, &err)); + m.run_cycle(); + CHECK(slot(m, 0) == 102); + m.run_cycle(); + CHECK(slot(m, 0) == 204); + CHECK(m.run_cycle() == vm::Fault::None); + CHECK(slot(m, 0) == 306); + // 20 line1:I 组合 → Q0_0 真值表(槽 0=急停 1=I0_0 2=I0_1 3=Q0_0) CHECK(make_machine("tests/cases/20_line1", &m, &err)); struct { @@ -356,9 +394,9 @@ static bool test_cases_positive() { {1, 0, 1, 0}, }; for (const auto& t : table) { - wslot(m, 0, t.es); wslot(m, 1, t.start); wslot(m, 2, t.stop); + wslot(m, 0, t.es); CHECK(m.run_cycle() == vm::Fault::None); CHECK(slot(m, 3) == t.expect); } @@ -424,13 +462,12 @@ static bool test_step() { // ---- 9. 手工映像:坏常量 id → BadConst ---- static bool test_bad_const() { - const std::vector funcs = {{ - { - isa::enc_imm(isa::Op::LOADK, 8, 99), // 常量表为空 - isa::enc_ret(), - }, - 12, - }}; + // LOADK const_id=99 但常量表为空 → BadConst + HFunc hf; + hf.nregs = 12; + EMIT(hf, isa::enc_imm, "010000", 0, 3, 8, 99); + EMIT(hf, isa::enc_ret); + const std::vector funcs = {hf}; const std::vector img = hand_image({}, funcs); vm::Machine m; std::string err; @@ -460,10 +497,10 @@ static bool test_verify() { CHECK(compiler::load_unit(p.base_dir + "/" + f, &u, &err)); units.push_back(std::move(u)); } - compiler::LinkResult link; - CHECK(compiler::link_project(p, units, &link, &err)); compiler::MachineConfig cfg; CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)); + compiler::LinkResult link; + CHECK(compiler::link_project(p, units, cfg, &link, &err)); std::vector img; CHECK(compiler::codegen_project(p, units, link, cfg, &img, &err)); img[100] ^= 0x01; // 篡改代码段一字节 @@ -474,14 +511,11 @@ static bool test_verify() { // 型号不匹配:手拼映像改型号(重算 SHA,仅型号不一致)→ create 拒绝 { - const std::vector consts; - const std::vector funcs = {{ - { - isa::enc_ret(), - }, - 8, - }}; - std::vector img = hand_image(consts, funcs); + HFunc hf; + hf.nregs = 8; + EMIT(hf, isa::enc_ret); + const std::vector funcs = {hf}; + std::vector img = hand_image({}, funcs); for (int i = 0; i < 5; ++i) { img[72 + i] = "OTHER"[i]; } diff --git a/vm/include/vm/Image.h b/vm/include/vm/Image.h index 33bb5bd..e6614d9 100644 --- a/vm/include/vm/Image.h +++ b/vm/include/vm/Image.h @@ -1,11 +1,12 @@ /** * @file Image.h - * @brief vm 自带的 .stb 只读视图(执行器侧;compiler 各有实现) + * @brief vm 自带的 .stb 只读视图(执行器侧;compiler 各有实现,V2) * @author * @date 2026-08-21 * - * @details 格式契约见 Doc/isa/指令与映像.md(12.13 修订:头 104 = 72 + 型号标识[32], - * 文件尾 SHA-256[32])。解析头与段、补型号匹配与 SHA-256 校验都在本模块完成。 + * @details 格式契约见 Doc/isa/指令与映像.md(V2:头 128B、version=2、型号 STATOR2、 + * 常量表 8B 无 tag、槽表段 8B/条 {addr,预留}、值段变宽紧凑、文件尾 SHA-256[32])。 + * 解析头与段、补型号匹配与 SHA-256 校验都在本模块完成。 */ #pragma once @@ -19,11 +20,11 @@ namespace vm { /// 执行器内建支持型号(与 machine.toml [meta] 对齐;.stb 型号不匹配直接拒绝)。 static const char* const kModelName = "STATOR"; - /// 执行器内建支持型号版本(与 machine.toml [meta] 对齐)。 - static const uint32_t kModelVersion = 1; + /// 执行器内建支持型号版本(V2;型号标识 = "STATOR2")。 + static const uint32_t kModelVersion = 2; /** - * @brief 映像头字段(104 字节;字段顺序与 Doc/isa/指令与映像.md 一致)。 + * @brief 映像头字段(V2:128 字节;字段顺序与 Doc/isa/指令与映像.md §8.1 一致)。 * @details 全部为小端值;型号标识 32 字节 @72 不在此结构内(见 model_id())。 */ struct ImageHeader { @@ -31,41 +32,45 @@ namespace vm { uint32_t dt_ms = 0; ///< 周期时长(毫秒,定时器/计数器推进步长) uint64_t project_hash = 0; ///< 工程哈希(FNV-1a 64,只读展示) uint32_t entry_fn_id = 0; ///< 入口函数(PROGRAM MAIN)在函数表的下标 - uint32_t n_globals = 0; ///< 全局变量槽数(数据段最前段) - uint32_t n_i = 0; ///< 输入变量(I)槽数 - uint32_t n_q = 0; ///< 输出变量(Q)槽数 - uint32_t n_m = 0; ///< 中间变量(M)槽数 + uint32_t n_globals = 0; ///< 全局变量槽数(槽表最前段) + uint32_t n_i = 0; ///< 输入槽数(保留恒 0) + uint32_t n_q = 0; ///< 输出槽数(保留恒 0) + uint32_t n_m = 0; ///< 中间槽数(保留恒 0) uint32_t n_consts = 0; ///< 常量表条目数 uint32_t n_funcs = 0; ///< 函数表行数 uint32_t offset_const = 0; ///< 常量表段起点(相对文件头) uint32_t offset_funcs = 0; ///< 函数表段起点 uint32_t offset_code = 0; ///< 字节码段起点 - uint32_t offset_fb = 0; ///< FB 表段起点 - uint32_t offset_data = 0; ///< 数据段起点(之后为 SHA-256 文件尾) + uint32_t offset_slots = 0; ///< 槽表段起点 + uint32_t offset_values = 0; ///< 值段起点(16 对齐) + uint32_t n_slots = 0; ///< 槽表条目数(@104) + uint32_t values_size = 0; ///< 值段字节数(@108) + uint32_t offset_meta = 0; ///< 元数据段偏移(@112;0 = 空段) + uint32_t max_stack = 0; ///< 栈区字节数(@116,编译期算) }; /** - * @brief 函数表一行(12 字节)。 + * @brief 函数表一行(12 字节;code_len 为字节数,变长指令)。 */ struct FuncRow { uint32_t nregs = 0; ///< 本函数寄存器数(函数头 nregs) uint32_t code_offset = 0; ///< 相对字节码段起点(字节) - uint32_t code_len = 0; ///< 指令条数 + uint32_t code_len = 0; ///< 字节码长度(字节) }; /** - * @brief 常量表一项(12 字节)。 + * @brief 常量表一项(8 字节:原始位模式,无 tag——LOADK 按 func 位段解释)。 */ struct ConstEntry { - uint32_t tag = 0; ///< 类型标记(isa::types::TypeTag:0=BOOL、1=INT、2=TIME) - uint64_t value = 0; ///< 值(LOADK 时按 tag 解释) + uint64_t value = 0; ///< 原始 64 位位模式 }; /** * @brief 只读视图:指向外部缓冲的校验过的 .stb 映像。 - * @details from() 完成魔数/版本/头/各段/尺寸一致性校验;型号匹配与 SHA-256 - * 由 model_matches() / sha_ok() 另行查询(Machine::create 里一起做)。 - * 所有方法在校验失败(ok() == false)时返回安全默认值。 + * @details from() 完成魔数/版本/头/六段/尺寸一致性校验(元数据段 0 = 空段); + * 型号匹配与 SHA-256 由 model_matches() / sha_ok() 另行查询 + * (Machine::create 里一起做)。所有方法在校验失败(ok() == false) + * 时返回安全默认值。 */ class Image { public: @@ -74,9 +79,9 @@ namespace vm { * @param buf 映像缓冲;可为 nullptr * @param len 缓冲字节数 * @return 校验结果;ok() 判成功,error() 取失败原因 - * @details 校验项:非空、长度 ≥ 头、魔数 STSC、版本 1、五个段偏移单调且在 - * SHA-256 尾之前、常量表/函数表尺寸一致、代码段 4 字节对齐、 - * 入口 fn_id 在函数表内。 + * @details 校验项:非空、长度 ≥ 头、魔数 STSC、版本 2、六段偏移单调且在 + * SHA-256 尾之前、常量表/函数表/槽表尺寸一致、值段大小一致、 + * 代码段对齐、入口 fn_id 在函数表内。 */ static Image from(const uint8_t* buf, size_t len); @@ -104,19 +109,29 @@ namespace vm { FuncRow func_row(size_t i) const; /** - * @brief 读常量表一项。 + * @brief 读常量表一项(8B 原始值)。 * @param i 常量下标 * @return 该条目;越界(或校验失败)时全 0 */ ConstEntry const_entry(size_t i) const; + /** + * @brief 读槽表一项的地址(值段内偏移)。 + * @param i 槽号 + * @return addr;越界(或校验失败)时 0 + */ + uint32_t slot_addr(size_t i) const; + + /// @return 槽表段起点;校验失败返回 nullptr。 + const uint8_t* slots_bytes() const; + /// @return 字节码段起点;校验失败返回 nullptr。 const uint8_t* code_bytes() const; - /// @return 数据段起点;校验失败返回 nullptr。 + /// @return 值段起点;校验失败返回 nullptr。 const uint8_t* data_bytes() const; - /// @return 数据段字节数(不含 SHA-256 尾);校验失败返回 0。 + /// @return 值段字节数(不含 SHA-256 尾);校验失败返回 0。 size_t data_len() const; /// @return 型号标识字符串(头 @72 的 32 字节,去 '\0' 截断)。 @@ -125,7 +140,7 @@ namespace vm { /** * @brief 型号标识是否匹配。 * @param name 期望型号名(如 "STATOR") - * @param version 期望版本(如 1) + * @param version 期望版本(如 2) * @return 头内 32 字节与 "name + version" 补零后逐字节相等 */ bool model_matches(const std::string& name, uint32_t version) const; diff --git a/vm/include/vm/Machine.h b/vm/include/vm/Machine.h index 289b90d..47b8276 100644 --- a/vm/include/vm/Machine.h +++ b/vm/include/vm/Machine.h @@ -1,17 +1,19 @@ /** * @file Machine.h - * @brief 寄存器虚拟机(12.9) + * @brief 寄存器虚拟机(V2:变长指令 / 槽表两级访问 / 栈方案 C / CAL 15 条) * @author * @date 2026-08-21 * - * @details 设计说明(详见 Doc/vm/扫描周期.md 与 Doc/vm/指令执行.md): - * - 只认 .stb 映像(vm 自带读实现;Machine::create 做型号/SHA-256 校验); - * 无 GC、无堆、无线程 - * - 扫描周期:MAIN pc=0 → RET;MAIN 帧跨周期保留,调用栈深度上限 64 - * - 帧:{ fn_id, regs[nregs], ret_pc, ret_fn_id };CALL 压帧复制 r0..r7, - * RET 复制回调用方(r0=结果) - * - 数据区:8 字节定宽槽(方案 a),指令 slot = 槽号,偏移 = slot × 8 - * - 可观察性(v1 预留):step / pc / fn_id / cycle_count / call_depth / reg + * @details 设计说明(详见 Doc/vm/扫描周期.md 与 Doc/isa/阶段4-执行侧V2.md): + * - 只认 V2 .stb(vm::Image);create 做结构 + SHA-256 + 型号(STATOR2)校验 + * - 变长取指:instr_len 前缀定长;译码按前缀分区 + func + uses_* 直推 + * - 数据访问两级:slot → 槽表[slot].addr → 值区按 func 读宽/扩展、写宽 + * - 栈方案 C:create 一次性预分配 max_stack;帧头 12B(fn_id/ret_pc/ret_fn_id) + * + regs(nregs×8,int64);压帧复制 r0..r7 全宽;字节 + 深度 64 双检查; + * MAIN 帧常驻栈底,周期结束回退 sp 到 MAIN 帧顶 + * - CAL 15 条(fb_id 分派):字段布局见 Doc/isa/指令集清单.md §8.1; + * RTC 用虚拟时钟(累计周期 × dt_ms,1970 纪元) + * - 可观察性:step / pc(字节)/ fn_id / cycle_count / call_depth / reg */ #pragma once @@ -22,6 +24,8 @@ #include "vm/Image.h" #include "isa/Instr.h" +#include "isa/Op.h" +#include "isa/Types.h" namespace vm { @@ -30,30 +34,29 @@ namespace vm { */ enum class Fault { None, ///< 正常 - CycleLimit, ///< 本周期指令数超过映像头 cycle_limit(用例 6) - StackOverflow, ///< 调用栈深度超过 64 - BadOp, ///< 非法操作码 / 寄存器越界 / CALL 目标越出函数表 - BadSlot, ///< slot × 8 + 8 越出数据区 + CycleLimit, ///< 本周期指令数超过映像头 cycle_limit + StackOverflow, ///< 调用栈字节越界或深度 > 64 + BadOp, ///< 非法操作码 / 寄存器越界 / CALL 目标越界 + BadSlot, ///< 槽号越界或值区访问越界 BadConst, ///< const_id 越出常量表 }; /** - * @brief 寄存器虚拟机:按扫描周期执行 .stb 映像。 - * @details 一个实例 = 一台机器:持有映像只读视图 + 数据区工作副本 + 调用栈。 - * MAIN 帧跨周期保留(PROGRAM 变量状态持久);每周期从 pc=0 执行到 - * MAIN 的 RET,期间调用栈可进可出(上限 64 帧)。 - * I 采样写入 / Q 读回由外部(executor)经 data() 完成,VM 只管数据区。 + * @brief 寄存器虚拟机(V2)。 + * @details 一个实例 = 一台机器:值区工作副本 + 预分配栈区 + 调用栈。 + * MAIN 帧常驻栈底(跨周期保留,PROGRAM 变量状态持久); + * 每周期从 pc=0 执行到 MAIN 的 RET。 */ class Machine { public: /** - * @brief 从映像字节构建机器(校验 + 拷贝数据段 + 建 MAIN 帧)。 + * @brief 从映像字节构建机器。 * @param image .stb 映像字节 * @param out 输出 Machine(未初始化) * @param err 错误输出;可为 nullptr(静默) - * @return true 成功;false(映像非法,err 已写) - * @details 校验顺序:解析 .stb(Image::from)→ SHA-256 → 型号标识匹配 - * (内建 kModelName + kModelVersion),任一不符直接拒绝。 + * @return true 成功;false(结构/SHA/型号任一不符,err 已写) + * @details 校验顺序:Image::from 结构 → SHA-256 → 型号 STATOR2; + * 通过后拷贝值区、预分配 max_stack 栈区、建 MAIN 帧。 */ static bool create(const std::vector& image, Machine* out, std::string* err); @@ -64,96 +67,81 @@ namespace vm { */ Fault run_cycle(); - // ---- 可观察性(v1 预留,12.11 供单步/回放/TUI)---- + // ---- 可观察性 ---- - /** - * @brief 执行一条指令,停在指令边界。 - * @return true 继续;false 周期结束(MAIN 的 RET)或发生故障 - */ + /// @brief 执行一条指令,停在指令边界;false = 周期结束或故障。 bool step(); - /// @brief 最近一次故障(step 返回 false 后查询)。 Fault fault() const; - /// @brief 周期是否已结束(MAIN 的 RET 后为 true)。 bool ended() const; - - /// @brief 映像头(dt_ms / cycle_limit 等)。 + /// @brief 映像头(dt_ms / cycle_limit / max_stack 等)。 const vm::ImageHeader& header() const; - - /// @brief 当前指令下标(相对当前函数字节码段)。 + /// @brief 当前 pc(字节偏移,相对当前函数字节码段)。 uint32_t pc() const; - /// @brief 当前函数 fn_id。 uint32_t fn_id() const; - /// @brief 本周期已执行指令数。 uint32_t cycle_count() const; - /// @brief 调用栈深度(MAIN 帧 = 1)。 uint32_t call_depth() const; - - /// @brief 数据区(I 采样写入 / Q 读回由外部做;8 字节定宽槽)。 + /// @brief 值区工作副本(I 采样写入 / Q 读回由外部做)。 uint8_t* data(); const uint8_t* data() const; - - /// @brief 数据区字节数。 + /// @brief 值区字节数。 size_t data_len() const; - + /// @brief 槽表条目地址(值段内偏移;越界返回 0)。 + uint32_t slot_addr(uint32_t slot) const; /// @brief 当前帧寄存器值(i < nregs)。 int64_t reg(uint8_t i) const; - - /// @brief 当前帧寄存器数(函数头 nregs)。 + /// @brief 当前帧寄存器数。 uint32_t nregs() const; - - /// @brief 当前指令(pc_ 处;单步/观察用;pc 越界时返回 RET 哨兵)。 - isa::Instr cur_instr() const; + /// @brief 当前指令字节(pc 处;单步/观察用)。 + const uint8_t* cur_instr_bytes() const; + /// @brief 当前指令长度。 + uint32_t cur_instr_len() const; private: - /** - * @brief 调用帧。 - */ + /// 调用帧(regs 指向预分配栈区子块)。 struct Frame { - uint32_t fn_id = 0; ///< 本帧函数 - std::vector regs; ///< 寄存器文件(大小 = 函数头 nregs) - uint32_t ret_pc = 0; ///< CALL 的下一条(非 MAIN 帧有意义) - uint32_t ret_fn_id = 0; ///< 返回目标函数 + uint32_t fn_id = 0; ///< 本帧函数 + uint32_t nregs = 0; ///< 寄存器数 + int64_t* regs = nullptr;///< 寄存器文件(栈区内) + uint32_t ret_pc = 0; ///< CALL 的下一条(字节;非 MAIN 帧有意义) + uint32_t ret_fn_id = 0; ///< 返回目标函数 }; - vm::Image image_; ///< 映像只读视图(vm 自实现,指向外部缓冲) - std::vector data_; ///< 数据区工作副本(8 字节定宽槽) - std::vector frames_; ///< 调用栈([0] = MAIN,跨周期保留) - /** - * @brief 边沿检测上次输入(每槽 2 字节:[slot*2] 第一边沿、[slot*2+1] 第二边沿; - * CTU/CTD/R_TRIG/F_TRIG 用第一、CTUD 用两个),跨周期保留。 - */ - std::vector edge_prev_; - uint32_t pc_ = 0; ///< 当前 pc(相对本帧函数字节码段) - uint32_t cycle_count_ = 0; ///< 本周期指令数 - Fault fault_ = Fault::None;///< 最近一次故障 - bool ended_ = false; ///< step 后周期是否已结束 - - /// @brief 当前(栈顶)帧引用。 Frame& cur_frame(); - /// @brief 当前(栈顶)帧常量引用。 const Frame& cur_frame() const; - - /// @brief 译码执行一条;false = 故障或周期结束。 bool exec_one(); - - /// @brief 压帧(复制 r0..r7);fn_id 越界以 BadOp 表示。 Fault do_call(uint32_t fn_id); - - /// @brief 弹帧 / 周期结束(复制 r0..r7 回调用方)。 Fault do_ret(); + Fault do_cal(uint16_t fb_id, uint32_t slot); - /// @brief 执行内置 FB(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG)。 - Fault do_cal(uint8_t op, uint16_t slot); + /// 值区字段读写(实例 addr + 偏移,按 func 宽度)。 + bool read_value(uint32_t addr, uint8_t func, int64_t* out); + bool write_value(uint32_t addr, uint8_t func, int64_t v); + /// 算术(按 func:整数饱和 / 无符号 / 浮点 IEEE)。 + int64_t exec_arith(const char* name, uint8_t func, int64_t a, int64_t b); + /// 比较(按 func:有/无符号、浮点)。 + int64_t exec_cmp(const char* name, uint8_t func, int64_t a, int64_t b); + /// 比较结果(模板:有符号/无符号/浮点共用)。 + template + static int64_t cmp_result(const char* name, T a, T b); + /// 实例字段读写(slot → addr + off)。 + bool read_field(uint32_t slot, int32_t off, uint8_t func, int64_t* out); + bool write_field(uint32_t slot, int32_t off, uint8_t func, int64_t v); - /// @brief 读数据区槽(8 字节定宽;越界返回 false)。 - bool slot_get(uint16_t slot, int64_t* out) const; - - /// @brief 写数据区槽(8 字节定宽;越界返回 false)。 - bool slot_set(uint16_t slot, int64_t v); + vm::Image image_; ///< 映像只读视图 + std::vector data_; ///< 值区工作副本 + std::vector stack_; ///< 预分配栈区(max_stack 字节) + size_t sp_ = 0; ///< 栈顶(MAIN 帧之后) + std::vector frames_; ///< 调用栈([0] = MAIN,跨周期保留) + std::vector edge_prev_; ///< 边沿检测(每槽 2 字节,跨周期保留) + uint32_t pc_ = 0; ///< 当前 pc(字节,相对本帧函数字节码段) + uint32_t cycle_count_ = 0; ///< 本周期指令数 + uint64_t cycles_total_ = 0; ///< 累计周期数(RTC 虚拟时钟用) + Fault fault_ = Fault::None; ///< 最近一次故障 + bool ended_ = false; ///< step 后周期是否已结束 }; } diff --git a/vm/src/Image.cpp b/vm/src/Image.cpp index 66bb53f..2568ce4 100644 --- a/vm/src/Image.cpp +++ b/vm/src/Image.cpp @@ -1,6 +1,6 @@ /** * @file Image.cpp - * @brief vm 自带的 .stb 只读视图实现(执行器侧;compiler 各有实现) + * @brief vm 自带的 .stb 只读视图实现(执行器侧;compiler 各有实现,V2) * @author * @date 2026-08-21 */ @@ -16,20 +16,52 @@ namespace { /// 映像魔数:"STSC" 小端。 const uint32_t kMagic = 0x43545353u; - /// 映像格式版本。 - const uint32_t kVersion = 1; - /// 映像头字节数(72 原字段 + 型号标识[32] @72,12.13 修订)。 - const size_t kHeaderSize = 104; - /// 常量表一行字节数(tag:4 + value:8)。 - const size_t kConstEntrySize = 12; + /// 映像格式版本(V2)。 + const uint32_t kVersion = 2; + /// 映像头字节数(V2:128)。 + const size_t kHeaderSize = 128; + /// 常量表一行字节数(value:8,无 tag)。 + const size_t kConstEntrySize = 8; /// 函数表一行字节数(nregs/code_offset/code_len 各 4)。 const size_t kFuncRowSize = 12; + /// 槽表一行字节数(addr:4 + 预留:4)。 + const size_t kSlotRowSize = 8; /// SHA-256 摘要长度(文件尾)。 const size_t kSha256Size = 32; /// 型号标识长度(头内 @72,不足补 '\0')。 const size_t kModelIdSize = 32; - /// 小端读 32 位。 + // ---- 头字段偏移(与 Doc/isa/指令与映像.md §8.1 一致;compiler 侧另有同值一份)---- + constexpr size_t kOffMagic = 0; ///< 魔数 "STSC" + constexpr size_t kOffVersion = 4; ///< 格式版本(V2 = 2) + constexpr size_t kOffCycleLimit = 8; ///< 每周期指令数上限 + constexpr size_t kOffDtMs = 12; ///< 周期时长(毫秒) + constexpr size_t kOffProjectHash = 16; ///< 工程哈希(8 字节) + constexpr size_t kOffEntryFnId = 24; ///< 入口函数 fn_id + constexpr size_t kOffNGlobals = 28; ///< 全局槽数 + constexpr size_t kOffNI = 32; ///< 输入(I)槽数(保留恒 0) + constexpr size_t kOffNQ = 36; ///< 输出(Q)槽数(保留恒 0) + constexpr size_t kOffNM = 40; ///< 中间(M)槽数(保留恒 0) + constexpr size_t kOffNConsts = 44; ///< 常量表条目数 + constexpr size_t kOffNFuncs = 48; ///< 函数表行数 + constexpr size_t kOffConst = 52; ///< 常量表段偏移 + constexpr size_t kOffFuncs = 56; ///< 函数表段偏移 + constexpr size_t kOffCode = 60; ///< 字节码段偏移 + constexpr size_t kOffSlots = 64; ///< 槽表段偏移 + constexpr size_t kOffValues = 68; ///< 值段偏移 + constexpr size_t kOffModelId = 72; ///< 型号标识[32] + constexpr size_t kOffNSlots = 104; ///< 槽表条目数 + constexpr size_t kOffValuesSize = 108; ///< 值段字节数 + constexpr size_t kOffMeta = 112; ///< 元数据段偏移(0 = 空段) + constexpr size_t kOffMaxStack = 116; ///< 栈区字节数 + + // ---- 段内行偏移 ---- + constexpr size_t kConstValueOff = 0; ///< 常量表行内:value(8 字节) + constexpr size_t kFuncNregsOff = 0; ///< 函数表行内:nregs + constexpr size_t kFuncCodeOff = 4; ///< 函数表行内:code_offset(字节) + constexpr size_t kFuncLenOff = 8; ///< 函数表行内:code_len(字节) + constexpr size_t kSlotAddrOff = 0; ///< 槽表行内:addr + uint32_t get_le32(const uint8_t* p) { return static_cast(p[0]) | (static_cast(p[1]) << 8) @@ -37,7 +69,6 @@ namespace { | (static_cast(p[3]) << 24); } - /// 小端读 64 位。 uint64_t get_le64(const uint8_t* p) { uint64_t v = 0; for (int i = 0; i < 8; ++i) { @@ -48,7 +79,6 @@ namespace { // ---- SHA-256(执行器侧实现,与 compiler 各一份)---- - /// SHA-256 轮常量 K[0..63]。 const uint32_t kShaK[64] = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, @@ -63,14 +93,8 @@ namespace { 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}; ///< 初始哈希值 @@ -78,11 +102,6 @@ namespace { 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) { @@ -104,7 +123,6 @@ namespace { } } - /// 压缩一个满块(64 字节,w[0..63] 展开 + 64 轮)。 void process() { uint32_t w[64]; for (int i = 0; i < 16; ++i) { @@ -137,10 +155,6 @@ namespace { 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; @@ -162,31 +176,18 @@ namespace { } } - /// 大端读 32 位。 static uint32_t get_be32(const uint8_t* p) { return (static_cast(p[0]) << 24) | (static_cast(p[1]) << 16) | (static_cast(p[2]) << 8) | static_cast(p[3]); } }; - /** - * @brief 一次性 SHA-256。 - * @param data 输入字节 - * @param len 字节数 - * @param out 32 字节摘要输出 - */ void sha256(const uint8_t* data, size_t len, uint8_t out[32]) { Sha256 s; s.update(data, len); s.final(out); } - /** - * @brief 按型号标识约定填充 32 字节:name + version(如 "STATOR1"),余下补 '\0'。 - * @param name 型号名 - * @param version 版本号 - * @param out 32 字节输出缓冲 - */ void fill_model_id(const std::string& name, uint32_t version, char out[32]) { const std::string id = name + std::to_string(version); for (size_t i = 0; i < 32; ++i) { @@ -198,9 +199,6 @@ namespace { /** * @brief 从原始字节构造只读视图(不拷贝,调用方保证生命周期)。 - * @param buf 映像缓冲;可为 nullptr - * @param len 缓冲字节数 - * @return 校验结果;ok() 判成功,error() 取失败原因 */ Image Image::from(const uint8_t* buf, size_t len) { Image v; @@ -214,38 +212,42 @@ Image Image::from(const uint8_t* buf, size_t len) { v.err_ = "image too short"; return v; } - if (get_le32(buf + 0) != kMagic) { + if (get_le32(buf + kOffMagic) != kMagic) { v.err_ = "bad magic"; return v; } - if (get_le32(buf + 4) != kVersion) { + if (get_le32(buf + kOffVersion) != kVersion) { v.err_ = "bad version"; return v; } ImageHeader& h = v.hdr_; - h.cycle_limit = get_le32(buf + 8); - h.dt_ms = get_le32(buf + 12); - h.project_hash = get_le64(buf + 16); - h.entry_fn_id = get_le32(buf + 24); - h.n_globals = get_le32(buf + 28); - h.n_i = get_le32(buf + 32); - h.n_q = get_le32(buf + 36); - h.n_m = get_le32(buf + 40); - h.n_consts = get_le32(buf + 44); - h.n_funcs = get_le32(buf + 48); - h.offset_const = get_le32(buf + 52); - h.offset_funcs = get_le32(buf + 56); - h.offset_code = get_le32(buf + 60); - h.offset_fb = get_le32(buf + 64); - h.offset_data = get_le32(buf + 68); + h.cycle_limit = get_le32(buf + kOffCycleLimit); + h.dt_ms = get_le32(buf + kOffDtMs); + h.project_hash = get_le64(buf + kOffProjectHash); + h.entry_fn_id = get_le32(buf + kOffEntryFnId); + h.n_globals = get_le32(buf + kOffNGlobals); + h.n_i = get_le32(buf + kOffNI); + h.n_q = get_le32(buf + kOffNQ); + h.n_m = get_le32(buf + kOffNM); + h.n_consts = get_le32(buf + kOffNConsts); + h.n_funcs = get_le32(buf + kOffNFuncs); + h.offset_const = get_le32(buf + kOffConst); + h.offset_funcs = get_le32(buf + kOffFuncs); + h.offset_code = get_le32(buf + kOffCode); + h.offset_slots = get_le32(buf + kOffSlots); + h.offset_values = get_le32(buf + kOffValues); + h.n_slots = get_le32(buf + kOffNSlots); + h.values_size = get_le32(buf + kOffValuesSize); + h.offset_meta = get_le32(buf + kOffMeta); + h.max_stack = get_le32(buf + kOffMaxStack); - // 段校验(数据段之后是 SHA-256 文件尾;这里仅保证有尾,sha_ok() 做完整性校验) - if (len < static_cast(h.offset_data) + kSha256Size) { + // 段校验(V2:值段之后是 SHA-256 文件尾;元数据段 0 = 空段) + if (len < static_cast(h.offset_values) + kSha256Size) { v.err_ = "missing sha256 tail"; return v; } - const uint64_t offs[5] = {h.offset_const, h.offset_funcs, h.offset_code, h.offset_fb, - h.offset_data}; + const uint64_t offs[5] = {h.offset_const, h.offset_funcs, h.offset_code, h.offset_slots, + h.offset_values}; for (int i = 0; i < 5; ++i) { if (offs[i] < kHeaderSize || offs[i] > len - kSha256Size) { v.err_ = "segment offset out of range"; @@ -264,8 +266,22 @@ Image Image::from(const uint8_t* buf, size_t len) { v.err_ = "function table size mismatch"; return v; } - if ((h.offset_fb - h.offset_code) % 4 != 0) { - v.err_ = "code segment not 4-byte aligned"; + if (offs[4] - offs[3] != static_cast(h.n_slots) * kSlotRowSize) { + v.err_ = "slot table size mismatch"; + return v; + } + // 值段大小:meta 段为 0(空段)时值段到 SHA 尾;否则到 meta 起点 + if (h.offset_meta != 0) { + if (static_cast(h.offset_meta) - offs[4] != h.values_size) { + v.err_ = "values segment size mismatch"; + return v; + } + } else if (static_cast(len - kSha256Size) - offs[4] != h.values_size) { + v.err_ = "values segment size mismatch"; + return v; + } + if ((h.offset_slots - h.offset_code) % 4 != 0) { + v.err_ = "code segment not aligned"; return v; } if (h.entry_fn_id >= h.n_funcs && h.n_funcs != 0) { @@ -279,8 +295,6 @@ Image Image::from(const uint8_t* buf, size_t len) { /** * @brief 从 vector 构造只读视图。 - * @param buf 映像缓冲(引用 data(),调用方保证生命周期) - * @return 同 from(const uint8_t*, size_t) */ Image Image::from(const std::vector& buf) { return from(buf.data(), buf.size()); @@ -288,68 +302,77 @@ Image Image::from(const std::vector& buf) { /** * @brief 读函数表一行。 - * @param i 函数下标 - * @return 该行字段;越界(或校验失败)时全 0 */ FuncRow Image::func_row(size_t i) const { FuncRow r; if (ok_ && i < hdr_.n_funcs) { const uint8_t* p = buf_ + hdr_.offset_funcs + i * kFuncRowSize; - r.nregs = get_le32(p + 0); - r.code_offset = get_le32(p + 4); - r.code_len = get_le32(p + 8); + r.nregs = get_le32(p + kFuncNregsOff); + r.code_offset = get_le32(p + kFuncCodeOff); + r.code_len = get_le32(p + kFuncLenOff); } return r; } /** - * @brief 读常量表一项。 - * @param i 常量下标 - * @return 该条目;越界(或校验失败)时全 0 + * @brief 读常量表一项(8B 原始值)。 */ ConstEntry Image::const_entry(size_t i) const { ConstEntry e; if (ok_ && i < hdr_.n_consts) { const uint8_t* p = buf_ + hdr_.offset_const + i * kConstEntrySize; - e.tag = get_le32(p); - e.value = get_le64(p + 4); + e.value = get_le64(p + kConstValueOff); } return e; } +/** + * @brief 读槽表一项的地址(值段内偏移)。 + */ +uint32_t Image::slot_addr(size_t i) const { + if (ok_ && i < hdr_.n_slots) { + const uint8_t* p = buf_ + hdr_.offset_slots + i * kSlotRowSize; + return get_le32(p + kSlotAddrOff); + } + return 0; +} + +/** + * @brief 槽表段起点。 + */ +const uint8_t* Image::slots_bytes() const { + return ok_ ? buf_ + hdr_.offset_slots : nullptr; +} + /** * @brief 字节码段起点。 - * @return 段指针;校验失败返回 nullptr */ const uint8_t* Image::code_bytes() const { return ok_ ? buf_ + hdr_.offset_code : nullptr; } /** - * @brief 数据段起点。 - * @return 段指针;校验失败返回 nullptr + * @brief 值段起点。 */ const uint8_t* Image::data_bytes() const { - return ok_ ? buf_ + hdr_.offset_data : nullptr; + return ok_ ? buf_ + hdr_.offset_values : nullptr; } /** - * @brief 数据段字节数。 - * @return 段长度(不含 SHA-256 尾);校验失败返回 0 + * @brief 值段字节数。 */ size_t Image::data_len() const { - return ok_ ? (len_ - kSha256Size) - hdr_.offset_data : 0; + return ok_ ? (len_ - kSha256Size) - hdr_.offset_values : 0; } /** * @brief 型号标识字符串。 - * @return 头 @72 起 32 字节,去 '\0' 截断;校验失败返回空串 */ std::string Image::model_id() const { if (!ok_) { return ""; } - std::string s(reinterpret_cast(buf_ + 72), kModelIdSize); + std::string s(reinterpret_cast(buf_ + kOffModelId), kModelIdSize); const size_t z = s.find('\0'); if (z != std::string::npos) { s.resize(z); @@ -359,19 +382,15 @@ std::string Image::model_id() const { /** * @brief 型号标识是否匹配。 - * @param name 期望型号名(如 "STATOR") - * @param version 期望版本(如 1) - * @return 头内 32 字节与 "name + version" 补零后逐字节相等 */ bool Image::model_matches(const std::string& name, uint32_t version) const { char want[kModelIdSize]; fill_model_id(name, version, want); - return std::memcmp(buf_ + 72, want, kModelIdSize) == 0; + return std::memcmp(buf_ + kOffModelId, want, kModelIdSize) == 0; } /** * @brief 文件尾 SHA-256 完整性校验。 - * @return 对 len-32 字节内容算摘要,与文件尾 32 字节相等 */ bool Image::sha_ok() const { if (!ok_) { diff --git a/vm/src/Machine.cpp b/vm/src/Machine.cpp index e247d31..52ccf6e 100644 --- a/vm/src/Machine.cpp +++ b/vm/src/Machine.cpp @@ -1,35 +1,29 @@ /** * @file Machine.cpp - * @brief 寄存器虚拟机实现(12.9:译码 switch + CALL/RET 帧栈 + CAL_* 定时器) + * @brief 寄存器虚拟机实现(V2:变长取指 / 槽表两级 / 栈方案 C / CAL 15 条) * @author * @date 2026-08-21 * - * @details 设计说明(详见 Doc/vm/指令执行.md): - * - 取指:函数表 → code_offset(字节)→ u32;执行后 pc 先 +1,跳转再 pc += off - * - 数据区 8 字节定宽槽:偏移 = slot × 8 - * - 寄存器越界检查仅对把字段当寄存器的指令;a|b 作偏移/槽号/常量 id 时跳过 + * @details 执行流程(exec_one): + * - 取指:函数表 → code_offset(字节)+ code_len(字节);pc 越界 → BadOp; + * instr_len 前缀定长搬字节 + * - 周期计数:cycle_count++;超 cycle_limit → CycleLimit + * - pc 先指向下一条(字节);跳转再 pc += off(相对下一条) + * - 译码:前缀分区 + func;寄存器越界按 uses_* 直推 + * - 分发:按指令名(if 链);算术/比较按 func(整数饱和/无符号/浮点 IEEE) */ #include "vm/Machine.h" #include +#include #include "isa/Encode.h" -#include "isa/Instr.h" -#include "isa/Op.h" -#include "isa/Types.h" namespace vm { /** - * @brief 从映像字节构建机器(校验 + 拷贝数据段 + 建 MAIN 帧)。 - * @param image .stb 映像字节 - * @param out 输出 Machine(未初始化) - * @param err 错误输出;可为 nullptr(静默) - * @return true 成功;false(映像非法,err 已写) - * @details 校验顺序:解析 .stb(Image::from)→ SHA-256 → 型号标识匹配; - * 通过后拷贝数据段为工作副本,初始化边沿检测缓冲(每槽 2 字节), - * 建 MAIN 帧(跨周期保留)并把执行状态复位。 + * @brief 从映像字节构建机器。 */ bool Machine::create(const std::vector& image, Machine* out, std::string* err) { @@ -40,7 +34,6 @@ bool Machine::create(const std::vector& image, Machine* out, } return false; } - // 12.13:型号匹配与 SHA-256 校验(不匹配/篡改直接拒绝) if (!out->image_.sha_ok()) { if (err) { *err = "image sha256 mismatch"; @@ -54,22 +47,40 @@ bool Machine::create(const std::vector& image, Machine* out, } return false; } - // 数据区工作副本(8 字节定宽槽) + // 值区工作副本 out->data_.assign(out->image_.data_bytes(), out->image_.data_bytes() + out->image_.data_len()); - out->edge_prev_.assign(out->data_len() / 8 * 2, 0); + // 边沿检测缓冲(每槽 2 字节) + out->edge_prev_.assign(out->image_.header().n_slots * 2, 0); + // 栈方案 C:预分配 max_stack + out->stack_.assign(out->image_.header().max_stack, 0); - // MAIN 帧(跨周期保留) + // MAIN 帧常驻栈底:帧头 12B + nregs×8 const uint32_t entry = out->image_.header().entry_fn_id; const vm::FuncRow main = out->image_.func_row(entry); - out->frames_.clear(); + const size_t main_need = 12 + static_cast(main.nregs) * 8; + if (main_need > out->stack_.size()) { + if (err) { + *err = "max_stack too small for MAIN frame"; + } + return false; + } Frame f; f.fn_id = entry; - f.regs.assign(main.nregs, 0); - out->frames_.push_back(std::move(f)); + f.nregs = main.nregs; + f.regs = reinterpret_cast(out->stack_.data() + 12); + f.ret_pc = 0; + f.ret_fn_id = entry; + for (uint32_t i = 0; i < main.nregs; ++i) { + f.regs[i] = 0; + } + out->frames_.clear(); + out->frames_.push_back(f); + out->sp_ = main_need; out->pc_ = 0; out->cycle_count_ = 0; + out->cycles_total_ = 0; out->fault_ = Fault::None; out->ended_ = false; return true; @@ -77,9 +88,6 @@ bool Machine::create(const std::vector& image, Machine* out, /** * @brief 跑一个扫描周期:MAIN pc=0 执行到 RET。 - * @return 故障(None = 正常完成) - * @details 循环执行 exec_one() 直到周期结束或故障;结束/故障后清空调用栈 - * (保留 MAIN 帧,PROGRAM 变量状态跨周期持久)。 */ Fault Machine::run_cycle() { cycle_count_ = 0; @@ -91,17 +99,16 @@ Fault Machine::run_cycle() { break; } } - // 周期结束:清调用栈(保留 MAIN 帧,PROGRAM 变量状态跨周期) + // 周期结束:回退 sp 到 MAIN 帧顶(保留 MAIN 帧) + const Frame& main = frames_[0]; + sp_ = 12 + static_cast(main.nregs) * 8; while (frames_.size() > 1) { frames_.pop_back(); } + ++cycles_total_; return fault_; } -/** - * @brief 执行一条指令,停在指令边界。 - * @return true 继续;false 周期结束(MAIN 的 RET)或发生故障 - */ bool Machine::step() { if (ended_ || fault_ != Fault::None) { return false; @@ -119,262 +126,453 @@ uint32_t Machine::call_depth() const { return static_cast(frames_.size uint8_t* Machine::data() { return data_.data(); } const uint8_t* Machine::data() const { return data_.data(); } size_t Machine::data_len() const { return data_.size(); } +uint32_t Machine::slot_addr(uint32_t slot) const { return image_.slot_addr(slot); } int64_t Machine::reg(uint8_t i) const { return cur_frame().regs[i]; } -/** - * @brief 当前帧寄存器数。 - * @return 栈顶帧 regs 大小(函数头 nregs) - */ uint32_t Machine::nregs() const { - return static_cast(cur_frame().regs.size()); + return cur_frame().nregs; +} + +const uint8_t* Machine::cur_instr_bytes() const { + const vm::FuncRow row = image_.func_row(cur_frame().fn_id); + if (pc_ >= row.code_len) { + return nullptr; + } + return image_.code_bytes() + row.code_offset + pc_; +} + +uint32_t Machine::cur_instr_len() const { + const uint8_t* p = cur_instr_bytes(); + return p != nullptr ? static_cast(isa::instr_len(p)) : 0; +} + +Machine::Frame& Machine::cur_frame() { return frames_.back(); } +const Machine::Frame& Machine::cur_frame() const { return frames_.back(); } + +// ---- 值区读写(两级访问:slot → addr → 值区按 func)---- + +/** + * @brief 读值区(addr 处按 func 宽度 + 扩展方向)。 + */ +bool Machine::read_value(uint32_t addr, uint8_t func, int64_t* out) { + const int w = isa::types::kFuncWidth[func]; + if (w <= 0 || addr + static_cast(w) > data_.size()) { + return false; + } + const uint8_t* p = data_.data() + addr; + if (func == 8) { // F32 位模式 + float f; + std::memcpy(&f, p, 4); + int32_t bits; + std::memcpy(&bits, &f, 4); + *out = static_cast(static_cast(bits)); + return true; + } + if (func == 9) { // F64 位模式 + double d; + std::memcpy(&d, p, 8); + uint64_t bits; + std::memcpy(&bits, &d, 8); + *out = static_cast(bits); + return true; + } + uint64_t v = 0; + for (int i = 0; i < w; ++i) { + v |= static_cast(p[i]) << (8 * i); + } + if (isa::types::kFuncSigned[func] && w < 8) { + const uint64_t sign = static_cast(1) << (8 * w - 1); + if (v & sign) { + v |= ~((static_cast(1) << (8 * w)) - 1); + } + } + *out = static_cast(v); + return true; } /** - * @brief 当前指令(pc_ 处)。 - * @return 指令字;pc_ 越出本函数字节码段时返回 RET 哨兵 - * @details 供单步/观察用;不修改任何执行状态。 + * @brief 写值区(addr 处按 func 宽度)。 */ -isa::Instr Machine::cur_instr() const { - const vm::FuncRow row = image_.func_row(cur_frame().fn_id); - const uint8_t* base = image_.code_bytes() + row.code_offset; - if (pc_ >= row.code_len) { - return isa::pack(isa::Op::RET, 0, 0, 0); +bool Machine::write_value(uint32_t addr, uint8_t func, int64_t v) { + const int w = isa::types::kFuncWidth[func]; + if (w <= 0 || addr + static_cast(w) > data_.size()) { + return false; } - return reinterpret_cast(base)[pc_]; + uint8_t* p = data_.data() + addr; + if (func == 8) { // F32:int64 低 32 位 → float 位模式 → 4 字节 + const uint32_t bits = static_cast(static_cast(v)); + float f; + std::memcpy(&f, &bits, 4); + std::memcpy(p, &f, 4); + return true; + } + if (func == 9) { // F64:int64 位模式 → 8 字节 + std::memcpy(p, &v, 8); + return true; + } + for (int i = 0; i < w; ++i) { + p[i] = static_cast((static_cast(v) >> (8 * i)) & 0xFFu); + } + return true; } -/// @brief 当前(栈顶)帧引用。 -Machine::Frame& Machine::cur_frame() { return frames_.back(); } +bool Machine::read_field(uint32_t slot, int32_t off, uint8_t func, int64_t* out) { + if (slot >= image_.header().n_slots) { + return false; + } + const uint32_t addr = image_.slot_addr(slot) + static_cast(off); + return read_value(addr, func, out); +} -/// @brief 当前(栈顶)帧常量引用。 -const Machine::Frame& Machine::cur_frame() const { return frames_.back(); } +bool Machine::write_field(uint32_t slot, int32_t off, uint8_t func, int64_t v) { + if (slot >= image_.header().n_slots) { + return false; + } + const uint32_t addr = image_.slot_addr(slot) + static_cast(off); + return write_value(addr, func, v); +} + +// ---- 指令执行 ---- /** * @brief 译码执行一条指令。 - * @return true 继续;false 故障(fault_ 已置)或周期结束 - * @details 流程:取指(越界 → BadOp)→ 周期计数(超 cycle_limit → CycleLimit)→ - * pc 先 +1(跳转按"相对下一条"叠加)→ 寄存器越界检查 → 按操作码分发。 - * 标量指令用 isa::types 饱和算术;LOAD/STORE 走 slot_get/slot_set; - * CALL/RET 走 do_call/do_ret;CAL_* 走 do_cal。 */ bool Machine::exec_one() { - // ---- 取指 ---- - const vm::FuncRow row = image_.func_row(cur_frame().fn_id); + Frame& fr = cur_frame(); + const vm::FuncRow row = image_.func_row(fr.fn_id); if (pc_ >= row.code_len) { fault_ = Fault::BadOp; return false; } - const uint8_t* base = image_.code_bytes() + row.code_offset; - const isa::Instr w = reinterpret_cast(base)[pc_]; + const uint8_t* code = image_.code_bytes() + row.code_offset + pc_; + const size_t ilen = isa::instr_len(code); + if (pc_ + ilen > row.code_len) { + fault_ = Fault::BadOp; + return false; + } - // ---- 周期指令计数 ---- ++cycle_count_; if (cycle_count_ > image_.header().cycle_limit) { fault_ = Fault::CycleLimit; return false; } + pc_ += static_cast(ilen); // 先指向下一条(字节) - // 先指向下一条(跳转指令再按"相对下一条"语义叠加偏移) - ++pc_; - - const isa::Op op = isa::op(w); - const uint8_t rd = isa::rd(w); - const uint8_t ra = isa::a(w); - const uint8_t rb = isa::b(w); - std::vector& regs = cur_frame().regs; - - // 寄存器越界检查(仅对把字段当寄存器的指令;a|b 作偏移/槽号/常量 id 时跳过) - const bool uses_rd = op == isa::Op::MOVE || op == isa::Op::NOT || op == isa::Op::AND || - op == isa::Op::OR || op == isa::Op::ADD || op == isa::Op::SUB || - op == isa::Op::MUL || op == isa::Op::DIV || op == isa::Op::CMP_EQ || - op == isa::Op::CMP_NE || op == isa::Op::CMP_LT || - op == isa::Op::CMP_LE || op == isa::Op::CMP_GT || - op == isa::Op::CMP_GE || op == isa::Op::JT || op == isa::Op::JF || - op == isa::Op::LOADK || op == isa::Op::LOAD_I || - op == isa::Op::LOAD_M || op == isa::Op::LOAD_GLOBAL || - op == isa::Op::STORE_Q || op == isa::Op::STORE_M || - op == isa::Op::STORE_GLOBAL; - const bool uses_ra = op == isa::Op::MOVE || op == isa::Op::NOT || op == isa::Op::AND || - op == isa::Op::OR || op == isa::Op::ADD || op == isa::Op::SUB || - op == isa::Op::MUL || op == isa::Op::DIV || op == isa::Op::CMP_EQ || - op == isa::Op::CMP_NE || op == isa::Op::CMP_LT || - op == isa::Op::CMP_LE || op == isa::Op::CMP_GT || - op == isa::Op::CMP_GE; - const bool uses_rb = uses_ra; - if ((uses_rd && rd >= regs.size()) || (uses_ra && ra >= regs.size()) || - (uses_rb && rb >= regs.size())) { + const isa::Decoded d = isa::decode(code, ilen); + const isa::OpFormat fmt = isa::format_of(d.prefix, d.op); + if (fmt == isa::OpFormat::RESERVED) { fault_ = Fault::BadOp; return false; } + // 寄存器越界(uses_* 前缀直推) + if ((isa::uses_rd(fmt) && d.rd >= fr.nregs) || + (isa::uses_rs1(fmt) && d.rs1 >= fr.nregs) || + (isa::uses_rs2(fmt) && d.rs2 >= fr.nregs)) { + fault_ = Fault::BadOp; + return false; + } + const uint8_t func = d.func; + int64_t v1 = 0, v2 = 0, r = 0; - switch (op) { - case isa::Op::MOVE: - regs[rd] = regs[ra]; - return true; - case isa::Op::LOADK: { - const uint16_t cid = isa::imm16(w); - if (cid >= image_.header().n_consts) { - fault_ = Fault::BadConst; - return false; - } - const vm::ConstEntry c = image_.const_entry(cid); - // 按类型标记解释常量值:BOOL 归一化 0/1,INT 符号扩展,TIME 全 64 位 - if (c.tag == isa::types::Bool) { - regs[rd] = c.value ? 1 : 0; - } else if (c.tag == isa::types::Int) { - regs[rd] = static_cast(c.value); - } else { - regs[rd] = static_cast(c.value); - } - return true; + // 按指令名分发 + const char* name = isa::mnemonic(d.prefix, d.op); + if (std::strcmp(name, "MOVE") == 0) { + // func 0..3 = 宽度 1/2/4/8B,高位清 0 + static const int kMoveWidth[4] = {1, 2, 4, 8}; + const int w = kMoveWidth[d.func & 3]; + int64_t v = fr.regs[d.rs1]; + if (w < 8) { + v &= (static_cast(1) << (8 * w)) - 1; } - case isa::Op::NOT: - regs[rd] = (regs[ra] == 0) ? 1 : 0; - return true; - case isa::Op::AND: - regs[rd] = (regs[ra] != 0 && regs[rb] != 0) ? 1 : 0; - return true; - case isa::Op::OR: - regs[rd] = (regs[ra] != 0 || regs[rb] != 0) ? 1 : 0; - return true; - case isa::Op::ADD: - regs[rd] = isa::types::sat_add(static_cast(regs[ra]), - static_cast(regs[rb])); - return true; - case isa::Op::SUB: - regs[rd] = isa::types::sat_sub(static_cast(regs[ra]), - static_cast(regs[rb])); - return true; - case isa::Op::MUL: - regs[rd] = isa::types::sat_mul(static_cast(regs[ra]), - static_cast(regs[rb])); - return true; - case isa::Op::DIV: - regs[rd] = isa::types::sat_div(static_cast(regs[ra]), - static_cast(regs[rb])); - return true; - case isa::Op::CMP_EQ: - case isa::Op::CMP_NE: - case isa::Op::CMP_LT: - case isa::Op::CMP_LE: - case isa::Op::CMP_GT: - case isa::Op::CMP_GE: { - const int64_t l = regs[ra]; - const int64_t r = regs[rb]; - bool res = false; - switch (op) { - case isa::Op::CMP_EQ: res = (l == r); break; - case isa::Op::CMP_NE: res = (l != r); break; - case isa::Op::CMP_LT: res = (l < r); break; - case isa::Op::CMP_LE: res = (l <= r); break; - case isa::Op::CMP_GT: res = (l > r); break; - case isa::Op::CMP_GE: res = (l >= r); break; - default: break; - } - regs[rd] = res ? 1 : 0; - return true; - } - case isa::Op::JMP: - pc_ += isa::off16(w); - return true; - case isa::Op::JT: - if (regs[rd] != 0) { - pc_ += isa::off16(w); - } - return true; - case isa::Op::JF: - if (regs[rd] == 0) { - pc_ += isa::off16(w); - } - return true; - case isa::Op::LOAD_I: - case isa::Op::LOAD_M: - case isa::Op::LOAD_GLOBAL: { - const uint16_t slot = isa::imm16(w); - if (!slot_get(slot, ®s[rd])) { - fault_ = Fault::BadSlot; - return false; - } - return true; - } - case isa::Op::STORE_Q: - case isa::Op::STORE_M: - case isa::Op::STORE_GLOBAL: { - const uint16_t slot = isa::imm16(w); - if (!slot_set(slot, regs[rd])) { - fault_ = Fault::BadSlot; - return false; - } - return true; - } - case isa::Op::CALL: - fault_ = do_call(isa::imm16(w)); - return fault_ == Fault::None; - case isa::Op::RET: - fault_ = do_ret(); - if (fault_ != Fault::None) { - return false; - } - return !ended_; // MAIN 的 RET → 周期结束(返回 false) - case isa::Op::CAL_TON: - case isa::Op::CAL_TOF: - case isa::Op::CAL_TP: - case isa::Op::CAL_CTU: - case isa::Op::CAL_CTD: - case isa::Op::CAL_CTUD: - case isa::Op::CAL_R_TRIG: - case isa::Op::CAL_F_TRIG: - fault_ = do_cal(static_cast(op), isa::imm16(w)); - return fault_ == Fault::None; - default: - fault_ = Fault::BadOp; + fr.regs[d.rd] = v; + return true; + } + if (std::strcmp(name, "LOADK") == 0) { + const uint16_t cid = static_cast(d.imm32 & 0xFFFF); + if (cid >= image_.header().n_consts) { + fault_ = Fault::BadConst; return false; + } + // 常量表 8B 原始值 → 按 func 读低 w 字节 + 扩展 + const uint64_t raw = image_.const_entry(cid).value; + const int w = isa::types::kFuncWidth[func]; + int64_t v = static_cast(raw); + if (func == 8) { + float f; + std::memcpy(&f, &raw, 4); + int32_t bits; + std::memcpy(&bits, &f, 4); + fr.regs[d.rd] = static_cast(static_cast(bits)); + return true; + } + if (func == 9) { + fr.regs[d.rd] = static_cast(raw); + return true; + } + if (w < 8) { + if (isa::types::kFuncSigned[func]) { + v = static_cast(static_cast(raw)); // 符号扩展(宽 ≤2) + if (w == 4) v = static_cast(static_cast(raw)); + if (w == 1) v = static_cast(static_cast(raw)); + } else { + v &= (static_cast(1) << (8 * w)) - 1; + } + } + fr.regs[d.rd] = v; + return true; } + if (std::strcmp(name, "LOAD") == 0) { + if (!read_value(image_.slot_addr(d.imm32), func, &v1)) { + fault_ = Fault::BadSlot; + return false; + } + fr.regs[d.rd] = v1; + return true; + } + if (std::strcmp(name, "STORE") == 0) { + if (!write_value(image_.slot_addr(d.imm32), func, fr.regs[d.rd])) { + fault_ = Fault::BadSlot; + return false; + } + return true; + } + if (std::strcmp(name, "LOAD_OFF") == 0) { + if (!read_field(d.imm32, d.off_hi, func, &v1)) { + fault_ = Fault::BadSlot; + return false; + } + fr.regs[d.rd] = v1; + return true; + } + if (std::strcmp(name, "STORE_OFF") == 0) { + if (!write_field(d.imm32, d.off_hi, func, fr.regs[d.rd])) { + fault_ = Fault::BadSlot; + return false; + } + return true; + } + if (std::strcmp(name, "LOAD_PTR") == 0) { + // 基址寻址:rs1 寄存器 = 值区地址(实例指针)+ 偏移,跳过槽表直读 + const uint32_t base = static_cast(fr.regs[d.rs1]); + if (!read_value(base + static_cast(d.off_hi), func, &v1)) { + fault_ = Fault::BadSlot; + return false; + } + fr.regs[d.rd] = v1; + return true; + } + if (std::strcmp(name, "STORE_PTR") == 0) { + const uint32_t base = static_cast(fr.regs[d.rs1]); + if (!write_value(base + static_cast(d.off_hi), func, + fr.regs[d.rd])) { + fault_ = Fault::BadSlot; + return false; + } + return true; + } + if (std::strcmp(name, "NOT") == 0) { + // 逻辑非(恒产 0/1):BOOL 域语义;位串逐位取反需专用指令(v1 无) + fr.regs[d.rd] = (fr.regs[d.rs1] == 0) ? 1 : 0; + return true; + } + if (std::strcmp(name, "AND") == 0 || std::strcmp(name, "OR") == 0 || + std::strcmp(name, "XOR") == 0 || std::strcmp(name, "NAND") == 0 || + std::strcmp(name, "NOR") == 0 || std::strcmp(name, "XNOR") == 0) { + const uint64_t a = static_cast(fr.regs[d.rs1]); + const uint64_t b = static_cast(fr.regs[d.rs2]); + uint64_t x = 0; + if (std::strcmp(name, "AND") == 0) x = a & b; + else if (std::strcmp(name, "OR") == 0) x = a | b; + else if (std::strcmp(name, "XOR") == 0) x = a ^ b; + else if (std::strcmp(name, "NAND") == 0) x = ~(a & b); + else if (std::strcmp(name, "NOR") == 0) x = ~(a | b); + else x = ~(a ^ b); + fr.regs[d.rd] = static_cast(x); + return true; + } + if (std::strcmp(name, "NEG") == 0) { + if (func == 8) { + float f; + std::memcpy(&f, &fr.regs[d.rs1], 4); + f = -f; + int32_t bits; + std::memcpy(&bits, &f, 4); + fr.regs[d.rd] = static_cast(static_cast(bits)); + } else if (func == 9) { + double dd; + std::memcpy(&dd, &fr.regs[d.rs1], 8); + dd = -dd; + std::memcpy(&fr.regs[d.rd], &dd, 8); + } else { + fr.regs[d.rd] = -fr.regs[d.rs1]; + } + return true; + } + if (std::strcmp(name, "ADD") == 0 || std::strcmp(name, "SUB") == 0 || + std::strcmp(name, "MUL") == 0 || std::strcmp(name, "DIV") == 0) { + r = exec_arith(name, func, fr.regs[d.rs1], fr.regs[d.rs2]); + fr.regs[d.rd] = r; + return true; + } + if (std::strncmp(name, "CMP_", 4) == 0) { + r = exec_cmp(name, func, fr.regs[d.rs1], fr.regs[d.rs2]); + fr.regs[d.rd] = r; + return true; + } + if (std::strcmp(name, "JMP") == 0) { + pc_ += static_cast(d.imm32); + return true; + } + if (std::strcmp(name, "JT") == 0) { + if (fr.regs[d.rd] != 0) { + pc_ += static_cast(d.imm32); + } + return true; + } + if (std::strcmp(name, "JF") == 0) { + if (fr.regs[d.rd] == 0) { + pc_ += static_cast(d.imm32); + } + return true; + } + if (std::strcmp(name, "CALL") == 0) { + fault_ = do_call(d.imm32); + return fault_ == Fault::None; + } + if (std::strcmp(name, "RET") == 0) { + fault_ = do_ret(); + if (fault_ != Fault::None) { + return false; + } + return !ended_; + } + if (std::strcmp(name, "CAL") == 0) { + const uint16_t fb_id = static_cast(d.imm32 & 0xFFFF); + const uint32_t slot = d.imm32 >> 16; + fault_ = do_cal(fb_id, slot); + return fault_ == Fault::None; + } + if (std::strcmp(name, "NOP") == 0) { + return true; + } + fault_ = Fault::BadOp; + return false; } /** - * @brief 读数据区槽(8 字节定宽,小端)。 - * @param slot 槽号 - * @param out 输出值 - * @return false = slot × 8 + 8 越出数据区 + * @brief 算术(按 func:整数饱和 / 无符号 / 浮点 IEEE)。 */ -bool Machine::slot_get(uint16_t slot, int64_t* out) const { - const size_t off = static_cast(slot) * 8; - if (off + 8 > data_.size()) { - return false; +int64_t Machine::exec_arith(const char* name, uint8_t func, int64_t a, int64_t b) { + const bool is_add = std::strcmp(name, "ADD") == 0; + const bool is_sub = std::strcmp(name, "SUB") == 0; + const bool is_mul = std::strcmp(name, "MUL") == 0; + if (func == 8) { + float x, y; + std::memcpy(&x, &a, 4); + std::memcpy(&y, &b, 4); + float f = 0; + if (is_add) f = x + y; + else if (is_sub) f = x - y; + else if (is_mul) f = x * y; + else f = x / y; + int32_t bits; + std::memcpy(&bits, &f, 4); + return static_cast(static_cast(bits)); } - int64_t v = 0; - for (int i = 0; i < 8; ++i) { - v |= static_cast(data_[off + i]) << (8 * i); + if (func == 9) { + double x, y; + std::memcpy(&x, &a, 8); + std::memcpy(&y, &b, 8); + double d = 0; + if (is_add) d = x + y; + else if (is_sub) d = x - y; + else if (is_mul) d = x * y; + else d = x / y; + return static_cast(d); // 位模式直存 } - *out = v; - return true; + // 整数:按宽度与符号选饱和模板 + const int w = isa::types::kFuncWidth[func]; + const bool sgn = isa::types::kFuncSigned[func]; + if (w == 1 && sgn) { + const int8_t x = static_cast(a), y = static_cast(b); + return is_add ? isa::types::sat_add(x, y) + : is_sub ? isa::types::sat_sub(x, y) + : is_mul ? isa::types::sat_mul(x, y) + : isa::types::sat_div(x, y); + } + if (w == 1 && !sgn) { + const uint8_t x = static_cast(a), y = static_cast(b); + return is_add ? isa::types::sat_add(x, y) + : is_sub ? isa::types::sat_sub(x, y) + : is_mul ? isa::types::sat_mul(x, y) + : isa::types::sat_div(x, y); + } + if (w == 2 && sgn) { + const int16_t x = static_cast(a), y = static_cast(b); + return is_add ? isa::types::sat_add(x, y) + : is_sub ? isa::types::sat_sub(x, y) + : is_mul ? isa::types::sat_mul(x, y) + : isa::types::sat_div(x, y); + } + if (w == 2 && !sgn) { + const uint16_t x = static_cast(a), y = static_cast(b); + return is_add ? isa::types::sat_add(x, y) + : is_sub ? isa::types::sat_sub(x, y) + : is_mul ? isa::types::sat_mul(x, y) + : isa::types::sat_div(x, y); + } + if (w == 4 && sgn) { + const int32_t x = static_cast(a), y = static_cast(b); + return is_add ? isa::types::sat_add(x, y) + : is_sub ? isa::types::sat_sub(x, y) + : is_mul ? isa::types::sat_mul(x, y) + : isa::types::sat_div(x, y); + } + if (w == 4 && !sgn) { + const uint32_t x = static_cast(a), y = static_cast(b); + return is_add ? isa::types::sat_add(x, y) + : is_sub ? isa::types::sat_sub(x, y) + : is_mul ? isa::types::sat_mul(x, y) + : isa::types::sat_div(x, y); + } + if (sgn) { + const int64_t x = a, y = b; + return is_add ? isa::types::sat_add(x, y) + : is_sub ? isa::types::sat_sub(x, y) + : is_mul ? isa::types::sat_mul(x, y) + : isa::types::sat_div(x, y); + } + const uint64_t x = static_cast(a), y = static_cast(b); + return is_add ? isa::types::sat_add(x, y) + : is_sub ? isa::types::sat_sub(x, y) + : is_mul ? isa::types::sat_mul(x, y) + : isa::types::sat_div(x, y); } /** - * @brief 写数据区槽(8 字节定宽,小端)。 - * @param slot 槽号 - * @param v 要写入的值 - * @return false = slot × 8 + 8 越出数据区 + * @brief 比较(按 func:有/无符号、浮点 IEEE)。 */ -bool Machine::slot_set(uint16_t slot, int64_t v) { - const size_t off = static_cast(slot) * 8; - if (off + 8 > data_.size()) { - return false; +int64_t Machine::exec_cmp(const char* name, uint8_t func, int64_t a, int64_t b) { + if (func == 8) { + float x, y; + std::memcpy(&x, &a, 4); + std::memcpy(&y, &b, 4); + return cmp_result(name, x, y); } - for (int i = 0; i < 8; ++i) { - data_[off + i] = static_cast((v >> (8 * i)) & 0xFFu); + if (func == 9) { + double x, y; + std::memcpy(&x, &a, 8); + std::memcpy(&y, &b, 8); + return cmp_result(name, x, y); } - return true; + if (isa::types::kFuncSigned[func]) { + return cmp_result(name, a, b); + } + return cmp_result(name, static_cast(a), static_cast(b)); } /** - * @brief 调用:压新帧。 - * @param fn_id 目标函数(函数表下标) - * @return Fault(fn_id 越界 → BadOp;栈深 ≥ 64 → StackOverflow;成功 → None) - * @details 新帧寄存器清零,ret_pc = 当前 pc(已指向 CALL 下一条); - * 按调用约定把调用方 r0..r7 复制进新帧(实参已由调用点 MOVE 进 r1..r7); - * pc 复位到新函数起点。 + * @brief 调用:压新帧(栈方案 C)。 */ Fault Machine::do_call(uint32_t fn_id) { if (fn_id >= image_.header().n_funcs) { @@ -384,27 +582,38 @@ Fault Machine::do_call(uint32_t fn_id) { return Fault::StackOverflow; } const vm::FuncRow row = image_.func_row(fn_id); + const size_t need = 12 + static_cast(row.nregs) * 8; + if (sp_ + need > stack_.size()) { + return Fault::StackOverflow; + } + Frame& caller = cur_frame(); + uint8_t* p = stack_.data() + sp_; + uint32_t* hdr = reinterpret_cast(p); + hdr[0] = fn_id; + hdr[1] = pc_; // 已指向 CALL 下一条(字节) + hdr[2] = caller.fn_id; Frame f; f.fn_id = fn_id; - f.regs.assign(row.nregs, 0); - f.ret_pc = pc_; // 已指向 CALL 的下一条 - f.ret_fn_id = cur_frame().fn_id; - // 调用约定:复制当前帧 r0..r7 → 新帧(实参已由调用点 MOVE 进 r1..r7) - const size_t n = std::min(8, std::min(cur_frame().regs.size(), f.regs.size())); - for (size_t i = 0; i < n; ++i) { - f.regs[i] = cur_frame().regs[i]; + f.nregs = row.nregs; + f.regs = reinterpret_cast(p + 12); + f.ret_pc = pc_; + f.ret_fn_id = caller.fn_id; + for (uint32_t i = 0; i < row.nregs; ++i) { + f.regs[i] = 0; } - frames_.push_back(std::move(f)); + // 调用约定:复制 r0..r7 全宽 + const size_t n = std::min(8, std::min(caller.nregs, f.nregs)); + for (size_t i = 0; i < n; ++i) { + f.regs[i] = caller.regs[i]; + } + frames_.push_back(f); + sp_ += need; pc_ = 0; return Fault::None; } /** * @brief 返回:弹帧 / 周期结束。 - * @return Fault(成功 → None) - * @details MAIN 帧的 RET:ended_ = true(周期结束,不弹帧)。 - * 非 MAIN 帧:按调用约定把 r0..r7 复制回调用方(r0 = 结果,r1..r7 原样), - * pc 恢复为 ret_pc,弹帧。 */ Fault Machine::do_ret() { if (frames_.size() <= 1) { @@ -413,153 +622,122 @@ Fault Machine::do_ret() { } Frame& cur = cur_frame(); Frame& caller = frames_[frames_.size() - 2]; - // 调用约定:复制当前帧 r0..r7 → 调用方(r0 = 结果,r1..r7 原样返回) - const size_t n = std::min(8, std::min(cur.regs.size(), caller.regs.size())); + const size_t n = std::min(8, std::min(cur.nregs, caller.nregs)); for (size_t i = 0; i < n; ++i) { caller.regs[i] = cur.regs[i]; } pc_ = cur.ret_pc; + sp_ -= 12 + static_cast(cur.nregs) * 8; frames_.pop_back(); return Fault::None; } /** - * @brief 执行内置 FB(CAL_*)。 - * @param op 操作码(CAL_TON..CAL_F_TRIG,8 个) - * @param slot 实例起始槽号 - * @return Fault(槽越界 → BadSlot;非 CAL_* → BadOp;成功 → None) - * @details 内建 FB 布局冻结(Doc/compiler/符号表与链接.md): - * - TON/TOF/TP:in/pt/q/et(字段 0/1/2/3) - * - CTU:cu/r/pv/q/cv(0/1/2/3/4);CTD:cd/ld/pv/q/cv(同) - * - CTUD:cu/cd/r/lu/pv/qu/qd/cv(0..7) - * - R_TRIG / F_TRIG:clk/q(0/1) - * 字段偏移 = 字段序号 × 8(8 字节定宽槽);边沿存 edge_prev_(每槽 2 字节)。 + * @brief 执行内置 FB(CAL,fb_id 分派 15 条)。 */ -Fault Machine::do_cal(uint8_t op, uint16_t slot) { - // 内建 FB 布局冻结(Doc/compiler/符号表与链接.md): - // TON/TOF/TP:in/pt/q/et(字段 0/1/2/3) - // CTU:cu/r/pv/q/cv(0/1/2/3/4);CTD:cd/ld/pv/q/cv(同) - // CTUD:cu/cd/r/lu/pv/qu/qd/cv(0..7) - // R_TRIG / F_TRIG:clk/q(0/1) - // 字段偏移 = 字段序号 × 8(8 字节定宽槽);边沿存 edge_prev_ +Fault Machine::do_cal(uint16_t fb_id, uint32_t slot) { const int64_t dt = image_.header().dt_ms; int64_t v = 0; - - const bool is_ton = op == static_cast(isa::Op::CAL_TON); - const bool is_tof = op == static_cast(isa::Op::CAL_TOF); - if (is_ton || is_tof) { - // in=0 pt=1 q=2 et=3 - if (!slot_get(static_cast(slot + 0), &v)) return Fault::BadSlot; + if (fb_id <= 2) { // TON/TOF/TP:in@0 pt@8 q@16 et@24 + const bool is_ton = fb_id == 0; + const bool is_tp = fb_id == 2; + if (!read_field(slot, 0, 0, &v)) return Fault::BadSlot; const bool in = v != 0; - if (!slot_get(static_cast(slot + 1), &v)) return Fault::BadSlot; + if (!read_field(slot, 8, 7, &v)) return Fault::BadSlot; const int64_t pt = v; int64_t et = 0; - if (!slot_get(static_cast(slot + 3), &et)) return Fault::BadSlot; - int64_t q = 0; + if (!read_field(slot, 24, 7, &et)) return Fault::BadSlot; + if (is_tp) { + // TP:in 上升沿启动 PT 时长脉冲 + const bool prev = edge_prev_[slot * 2] != 0; + const bool rising = in && !prev; + int64_t q = 0; + if (rising) { + et = 0; + } + const bool timing = rising || (et > 0); + if (timing) { + et += dt; + if (pt > 0 && et >= pt) { + et = 0; + q = 0; + } else { + q = 1; + } + } else { + et = 0; + q = 0; + } + edge_prev_[slot * 2] = in ? 1 : 0; + if (!write_field(slot, 24, 7, et)) return Fault::BadSlot; + if (!write_field(slot, 16, 0, q)) return Fault::BadSlot; + return Fault::None; + } if (is_ton) { - // TON:in 真 → et += dt(到 pt 停)、q = et ≥ pt;in 假 → et = 0、q = 0 if (in) { et += dt; if (pt > 0 && et >= pt) { et = pt; } - q = (et >= pt) ? 1 : 0; + v = (et >= pt) ? 1 : 0; } else { et = 0; - q = 0; + v = 0; } - } else { - // TOF:in 真 → q = 1、et = 0;掉电 → et += dt、et ≥ pt → q = 0 + } else { // TOF if (in) { et = 0; - q = 1; + v = 1; } else { et += dt; - q = (pt > 0 && et >= pt) ? 0 : 1; + v = (pt > 0 && et >= pt) ? 0 : 1; } } - if (!slot_set(static_cast(slot + 3), et)) return Fault::BadSlot; - if (!slot_set(static_cast(slot + 2), q)) return Fault::BadSlot; + if (!write_field(slot, 24, 7, et)) return Fault::BadSlot; + if (!write_field(slot, 16, 0, v)) return Fault::BadSlot; return Fault::None; } - - if (op == static_cast(isa::Op::CAL_TP)) { - // in=0 pt=1 q=2 et=3 + 上次 in(edge_prev_[slot*2]) - if (static_cast(slot) * 2 + 1 >= edge_prev_.size()) return Fault::BadSlot; - if (!slot_get(static_cast(slot + 0), &v)) return Fault::BadSlot; - const bool in = v != 0; - if (!slot_get(static_cast(slot + 1), &v)) return Fault::BadSlot; - const int64_t pt = v; - int64_t et = 0; - if (!slot_get(static_cast(slot + 3), &et)) return Fault::BadSlot; - // TP(脉冲):in 上升沿启动 PT 时长的脉冲,期间 in 变化不影响 - const bool prev = edge_prev_[slot * 2] != 0; - const bool rising = in && !prev; - int64_t q = 0; - if (rising) { - et = 0; - } - const bool timing = rising || (et > 0); - if (timing) { - et += dt; - if (pt > 0 && et >= pt) { - et = 0; // 脉冲结束:et 归零,下周期不再计时 - q = 0; - } else { - q = 1; - } - } else { - et = 0; - q = 0; - } - edge_prev_[slot * 2] = in ? 1 : 0; - if (!slot_set(static_cast(slot + 3), et)) return Fault::BadSlot; - if (!slot_set(static_cast(slot + 2), q)) return Fault::BadSlot; - return Fault::None; - } - - if (op == static_cast(isa::Op::CAL_CTU) || - op == static_cast(isa::Op::CAL_CTD)) { - // CTU:cu/r/pv/q/cv;CTD:cd/ld/pv/q/cv - if (static_cast(slot) * 2 >= edge_prev_.size()) return Fault::BadSlot; - if (!slot_get(static_cast(slot + 0), &v)) return Fault::BadSlot; - const bool edge_in = v != 0; // cu(CTU)/ cd(CTD) - if (!slot_get(static_cast(slot + 1), &v)) return Fault::BadSlot; - const bool ld_in = v != 0; // r(CTU)/ ld(CTD) - if (!slot_get(static_cast(slot + 2), &v)) return Fault::BadSlot; + if (fb_id == 3 || fb_id == 4 || fb_id == 5 || fb_id == 6) { + // CTU/CTD:cu/cd@0 r/ld@1 pv@2 q@4 cv@6;DCTU/DCTD:pv@4 q@8 cv@12 + const bool is_d = fb_id == 4 || fb_id == 6; + const bool is_ctu = fb_id == 3 || fb_id == 4; + const uint8_t fw = is_d ? 5 : 3; // DINT(5) / INT(3) + if (!read_field(slot, 0, 0, &v)) return Fault::BadSlot; + const bool edge_in = v != 0; + if (!read_field(slot, 1, 0, &v)) return Fault::BadSlot; + const bool ld_in = v != 0; + if (!read_field(slot, is_d ? 4 : 2, fw, &v)) return Fault::BadSlot; const int64_t pv = v; int64_t cv = 0; - if (!slot_get(static_cast(slot + 4), &cv)) return Fault::BadSlot; + if (!read_field(slot, is_d ? 12 : 6, fw, &cv)) return Fault::BadSlot; const bool prev = edge_prev_[slot * 2] != 0; if (ld_in) { cv = pv; } else if (edge_in && !prev) { - cv += (op == static_cast(isa::Op::CAL_CTU)) ? 1 : -1; + cv += is_ctu ? 1 : -1; } edge_prev_[slot * 2] = edge_in ? 1 : 0; - const int64_t q = (op == static_cast(isa::Op::CAL_CTU)) - ? ((cv >= pv) ? 1 : 0) - : ((cv <= 0) ? 1 : 0); - if (!slot_set(static_cast(slot + 4), cv)) return Fault::BadSlot; - if (!slot_set(static_cast(slot + 3), q)) return Fault::BadSlot; + const int64_t q = is_ctu ? ((cv >= pv) ? 1 : 0) : ((cv <= 0) ? 1 : 0); + if (!write_field(slot, is_d ? 12 : 6, fw, cv)) return Fault::BadSlot; + if (!write_field(slot, is_d ? 8 : 4, 0, q)) return Fault::BadSlot; return Fault::None; } - - if (op == static_cast(isa::Op::CAL_CTUD)) { - // cu=0 cd=1 r=2 lu=3 pv=4 qu=5 qd=6 cv=7 - if (static_cast(slot) * 2 + 1 >= edge_prev_.size()) return Fault::BadSlot; - if (!slot_get(static_cast(slot + 0), &v)) return Fault::BadSlot; + if (fb_id == 7 || fb_id == 8) { + // CTUD/DCTUD:cu@0 cd@1 r@2 lu@3 pv@4/4 qu@6/8 qd@7/9 cv@8/12 + const bool is_d = fb_id == 8; + const uint8_t fw = is_d ? 5 : 3; + if (!read_field(slot, 0, 0, &v)) return Fault::BadSlot; const bool cu = v != 0; - if (!slot_get(static_cast(slot + 1), &v)) return Fault::BadSlot; + if (!read_field(slot, 1, 0, &v)) return Fault::BadSlot; const bool cd = v != 0; - if (!slot_get(static_cast(slot + 2), &v)) return Fault::BadSlot; + if (!read_field(slot, 2, 0, &v)) return Fault::BadSlot; const bool r = v != 0; - if (!slot_get(static_cast(slot + 3), &v)) return Fault::BadSlot; + if (!read_field(slot, 3, 0, &v)) return Fault::BadSlot; const bool lu = v != 0; - if (!slot_get(static_cast(slot + 4), &v)) return Fault::BadSlot; + if (!read_field(slot, 4, fw, &v)) return Fault::BadSlot; const int64_t pv = v; int64_t cv = 0; - if (!slot_get(static_cast(slot + 7), &cv)) return Fault::BadSlot; + if (!read_field(slot, is_d ? 12 : 8, fw, &cv)) return Fault::BadSlot; const bool prev_cu = edge_prev_[slot * 2] != 0; const bool prev_cd = edge_prev_[slot * 2 + 1] != 0; if (r) { @@ -574,28 +752,105 @@ Fault Machine::do_cal(uint8_t op, uint16_t slot) { edge_prev_[slot * 2 + 1] = cd ? 1 : 0; const int64_t qu = (cv >= pv) ? 1 : 0; const int64_t qd = (cv <= 0) ? 1 : 0; - if (!slot_set(static_cast(slot + 7), cv)) return Fault::BadSlot; - if (!slot_set(static_cast(slot + 5), qu)) return Fault::BadSlot; - if (!slot_set(static_cast(slot + 6), qd)) return Fault::BadSlot; + if (!write_field(slot, is_d ? 12 : 8, fw, cv)) return Fault::BadSlot; + if (!write_field(slot, is_d ? 8 : 6, 0, qu)) return Fault::BadSlot; + if (!write_field(slot, is_d ? 9 : 7, 0, qd)) return Fault::BadSlot; return Fault::None; } - - if (op == static_cast(isa::Op::CAL_R_TRIG) || - op == static_cast(isa::Op::CAL_F_TRIG)) { - // clk=0 q=1 - if (static_cast(slot) * 2 >= edge_prev_.size()) return Fault::BadSlot; - if (!slot_get(static_cast(slot + 0), &v)) return Fault::BadSlot; + if (fb_id == 9 || fb_id == 10) { // R_TRIG/F_TRIG:clk@0 q@1 + if (!read_field(slot, 0, 0, &v)) return Fault::BadSlot; const bool clk = v != 0; const bool prev = edge_prev_[slot * 2] != 0; - const int64_t q = (op == static_cast(isa::Op::CAL_R_TRIG)) - ? ((clk && !prev) ? 1 : 0) - : ((!clk && prev) ? 1 : 0); + const int64_t q = (fb_id == 9) ? ((clk && !prev) ? 1 : 0) + : ((!clk && prev) ? 1 : 0); edge_prev_[slot * 2] = clk ? 1 : 0; - if (!slot_set(static_cast(slot + 1), q)) return Fault::BadSlot; + if (!write_field(slot, 1, 0, q)) return Fault::BadSlot; + return Fault::None; + } + if (fb_id == 11) { // SR:S1@0 R@1 Q1@2(置位优先) + if (!read_field(slot, 0, 0, &v)) return Fault::BadSlot; + const bool s1 = v != 0; + if (!read_field(slot, 1, 0, &v)) return Fault::BadSlot; + const bool r = v != 0; + int64_t q = 0; + if (!read_field(slot, 2, 0, &q)) return Fault::BadSlot; + if (s1) { + q = 1; + } else if (r) { + q = 0; + } + if (!write_field(slot, 2, 0, q)) return Fault::BadSlot; + return Fault::None; + } + if (fb_id == 12) { // RS:SET@0 RESET1@1 Q1@2(复位优先) + if (!read_field(slot, 0, 0, &v)) return Fault::BadSlot; + const bool set = v != 0; + if (!read_field(slot, 1, 0, &v)) return Fault::BadSlot; + const bool reset = v != 0; + int64_t q = 0; + if (!read_field(slot, 2, 0, &q)) return Fault::BadSlot; + if (reset) { + q = 0; + } else if (set) { + q = 1; + } + if (!write_field(slot, 2, 0, q)) return Fault::BadSlot; + return Fault::None; + } + if (fb_id == 13) { // PWM:in@0 pt@8 duty@16(REAL) q@20 et@24 + if (!read_field(slot, 0, 0, &v)) return Fault::BadSlot; + const bool in = v != 0; + if (!read_field(slot, 8, 7, &v)) return Fault::BadSlot; + const int64_t pt = v; + if (!read_field(slot, 16, 8, &v)) return Fault::BadSlot; + float duty = 0.0f; + { + const uint32_t bits = static_cast(v); + std::memcpy(&duty, &bits, 4); + } + int64_t et = 0; + if (!read_field(slot, 24, 7, &et)) return Fault::BadSlot; + int64_t q = 0; + if (in) { + et += dt; + if (pt > 0 && et >= pt) { + et -= pt; // 周期回绕 + } + const int64_t hi = static_cast(static_cast(pt) * duty); + q = (et < hi) ? 1 : 0; + } else { + et = 0; + q = 0; + } + if (!write_field(slot, 24, 7, et)) return Fault::BadSlot; + if (!write_field(slot, 20, 0, q)) return Fault::BadSlot; + return Fault::None; + } + if (fb_id == 14) { // RTC:enable@0 date@4(DATE) tod@8(TOD)(虚拟时钟) + if (!read_field(slot, 0, 0, &v)) return Fault::BadSlot; + const bool enable = v != 0; + if (enable) { + const uint64_t total_ms = cycles_total_ * static_cast(dt); + const uint32_t day = static_cast(total_ms / 86400000ull); + const uint32_t tod = static_cast(total_ms % 86400000ull); + if (!write_field(slot, 4, 4, day)) return Fault::BadSlot; + if (!write_field(slot, 8, 4, tod)) return Fault::BadSlot; + } return Fault::None; } - return Fault::BadOp; } +// ---- 比较结果(模板:cmp_result)---- + +template +int64_t Machine::cmp_result(const char* name, T a, T b) { + if (std::strcmp(name, "CMP_EQ") == 0) return (a == b) ? 1 : 0; + if (std::strcmp(name, "CMP_NE") == 0) return (a != b) ? 1 : 0; + if (std::strcmp(name, "CMP_LT") == 0) return (a < b) ? 1 : 0; + if (std::strcmp(name, "CMP_LE") == 0) return (a <= b) ? 1 : 0; + if (std::strcmp(name, "CMP_GT") == 0) return (a > b) ? 1 : 0; + return (a >= b) ? 1 : 0; // CMP_GE +} + } // namespace vm