Compare commits

...
79 Commits
Author SHA1 Message Date
Admin af81109e8a Merge dev_optimize: STATOR2 全链路(19 类型/变长指令/槽表两级/栈方案 C/CAL 15 条/VAR_TEMP)+ FB 共享体基址寻址(LOAD_PTR/STORE_PTR) 2026-08-27 09:27:20 +08:00
Admin c53e84f180 步骤E:文档收尾——基址寻址落地同步(编译器优化/寄存器码/用例对照/指令与映像/指令集清单/指令执行) 2026-08-27 09:23:12 +08:00
Admin 766e3a39b2 步骤D:用例 22_multi_instance(FB 共享体多实例隔离,a=102/204/306)+ 全量回归 2026-08-27 09:21:22 +08:00
Admin c19e306512 步骤C:FB 函数化——共享体(r1 实例指针 + LOAD_PTR/STORE_PTR 基址寻址 + CALL),退役内联/fb_temp,嵌套 r1 保护 2026-08-27 09:19:58 +08:00
Admin f32a8c6aa6 步骤B:vm 执行 LOAD_PTR/STORE_PTR(rs1 值区地址基址+偏移,越界 BadSlot) 2026-08-27 09:14:51 +08:00
Admin 68261cd78b 步骤A:isa+toml 新增 RS_OFF 形态(LOAD_PTR/STORE_PTR,0010 前缀 op=8/9)+ 强校验放行 + isa_test roundtrip 2026-08-27 09:13:38 +08:00
Admin 92e91350cc 步骤4:用例 21_var_temp(VAR_TEMP 每周期重置语义)+ 文档更新 + 修复 store_target STORE 缺 func 隐藏 bug 2026-08-26 18:01:08 +08:00
Admin 536ad1a96b 步骤3:Codegen VAR_TEMP——初值装载(emit_temp_init)+ FB 内联 Temp 固定寄存器(nlocals 隔离临时区)+ VarRef/赋值 fallback 2026-08-26 17:58:32 +08:00
Admin 94634dc0b0 步骤2:VAR_TEMP 链接层(FB 布局排除 Temp 字段 + 顶层防御校验 + scope 符号映射 Local)+ linker_test 2026-08-26 17:55:24 +08:00
Admin aa0c66780c 步骤1:VAR_TEMP 词法+语法支持(token/关键字/VarSection::Temp/保留字放行)+ 测试 2026-08-26 17:53:25 +08:00
Admin 6fa4eda020 阶段5-5:全量验证表留档(20 用例编译+执行+line1 真值表,全部通过) 2026-08-26 11:38:38 +08:00
Admin 38202fd21e 阶段5-4:executor --sidecar 显式指定 I/O 绑定文件(缺省同名推导不变,向后兼容) 2026-08-26 11:34:11 +08:00
Admin 7e95e8200e 阶段5-3:用例反汇编对照.md 的 LREAL 缺陷条目更新为已修(5-1) 2026-08-26 11:25:03 +08:00
Admin 84de006665 阶段5-2:文档收尾——vm 指令执行/扫描周期、compiler 寄存器码/符号表/STCompiler说明、executor 入口全部更新为 V2 2026-08-26 10:40:20 +08:00
Admin 98d2e86dca 阶段5-1:修 LREAL 字面量装载(compile_expr 携带目标 func,F64/F32 位模式区分) 2026-08-26 10:26:34 +08:00
Admin bc10307e0a 阶段4-3:executor V2 适配(cur_instr_bytes/slot_addr,修 V1 定宽槽残留) 2026-08-26 10:20:46 +08:00
Admin 5eda33fed6 阶段4-2c:vm_test V2 重写(EMIT 宏/手拼 4 测试/槽宽读写)+ slot_addr 公开访问器 2026-08-26 10:19:47 +08:00
Admin 06ff7d8359 阶段4-2b:vm/Machine V2(变长取指/槽表两级/栈方案 C/CAL 15 条)
- 变长取指(instr_len 前缀定长)、译码(前缀分区+func+uses_* 直推)
- 槽表两级访问:slot→slot_addr→值区按 func 读宽/扩展、写宽(含 F32/F64 位模式)
- 栈方案 C:预分配 max_stack、帧头 12B(fn_id/ret_pc/ret_fn_id)+ regs 指向栈区、
  r0..r7 全宽复制、字节+深度 64 双检查、MAIN 帧常驻、周期结束回退 sp
- 指令语义:MOVE(func 宽度高位清 0)、LOADK(常量 8B 按 func)、NOT 逻辑非、
  AND/OR/XOR 逐位、算术(各宽度饱和模板+无符号+浮点 IEEE)、CMP(按 func 符号)、
  跳转(off 字节相对下一条)、CALL/RET 帧
- CAL 15 条 fb_id 分派:TON/TOF/TP/CTU/DCTU/CTD/DCTD/CTUD/DCTUD/R_TRIG/F_TRIG/
  SR/RS/PWM(duty×pt 高电平)/RTC(虚拟时钟:周期×dt_ms,1970 纪元)
- 边沿检测 edge_prev_(每槽 2 字节)
- 验证:line1 全链路(I 采样→FB→短路→Q=1 真值表一致)、急停 Q=0、
  13_call x=7、07 算术 42/10/32/eq、05 while n=10
- 状态:vm_test(48 处)/executor(1 处)待 4-2c/4-3
2026-08-26 10:03:40 +08:00
Admin dbedc17032 阶段4-2a:vm/Image V2(头 128B/6 段校验/槽表访问器)
- ImageHeader:V2 字段(offset_slots/offset_values + n_slots/values_size/
  offset_meta/max_stack);kModelVersion=2(STATOR2)
- 段校验:const/funcs/code/slots/values + meta=0 空段处理、尺寸一致性、对齐
- 常量表 8B 无 tag;FuncRow.code_len 字节;slot_addr/slots_bytes 访问器
- 验证:V2 产物解析 ok、model_matches(STATOR,2)/sha 校验通过
- 状态:Machine.cpp 仍破(4-2b)
2026-08-26 09:59:18 +08:00
Admin 4f3da55ed7 阶段4-1:isa 重构 V2 完成(Types/Op/Instr/Encode + isa_test 91 checks)
- Types.h:func 宽度/符号表(kFuncWidth/kFuncSigned)、饱和模板(各宽度
  __int128 中间量 + 除法 A:除零返回被除数/MIN/-1 夹界/截断)、删 TypeTag
- Op.h/.cpp:前缀分区 + 72 条指令表(与 machine.toml 强校验一致)、
  uses_rd/rs1/rs2 直推、kFbNames(15 内建 FB)
- Instr.h:变长 instr_len、位号 get/set(含 JC off@55..24、SLOT_OFF off@87..56)、
  enc_* 发射器
- Encode.h/.cpp:decode/encode 往返、disasm(类型后缀 + CAL fb 名 + 未知 0x dump)
- isa_test V2:饱和边界/func 表/前缀定长/位号往返/编解码/disasm/uses_*/指令表

修复的实现 bug:find_op 字符串比较(指针==)、set_byte0 11/10 与 010xxx 分支、
CAL 字段约定((slot<<16)|fb_id)、disasm 未知前缀 used 长度

状态:vm/executor 编译暂破(V1 代码引用旧 isa 接口),4-2 同步
2026-08-26 09:56:43 +08:00
Admin 384e681ef3 文档:阶段4 执行步骤细化(4-1a..4-4,一次一个,isa/vm 连续完成) 2026-08-26 09:48:05 +08:00
Admin 7b4961f7ed 阶段3-5:编译器验证——13 正例源码↔反汇编对照 + --dump-map
- STCompiler --dump-map:编译后打印变量/槽映射(函数局部 r 寄存器、
  全局 slot/off/type/func、实例 slot/off/fb/size/字段偏移)
- CodegenMap 输出结构(codegen_project 可选参数)
- 修复参数循环 bug(i+1<argc 漏掉末尾单参数选项)
- 结构自检(临时 stbcheck):13 用例寄存器/func/跳转/槽/长度全部通过
- 13 正例逐条对照全部通过,产出 Doc/compiler/用例反汇编对照.md
- 已知缺陷记录:LREAL 字面量装载按 REAL(TODO)
2026-08-26 09:47:24 +08:00
Admin b19a61aabf 阶段3-4:新类型运算验证 + 修复(011 前缀位、func 大小写、位串 AND/OR)
新类型运算验证(综合用例全部正确):
- REAL 算术 ADD.F32、LREAL 算术 ADD.F64
- TIME+ ADD.I64、TIME 比较 CMP_GT.I64
- DATE/TOD/DT 比较 CMP_GT.U32/U32/U64
- DINT 算术 ADD.I32、UDINT 字面量装载+截断
- WORD 位串 AND(短路 JF+MOVE)
- 负例:UINT 溢出拦截

修复的 bug:
1. set_byte0 '011' 分支 0x40→0x60(RRR 指令被误判为 010 区,
   ADD 等显示错乱)——阶段 2 遗留
2. Codegen func_of_name 大小写不敏感(符号表小写/type 表大写,
   否则 REAL 等返回 func 0)
3. Typecheck AND/OR 支持位串(BOOL 逻辑或 bit 按宽度逐位)

已知缺陷(记录):LREAL 字面量装载按 REAL(LOADK.F32),
ADD.F64 运算位模式错位——需 compile_expr 携带目标 func(TODO)。
2026-08-26 09:40:12 +08:00
Admin 2c5852797a 阶段3-3:新类型语法验证 + 修复(parse_primary 补字面量分支、负号折叠、NEG 指令)
- Parser:parse_primary 补 FLOAT_LIT/DATE_LIT/TOD_LIT/DT_LIT
  (表达式路径,此前仅初值路径支持)
- Typecheck:literal_range_ok 支持 Neg(LitInt/LitReal) 折叠负号
  (UINT := -1 → 'negative literal for unsigned type')
- Codegen:Neg → NEG 指令(RR 4B,func 按操作数类型)

验证通过:
- 19 类型声明 + 字面量:DINT/REAL(1.5)/LREAL(2.5e3)/BYTE(16#FF)/UINT(8#17)/
  WORD(2#1010)/DATE(D#2026-8-24)/TOD(TOD#12:30:00)/DT(DT#...)/BOOL/TIME(T#1s250ms)
- 负例:UINT:= -1 越域、INT:= DINT 禁隐式、BOOL:= 2 越界、
  DINT:= 16#FFFFFFFF 溢出、D#2026-13-40 日期非法
- ctest 12/14(vm/cases 待阶段 4)
2026-08-26 09:35:39 +08:00
Admin 316b252afc 文档:阶段4-执行侧V2 待办清单(isa 重构/vm 同步/executor/全链路/文档收尾) 2026-08-26 09:33:23 +08:00
Admin 8ded7090c8 阶段3-2:codegen_test V2 重写(去 isa 依赖,L5 范围断言)
- build_case 完整编译管线(parse→load→link(cfg)→check(cfg)→codegen(cfg))
- disasm_lines 按 instr_len 迭代生成指令文本
- 7 用例断言:头字段(version=2/STATOR2/max_stack/n_slots/values_size)、
  常量表 8B 无 tag、CAL.TON/STORE_OFF 字段访问、CALL+max_stack、
  line1 全链路(n_slots=5:4 全局+1 实例)、跳转回填、全局初值
- CMake 恢复 codegen_test(链 compiler 去 isa);ctest 12/14
2026-08-26 09:31:53 +08:00
Admin 6be2873c88 阶段3-1:编译器语言层 V2 完成(19 类型/字面量扩展/禁隐式转换/配置驱动 FB)
Lexer V2:
- 19 类型关键字(BOOL..DT);FB 名不再设关键字(IDENT)
- FLOAT_LIT(1.5/2.5e3)、进制前缀(2#/8#/16# 换算十进制)
- 日期字面量 D#/TOD#/DT#(历法换算 1970 纪元、闰年、值域检查)

Parser V2:
- TypeRef 字符串化(TypeKind{Scalar,FbBuiltin,FbUser},名字小写)
- ExprKind 增 LitReal/LitDate/LitTod/LitDt;Expr.double_value
- IDENT 类型统一 FbUser(内建判定在 Linker 查 fb 表)

Typecheck V2:
- TType 19 + Unresolved(字面量未定型)
- 禁隐式转换(赋值/算术/比较严格同型)
- resolve_to:未定型→消费点定型+值域检查(BOOL 0/1、无符号负数、
  溢出、浮点仅 REAL/LREAL);算术按 kind 允许(TIME 仅 ±)
- Call 实参按函数参数类型;check_project 加 cfg

Linker V2:
- FbField.type_name 字符串化;内建 FB 布局查 machine.toml [[fb]] 表
  (删 8 个硬编码布局,带缓存);link_project 加 cfg

Codegen V2:
- 浮点/日期字面量(LOADK func 8/4/4/6;LitReal 按 REAL 装载,LREAL TODO)
- 算术/比较 func 按操作数类型(expr_func)
- TypeKind 连锁适配

main.cpp:link/check 传 cfg;--disasm 头 22 字段逐行中文注释(L5)
+ slots 节;StbView 补 offset_const/offset_funcs

测试适配:parser/linker/typecheck/vm/cases 适配新接口与 V2 语义。
ctest 11/13(vm_cycles/cases_all 待阶段 4)。codegen_test 仍剥离(阶段 3-2 重写)。
2026-08-26 09:29:31 +08:00
Admin 6ab2195a6b 阶段3文档:L5 定案——--disasm 头 22 字段一行一个+中文注释、新增 slots 节 2026-08-26 09:07:12 +08:00
Admin 250a0c9fac 阶段3文档:L4/L6/L7/L8 定案(字面量 AST / FB 三层判定 / TypeKind 字符串化 / Linker 查 toml)——决策点全部闭合 2026-08-26 08:58:19 +08:00
Admin 1335283279 阶段3:A4 修订——V2 支持日期字面量(D#/TOD#/DT#,纪元 1970,无小数秒,年 1..9999) 2026-08-26 08:53:07 +08:00
Admin a7f89dc180 阶段3文档:L2/L9/L3 定案——变量禁隐式转换、整数/浮点字面量按上下文适配(冲突报错)、时间字面量格式要求 2026-08-26 08:48:24 +08:00
Admin 65ccd6c2b7 阶段3文档:L2 定案(禁止任何隐式类型转换,字面量类型化)+ 新增 L9(字面量类型化语法) 2026-08-25 20:29:11 +08:00
Admin f7de78e528 文档:阶段3-语言层V2 计划与待定决策点(L1..L8,逐个解决) 2026-08-25 20:26:00 +08:00
Admin 4785cf3e3b 阶段2-6:V2 生成侧 bug 修复与验证收尾
- Codec:set_byte0 的 11/10 前缀位修正(0xC0/0x80);新增 JC 形态偏移
  专用读写(off@55..24,字节 3..6)——修复 JF rd 被 off 覆盖
- Stb:offset_meta=0(空段)时值段大小校验按 SHA 尾计算(修复
  values segment size mismatch)
- Codegen:实例字段带 func(field_func),LOAD_OFF/STORE_OFF 按字段
  类型设 func(pt=TIME → I64)
- main.cpp:--disasm 按 instr_len 迭代变长字节码
- 验证:13 个正例全部编译出 V2 产物 + disasm 自读通过(RET/CAL.TON/
  STORE_OFF.I64/JF 偏移等抽查正确);ctest 11/13(vm/cases 待阶段 4)
2026-08-25 20:23:32 +08:00
Admin 924c0c1923 阶段2-4/2-5:Stb V2(头 128B/槽表段/值段/常量表 8B)+ Codegen V2 重写
Stb.h/.cpp V2:
- 头 128B:kOffSlots/kOffValues/kOffNSlots/kOffValuesSize/kOffMeta/kOffMaxStack
- 常量表 8B(无 tag)、槽表行 8B {addr,预留}、static_assert 更新
- StbView:新字段访问器 + slots_bytes + 段校验 6 段(含元数据空段)

Codegen V2:
- 变长指令发射(Codec 位号,emit/E_rr..E_cal/E_slot_off)
- 槽表→值段布局(1/2/4/8 对齐;实例=1 槽表条目指向字段块)
- 常量表位模式去重(LOADK 按 func 解释);字节化跳转回填
- max_stack(调用图 DAG 最长路径 + 环检测递归报错)
- 用户 FB 内联(LOAD_OFF/STORE_OFF 字段访问);内建 FB → CAL fb_id,slot
- 头 128B 拼装(version=2/STATOR2/段序)

配套:main.cpp 常量显示去 tag;codegen_test 临时剥离(阶段 3 重写);
machine_test 修正(72 op + 负例修复)。ctest:10/13 绿(vm/cases 预期内剥离)。
2026-08-25 20:14:01 +08:00
Admin 246fe09646 阶段2-3:Codec V2 化(变长指令字编解码 + 配置驱动 disasm 类型后缀)
- instr_len 前缀定长(1/01/001/0001 → 4/8/16/32B)
- prefix_of/op_of/func_of + 全部位号 get/set(rd/rs1/rs2/imm32/slot32/off32/
  fn_id32/fb_id16/cal_slot32/off32_hi/imm64)
- set_byte0 按前缀写(11/10/011/010xxx 形态位/0010/0011/0001)
- disasm:find_op_by_key 查配置;full/float/dst 类显示 .TYPE 后缀
  (U8/I8/.../F64);CAL 打印 fb 名(CAL.TON s5);未知输出 ??? 0x hex
- main.cpp --disasm 调用适配(TODO V2 待 Stb 重写)
- 状态:编译仅剩 Codegen.cpp(84 处 V1 残留,下一步阶段2-5 重写)
2026-08-25 20:08:37 +08:00
Admin a4335693d4 阶段2-2:MachineConfig 重写为 V2(强校验 5 项)+ TypeInfo/machine_test V2 适配
- ConfigType:base/kind/func(V1 的 tag/range 删除);ConfigOp:prefix/op/fmt/
  func_mode(V1 的 opcode/class 删除);ConfigFb:fb_id(V1 的 opcode 删除)
- 强校验:meta(version=2) / type 19 条全集·base 命中·kind 合法·func 与
  (width,signed,float) 互锁 / op prefix 合法·prefix↔fmt 互锁·(prefix,op) 分区
  唯一·func 类别·params 互锁 / fb 15 条全集·fb_id 唯一·字段命中 type 表 /
  op-fb 互锁(CAL↔fb 表)
- TypeMeta:tag/range → func/kind
- machine_test:V2 断言(正例 68 op/19 type/15 fb + 14 个负例)
- 状态:compiler 库暂挂于 V1 Codec/Codegen/Stb/main(下一步按
  Codec → Stb → Codegen → main 顺序 V2 化)
2026-08-25 20:04:49 +08:00
Admin fc76fea47b 修正:内建 FB 实为 15 条(fb_id 0..14:TON..F_TRIG 11 + SR/RS/PWM/RTC 4),文档统一'14 条'→'15 条' 2026-08-25 19:59:39 +08:00
Admin dd5e69d2f7 阶段2-1:machine.toml 重写为 V2 schema(19 type + 68 op + 14 fb)
- type 表:19 条 IEC 类型(base 基元 + kind 语义 + func 映射,TOD=func4 修正)
- op 表:prefix+op 分区编码(4B RR 21 条/NONE 2 条、8B RRR 26 条/010 区 8 条、
  16B SLOT+off 8 条/IMM64 1 条、32B 预留 6 条)+ fmt + func 类别
  (none/width/uint/full/float/dst)+ params 互锁
- fb 表:14 条(fb_id 0..14,含 SR/RS/PWM/RTC 字段,字段类型命中 type 表)
- 注:MachineConfig 仍为 V1 解析,下一步同步(machine_test 暂红预期内)
2026-08-25 19:59:24 +08:00
Admin 7745dadd2d 阶段1-3:stb文件格式.md 重写为 V2 结构(头 128B/槽表段/值段/元数据段)
- 段序:常量表→函数表→字节码→槽表段→值段→元数据段→SHA
- 头 128B 全字段表(version=2、n_slots/values_size/offset_meta/max_stack)
- 常量表 8B 无 tag、函数表 code_len 字节化、变长指令段 16 对齐
- 槽表 8B {addr,预留}、值段变宽紧凑(FB 布局引用清单 §8.1)
- 读取校验链(结构/SHA/型号);line1 真实拆解标注阶段 5(C5)
2026-08-25 17:35:29 +08:00
Admin 7caa407485 阶段1-2:指令配置.md 重写为 machine.toml V2 schema(N5 定稿)
- op 表:prefix+op(分区内编号)替代 opcode;fmt 与前缀互锁;func 类别
  (none/width/full/float/dst)
- type 表:19 条(base 基元别名 + kind 语义 + func 显式映射,互锁强校验)
- fb 表:14 条(fb_id 0..14 + 字段类型,布局偏移编译期按 1/2/4/8 对齐算)
- 强校验清单 5 项 + 加东西流程 + 与 V1 schema 差异表
2026-08-25 11:55:05 +08:00
Admin ec1df225e2 修正:TOD 为 4B 无符号,func = 4(非 6,与 DWORD/UDINT/DATE 同组);func 6 仅 LWORD/ULINT/DT 2026-08-25 11:54:41 +08:00
Admin 59d7ff3159 阶段1-1:指令与映像.md 重写为 V2 规格(STATOR2)
- §2 特性总表(变长指令/前缀分区/func/栈方案C/除法A)
- §3 指令编码(前缀分区表 + func 位段 + 操作数区位号 + uses_* 直推)
- §4 寄存器与调用栈(帧按 nregs、max_stack、递归编译期报错)
- §5 类型系统(19 类型→func 映射、BOOL 强制、日期表示)
- §6 指令语义(除法 A、浮点 IEEE、跳转字节化、内建 FB 14 条)
- §7 数据模型(槽表→值区)、§8 映像格式(头 128B/段序/校验)
- §9 sidecar、§10 反汇编(类型后缀)、§11 目录、§12 实现约定
2026-08-25 11:29:15 +08:00
Admin 53df69f4be 文档:阶段1规格总览(V2 主参考)——定案速览 + 三份规范重写任务 + 延后项
- §1 阶段 1 任务(重写指令与映像/指令配置/stb文件格式)
- §2..5 定案速览:指令编码/数据模型/执行语义/映像格式
- §6 machine.toml V2 schema 方向(N5,阶段 1 内定)
- §7 延后项(N1..N4、C5);§8 参考文档索引
2026-08-24 16:18:57 +08:00
Admin 3431f83e7c 文档维护:C1/C3/C6 行同步定案标记、§9.2 标注全部闭合、历史记录修正 2026-08-24 16:18:00 +08:00
Admin 638e0ef058 指令集优化:整数除法语义 A 定案写入(除零返回被除数、MIN/-1 饱和、截断除法、MOD 符号随被除数) 2026-08-24 16:17:00 +08:00
Admin 146abd2929 内建 FB 字段表终稿:14 条 IEC 风格字段 + 对齐/偏移/大小(C3/C6 闭合)
- SR(S1/R/Q1 置位优先)、RS(SET/RESET1/Q1 复位优先)、PWM(in/pt/duty/q/et)、
  RTC(enable/date/tod)
- 字段布局终稿(1/2/4/8B 对齐):TON 32B、CTU 8B、DCTU 16B、CTUD 10B、DCTUD 16B、
  TRIG 2B、SR/RS 3B、PWM 32B、RTC 12B
- RTC 时钟源 = 虚拟时钟(周期×dt_ms,确定性);PWM/SR/RS 语义注记
2026-08-24 16:12:37 +08:00
Admin 7769b515b0 文档维护:§4 表格清理(A1/C2/C4 同步定案)+ version=2 成文 + 帧头/max_stack 细则
- M1~M3:A1(U12)、C2(U7)、C4(U14)行标记已定案
- §2.5 头布局:version=2(V2 格式,V1=1)单列
- §2.2 栈设计补全:帧头 {fn_id:u32, ret_pc:u32, ret_fn_id:u32} 12B;
  max_stack = MAIN + DAG 最坏路径 Σ(12+nregs×8),写入 @116
2026-08-24 16:09:09 +08:00
Admin 2cc09903c9 指令编码:CALL/CAL rd 恒 0 约定成文(U14) 2026-08-24 16:05:56 +08:00
Admin 721dfabf87 U13/U14 定案:DATE/TOD/DT 表示(DT={date:32|tod:32} 拼接)+ CALL/CAL rd 恒 0 —— 全部 U 项闭合,V2 规格冻结 2026-08-24 16:05:46 +08:00
Admin d38d78c942 指令集优化:U12 定案——元数据段 v1 0 长度占位(offset_meta=0),schema 延后 2026-08-24 16:03:27 +08:00
Admin 6869624f09 U10 定案:寄存器/调用约定维持 + 栈方案 C(预分配栈区 + max_stack 编译期算 + 双检查 + 递归编译期报错)
- §2.2 修正:256 = 寻址上限(非每帧分配数);帧按 nregs 动态分配
- 新增调用栈设计:max_stack@116(调用图最坏路径)、预分配零分配、
  字节/深度双检查、MAIN 帧常驻
- 递归 = 链接层环检测编译期报错(link error: recursive call)
- 编译器优化.md:max_stack 计算职责 + Linker 环检测
- §9.2 U10 闭合;影响面/决策历史同步
2026-08-24 16:00:45 +08:00
Admin 44370c448e 指令集优化:U8/U9 定案——槽表条目 8B({addr:u32,预留:u32})、值段 1/2/4/8B 对齐编译器保证 2026-08-24 15:48:32 +08:00
Admin c9d7c16d6d 指令集优化:U7 定案——头 128B 修正版(offset_meta 占位、values_size 命名、n_i/n_q/n_m 保留恒 0、n_slots 关系、16 对齐) 2026-08-24 15:47:07 +08:00
Admin c193c760ef 指令集优化:U6 定案——op 预算按指令集清单/指令编码文档方案(前缀分区) 2026-08-24 15:41:58 +08:00
Admin c9024a060b U5 定案:内建 FB 14 条全部 v1 实现(6 计数拆条 + 定时/触发 5 + SR/RS、PWM、RTC 3)
- 指令集清单 §8:SR/RS、PWM、RTC 状态 扩展→核心
- 指令集优化:§9.2 U5 闭合、§4 A3 行更新、决策历史补记
2026-08-24 15:40:55 +08:00
Admin af7054480f 指令集优化:U4 定案——结构体 v1 只留编码不实现语法(预留) 2026-08-24 15:38:04 +08:00
Admin a6b6990834 指令集优化:清零已确认/作废项(U1/U2/U3/U11/U15/U16 标记闭合,A3 修正为 fb 表拆条,K 清单补 K24..K26)
- U1 作废(fmt 位段取消);U2/U3 被前缀编码取代;U11 已定案(010 101 分区)
- U15 被 func=0 取代;U16 随 K7 确认
- §4 A3 行修正:计数宽度 = fb 表拆条(U5 待最终确认)
- §9.1 补 K24 前缀分区 / K25 fmt 取消 / K26 MOVE 语义
2026-08-24 15:37:17 +08:00
Admin ee1e06c628 指令编码:010 区也改前缀分区(形态位3+子op2),全部形态前缀直推、零查表
- 010 000/001/010/011/100/101 = IMM/SLOT/JMP/JC/CALL/CAL
- 8 条指令编码更新:LOADK=01000000、LOAD=01000100、STORE=01000101、
  JMP=01001000、JT=01001100、JF=01001101、CALL=01010000、CAL=01010100
- uses_* 全部前缀直推(无查表);三份文档同步
- 010 区预算:6 形态区 × 4 + 2 预留形态区
2026-08-24 15:33:26 +08:00
Admin 1c0c4d02f2 指令集优化:§2.1 同步前缀分区方案(fmt/type 位段表述更新为前缀分区+func) 2026-08-24 15:31:14 +08:00
Admin 134f1748ec 指令编码:fmt 位段取消,形态改由前缀分区表达(Q1=010 查表 / Q2 空位待定 / Q3 全组分区)
- 前缀分区:11=RR、10=NONE、011=RRR、010=其他8条(查表)、0010=SLOT+off、
  0011=IMM64、0001=预留(32B)
- 字节 1 = [func:4 | 空:4](空位待全部编码后定)
- 指令集清单:全部 72 行编码按新分区重排;§1 位号表/位图更新
- uses_* 推导:前缀直推 + 010 区查表;编译器优化/指令集优化同步
- op 预算:RR 21/64、NONE 2/64、RRR 26/32、其他 8/32、SLOT+off 8/16、IMM64 1/16
2026-08-24 15:30:59 +08:00
Admin fda4f7a3ec 指令集清单:新增 §1 fmt 格式与字段位号(opcode/func/imm/rs1/rs2/rd 逐位标注)
- 1.1 通用头部位号表(长度前缀/opcode/func/fmt)
- 1.2 fmt 位号总表(11 形态 + 预留,操作数区 bit16 起)
- 1.3 各位图:RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE/SLOT+off/IMM64
  逐位标注(含 pad 填充位);32B 预留格式待定
- 16B 形态分配 fmt 9(SLOT+off)/10(IMM64);指令编码.md 同步
- 原指令章节顺延为 2..12
2026-08-24 15:01:54 +08:00
Admin ed78f06e29 指令集清单:全部指令按 RISC-V 式编码(前缀+op / func / fmt)
- 4B 组 23 条(MOVE/NOP/NOT/RET/算术单目/浮点/舍入/CONV)
- 8B 组 34 条(算术/逻辑/移位/位操作/CMP/数据/调用/跳转)
- 16B 组 10 条(字段访问/LOADK64/字符串/数组长度)
- 32B 组 6 条预留(JTBL/复杂字符串/数组索引)
- func 语义按指令族(MOVE=宽度、LOAD/算术=全矩阵、浮点单目=8/9)
- 内建 FB fb_id 0..13(TON..RTC);op 预算余量表
2026-08-24 14:45:35 +08:00
Admin 5b10c2c826 文档:新建指令编码.md——RISC-V 式 op+func 编码定案存档
- 前缀长度组(1/01/001/0001 定 4/8/16/32B)+ op 按组分治
- 字节 1 = [func:4|fmt:4];func 10 组合(宽度×符号+F32/F64)语义随 op
- fmt 4 位 9 格式(格式合并作废,CALL/CAL 独立)
- 19 类型 → 10 func 编译期映射;BOOL 0/1 编译器强制
- 与 RISC-V 对照表 + 决策历史
2026-08-24 14:43:16 +08:00
Admin 21c4b953f9 文档:指令集清单章节编号独立化 + 头部去重 2026-08-24 14:08:40 +08:00
Admin 770e92d109 文档:指令集清单拆分为独立文档(Doc/isa/指令集清单.md),优化文档保留引用 2026-08-24 14:08:34 +08:00
Admin e98054706c 指令集优化:§10 全功能指令集清单(草案,10 类 + op 预算)
- 数据搬运/逻辑位运算/算术/比较/转换/控制流/内建FB/字符串/数组/系统
- 按长度组(前缀编码)与格式标注,状态列区分核心(v1)/扩展/预留
- 内建 FB 表 11 条核心(含 DCTU 双字)+ SR/PWM/RTC 扩展
- op 预算核对:4B组128 / 8B组64 / 16B组32 / 32B组16,各组远超需求
2026-08-24 14:07:59 +08:00
Admin a91528a228 指令集优化:新增 §9 确认核验清单(K1..K23 已确认 / U1..U16 待确认,未回答≠同意) 2026-08-24 13:48:06 +08:00
Admin 8f7140be64 指令集优化:B1/B2/B4 定案(1 CAL+fb_id:16、领域指令不进指令集)、C1..C6 未定项记录 2026-08-24 11:57:38 +08:00
Admin 5e6f2b45fc 指令集优化:A9 定案(字面量按上下文适配,默认 DINT 兜底) 2026-08-24 11:42:38 +08:00
Admin 9e2a6e95f1 指令集优化:A3..A8 定案记录 + A9/A10 反问待讨论
- A3 计数器单字(INT)/双字(DINT)两种;A4 日期字面量 v1 不支持
- A5 TIME 运算/浮点 IEEE 按推荐;A6 sidecar 绑定一致;A7 disasm 类型后缀
- A8 type 表=基元别名(REAL→float32、LREAL→float64)
- §8 新增待讨论:A9 字面量上下文恒存在(默认 DINT 仅兜底);
  A10 FB 8 op 可收敛为 1 CAL + fb 类型 id(op 位段省 7 个)
2026-08-24 11:40:08 +08:00
Admin 91fc00ed6d 指令集优化:A2 定案——LOADK 用 type 位段解释常量,常量表条目 8B 无 tag,IMM64 预留不发射 2026-08-24 11:26:05 +08:00
Admin 6e8b9896f2 指令集优化:V2 核心决策全部定案(type:5/格式合并/19 类型/禁隐式转换/进制前缀)
- §2.1 字节布局 [len:2|op:6]+[type:5|fmt:3];格式 9→7 形态(CALL→IMM、CAL→SLOT)
- §2.4 19 IEC 基础类型(+DATE/TOD/DT)+13 预留;浮点/进制前缀/禁隐式转换+字面量适配
- §2.6 LOADF 取消、FB 共享体定案(16B 变体=基址寻址)
- §4 改为 A1..A10 尚未确定细节(元数据段/常量内嵌/FB 字段类型化/日期字面量/
  算术语义/sidecar/disasm/machine.toml schema/字面量推断/CALL 确认)
- 编译器优化.md 同步定案状态
2026-08-24 11:19:40 +08:00
Admin 109f9a7619 文档:编译器优化 V2 草案(编译策略层,与指令集优化.md 分层)
- §2 V1 现状:FB 内联/FC CALL/CAL_* 三种调用、短路跳转、固定寄存器分配、
  调用约定、跳转回填、常量表去重
- §3 V2 变更:FB 基址寻址消除内联、type 位段驱动语义、槽表/值区布局职责、
  变长发射字节化回填、类型扩展与升宽规则
- §4 待确认(引用指令集优化 §4 编译器相关项);§5 编译器侧影响面;§6 阶段;§7 决策历史
2026-08-24 10:53:47 +08:00
Admin 3ec803c5a8 文档:指令集优化——补 REAL/LREAL(float/double)、FB/FC 内联现状与 V2 基址寻址方向
- §2.4 类型表 16 种满编(type:4),含 C 对应列;浮点表示/常量 tag/字面量
- §2.7 新增:V1 三种调用处理(FB 内联/FC CALL/CAL_*)+ V2 基址寻址消除内联膨胀
- §3 差异表加 FB 行;§4 待确认加 FB 共享体与浮点字面量;§7 决策历史补记
2026-08-24 10:52:46 +08:00
Admin b70bea6b54 文档:指令集优化 V2 草案(变长指令/槽表值区/IEC 类型扩展/待确认问题)
- 动机:opcode 位域化、8B 定宽槽浪费、类型扩展、结构体预留、变长指令
- 已定案:变长指令(长度组在 opcode)、寄存器 8 位编号维持、槽表→值区
  两级模型、宽度编码进 opcode、初值同文件分段、全 32 位寻址、STATOR2
- 待确认:跳转字节化 / type:4 vs 5 / 长度组映射 / I/Q/M 合并 / 结构体范围
  / ST 层类型支持 / 转换规则 / 字面量
- 影响面清单 + 6 阶段实施计划 + 决策历史
2026-08-24 10:46:25 +08:00
Admin 06a497636e 文档:executor 计划项——可选 --sidecar 参数(显式指定 I/O 绑定文件,缺省回退同名推导) 2026-08-24 09:31:51 +08:00
Admin d109a3ca77 executor: 移除冗余 Image::from 前置校验,信任链统一由 Machine::create 报告 2026-08-24 09:04:46 +08:00
Admin 7aa2bbefcf stb 头字段偏移常量表:消除 vm/compiler 两侧裸魔法数
- vm/Image.cpp:匿名空间 constexpr 偏移常量(kOffMagic..kOffModelId + 段内行偏移),
  from/model_id/model_matches/func_row/const_entry 全部替换
- compiler/Stb.h:同值 inline constexpr 表(Codegen 写侧 + Stb 读侧共用)
- compiler/Codegen.cpp assemble_image:put_* 头字段/行内偏移替换
- compiler/Stb.cpp StbView:读侧偏移替换
- 两侧 static_assert:头大小 = 72 + 型号标识、字段偏移单调、行内布局自洽
2026-08-24 08:55:57 +08:00
70 changed files with 7978 additions and 3994 deletions
+33 -23
View File
@@ -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 <name>.stb --disasm --machine <machine.toml>
```bash
$ STCompiler line1.stb --disasm --machine machine.toml
image: line1.stb (288 bytes, 2 functions, 4 globals, entry fn 1)
dt_ms=10 cycle_limit=100000 hash=0xc3fe4ae74ad45900 model=STATOR1 sha=ok
functions:
fn 0: nregs=8, offset=0, len=1
0x0080 RET
fn 1: nregs=13, offset=4, len=17
0x0084 LOAD_I r8, 1
0x0088 STORE_GLOBAL r8, 4
image: line1.stb (399 bytes)
header:
magic = 0x43545353 # 魔数 "STSC"
version = 2 # 格式版本(V2
model = STATOR2 # 型号标识
...
data (56 bytes):
n_slots = 5 # 槽表条目数
values_size = 7 # 值段字节数
max_stack = 116 # 栈区字节数(编译期算)
sha = ok # 文件尾 SHA-256 完整性
functions:
fn 0: nregs=8, offset=0, len=4
0x0098 RET
fn 1: nregs=13, offset=4, len=164
0x009c LOAD.U8 r8, s1
0x00a4 STORE_OFF.U8 r8, s4, 0
...
slots:
[0] addr=0
data (7 bytes):
0x0000: ...
```
说明:
- 指令偏移是**文件绝对字节偏移**,可直接对照 `xxd line1.stb`
- 映像里**没有符号名**(函数表只有 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`
+51 -54
View File
@@ -1,33 +1,33 @@
# 寄存器码(12.8
# 寄存器码(V2
compiler 模块的代码生成。输入:`Project` + `Unit`AST+ `LinkResult`;输出:`.stb` 映像字节(`std::vector<uint8_t>`)。全工程定案见 [`初步计划.md`](../初步计划.md) 12.8,指令/映像布局见 [`指令与映像.md`](../isa/指令与映像.md)。
compiler 模块的代码生成。输入:`Project` + `Unit`AST+ `LinkResult`;输出:`.stb` 映像字节(`std::vector<uint8_t>`)。全工程定案见 [`初步计划.md`](../初步计划.md) 12.8,指令/映像布局见 [`指令与映像.md`](../isa/指令与映像.md),槽表/值段格式见 [`stb文件格式.md`](../isa/stb文件格式.md)
## 做 / 不做
**做**
- 寄存器分配:局部/变量固定寄存器,表达式临时值往后编号,`nregs` 写进函数头
- 跳转先留洞、后回填;`AND` / `OR` **必须编成跳转**(短路),禁止两边都算
- 常量表(`LOADK`)、全局/实例槽(`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 对齐) | 槽表条目(8Baddr:u32 + 预留)指到值区偏移;状态跨周期持久 |
- **数据区** = 全局块(声明序,**每槽 8 字节定宽**,见 [`指令与映像.md`](../isa/指令与映像.md))→ FB 实例块(按实例声明序,每字段一槽)
- **指令 slot = 槽号**u160..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 <operand → t> NOT rd, t
@@ -65,7 +65,7 @@ AND/OR 短路(必须跳转):
L_end:
比较 <l→t1> <r→t2> CMP_xx rd, t1, t2
算术 <l→t1> <r→t2> ADD/SUB/MUL/DIV rd, t1, t2
负号 <operand → t> SUB rd, r0, t 0 - tr0 恒 0
负号 <operand → t> NEG rd, t
IF <cond → t> JF t, L_else
<then body> JMP L_end
@@ -73,34 +73,44 @@ IF <cond → t> JF t, L_else
L_end:
WHILE L_loop: <cond → t> JF t, L_end <body> JMP L_loop L_end:
FB 调用(用户,内联展开):
<每个实参求值 → 临时> STORE_GLOBAL <实例基址+字段偏移>, 临时
<内联体:读输入字段、算输出、写输出字段>
内建 实参同左 → CAL_TON/CAL_TOF/CAL_CTU <实例偏移>
FB 调用(用户,共享体):
<每个实参求值 → 临时> STORE_OFF <实例槽>, <字段偏移>, 临时
LOADK r1, <实例值区地址> CALL <fb_fn_id>
(FB 函数体内:字段访问 = LOAD_PTR/STORE_PTR rd, r1, <字段偏移>
内建 实参同左 → CAL <fb_id>, <实例槽>
```
- 常量统一进常量表(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
头 128Bmagic / 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
字节码:各函数指令连续
槽表:每条目 8Baddr: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. 用例 19、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
+129
View File
@@ -0,0 +1,129 @@
# 用例反汇编对照(源码 ↔ 指令验证)
> 状态:**13 个正例全部对照通过(2026-08-26**。方法:`STCompiler` 编译 → `--dump-map`
> 取变量/槽映射 → `--disasm` 取指令序列 → 逐条对照源码语义。
> 每用例记录「源码语句 ↔ 指令序列 ↔ 结论」;完整输出见 build/audit/(临时)。
## 01_empty_main:最小映像
```text
PROGRAM MAIN → fn0: RETnregs=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_normalWHILE 回填
| 源码 | 指令 | 结论 |
|---|---|---|
| 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_literalTIME 字面量
| 源码 | 指令 | 结论 |
|---|---|---|
| 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=r0main: x=r8 | ✓ |
## 15_fb_instance:用户 FB 共享体(函数化)
| 源码 | 指令 | 结论 |
|---|---|---|
| starter(start:=TRUE,...) | LOADK(TRUE)/STORE_OFF r9,s0,0LOADK(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,0STORE_OFF.I64 s0,8 | ✓ pt=TIME→I64 |
| 调用 | CAL.TON s0 | ✓ fb 名显示 |
| q := t.Q | LOAD_OFF.U8 s0,16 | ✓ 偏移 16 |
值段 32Bin@0/pt@8/q@16/et@24)✓
## 18_tof_ctu:双实例 + 字段偏移
| 源码 | 指令 | 结论 |
|---|---|---|
| tf(in:=FALSE, pt:=T#20ms) | STORE_OFF s0,0/8CAL.TOF s0LOAD_OFF s0,16 | ✓ |
| c(cu:=TRUE,r:=FALSE,pv:=3) | STORE_OFF s1,0/1STORE_OFF.I16 s1,2CAL.CTU s1 | ✓ pv=INT 2B |
| q2 := c.Q / cnt := c.CV | LOAD_OFF.U8 s1,4LOAD_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,0LOAD.U8 r9,s2 → STORE_OFF s4,1 → LOADK.U32 r1 → CALL 0 | ✓ I 读 + 共享体调用 |
| Q := start AND NOT stop AND NOT EmergencyStop | fn0LOAD_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 ✓
## 结构自检(stbcheck13 用例)
寄存器越界 / func 非法 / 跳转越界 / 槽号越界 / 指令长度越界:**全部通过**。
## 结论
- 编译器生成正确性:**13 正例全部对照通过**
- 结构自检:**全部通过**
- 已知缺陷(已修,阶段5-1):LREAL 字面量按目标 func 装载(LREAL → LOADK.F64REAL → LOADK.F32);实测位模式 0x3FC00000float 1.5/ 0x4004000000000000double 2.5)精确
+25 -17
View File
@@ -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 条
## 错误
+107
View File
@@ -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)
- 阶段 2isa)后接**阶段 3compiler 侧)**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_PTRRS+off+ FB 函数化(r1 实例指针),内联机制退役
- 类型扩展:19 IEC 基础类型(type:5);禁隐式转换 + 字面量适配;进制前缀定案
+45
View File
@@ -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 名 TokINT/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 dumpcodegen_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/TimeParser/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 正例 + 新类型语法用例编译;ctestcodegen_slice1 恢复)
## 4. 验证标准
- 13 个正例 + 新语法用例(DINT/REAL 声明、进制/浮点字面量)编译通过
- codegen_slice1 恢复绿;ctest 12/13vm_cycles/cases_all 待阶段 4
+9 -4
View File
@@ -7,7 +7,7 @@ BytecodeExecutor 模块:可执行入口。CMake 目标:`BytecodeExecutor`
## 用法
```text
BytecodeExecutor <name>.stb [--cycles N] [--replay <file>] [--step]
BytecodeExecutor <name>.stb [--cycles N] [--replay <file>] [--step] [--sidecar <path>]
```
- `--cycles N`:跑 N 个周期(缺省 10
@@ -15,12 +15,17 @@ BytecodeExecutor <name>.stb [--cycles N] [--replay <file>] [--step]
- `--step`:单步——采样 I 后逐指令打印(pc/fn/反汇编 + 执行后非零寄存器),跑 1 个周期,最后打印 I/Q
- 每周期打印 `cycle N: I=[...] Q=[...]`(按 sidecar 绑定顺序);故障打印 `FAULT(n)` 并退出码 1
### sidecar 定位(当前 + 计划)
- **当前(已实现)**:sidecar 路径按「同名替换扩展名」推导——`<name>.stb``<name>.runtime.toml`。依据:sidecar 是编译产物的配对文件,同名约束由 STCompiler 写侧强制,约定即可。
- **已实现**`--sidecar <path>` 显式指定 I/O 绑定文件。适用场景:同一程序多套接线配置并存(厂内/现场不同 sidecar)、接线文件与 `.stb` 不同目录。缺省行为**不变**(未指定时回退到同名推导),向后兼容。
## 职责
- 加载 `.stb` 映像与 `<name>.runtime.toml` sidecarI/O 绑定 var → 槽号 → channel/bit
- 加载 `.stb` 映像与 `<name>.runtime.toml` sidecarI/O 绑定 var → 槽号 → channel/bitV2 槽号为槽表条目下标
- 读取时**校验型号标识与 SHA-256**:型号不匹配 / 文件被篡改 → 直接报错拒绝(执行器只接受与自身内建型号一致的映像)
- 按 sidecar 采样 I、写回 Q(不创造变量)
- `dt_ms` / `cycle_limit` 从映像头取,推进内置 FBTON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG
- 按 sidecar 采样 I、写回 Q(不创造变量;V2 槽表两级:绑定 var → 槽号 → `slot_addr` → 值区
- `dt_ms` / `cycle_limit` 从映像头取,推进内置 FB15 条:TON/TOF/TP/CTU/DCTU/CTD/DCTD/CTUD/DCTUD/R_TRIG/F_TRIG/SR/RS/PWM/RTC
- 单步、看寄存器和映像、按录制的 `I` 回放
## 边界
+84 -77
View File
@@ -1,127 +1,134 @@
# .stb 映像文件结构
# .stb 映像文件结构V2STATOR2
编译产物 `.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_funcs .. offset_code │
├──────────────────────────────────────────────────┤
│ 字节码段 offset_code .. offset_fb
│ 字节码段 offset_code .. offset_slots16 对齐)
├──────────────────────────────────────────────────┤
FB 布局段 offset_fb .. offset_datav1 为空)
槽表段 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` | 1MAIN |
| 28 | 4 | 全局槽数 `n_globals` | 4 |
| 32 | 4 | I 槽数 `n_i` | 0v1 不拆 |
| 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` = 行下标(u32IMM 形态 imm32 字段)
## 函数表段(`offset_funcs` 起)
一行 12 字节:`[nregs:u32][code_offset:u32][code_len:u32]`
- **行下标即 `fn_id`**`code_offset` 相对字节码段起点(**字节**);`code_len` 为**指令条数**
- 入口 = 头 `entry_fn_id` 指向的行
line12 行):
| 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 + 帧头 12Bmax_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_0io.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 | EmergencyStopBOOL,初值 0 |
| 1 | 0xD0 | I0_0BOOL |
| 2 | 0xD8 | I0_1BOOL |
| 3 | 0xE0 | Q0_0BOOL |
| 4 | 0xE8 | starter.startBOOL |
| 5 | 0xF0 | starter.stopBOOL |
| 6 | 0xF8 | starter.qBOOL |
| 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 / 执行器的关系
- `<name>.stb` + `<name>.runtime.toml`sidecar`[[io.input]]`/`[[io.output]]`var → 槽号 → channel/bit
- I/O 绑定**不进映像**;绑定粒度 `slot = 槽号`(指向槽表条目),channel/bit 语义不变
- executor 按 sidecar 采样 I / 写回 Q`dt_ms`/`cycle_limit` 从映像头取;`max_stack` 供 VM 预分配栈区
## 用工具验证
- `ctest``codegen_slice1`(写侧 model/sha 自检)、`vm_cycles`读侧型号/SHA 拒绝)、`machine_config`
- `STCompiler x.stb --disasm --machine machine.toml`:显示 `model=``sha=ok|BAD`
- 手工检查`xxd line1.stb` 对照上表逐段核对
## 与执行器的关系
`BytecodeExecutor` 只读本格式:头(`cycle_limit`/`dt_ms`/型号标识)、函数表(帧 `nregs` + 代码定位)、数据段(全局 + FB 实例状态)。读取时**型号匹配 + SHA-256 校验**,任一不符 → 报错拒绝。I/O 绑定**不在**映像里,在 sidecar `<name>.runtime.toml`var → 槽号 → channel/bit)。
- `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 推演重算)
+281 -184
View File
@@ -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..r7r0 = 结果;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/rsIMM/SLOT/JC/SLOT+off/RS+off/IMM64 的 rdRS+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,百字节级)
- 寄存器值 int64codegen `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`SLOTfunc 全矩阵):槽表[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`RRfunc = 目标类型)
### 6.4 比较
- `CMP_EQ..GE`RRRfunc 全矩阵):按 func 有/无符号、浮点 IEEENaN 传播)
### 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 内建 FB15 条)
| 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<hex>` | — |
跳转偏移带符号、恒显正负号(`%+d`);寄存器一律 `r<n>`;常量/槽号/`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 引用)、值区地址 u324GB)
- 槽表分配: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 64basis `0xcbf29ce484222325`prime `0x100000001b3`
`[[io.input]]` / `[[io.output]]` 不进映像。
---
## 映像文件(`.stb`)与 sidecar
编译产物是单文件 `<name>.stb`:映像头 + 各段按本文布局连续排,魔数 `STSC`、版本 `1`、小端。`STCompiler` 写,`BytecodeExecutor` 读。
- `dt_ms` / `cycle_limit` 在映像头,执行器只认映像,不重复配置。
- I/O 绑定不进映像:`STCompiler` 另写 sidecar `<name>.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
- `<name>.stb`(映像)+ `<name>.runtime.toml`sidecar`[[io.input]]`/`[[io.output]]`var→slot→channel/bit
- I/O 绑定**不进映像**;绑定粒度 `slot = 槽号`(指向槽表条目),channel/bit 语义不变
- executor 按 sidecar 采样 I / 写回 Q`dt_ms`/`cycle_limit` 从映像头取
---
## 10. 反汇编格式(A7 定案)
按 func 显示**类型后缀**
```text
LOAD.I16 r5, s3 STORE.U32 s1, r7 ADD.F64 r1, r2, r3
MOVE r1, r2 LOAD_OFF.I8 r4, s2, 8
JMP +4 JT r0, -1 CALL 1 CAL.TON s5
```
前缀分区决定打印模板;010 区/16B 区按形态直推。
---
## 11. 目录与 CMake
```text
isa/
CMakeLists.txt
README.md # 代码目录入口,指向本文
README.md
include/isa/
Types.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.tomlV2 schema)与代码**强校验**:前缀分区/op/func 互锁(见指令配置.md,阶段 1 重写
- 操作码有变:先改 machine.toml + 本目录 + 测试,再动两端
- 错误类别:`isa` 层无错误前缀(编解码纯函数);vm/compiler 各自带前缀
+142
View File
@@ -0,0 +1,142 @@
# 指令编码(V2RISC-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/浮点单目/舍入/CONV21 |
| `10` | 4B | 10 | **NONE** | 6 位 | 64 | NOP/RET2 |
| `011` | 8B | 011 | **RRR** | 5 位 | 32 | 算术/逻辑/移位/位操作/比较(26) |
| `010 000` | 8B | 010 000 | **IMM** | 2 位 | 4 | LOADK1 |
| `010 001` | 8B | 010 001 | **SLOT** | 2 位 | 4 | LOAD/STORE2 |
| `010 010` | 8B | 010 010 | **JMP** | 2 位 | 4 | JMP1 |
| `010 011` | 8B | 010 011 | **JC** | 2 位 | 4 | JT/JF2 |
| `010 100` | 8B | 010 100 | **CALL** | 2 位 | 4 | CALL1 |
| `010 101` | 8B | 010 101 | **CAL** | 2 位 | 4 | CAL1 |
| `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 | LOADK641 |
| `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..48rd 恒 0 |
| `010 101` | CAL | 00 | CAL | fb_id16=31..16、slot32=63..32rd 恒 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..24NONE: pad=31..16 |
| 011/010 | 8B | RRR: rd+rs1+rs2+pad010 区六形态见 §5 |
| 0010/0011 | 16B | SLOT+off: rd+slot32+off32+padIMM64: 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`=NONE4B);`0010`=SLOT+off、`0011`=IMM6416B
- **fmt 位段取消**:形态全部由前缀分区表达(010 区内部再分区,零查表);字节 1 = [func:4 | 空:4],空位待全部编码后定
- func 4 位(10 组合);MOVE 只管宽度;19 类型 → 10 func 编译期映射;BOOL 编译器强制 0/1
+127 -114
View File
@@ -1,176 +1,189 @@
# 机器定义文件(machine.toml
# 机器定义文件(machine.toml V2
指令集与类型的**唯一事实来源**,**只给编译器**STCompiler/compiler)使用。执行器(vm)不读本文件——它是下位机程序,内建全部指令。
V2 规格(STATOR2,阶段 1 定稿;N5 闭合)。schema 变更:opcode → 前缀分区 + opclass/format → fmtCAL_* 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_id16V1 的 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/RET26 条) |
| **INSTANCE**(有实例) | 数据区实例块(内建 FB | CAL_TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG8 条) |
format 不可省:同是"第二个操作数是数字",`LOADK`/`LOAD_I`/`CALL` 渲染相同但语义不同(常量 id / 槽号 / fn_id,由参数名表达);"数字 vs 寄存器 vs 偏移"的渲染差异(`r%d` / `%u` / `%+d`)只有 format 能表达。
## 配置文件格式(TOML
## 配置文件格式(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 条见 §类型系统,格式同
# ---- 指令(V2prefix + 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 行强校验一致
# ---- 内建 FB15 条;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(浮点)、timeTIME 毫秒)、dateDATE/TOD/DTv1 不运算)
- **语言层 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 → 重建 vmcompiler 免重建)
加语言类型: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. CLISTCompiler --machine <path>(编译路径必填)
7. 测试:machine 解析/校验负例、型号不匹配报错、SHA-256 篡改检测、
冻结哈希更新、全量回归
8. 文档 + 提交
```
## 实现前确认项(4 项,未定案)
1. 配置文件名与位置:建议 `compiler/machine.toml`(只归编译器用)
2. 型号标识生成规则:建议 `[meta] name + version` 拼成 32 字节(如 `"STATOR" + "1"` → 补 `'\0'`);vm 内建"支持型号列表"比对
3. compiler 内部 `TypeKind`Bool/Int/Time):保留为语言语义枚举(类型检查规则),元数据从配置 base 查
4. 依赖方向确认:`compiler` 不再依赖 `isa``compiler → toml++` 自有;`STCompiler → compiler``vm → isa`
| 项 | V1 | V2 |
|---|---|---|
| meta.version | 1 | **2** |
| op 标识 | `opcode`0..33 全局) | **`prefix` + `op`**(分区内) |
| 分类 | classplain/instance+ format | **fmt**(与 prefix 互锁) |
| 数据语义 | tag0/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 |
+356
View File
@@ -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`=NONE4B);`011`=RRR、`010 000/001/010/011/100/101`=IMM/SLOT/JMP/JC/CALL/CAL8B);`0010`=SLOT+off、`0011`=IMM6416B);`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 引用)、值区地址 u324GB)
### 2.4 类型系统:IEC 全基础类型(19 种,type:5
`type` 位段 **type:532 种)**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/DTU13 定案)**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 magicSTSC,不变)
4..7 version = 2V2 格式;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 处理 | 原因 |
|---|---|---|
| 用户 FBFUNCTION_BLOCK | **调用点内联展开**(字节码随调用点数膨胀) | 12.8 定案:FB 体访问**绝对字段地址**,不同实例基址不同,共享体无法复用 |
| FUNCTIONFC | 独立函数 + `CALL fn_id` 帧栈 | FC 无状态,寄存器约定(r1..r7 / r0),无地址冲突;递归在前层被拒 |
| 内建 FBTON/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 **内联展开** | **基址寻址共享体**(消除膨胀) |
| 寻址空间 | 槽 65535512KB | 槽 2^32、值区 4GB |
| 初值 | 槽内 | 值段(逻辑分段,同文件) |
| 跳转/pc | 条数单位 | **字节单位** |
| 型号 | STATOR1 | STATOR2 |
## 4. 尚未确定项(细节,待逐项闭合)
核心决策已全部定案,以下为进入实现前需闭合的设计细节:
| # | 项 | 状态 |
|---|---|---|
| ~~A1~~ | ~~元数据段格式~~ | **已定案(U12**v1 0 长度占位(offset_meta=0),schema 延后 |
| ~~A2~~ | ~~常量内嵌 vs 常量表~~ | **已定案**:常量表(8B LOADK 查表);LOADK 用 type 位段解释;条目 8B 无 tagIMM64 预留不发射 |
| A3 | 内建 FB 字段类型化 | **已定案**:字段类型化(TON: in/pt/q/et = BOOL/TIME/BOOL/TIMER_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.5offset_meta/max_stack/values_size 等) |
| C3 | machine.toml `[[fb]]` 表 schema | **字段/布局已定案**(指令集清单 §8.1);`[[fb]]` **配置格式**并入 N5machine.toml V2 schema,阶段 1 |
| ~~C4~~ | ~~CALL/CAL 的 rd 恒 0 成文~~ | **已定案(U14**:已写入指令编码.md §5 |
| C5 | line1 推演重算 | 实施阶段(阶段 5)才需要 |
| ~~C6~~ | ~~计数器字段表终稿~~ | **已定案**:指令集清单 §8.1CTU 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 个 opcode24..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 V2type 表 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:532 种)**
- 格式合并:用户确认原子类型固定后选位段方案;数位得出 type:5CALL→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:532 种,含 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-floatfloat64-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、RTCv1 实现** |
| ~~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 @112v1=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~~ | **已定案**CALL010 100 区)/ CAL010 101 区)的 rd 字节恒 0(未使用),按指令编码/指令集清单文档编码 |
| ~~U15~~ | ~~无类型指令 type=NONE~~ | **已被 func=0 取代**(func 位段,无类型指令编码 0 不使用) |
| ~~U16~~ | ~~off32 相对下一条公式~~ | **已确认**(随 K7 跳转字节化接受) |
## 10. 全功能指令集清单
全功能目标下的完整指令清单(10 类 ~55 条,含 op 预算核对)已拆分为独立文档:
[`指令集清单.md`](指令集清单.md)。状态列:`核心` = v1 实现;`扩展` = v1 后实现;`预留` = 仅留编码/未来。
+280
View File
@@ -0,0 +1,280 @@
# 指令集清单(V2 编码定案)
> 状态:**已编码**。全功能指令清单,全部指令已按 [`指令编码.md`](指令编码.md) 的
> RISC-V 式格式(前缀长度组 + op + func + fmt)分配编码。
> 状态列:`核心` = v1 实现(含内建 FB 全部 15 条);`扩展` = v1 后实现;`预留` = 仅留编码/未来。
## 编码规则摘要(详见指令编码.md)
- **字节 0** = 长度前缀 + 形态分区 + 主操作码:`11`/`10`+op:64B)、`011`/`010 xxx`+op:28B)、`0010`/`0011`+op:416B)、`0001`+op:432B
- **字节 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/CALfunc 编码为 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..56RS+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 | 操作数区 30Bbit 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 位子 opbit1..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. 内建 FBCAL + 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=8pad 1..7)、duty=16、q=20、et=24pad 21..23 | 32B |
| RTC | enable=0、date=4pad 1..3)、tod=8 | 12B |
> **RTC 时钟源(确定性注记)**:v1 无墙钟(确定性原则)——RTC 用**虚拟时钟**:映像加载起
> 周期计数 × dt_ms 累计推导日期时间;不读系统时钟,保证确定性(A5 定案延伸)。
> **PWM 语义**in 使能时按 pt 周期输出 q(duty×pt 高电平),et 为当前周期已运行时间。
> **SR/RS 语义**SR 置位优先(S1=1 → Q1=1R 仅在 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` 区)。
+99
View File
@@ -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
头 128B16 对齐):
0 magic "STSC"(不变)
4 version = 2V1=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/datan_i/n_q/n_m 保留恒 0
64 offset_slots 68 offset_values16 对齐)
72..103 型号标识 STATOR2
104 n_slots= n_globals + 实例/字段槽数) 108 values_size
112 offset_metav1=0 空段) 116 max_stack 120..127 预留 8B
段序:头 → 常量表 → 函数表 → 字节码 → 槽表段 → 值段 → 元数据段(v1 空)→ SHA-256[32]
```
## 6. machine.toml V2 schemaN5,阶段 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 映射) |
+61
View File
@@ -0,0 +1,61 @@
# 阶段 4/5:执行侧 V2 与全链路(待办清单)
> 状态:**待办**。阶段 3(编译器语言层 V2)已完成;本文记录阶段 4(isa+vm 执行侧 V2
> 与阶段 5(全链路收尾)的待办项,供后续执行。
## 阶段 4:isa 重构 + vm 同步(执行侧 V2)
### 0. 执行步骤总览(细化为子步骤,一次一个)
| 步骤 | 内容 | 验证 |
|---|---|---|
| 4-1a | isa/Types.hfunc 宽度/符号表 + 饱和模板(除法 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/.cppencode/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/RTCRTC 虚拟时钟=周期×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 | **C5line1 推演重算**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)——用户决定时机 |
+32 -84
View File
@@ -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 clockRTC 用累计周期数
```
## 边界
- 只认 `.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;
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<uint8_t>& image, Machine* out, std::string* err);
Fault run_cycle(); // MAIN pc=0 → RETMAIN 帧跨周期保留)
// 可观察性(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
### 步骤 3CALL / RET 帧栈 + 故障(已落地)
- `CALL fn_id`:压新帧(复制 r0..r7)→ pc=0`RET`:复制 r0..r7 回调用方 → 弹栈(MAIN 的 RET = 周期结束)
- `cycle_limit` 超限(用例 6)、栈深 > 64、`BadOp` / `BadSlot` / `BadConst` → 周期中止返回故障
### 步骤 4:定时器与边沿(已落地)
- 执行时用映像 `dt_ms` 推进(每周期一次调用 = 推进一次),不读系统时钟
- TONin 真 → et += dt(到 pt 停)、q = et ≥ ptin 假 → et = 0
- TOFin 真 → q=1、et=0;掉电 → et += dt、et ≥ pt → q=0
- CTUcu 上升沿(**上次 cu 存 VM 侧 vector**,按实例基槽索引)→ cv+1r → 复位;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` 故障
- TONdt=10、pt=30 → 第 3 周期 q=1CTU:上升沿计数
- **确定性**:同一 I 序列跑两遍,数据区完全一致
### 步骤 7:验证 + 提交(已落地,ctest 14/14
- `cmake --build` + `ctest`(新增 `vm_cycles` 后 10/10 全绿);20 用例扫描:14 正例执行通过
+110 -99
View File
@@ -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 = 0pc = 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<int64_t> 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<Frame>`**深度上限 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 + 1CALL 下一条)、ret_fn_id = 当前函数
2. 复制当前帧 r0..r7 → 新帧 r0..r7(实参已在调用点 MOVE 进 r1..r7——全宽复制
3. 新帧 ret_pc = 当前 pc已指向 CALL 下一条,字节)、ret_fn_id = 当前函数
4. 压栈,pc = 0fn_id = callee
RET:
1. 若当前是 MAIN(栈深 0)→ 周期结束
1. 若当前是 MAIN(栈深 1)→ 周期结束ended_ = true
2. 否则:复制当前帧 r0..r7 → 调用方帧 r0..r7r0 = 结果,r1..r7 原样返回)
3. 弹栈,pc = ret_pcfn_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/764 位整数值)
## 数据区访问(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) = rs8 字节原样
槽号 slot(指令操作数u32)→ 槽表条目(8Baddr: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/1INT → `(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_valueF32/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` | 见内置 FB15 条) |
| `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;编译期类型检查保证使用域)
```
### 周期指令计数
## 内置 FBCAL15 条)
- 每条指令执行后 `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/etCTU = cu/r/pv/q/cvCTD = cd/ld/pv/q/cvCTUD = cu/cd/r/lu/pv/qu/qd/cvR_TRIG/F_TRIG = clk/q)。每次执行 = 推进一次,Δt = 映像头 `dt_ms`(不读系统时钟)。边沿检测的"上次输入"存 VM 侧 `edge_prev_`(每实例基槽 2 字节),跨周期保留:
```text
CAL_TON
in 为真:et += dtet ≥ pt 时停在 pt);q = (et ≥ pt) ? 1 : 0
in 为假:et = 0q = 0
CAL_TOF
in 为真:q = 1et = 0
in 掉电:et += dtet ≥ pt 时 q = 0
CAL_TP(脉冲):
in 上升沿启动 PT 时长脉冲(期间 in 变化不影响)
计时中et += dtet ≥ pt q = 0、et 归零(脉冲结束);否则 q = 1
非计时:et = 0q = 0
CAL_CTUcu 上升沿 → cv += 1r → cv = 0q = (cv ≥ pv) ? 1 : 0
CAL_CTDcd 上升沿 → cv -= 1ld → cv = pvq = (cv ≤ 0) ? 1 : 0
CAL_CTUDcu 上升沿 → cv += 1cd 上升沿 → cv -= 1r → cv = 0lu → cv = pv
qu = (cv ≥ pv) ? 1 : 0qd = (cv ≤ 0) ? 1 : 0
CAL_R_TRIGq = clk 且上次 clk 假(上升沿)
CAL_F_TRIGq = !clk 且上次 clk 真(下降沿)
```
| fb_id | FB | 字段布局(偏移:类型) | 语义 |
|---|---|---|---|
| 0 | TON | in@0:Bpt@8:TIMEq@16:Bet@24:TIME | in 真:et += dtet ≥ pt 停在 pt);q = (et ≥ pt) |
| 1 | TOF | 同 TON | in 真:q=1、et=0in 掉电:et += dtet ≥ pt → q=0 |
| 2 | TP | 同 TON | in 上升沿启动 PT 时长脉冲;计时 et += dt,期满 q=0、et=0 |
| 3 | CTU | cu@0:Br@1:Bpv@2:INTq@4:Bcv@6:INT | cu 上升沿 cv += 1r → cv=0q = (cv ≥ pv) |
| 4 | DCTU | cu@0:Br@1:Bpv@4:DINTq@8:Bcv@12:DINT | 同 CTUDINT |
| 5 | CTD | cd@0:Bld@1:Bpv@2:INTq@4:Bcv@6:INT | cd 上升沿 cv -= 1ld → cv=pvq = (cv ≤ 0) |
| 6 | DCTD | 同 DCTU 布局 | 同 CTDDINT |
| 7 | CTUD | cu@0:Bcd@1:Br@2:Blu@3:Bpv@4:INTqu@6:Bqd@7:Bcv@8:INT | cu 上升沿 +1、cd 上升沿 -1r → 0lu → pvqu=(cv≥pv)、qd=(cv≤0) |
| 8 | DCTUD | cu@0cd@1r@2lu@3:Bpv@4:DINTqu@8qd@9:Bcv@12:DINT | 同 CTUDDINT |
| 9 | R_TRIG | clk@0:Bq@1:B | q = clk 且上次 clk 假(上升沿) |
| 10 | F_TRIG | clk@0:Bq@1:B | q = !clk 且上次 clk 真(下降沿) |
| 11 | SR | S1@0:BR@1:BQ1@2:B | **置位优先**S1 → Q1=1;否则 R → Q1=0 |
| 12 | RS | SET@0:BRESET1@1:BQ1@2:B | **复位优先**RESET1 → Q1=0;否则 SET → Q1=1 |
| 13 | PWM | in@0:Bpt@8:TIMEduty@16:REALq@20:Bet@24:TIME | in 真et += dt≥ pt 回绕);q = (et < pt × duty)in 假:et=0、q=0 |
| 14 | RTC | enable@0:Bdate@4:DATEtod@8:TOD | 虚拟时钟:累计周期数 × dt_ms(1970 纪元);enable 真时写 date/tod |
## 故障
@@ -130,38 +141,38 @@ CAL_F_TRIGq = !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(紧凑 U80=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) 低字节 = 1I0_0
1 STORE_GLOBAL r8, 4 槽 4starter.start= 1
2 LOAD_GLOBAL r9, 2 r9 = 0I0_1
3 STORE_GLOBAL r9, 5 槽 5starter.stop= 0
4 LOAD_GLOBAL r8, 8 内联体:r8 = 槽 4 = 1start
5 JF r8, +3 r8≠0 不跳;pc = 6
6 LOAD_GLOBAL r10, 9 r10 = 槽 5 = 0stop
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 = 0EmergencyStop
11 NOT r11, r12 r11 = 1
12 MOVE r8, r11 r8 = 1
13 STORE_GLOBAL r8, 10 槽 6starter.q= 1
14 LOAD_GLOBAL r8, 10 r8 = 槽 6 = 1
15 STORE_Q r8, 3 槽 3Q0_0= 1
16 RET 周期结束;executor 读槽 3 → 硬件
0x9c LOAD.U8 r8, s1 r8 = value[1] = 1I0_0
0xa4 STORE_OFF.U8 r8, s4, 0 starter.S1(实例基址 4 + 0= 1
0xb4 LOAD.U8 r9, s2 r9 = value[2] = 0I0_1
0xbc STORE_OFF.U8 r9, s4, 1 starter.R4 + 1= 0
0xcc LOAD_OFF.U8 r8, s4, 0 r8 = 1S1
0xdc JF r8, +24 r8≠0 不跳;pc = 0xe4 + 0
0xe4 LOAD_OFF.U8 r10, s4, 1 r10 = 0R
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] = 0EmergencyStop
0x10c NOT r11, r12 r11 = 1
0x110 MOVE r8, r11 r8 = 1
0x114 STORE_OFF.U8 r8, s4, 2 starter.Q14 + 2= 1
0x124 LOAD_OFF.U8 r8, s4, 2 r8 = 1Q1
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 端到端验证)
+41
View File
@@ -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_0I0_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,全部绿。**
+16
View File
@@ -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 计划**:语言层 V2Lexer/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,跑扫描周期 |
+102 -34
View File
@@ -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 <cstddef>
#include <cstdint>
#include <string>
#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 op4B 6 位 / 8B 5 位或 2 位 / 16B·32B 4 位)
*/
uint8_t op_of(const uint8_t* code);
/**
* @brief 取 func 位段(字节 1 高 4 位)。
* @param code 指令字节
* @return 0..150..9 有效)
*/
uint8_t func_of(const uint8_t* code);
/// @brief 取 rd23..16)。
uint8_t rd_of(const uint8_t* code);
/// @brief 取 rs131..24)。
uint8_t rs1_of(const uint8_t* code);
/// @brief 取 rs239..32)。
uint8_t rs2_of(const uint8_t* code);
/// @brief 取 imm3255..24IMM 形态)。
uint32_t imm32_of(const uint8_t* code);
/// @brief 取 slot3255..24SLOT 形态)。
uint32_t slot32_of(const uint8_t* code);
/// @brief 取 off3247..16JMP 形态,有符号字节偏移)。
int32_t off32_of(const uint8_t* code);
/// @brief 取 off3255..24JC 形态,有符号字节偏移)。
int32_t off32_jc_of(const uint8_t* code);
/// @brief 取 fn_id3247..16CALL 形态)。
uint32_t fn_id32_of(const uint8_t* code);
/// @brief 取 fb_id1631..16CAL 形态)。
uint16_t fb_id16_of(const uint8_t* code);
/// @brief 取 CAL 的实例槽号(63..32)。
uint32_t cal_slot32_of(const uint8_t* code);
/// @brief 取 16B 形态的 off3287..56)。
int32_t off32_hi_of(const uint8_t* code);
/// @brief 取 imm6487..24IMM64 形态)。
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 写 rd23..16)。
void set_rd(uint8_t* out, uint8_t rd);
/// @brief 写 rs131..24)。
void set_rs1(uint8_t* out, uint8_t rs1);
/// @brief 写 rs239..32)。
void set_rs2(uint8_t* out, uint8_t rs2);
/// @brief 写 imm3255..24)。
void set_imm32(uint8_t* out, uint32_t v);
/// @brief 写 slot3255..24)。
void set_slot32(uint8_t* out, uint32_t v);
/// @brief 写 off3247..16JMP 形态)。
void set_off32(uint8_t* out, int32_t v);
/// @brief 写 off3255..24JC 形态)。
void set_off32_jc(uint8_t* out, int32_t v);
/// @brief 写 fn_id3247..16)。
void set_fn_id32(uint8_t* out, uint32_t v);
/// @brief 写 fb_id1631..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 形态的 off3287..56)。
void set_off32_hi(uint8_t* out, int32_t v);
/// @brief 写 imm6487..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<hex>`。
* @details 文本格式与 Doc/isa/指令与映像.md §10 一致:
* `MOVE r1, r2`、`LOAD.I16 r5, s3`full/float/dst 类显示类型后缀)、
* `JMP +4`、`JT r0, -1`、`CALL 1`、`CAL.TON s5`、`RET`
* 未知 (prefix, op) 输出 `??? 0x<hex dump>`。
*/
void disasm(const MachineConfig& cfg, Instr w, char* out, size_t cap);
void disasm(const MachineConfig& cfg, const uint8_t* code, size_t len,
char* out, size_t cap);
}
+15 -1
View File
@@ -11,6 +11,7 @@
#pragma once
#include <cstdint>
#include <map>
#include <string>
#include <vector>
@@ -21,6 +22,18 @@
namespace compiler {
/**
* @brief 变量/槽映射(--dump-map 用;验证源码 ↔ 反汇编对照)。
*/
struct CodegenMap {
/// 每函数:{ 函数名, { 局部变量名 → "寄存器号" } }
std::vector<std::pair<std::string, std::map<std::string, std::string>>> funcs;
/// 全局:{ 变量名 → "slot=.. off=.. type=.." }
std::map<std::string, std::string> globals;
/// 实例:{ "POU名/实例名" → "slot=.. off=.. fb=.. size=.. 字段:名@off.." }
std::map<std::string, std::string> instances;
};
/**
* @brief 编译工程为 .stb 映像字节(在链接 + 类型检查成功后调用)。
* @param proj 工程定义(cycle_limit / dt_ms / 哈希)
@@ -34,5 +47,6 @@ namespace compiler {
*/
bool codegen_project(const Project& proj, const std::vector<SourceUnit>& units,
const LinkResult& link, const MachineConfig& cfg,
std::vector<uint8_t>* image, std::string* err);
std::vector<uint8_t>* image, std::string* err,
CodegenMap* map = nullptr);
}
+27 -13
View File
@@ -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 起)
+4 -3
View File
@@ -19,6 +19,7 @@
#include <string>
#include <vector>
#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<SourceUnit>& units,
LinkResult* out, std::string* err);
const MachineConfig& cfg, LinkResult* out, std::string* err);
/**
* @brief 读取并解析一个 .st 文件成 SourceUnit。
+55 -39
View File
@@ -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/指令配置.mdV2 schema
* - 配置只给编译器(toml++ 运行时加载);vm 不读配置(内建全指令集)
* - 强校验 5 项:字段必填 / type.base 命中内建基元·range 合法·tag 契约 /
* op opcode 唯一·类别-格式互锁 / fb.opcode 与 op 一致·字段类型命中 type 表
* - 强校验 5 项:metaversion=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 ≤ maxtag 与 .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 kind; ///< 语言语义:bool/bit/int/uint/float/time/date
uint32_t func = 0; ///< type 位段值(0..9
};
/**
* @brief 配置指令。
* @details 对应 machine.toml [[op]] 行:opcode 唯一且 0..255class 与 format
* 互锁(INSTANCE ↔ CAL);params 数量与 format 期望一致。
* @brief 配置指令V2prefix + 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; ///< classinstance / plain
std::string format; ///< 操作数形态:RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE
std::vector<std::string> 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<std::string> 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)布局登记V2fb_id
* @details 对应 machine.toml [[fb]] 行;fb_id 唯一(0..1415 条全集);
* CAL 指令(010101)的 fb_id 由本表提供。
*/
struct ConfigFb {
std::string name; ///< FB 名(写,如 ton/ctu
uint32_t opcode = 0; ///< 对应 op 行 opcodeCAL_*
std::string name; ///< FB 名(写,如 TON/CTU
uint32_t fb_id = 0; ///< FB 类型 id0..14
std::vector<ConfigFbField> 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 成功;falseerr 已写,前缀 "machine error"
* @details 强校验项:字段必填 / type.base 命中内建基元·range 合法·tag 契约 /
* op opcode 唯一·类别-格式互锁 / fb.opcode 与 op 一致·字段类型命中
* type 表(清单见文件头)。
* @details 强校验项:metaname/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 类型 id0..14
* @return 命中指针;未找到返回 nullptr
*/
const ConfigFb* find_fb_by_id(uint32_t fb_id) const;
/// @brief 内建基元表(bit / int8..uint64 / float32 / float64)。
static const std::vector<PrimType>& prims();
@@ -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<ConfigType> types_; ///< 配置类型表
std::vector<ConfigOp> ops_; ///< 配置指令表
std::vector<ConfigFb> fbs_; ///< 内建 FB 表
+12 -7
View File
@@ -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<Expr> lhs; ///< 二目左操作数
std::unique_ptr<Expr> rhs; ///< 二目右操作数
std::unique_ptr<Expr> operand; ///< Not / Neg 的操作数
+101 -32
View File
@@ -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/指令与映像.mdV2
* V2:头 128Bversion=2、型号标识 STATOR2 @72、n_slots/values_size/offset_meta/max_stack),
* 常量表 8B/条(无 tagLOADK 按 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; ///< 常量表行内:value8 字节,无 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; ///< 类型 tag0=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() == falseerror() 取原因。
*/
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<uint32_t>(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,24 +191,33 @@ 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_constfrom 已校验段起点)
uint32_t offs_of_const() const;
/// 读头内 u32(小端;内部辅助)。
uint32_t get_le32_(size_t off) const {
return static_cast<uint32_t>(buf_[off])
| (static_cast<uint32_t>(buf_[off + 1]) << 8)
| (static_cast<uint32_t>(buf_[off + 2]) << 16)
| (static_cast<uint32_t>(buf_[off + 3]) << 24);
}
const uint8_t* buf_; ///< 映像缓冲(不拥有)
size_t len_; ///< 缓冲字节数
@@ -166,8 +231,12 @@ namespace compiler {
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; ///< 数据段偏移
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; ///< 栈区字节数
};
/**
+9 -15
View File
@@ -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 位段值 func0..9;无配置返回 0。
uint32_t func() const { return config ? config->func : 0; }
/// @brief 语言语义 kindbool/bit/int/uint/float/time/date);无配置返回空串
const std::string& kind() const {
static const std::string kEmpty;
return config ? config->kind : kEmpty;
}
/// @brief 是否有效(config 与 prim 都已命中)。
+11 -7
View File
@@ -15,19 +15,23 @@
#include <vector>
#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 表达式求值类型(V219 标量 + 未定型)。
* @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<SourceUnit>& units,
const LinkResult& link, std::string* err);
const LinkResult& link, const MachineConfig& cfg, std::string* err);
}
+755 -211
View File
File diff suppressed because it is too large Load Diff
+308 -57
View File
@@ -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..16imm32/slot32=55..24off32/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<uint32_t>(opcode)
| (static_cast<uint32_t>(rd) << 8)
| (static_cast<uint32_t>(a) << 16)
| (static_cast<uint32_t>(b) << 24);
}
namespace {
/// @brief 取操作码(低 8 位)。
uint8_t op_of(Instr w) { return static_cast<uint8_t>(w & 0xFFu); }
/// @brief 取 rd 字段(第 8..15 位)。
uint8_t rd_of(Instr w) { return static_cast<uint8_t>((w >> 8) & 0xFFu); }
/// @brief 取 a 字段(第 16..23 位)。
uint8_t a_of(Instr w) { return static_cast<uint8_t>((w >> 16) & 0xFFu); }
/// @brief 取 b 字段(第 24..31 位)。
uint8_t b_of(Instr w) { return static_cast<uint8_t>((w >> 24) & 0xFFu); }
/**
* @brief 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<uint16_t>(a_of(w) | (static_cast<uint16_t>(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<int16_t>(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"; // 4Bbit6 区分 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 op4B 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; // 4B6 位
if ((b & 0xC0) == 0x40) {
return (b & 0x20) ? (b & 0x1F) : (b & 0x03); // 8B011 → 5 位;010 → 2 位
}
return b & 0x0F; // 16B/32B4 位
}
/**
* @brief func 1 4
* @param code
* @return 0..150..9
*/
uint8_t func_of(const uint8_t* code) {
return static_cast<uint8_t>((code[1] >> 4) & 0x0F);
}
uint8_t rd_of(const uint8_t* code) { return code[2]; }
uint8_t rs1_of(const uint8_t* code) { return code[3]; }
uint8_t rs2_of(const uint8_t* code) { return code[4]; }
/**
* @brief imm3255..24 4
* @param code
* @return 32
*/
uint32_t imm32_of(const uint8_t* code) {
return static_cast<uint32_t>(code[3])
| (static_cast<uint32_t>(code[4]) << 8)
| (static_cast<uint32_t>(code[5]) << 16)
| (static_cast<uint32_t>(code[6]) << 24);
}
uint32_t slot32_of(const uint8_t* code) { return imm32_of(code); }
/**
* @brief off3247..16JMP 4
* @param code
* @return
*/
int32_t off32_of(const uint8_t* code) {
return static_cast<int32_t>(static_cast<uint32_t>(code[2])
| (static_cast<uint32_t>(code[3]) << 8)
| (static_cast<uint32_t>(code[4]) << 16)
| (static_cast<uint32_t>(code[5]) << 24));
}
/**
* @brief off3255..24JC 4
* @param code
* @return
*/
int32_t off32_jc_of(const uint8_t* code) {
return static_cast<int32_t>(static_cast<uint32_t>(code[3])
| (static_cast<uint32_t>(code[4]) << 8)
| (static_cast<uint32_t>(code[5]) << 16)
| (static_cast<uint32_t>(code[6]) << 24));
}
uint32_t fn_id32_of(const uint8_t* code) {
return static_cast<uint32_t>(code[2])
| (static_cast<uint32_t>(code[3]) << 8)
| (static_cast<uint32_t>(code[4]) << 16)
| (static_cast<uint32_t>(code[5]) << 24);
}
/**
* @brief fb_id1631..16
* @param code
* @return FB id
*/
uint16_t fb_id16_of(const uint8_t* code) {
return static_cast<uint16_t>(code[2] | (static_cast<uint16_t>(code[3]) << 8));
}
/**
* @brief CAL 63..32
* @param code
* @return
*/
uint32_t cal_slot32_of(const uint8_t* code) {
return static_cast<uint32_t>(code[4])
| (static_cast<uint32_t>(code[5]) << 8)
| (static_cast<uint32_t>(code[6]) << 16)
| (static_cast<uint32_t>(code[7]) << 24);
}
/**
* @brief 16B off3287..56
* @param code
* @return
*/
int32_t off32_hi_of(const uint8_t* code) {
return static_cast<int32_t>(static_cast<uint32_t>(code[7])
| (static_cast<uint32_t>(code[8]) << 8)
| (static_cast<uint32_t>(code[9]) << 16)
| (static_cast<uint32_t>(code[10]) << 24));
}
/**
* @brief imm6487..24
* @param code
* @return 64
*/
uint64_t imm64_of(const uint8_t* code) {
uint64_t v = 0;
for (int i = 0; i < 8; ++i) {
v |= static_cast<uint64_t>(code[3 + i]) << (8 * i);
}
return v;
}
/**
* @brief 0 +
* @param out 16B
* @param prefix
* @param op
*/
void set_byte0(uint8_t* out, const std::string& prefix, uint32_t op) {
if (prefix == "11") {
out[0] = static_cast<uint8_t>(0xC0 | (op & 0x3F)); // bit7..6 = 11
} else if (prefix == "10") {
out[0] = static_cast<uint8_t>(0x80 | (op & 0x3F)); // bit7..6 = 10
} else if (prefix == "011") {
out[0] = static_cast<uint8_t>(0x60 | (op & 0x1F)); // bit7..5 = 011
} else if (prefix.size() == 6 && prefix.compare(0, 3, "010") == 0) {
const uint32_t shape = static_cast<uint32_t>(prefix[3] - '0') * 4 +
static_cast<uint32_t>(prefix[4] - '0') * 2 +
static_cast<uint32_t>(prefix[5] - '0');
out[0] = static_cast<uint8_t>(0x40 | (shape << 2) | (op & 0x03));
} else if (prefix == "0010") {
out[0] = static_cast<uint8_t>(0x20 | (op & 0x0F));
} else if (prefix == "0011") {
out[0] = static_cast<uint8_t>(0x30 | (op & 0x0F));
} else { // "0001"
out[0] = static_cast<uint8_t>(0x10 | (op & 0x0F));
}
}
void set_func(uint8_t* out, uint8_t func) {
out[1] = static_cast<uint8_t>((out[1] & 0x0F) | ((func & 0x0F) << 4));
}
void set_rd(uint8_t* out, uint8_t rd) { out[2] = rd; }
void set_rs1(uint8_t* out, uint8_t rs1) { out[3] = rs1; }
void set_rs2(uint8_t* out, uint8_t rs2) { out[4] = rs2; }
void set_imm32(uint8_t* out, uint32_t v) {
out[3] = static_cast<uint8_t>(v & 0xFF);
out[4] = static_cast<uint8_t>((v >> 8) & 0xFF);
out[5] = static_cast<uint8_t>((v >> 16) & 0xFF);
out[6] = static_cast<uint8_t>((v >> 24) & 0xFF);
}
void set_slot32(uint8_t* out, uint32_t v) { set_imm32(out, v); }
void set_off32(uint8_t* out, int32_t v) {
const uint32_t u = static_cast<uint32_t>(v);
out[2] = static_cast<uint8_t>(u & 0xFF);
out[3] = static_cast<uint8_t>((u >> 8) & 0xFF);
out[4] = static_cast<uint8_t>((u >> 16) & 0xFF);
out[5] = static_cast<uint8_t>((u >> 24) & 0xFF);
}
void set_off32_jc(uint8_t* out, int32_t v) {
const uint32_t u = static_cast<uint32_t>(v);
out[3] = static_cast<uint8_t>(u & 0xFF);
out[4] = static_cast<uint8_t>((u >> 8) & 0xFF);
out[5] = static_cast<uint8_t>((u >> 16) & 0xFF);
out[6] = static_cast<uint8_t>((u >> 24) & 0xFF);
}
void set_fn_id32(uint8_t* out, uint32_t v) { set_off32(out, static_cast<int32_t>(v)); }
void set_fb_id16(uint8_t* out, uint16_t v) {
out[2] = static_cast<uint8_t>(v & 0xFF);
out[3] = static_cast<uint8_t>((v >> 8) & 0xFF);
}
void set_cal_slot32(uint8_t* out, uint32_t v) {
out[4] = static_cast<uint8_t>(v & 0xFF);
out[5] = static_cast<uint8_t>((v >> 8) & 0xFF);
out[6] = static_cast<uint8_t>((v >> 16) & 0xFF);
out[7] = static_cast<uint8_t>((v >> 24) & 0xFF);
}
void set_off32_hi(uint8_t* out, int32_t v) {
const uint32_t u = static_cast<uint32_t>(v);
out[7] = static_cast<uint8_t>(u & 0xFF);
out[8] = static_cast<uint8_t>((u >> 8) & 0xFF);
out[9] = static_cast<uint8_t>((u >> 16) & 0xFF);
out[10] = static_cast<uint8_t>((u >> 24) & 0xFF);
}
void set_imm64(uint8_t* out, uint64_t v) {
for (int i = 0; i < 8; ++i) {
out[3 + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFF);
}
}
/**
* @brief machine.toml prefix/op/func/fmt
* @param cfg
* @param code
* @param len
* @param out
* @param cap '\0'cap==0 out
* @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<unsigned>(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<unsigned>(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<unsigned>(rd),
static_cast<unsigned>(a_of(w)));
snprintf(out, cap, "%s%s r%u, r%u", op->name.c_str(), suffix.c_str(),
static_cast<unsigned>(rd_of(code)), static_cast<unsigned>(rs1_of(code)));
} else if (f == "RRR") {
snprintf(out, cap, "%s r%u, r%u, r%u", op->name.c_str(), static_cast<unsigned>(rd),
static_cast<unsigned>(a_of(w)), static_cast<unsigned>(b_of(w)));
} else if (f == "IMM" || f == "SLOT") {
snprintf(out, cap, "%s r%u, %u", op->name.c_str(), static_cast<unsigned>(rd),
static_cast<unsigned>(imm16_of(w)));
snprintf(out, cap, "%s%s r%u, r%u, r%u", op->name.c_str(), suffix.c_str(),
static_cast<unsigned>(rd_of(code)), static_cast<unsigned>(rs1_of(code)),
static_cast<unsigned>(rs2_of(code)));
} else if (f == "IMM") {
snprintf(out, cap, "%s%s r%u, %u", op->name.c_str(), suffix.c_str(),
static_cast<unsigned>(rd_of(code)), static_cast<unsigned>(imm32_of(code)));
} else if (f == "SLOT") {
snprintf(out, cap, "%s%s r%u, s%u", op->name.c_str(), suffix.c_str(),
static_cast<unsigned>(rd_of(code)), static_cast<unsigned>(slot32_of(code)));
} else if (f == "JMP") {
snprintf(out, cap, "%s %+d", op->name.c_str(), static_cast<int>(off16_of(w)));
snprintf(out, cap, "%s %+d", op->name.c_str(), static_cast<int>(off32_of(code)));
} else if (f == "JC") {
snprintf(out, cap, "%s r%u, %+d", op->name.c_str(), static_cast<unsigned>(rd),
static_cast<int>(off16_of(w)));
} else if (f == "CALL" || f == "CAL") {
snprintf(out, cap, "%s %u", op->name.c_str(), static_cast<unsigned>(imm16_of(w)));
} else { // NONE
snprintf(out, cap, "%s r%u, %+d", op->name.c_str(),
static_cast<unsigned>(rd_of(code)), static_cast<int>(off32_jc_of(code)));
} else if (f == "CALL") {
snprintf(out, cap, "%s %u", op->name.c_str(), static_cast<unsigned>(fn_id32_of(code)));
} else if (f == "CAL") {
const ConfigFb* fb = cfg.find_fb_by_id(fb_id16_of(code));
if (fb != nullptr) {
snprintf(out, cap, "CAL.%s s%u", fb->name.c_str(),
static_cast<unsigned>(cal_slot32_of(code)));
} else {
snprintf(out, cap, "CAL ?%u s%u", static_cast<unsigned>(fb_id16_of(code)),
static_cast<unsigned>(cal_slot32_of(code)));
}
} else if (f == "SLOT+off") {
snprintf(out, cap, "%s%s r%u, s%u, %d", op->name.c_str(), suffix.c_str(),
static_cast<unsigned>(rd_of(code)), static_cast<unsigned>(slot32_of(code)),
static_cast<int>(off32_hi_of(code)));
} else if (f == "RS+off") {
snprintf(out, cap, "%s%s r%u, r%u, %d", op->name.c_str(), suffix.c_str(),
static_cast<unsigned>(rd_of(code)), static_cast<unsigned>(rs1_of(code)),
static_cast<int>(off32_hi_of(code)));
} else { // NONE / 预留
snprintf(out, cap, "%s", op->name.c_str());
}
}
+682 -440
View File
File diff suppressed because it is too large Load Diff
+251 -15
View File
@@ -31,6 +31,7 @@
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <string>
@@ -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},
// 类型
// 类型V219 种 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 类型名不再设关键字(V2IDENT + 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<unsigned char>(c))) {
const uint32_t wstart = pos_;
size_t j = pos_;
while (j < src_.size() &&
std::isalpha(static_cast<unsigned char>(src_[j]))) {
++j;
}
if (j < src_.size() && src_[j] == '#') {
std::string w = src_.substr(wstart, j - wstart);
for (char& ch : w) {
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
}
if (w == "t") return lex_time(); // T#
if (w == "d") return lex_date(); // D#
if (w == "tod") return lex_tod(); // TOD#
if (w == "dt") return lex_dt(); // DT#
}
}
// 标识符 / 关键字:统一折小写存入 text(内部形),
@@ -288,14 +314,50 @@ namespace {
return true;
}
// 整数:十进制 [0-9]+int64_t 存储;
// 溢出检查在累加前做(v 上限 INT64_MAX)。
// 数字:进制整数(2#/8#/16#)→ 浮点(. / e)→ 十进制整数
if (std::isdigit(static_cast<unsigned char>(c))) {
const uint32_t start = pos_;
while (!at_end() && std::isdigit(static_cast<unsigned char>(cur()))) {
advance();
}
// 进制前缀:<基数>#<数字>2/8/16
if (!at_end() && cur() == '#') {
const std::string base_str = src_.substr(start, pos_ - start);
const uint32_t base = static_cast<uint32_t>(std::stoul(base_str));
if (base != 2 && base != 8 && base != 16) {
return fail("bad radix prefix");
}
return lex_base_int(start, base);
}
// 浮点:小数点或指数(.5 需数字前缀,由本分支覆盖)
bool is_float = false;
if (!at_end() && cur() == '.' && pos_ + 1 < src_.size() &&
std::isdigit(static_cast<unsigned char>(src_[pos_ + 1]))) {
is_float = true;
advance(); // .
while (!at_end() && std::isdigit(static_cast<unsigned char>(cur()))) {
advance();
}
}
if (!at_end() && (cur() == 'e' || cur() == 'E')) {
is_float = true;
advance();
if (!at_end() && (cur() == '+' || cur() == '-')) {
advance();
}
if (at_end() || !std::isdigit(static_cast<unsigned char>(cur()))) {
return fail("bad float literal");
}
while (!at_end() && std::isdigit(static_cast<unsigned char>(cur()))) {
advance();
}
}
const std::string digits = src_.substr(start, pos_ - start);
if (is_float) {
push(Tok::FLOAT_LIT, digits, 0);
out_->back().double_value = std::strtod(digits.c_str(), nullptr);
return true;
}
uint64_t v = 0;
for (const char ch : digits) {
const uint64_t d = static_cast<uint64_t>(ch - '0');
@@ -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<uint64_t>(ch - '0');
} else if (ch >= 'a' && ch <= 'f') {
d = static_cast<uint64_t>(ch - 'a' + 10);
} else if (ch >= 'A' && ch <= 'F') {
d = static_cast<uint64_t>(ch - 'A' + 10);
} else {
break;
}
if (d >= base) {
return fail("bad base literal digit");
}
if (v > (static_cast<uint64_t>(INT64_MAX) - d) / base) {
return fail("base literal too large");
}
v = v * base + d;
any = true;
advance();
}
if (!any) {
return fail("bad base literal");
}
push(Tok::INT_LIT, src_.substr(start, pos_ - start), static_cast<int64_t>(v));
return true;
}
/// 公历 → 1970-01-01 起的天数(Hinnant 算法;1970-01-01 = 0)。
static int64_t days_from_civil(int64_t y, unsigned m, unsigned d) {
y -= m <= 2;
const int64_t era = (y >= 0 ? y : y - 399) / 400;
const unsigned yoe = static_cast<unsigned>(y - era * 400);
const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
return era * 146097 + static_cast<int64_t>(doe) - 719468;
}
/// 某年某月的天数(含闰年)。
static unsigned days_in_month(int64_t y, unsigned m) {
static const unsigned kDays[12] = {31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31};
if (m == 2 && (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0))) {
return 29;
}
return kDays[m - 1];
}
/**
* @brief y-m-d1970
* @param out_days
* @return true false /
*/
bool parse_date_part(int64_t* out_days) {
int64_t y = 0, m = 0, d = 0;
if (!read_uint(&y)) return false;
if (!expect_char('-')) return false;
if (!read_uint(&m)) return false;
if (!expect_char('-')) return false;
if (!read_uint(&d)) return false;
if (y < 1 || y > 9999) return false;
if (m < 1 || m > 12) return false;
if (d < 1 || d > days_in_month(y, static_cast<unsigned>(m))) return false;
*out_days = days_from_civil(y, static_cast<unsigned>(m), static_cast<unsigned>(d));
return true;
}
/// 读一个无符号整数段(消费数字)。
bool read_uint(int64_t* out) {
if (at_end() || !std::isdigit(static_cast<unsigned char>(cur()))) {
return false;
}
int64_t v = 0;
while (!at_end() && std::isdigit(static_cast<unsigned char>(cur()))) {
v = v * 10 + static_cast<int64_t>(cur() - '0');
advance();
}
*out = v;
return true;
}
/// 期望指定字符并消费;不匹配返回 false。
bool expect_char(char want) {
if (at_end() || cur() != want) {
return false;
}
advance();
return true;
}
/**
* @brief D#<>-<>-<>
* @return true push DATE_LITint_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_LITint_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_LITint64 = date<<32 | tod
* false /err
*/
bool lex_dt() {
const uint32_t start = pos_;
advance(); // D
advance(); // T
advance(); // #
int64_t days = 0;
if (!parse_date_part(&days)) {
return fail("bad dt literal");
}
if (!expect_char('-')) return fail("bad dt literal");
int64_t h = 0, m = 0, s = 0;
if (!read_uint(&h)) return fail("bad dt literal");
if (!expect_char(':')) return fail("bad dt literal");
if (!read_uint(&m)) return fail("bad dt literal");
if (!expect_char(':')) return fail("bad dt literal");
if (!read_uint(&s)) return fail("bad dt literal");
if (h > 23 || m > 59 || s > 59) {
return fail("bad dt literal");
}
const uint64_t tod = static_cast<uint64_t>(h * 3600000 + m * 60000 + s * 1000);
const int64_t dt = (static_cast<uint64_t>(days) << 32) | tod;
push(Tok::DT_LIT, src_.substr(start, pos_ - start), dt);
return true;
}
// ---- 状态 ----
const std::string& src_; ///< 源文本(外部所有,不拷贝)
const std::string& sf_; ///< 源文件名(报错用)
+64 -62
View File
@@ -77,61 +77,48 @@ 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 名)
}
return "?";
/// 小写化(内建 FB 名 → fb 表名映射用)。
std::string lower_of(const std::string& s) {
std::string out = s;
for (char& ch : out) {
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
}
return out;
}
/**
* @brief 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/符号表与链接.md12.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;
const FbLayout* builtin_layout(const MachineConfig& cfg, const std::string& name) {
static std::map<std::string, FbLayout> cache;
auto it = cache.find(name);
if (it != cache.end()) {
return &it->second;
}
std::string up = name;
for (char& ch : up) {
ch = static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
}
const ConfigFb* fb = cfg.find_fb(up);
if (fb == nullptr) {
return nullptr;
}
FbLayout lay;
lay.type_name = name;
for (const ConfigFbField& f : fb->fields) {
FbField field;
field.name = f.name;
field.type_name = lower_of(f.type);
lay.fields.push_back(field);
}
auto r = cache.emplace(name, std::move(lay));
return &r.first->second;
}
/**
* @brief
@@ -148,8 +135,8 @@ namespace {
* @param err nullptr
*/
Linker(const Project& proj, const std::vector<SourceUnit>& 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 / 内部 varexternalglobal 不是字段
// 字段 = input / output / 内部 varexternal/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_typesPOU 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<char>(std::toupper(static_cast<unsigned char>(ch)));
}
if (cfg_.find_fb(up) != nullptr) {
const FbLayout* bl = builtin_layout(cfg_, d.type.name);
if (!bl) {
return fail(file, "unknown builtin FB '" + d.type.name + "'",
d.line, d.col);
@@ -693,6 +694,7 @@ namespace {
// ---- 成员 ----
const Project& proj_; ///< 工程定义(toml
const std::vector<SourceUnit>& units_; ///< 全部源文件 AST
const MachineConfig& cfg_; ///< 机器定义(内建 FB 布局来源)
LinkResult* out_; ///< 链接结果
std::string* err_; ///< 错误输出(可空)
std::filesystem::path gvl_path_; ///< 规范化后的 gvl 路径
@@ -712,13 +714,13 @@ namespace {
* @return true false err "link error"
*/
bool link_project(const Project& proj, const std::vector<SourceUnit>& 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();
}
+259 -121
View File
@@ -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=0INT=1TIME=2
* 4. op.opcode 0..255class/format -INSTANCE CAL
* params format
* 5. fb.opcode op instance
* @details Doc/isa/.md 1~5V2
* 1. metaname version = 2
* 2. type19 BOOL..DTbase kind
* func 0..9 base (width, signed, is_float)
* 3. opprefix prefix fmt op func
* params fmt enabled
* 4. fb15 fb_id 0..14fb_id type
* 5. op-fb CAL010101 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+offLOAD_PTR/STORE_PTR
if (prefix == "0011") return "IMM64";
if (prefix == "0001") return "预留";
return "";
}
/// @brief func 类别是否合法(V2none/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 是否合法(V2bool/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 falseerr
*/
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.tomlV2
* @param path machine.toml
* @param err nullptr
* @return true falseerr "machine error"
* @details 1~5
* 1. TOML meta/type/op/fb
* 2. type.base range min max
* 3. tag .stb BOOL=0INT=1TIME=2
* 4. op.opcode 0..255class/format -INSTANCE CAL
* params format
* 5. fb.opcode op instance
* @details 1~5 V2
* 1. metaname version = 2
* 2. type19 base kind func base
* 3. opprefix prefixfmt (prefix,op)
* func params fmt
* 4. fb15 fb_id 0..14fb_id type
* 5. op-fb CAL010101 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<uint32_t>(ver);
// ---- type ----
// ---- 2. type19 条全集)----
{
const toml::array* arr = root["type"].as_array();
if (!arr || arr->empty()) {
fail(err, "missing 'type' table");
return false;
}
std::map<uint32_t, std::string> tag_owner; // tag → 类型名
const char* kAllTypes[19] = {
"BOOL", "BYTE", "WORD", "DWORD", "LWORD", "SINT", "INT", "DINT",
"LINT", "USINT", "UINT", "UDINT", "ULINT", "TIME", "REAL",
"LREAL", "DATE", "TOD", "DT",
};
std::map<std::string, bool> seen;
for (const auto& el : *arr) {
const toml::table* t = el.as_table();
if (!t) {
@@ -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<uint32_t>(func);
if (seen.count(ct.name)) {
fail(err, "duplicate type '" + ct.name + "'");
return false;
}
seen[ct.name] = true;
if (!is_known_kind(ct.kind)) {
fail(err, "type '" + ct.name + "': bad kind '" + ct.kind + "'");
return false;
}
const PrimType* prim = find_prim(ct.base);
if (prim == nullptr) {
fail(err, "type '" + ct.name + "': unknown base '" + ct.base + "'");
return false;
}
int64_t tag = 0;
if (!req_int(*t, "type", "tag", &tag, err)) return false;
if (tag < 0 || !is_known_tag(static_cast<uint32_t>(tag))) {
fail(err, "type '" + ct.name + "': tag out of contract (0..2)");
// 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<uint32_t>(tag);
if (tag_owner.count(ct.tag)) {
fail(err, "duplicate tag " + std::to_string(ct.tag));
return false;
}
tag_owner[ct.tag] = ct.name;
if (t->contains("range")) {
const toml::array* r = (*t)["range"].as_array();
if (!r || r->size() != 2) {
fail(err, "type '" + ct.name + "': range must be [min, max]");
return false;
}
ct.has_range = true;
ct.range_min = (*r)[0].value_or<int64_t>(0);
ct.range_max = (*r)[1].value_or<int64_t>(0);
if (ct.range_min > ct.range_max) {
fail(err, "type '" + ct.name + "': range min > max");
return false;
}
}
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<uint32_t, std::string> code_owner;
std::map<std::string, uint32_t> name_owner;
std::map<std::string, bool> key_owner; // "prefix/op" → 已存在
std::map<std::string, bool> name_owner; // 助记符 → 已存在
for (const auto& el : *arr) {
const toml::table* t = el.as_table();
if (!t) {
@@ -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<uint32_t>(code);
if (code_owner.count(op.opcode)) {
fail(err, "duplicate opcode " + std::to_string(op.opcode));
return false;
}
code_owner[op.opcode] = op.name;
op.op = static_cast<uint32_t>(opno);
if (name_owner.count(op.name)) {
fail(err, "duplicate op name '" + op.name + "'");
return false;
}
name_owner[op.name] = 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<int>(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. fb15 条全集 fb_id 0..14----
{
const toml::array* arr = root["fb"].as_array();
if (!arr || arr->empty()) {
fail(err, "missing 'fb' table");
return false;
}
std::map<uint32_t, bool> id_owner;
std::map<std::string, bool> name_owner;
for (const auto& el : *arr) {
const toml::table* t = el.as_table();
if (!t) {
@@ -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<uint32_t>(code);
// opcode 必须命中 instance 类 op 行
const ConfigOp* op = find_op_by_code(fb.opcode);
if (op == nullptr || !op->is_instance) {
fail(err, "fb '" + fb.name + "': opcode " + std::to_string(code) +
" must match an instance op");
int64_t fid = 0;
if (!req_int(*t, "fb", "fb_id", &fid, err)) return false;
if (fid < 0 || fid > 14) {
fail(err, "fb '" + fb.name + "': fb_id out of range (0..14)");
return false;
}
fb.fb_id = static_cast<uint32_t>(fid);
if (id_owner.count(fb.fb_id)) {
fail(err, "duplicate fb_id " + std::to_string(fb.fb_id));
return false;
}
id_owner[fb.fb_id] = true;
if (name_owner.count(fb.name)) {
fail(err, "duplicate fb name '" + fb.name + "'");
return false;
}
name_owner[fb.name] = true;
const auto fv = (*t)["fields"];
if (!fv || !fv.is_array()) {
fail(err, "fb '" + fb.name + "': missing 'fields'");
@@ -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 互锁:CAL010101)存在 ↔ 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 id0..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
+89 -58
View File
@@ -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 TokBOOL..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.tomlTypecheck
* @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; // 内建或用户 FBLinker/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;
+49 -28
View File
@@ -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<size_t>(v.offset_data_) + kSha256Size) {
// 段校验(V2:值段之后是 SHA-256 文件尾;元数据段 v1 为 0 = 空段
if (len < static_cast<size_t>(v.offset_values_) + kSha256Size) {
v.err_ = "missing sha256 tail";
return v;
}
const uint64_t offs[5] = {get_le32(buf + 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<uint64_t>(v.n_slots_) * kSlotRowSize) {
v.err_ = "slot table size mismatch";
return v;
}
// 值段大小:meta 段为 0(空段)时值段到 SHA 尾;否则到 meta 起点
if (v.offset_meta_ != 0) {
if (static_cast<uint64_t>(v.offset_meta_) - offs[4] != v.values_size_) {
v.err_ = "values segment size mismatch";
return v;
}
} else if (static_cast<uint64_t>(len - kSha256Size) - offs[4] != v.values_size_) {
v.err_ = "values segment size mismatch";
return v;
}
if ((v.offset_slots_ - v.offset_code_) % 4 != 0) {
v.err_ = "code segment not aligned";
return v;
}
if (v.entry_fn_id_ >= v.n_funcs_ && v.n_funcs_ != 0) {
@@ -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<const char*>(buf_ + 72), kModelIdSize);
std::string s(reinterpret_cast<const char*>(buf_ + kOffModelId), kModelIdSize);
const size_t z = s.find('\0');
if (z != std::string::npos) {
s.resize(z);
@@ -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)。
+304 -108
View File
@@ -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 / TIMEINT TIME
* - FUNCTION EXTERNAL 14
* FB IF/WHILE BOOL
* - "type error" + Expr/Stmt
* - 19 IEC + Unresolved****
* //
* - / Unresolved
* /// + ****
* TRUE/FALSEBOOLT#TIMED#/TOD#/DT#
* - BOOL 0/1IF/WHILE BOOL
* - "type error" +
*
*
* - tname TType BOOL/INT/TIME
* - to_type(TypeKind) TypeKind TTypeFB false
* - tname / tname_lower TType 19 + Unresolved
* - to_type(string) type_name TType
* - Checker::Checker ///
* - Checker::Checker ////
* - Checker::run POU
* - Checker::fail "type error: <msg> (file)" false
* - find_scope POU
* - find_sym
* - find_global
* - find_pou POU AST
* - fail "type error: <msg> (file)" false
* - resolve_to
*
* - literal_range_ok //BOOL 0/1
* - arith_allowed int/uint/float
* TIME Add/Sub
* - check_stmt /FB /IF/WHILE
* - assign_target_type //FUNCTION
* - assign_target_type //
* - check_cond BOOL BOOL
* - check_expr
* - check_project POU
*/
#include "compiler/Typecheck.h"
#include <cmath>
#include <cstdio>
#include <string>
#include <vector>
#include "compiler/TypeInfo.h"
namespace compiler {
namespace {
/**
* @brief TType
* @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/TIMEFB 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 TType19
* @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<TType>(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<SourceUnit>& 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: <msg> (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_exprgot
* @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 falseerr
*/
bool literal_range_ok(const std::string& file, const Expr& e, TType want) {
// 一元负 + 字面量:折叠负号后按负值检查(-1 赋无符号 → 越域)
if (e.kind == ExprKind::Neg && e.operand != nullptr &&
(e.operand->kind == ExprKind::LitInt ||
e.operand->kind == ExprKind::LitReal)) {
Expr folded;
folded.kind = e.operand->kind;
if (e.operand->kind == ExprKind::LitInt) {
folded.int_value = -e.operand->int_value;
} else {
folded.double_value = -e.operand->double_value;
}
return literal_range_ok(file, folded, want);
}
if (e.kind == ExprKind::LitInt) {
if (want == TType::Real || want == TType::Lreal) {
return true; // 整数字面量 → 浮点目标合法
}
const TypeMeta tm = type_meta(cfg_, tname_lower(want));
if (!tm) {
return fail(file, "unknown target type for literal");
}
if (tm.kind() == "bool") {
if (e.int_value != 0 && e.int_value != 1) {
return fail(file, "BOOL literal must be 0 or 1");
}
return true;
}
if (!tm.is_signed() && e.int_value < 0) {
return fail(file, "negative literal for unsigned type " +
std::string(tname(want)));
}
const int w = static_cast<int>(tm.width());
if (tm.is_signed()) {
const int64_t minv =
(w == 8) ? INT64_MIN : -(static_cast<int64_t>(1) << (8 * w - 1));
const int64_t maxv =
(w == 8) ? INT64_MAX : (static_cast<int64_t>(1) << (8 * w - 1)) - 1;
if (e.int_value < minv || e.int_value > maxv) {
return fail(file, "integer literal out of range for " +
std::string(tname(want)));
}
} else {
const uint64_t maxv =
(w == 8) ? UINT64_MAX : ((static_cast<uint64_t>(1) << (8 * w)) - 1);
if (static_cast<uint64_t>(e.int_value) > maxv) {
return fail(file, "integer literal out of range for " +
std::string(tname(want)));
}
}
return true;
}
if (e.kind == ExprKind::LitReal) {
if (want == TType::Real) {
if (std::fabs(e.double_value) > 3.40282347e38) {
return fail(file, "float literal out of range for REAL");
}
return true;
}
if (want == TType::Lreal) {
return true;
}
return fail(file, "float literal cannot be assigned to " +
std::string(tname(want)));
}
return true; // 其他 Unresolved(不应发生)
}
/**
* @brief
* @param file
* @param t
* @param is_add_sub Add/SubTIME ± ×/÷
* @return true falseerr
*/
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 BOOLIF / WHILE
* @param file
* @param sc
* @param e
* @return true falseerr
* @brief BOOLIF / 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 / **
* 14FUNCTION VAR_EXTERNAL
* @param file
* @param sc
* @param pou POU
* @param target
* @param out
* @return true falseerr
* 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 falseerr
* @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,19 +540,42 @@ 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";
// 两侧都是未定型字面量 → BOOL 逻辑(TRUE/FALSE 或 0/1
if (l == TType::Unresolved && r == TType::Unresolved) {
if (!resolve_to(file, *e.lhs, l, TType::Bool) ||
!resolve_to(file, *e.rhs, r, TType::Bool)) {
return fail(file, std::string(op) + " operands must be BOOL");
}
*out = TType::Bool;
return true;
}
// 同型定型;允许 BOOL(逻辑)或位串(按宽度逐位)
const TType want = (l == TType::Unresolved) ? r : l;
if (!resolve_to(file, *e.lhs, l, want) ||
!resolve_to(file, *e.rhs, r, want)) {
return fail(file, std::string(op) + " operands must have same type");
}
const TypeMeta tm = type_meta(cfg_, tname_lower(want));
if (want != TType::Bool && (!tm || tm.kind() != "bit")) {
return fail(file, std::string(op) + " operands must be BOOL or bit-string");
}
*out = want;
return true;
}
case ExprKind::Cmp: {
TType l, r;
if (!check_expr(file, sc, *e.lhs, &l) || !check_expr(file, sc, *e.rhs, &r)) {
return false;
}
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) {
// 实参类型 = 函数 VAR_INPUT 声明序
std::vector<TType> params;
for (const VarBlock& b : f->blocks) {
if (b.section != VarSection::Input) {
continue;
}
for (const VarDecl& d : b.vars) {
TType pt;
if (to_type(d.type.name, &pt)) {
params.push_back(pt);
}
}
}
for (size_t i = 0; i < e.args.size(); ++i) {
TType at;
if (!check_expr(file, sc, *a, &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.kind, out)) {
}
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<SourceUnit>& 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<SourceUnit>& 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();
}
+77 -18
View File
@@ -34,6 +34,7 @@ namespace {
" -o <name>.stb 编译并写出映像文件\n"
" --machine <path> 机器定义(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<unsigned long long>(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<unsigned>(compiler::kMagic));
std::printf(" version = %u # 格式版本(V2)\n",
static_cast<unsigned>(compiler::kVersion));
std::printf(" model = %-14s # 型号标识\n", v.model_id().c_str());
std::printf(" cycle_limit = %-14u # 每周期指令数上限\n", v.cycle_limit());
std::printf(" dt_ms = %-14u # 周期时长(毫秒)\n", v.dt_ms());
std::printf(" hash = 0x%016llx # 工程哈希(FNV-1a 64\n",
static_cast<unsigned long long>(v.project_hash()));
std::printf(" entry_fn_id = %-14u # 入口函数(MAINfn_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<unsigned long long>(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<const uint32_t*>(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<uint32_t>(ilen);
}
}
if (v.n_slots()) {
std::printf("slots:\n");
for (uint32_t i = 0; i < v.n_slots(); ++i) {
const uint32_t addr =
static_cast<uint32_t>(v.slots_bytes()[i * 8])
| (static_cast<uint32_t>(v.slots_bytes()[i * 8 + 1]) << 8)
| (static_cast<uint32_t>(v.slots_bytes()[i * 8 + 2]) << 16)
| (static_cast<uint32_t>(v.slots_bytes()[i * 8 + 3]) << 24);
std::printf(" [%u] addr=%u\n", i, addr);
}
}
@@ -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<uint8_t> 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;
+19 -15
View File
@@ -5,7 +5,7 @@
* @date 2026-08-21
*
* @details 12.10 .stb + sidecar
* - `BytecodeExecutor <name>.stb [--cycles N] [--replay <file>] [--step]`
* - `BytecodeExecutor <name>.stb [--cycles N] [--replay <file>] [--step] [--sidecar <path>]`
* - vm + isa compilerdt_ms / cycle_limit
* - I / Q sidecarvar channel/bit
* - --replay 0/1 io.input
@@ -28,11 +28,12 @@ namespace {
/// 打印命令行用法。
void usage() {
std::printf("usage: BytecodeExecutor <name>.stb [--cycles N] [--replay <file>] [--step]\n"
" 加载 .stb + <name>.runtime.toml,按扫描周期执行\n"
std::printf("usage: BytecodeExecutor <name>.stb [--cycles N] [--replay <file>] [--step] [--sidecar <path>]\n"
" 加载 .stb + sidecar(缺省 <name>.runtime.toml,按扫描周期执行\n"
" --cycles N 跑 N 个周期(缺省 10)\n"
" --replay <file> 读录制的 I 序列(每行按 io.input 顺序的 0/1\n"
" --step 单步:逐指令打印 pc/fn/反汇编/寄存器(跑 1 个周期)\n"
" --sidecar <path> 显式指定 I/O 绑定文件(缺省同名推导)\n"
" --help 打印本帮助\n");
}
@@ -129,7 +130,7 @@ namespace {
* @return 1 0
*/
int slot_bool(vm::Machine& m, uint32_t slot) {
return m.data()[static_cast<size_t>(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<size_t>(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 <file> / --step(缺省 cycles=10、无回放、非单步)
// 参数:--cycles N / --replay <file> / --step / --sidecar <path>
//(缺省 cycles=10、无回放、非单步、sidecar 同名推导)
uint32_t cycles = 10;
bool step_mode = false;
std::string replay;
std::string sidecar_override;
for (int i = 2; i < argc; ++i) {
if (std::strcmp(argv[i], "--cycles") == 0 && i + 1 < argc) {
cycles = static_cast<uint32_t>(std::stoul(argv[i + 1]));
@@ -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<char>(in), std::istreambuf_iterator<char>());
}
// 结构校验(Image::from+ 型号/SHA-256 校验(Machine::create
const vm::Image img = vm::Image::from(bytes);
if (!img.ok()) {
std::fprintf(stderr, "error: %s\n", img.error().c_str());
return 1;
}
// 信任链(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 路径推导<name>.stb → <name>.runtime.toml
std::string sidecar_path = stb_path;
// sidecar 路径:--sidecar 显式指定,否则同名推导 <name>.stb → <name>.runtime.toml
std::string sidecar_path = sidecar_override.empty()
? stb_path
: sidecar_override;
if (sidecar_override.empty()) {
const size_t dot = sidecar_path.find_last_of('.');
if (dot != std::string::npos) {
sidecar_path = sidecar_path.substr(0, dot);
}
sidecar_path += ".runtime.toml";
}
std::vector<Binding> bindings;
if (!parse_sidecar(sidecar_path, &bindings, &err)) {
std::fprintf(stderr, "error: %s\n", err.c_str());
@@ -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()) {
+2 -1
View File
@@ -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)
+28 -22
View File
@@ -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; ///< rd23..16
uint8_t rs1 = 0; ///< rs131..24
uint8_t rs2 = 0; ///< rs239..32
uint32_t imm32 = 0; ///< imm/slot/fn_id(按形态)
int32_t off_hi = 0; ///< SLOT_OFF 的 off3287..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<w>`
* @details full/float/dst .U8/.I16/.F32
* CAL fb `CAL.TON s5` (prefix,op) `??? 0x<hex>`
*/
void disasm(Instr w, char* out, size_t cap);
void disasm(const uint8_t* code, size_t len, char* out, size_t cap);
}
+269 -129
View File
@@ -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..16imm32/slot32=55..24 3..6
* off32(JMP)=47..16 2..5off32_jc(JC)=55..24 3..6
* fn_id32=47..16fb_id16=31..16CAL slot32=63..32
* SLOT_OFF off32=87..56 7..10imm64=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<uint32_t>(static_cast<uint8_t>(op))
| (static_cast<uint32_t>(rd) << 8)
| (static_cast<uint32_t>(a) << 16)
| (static_cast<uint32_t>(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<Op>(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<uint8_t>((w >> 8) & 0xFFu);
/// @brief 取 func 位段(字节 1 高 4 位)。
inline uint8_t func_of(const uint8_t* code) {
return static_cast<uint8_t>((code[1] >> 4) & 0x0F);
}
/**
* @brief a
* @param w
* @return a 16..23
*/
inline uint8_t a(Instr w) {
return static_cast<uint8_t>((w >> 16) & 0xFFu);
/// @brief 取 rd23..16 = 字节 2)。
inline uint8_t rd_of(const uint8_t* code) { return code[2]; }
/// @brief 取 rs131..24 = 字节 3)。
inline uint8_t rs1_of(const uint8_t* code) { return code[3]; }
/// @brief 取 rs239..32 = 字节 4)。
inline uint8_t rs2_of(const uint8_t* code) { return code[4]; }
/// @brief 取 imm3255..24 = 字节 3..6,小端)。
inline uint32_t imm32_of(const uint8_t* code) {
return static_cast<uint32_t>(code[3])
| (static_cast<uint32_t>(code[4]) << 8)
| (static_cast<uint32_t>(code[5]) << 16)
| (static_cast<uint32_t>(code[6]) << 24);
}
/**
* @brief b
* @param w
* @return b 24..31
*/
inline uint8_t b(Instr w) {
return static_cast<uint8_t>((w >> 24) & 0xFFu);
/// @brief 取 slot32(同 imm32 位号)。
inline uint32_t slot32_of(const uint8_t* code) { return imm32_of(code); }
/// @brief 取 off3247..16 = 字节 2..5JMP 形态,有符号)。
inline int32_t off32_of(const uint8_t* code) {
return static_cast<int32_t>(static_cast<uint32_t>(code[2])
| (static_cast<uint32_t>(code[3]) << 8)
| (static_cast<uint32_t>(code[4]) << 16)
| (static_cast<uint32_t>(code[5]) << 24));
}
/**
* @brief a|b 16
* @param w
* @return 16 const_id / fn_id / slot
*/
inline uint16_t imm16(Instr w) {
return static_cast<uint16_t>(a(w) | (static_cast<uint16_t>(b(w)) << 8));
/// @brief 取 off3255..24 = 字节 3..6JC 形态,有符号)。
inline int32_t off32_jc_of(const uint8_t* code) {
return static_cast<int32_t>(static_cast<uint32_t>(code[3])
| (static_cast<uint32_t>(code[4]) << 8)
| (static_cast<uint32_t>(code[5]) << 16)
| (static_cast<uint32_t>(code[6]) << 24));
}
/**
* @brief a|b
* @param w
* @return = + off
*/
inline int16_t off16(Instr w) {
return static_cast<int16_t>(imm16(w));
/// @brief 取 fn_id3247..16 = 字节 2..5)。
inline uint32_t fn_id32_of(const uint8_t* code) {
return static_cast<uint32_t>(code[2])
| (static_cast<uint32_t>(code[3]) << 8)
| (static_cast<uint32_t>(code[4]) << 16)
| (static_cast<uint32_t>(code[5]) << 24);
}
/**
* @brief 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_id1631..16 = 字节 2..3)。
inline uint16_t fb_id16_of(const uint8_t* code) {
return static_cast<uint16_t>(code[2] | (static_cast<uint16_t>(code[3]) << 8));
}
/**
* @brief 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<uint32_t>(code[4])
| (static_cast<uint32_t>(code[5]) << 8)
| (static_cast<uint32_t>(code[6]) << 16)
| (static_cast<uint32_t>(code[7]) << 24);
}
/**
* @brief IMM
* @param op LOADK
* @param rd
* @param imm 16 const_id a|b
* @return
*/
inline Instr enc_imm(Op op, uint8_t rd, uint16_t imm) {
return pack(op, rd, static_cast<uint8_t>(imm & 0xFFu),
static_cast<uint8_t>((imm >> 8) & 0xFFu));
/// @brief 取 SLOT_OFF 的 off3287..56 = 字节 7..10,有符号)。
inline int32_t off32_hi_of(const uint8_t* code) {
return static_cast<int32_t>(static_cast<uint32_t>(code[7])
| (static_cast<uint32_t>(code[8]) << 8)
| (static_cast<uint32_t>(code[9]) << 16)
| (static_cast<uint32_t>(code[10]) << 24));
}
/**
* @brief JMP
* @param off a|b
* @return
*/
inline Instr enc_jmp(int16_t off) {
return pack(Op::JMP, 0,
static_cast<uint8_t>(static_cast<uint16_t>(off) & 0xFFu),
static_cast<uint8_t>((static_cast<uint16_t>(off) >> 8) & 0xFFu));
/// @brief 取 imm6487..24 = 字节 3..10)。
inline uint64_t imm64_of(const uint8_t* code) {
uint64_t v = 0;
for (int i = 0; i < 8; ++i) {
v |= static_cast<uint64_t>(code[3 + i]) << (8 * i);
}
return v;
}
/**
* @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<uint8_t>(static_cast<uint16_t>(off) & 0xFFu),
static_cast<uint8_t>((static_cast<uint16_t>(off) >> 8) & 0xFFu));
// ---- 发射器(写字节,调用方提供 ≥16 字节缓冲;返回指令长度)----
/// @brief 写字节 0(前缀 + 分区内 op)。
inline void set_byte0(uint8_t* out, const char* prefix, uint8_t op) {
if (prefix[0] == '1') {
// 11bit7..6 = 1110bit7..6 = 10
out[0] = static_cast<uint8_t>((prefix[1] == '1' ? 0xC0 : 0x80) | (op & 0x3F));
} else if (prefix[0] == '0') {
if (prefix[1] == '1' && prefix[2] == '1') {
out[0] = static_cast<uint8_t>(0x60 | (op & 0x1F)); // 011
} else if (prefix[1] == '1') {
// 010xxx:形态位 3 位 + 子 op 2 位
const uint8_t shape = static_cast<uint8_t>(
(prefix[3] - '0') * 4 + (prefix[4] - '0') * 2 + (prefix[5] - '0'));
out[0] = static_cast<uint8_t>(0x40 | (shape << 2) | (op & 0x03));
} else if (prefix[2] == '1') {
out[0] = static_cast<uint8_t>(0x20 | (op & 0x0F)); // 0010/0011
} else {
out[0] = static_cast<uint8_t>(0x10 | (op & 0x0F)); // 0001
}
} else {
out[0] = 0;
}
}
/**
* @brief 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<uint8_t>((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<uint8_t>(v & 0xFF);
out[4] = static_cast<uint8_t>((v >> 8) & 0xFF);
out[5] = static_cast<uint8_t>((v >> 16) & 0xFF);
out[6] = static_cast<uint8_t>((v >> 24) & 0xFF);
}
/**
* @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<uint32_t>(v);
out[2] = static_cast<uint8_t>(u & 0xFF);
out[3] = static_cast<uint8_t>((u >> 8) & 0xFF);
out[4] = static_cast<uint8_t>((u >> 16) & 0xFF);
out[5] = static_cast<uint8_t>((u >> 24) & 0xFF);
}
inline void set_off32_jc(uint8_t* out, int32_t v) { // JC:字节 3..6
const uint32_t u = static_cast<uint32_t>(v);
out[3] = static_cast<uint8_t>(u & 0xFF);
out[4] = static_cast<uint8_t>((u >> 8) & 0xFF);
out[5] = static_cast<uint8_t>((u >> 16) & 0xFF);
out[6] = static_cast<uint8_t>((u >> 24) & 0xFF);
}
inline void set_fn_id32(uint8_t* out, uint32_t v) { set_off32(out, static_cast<int32_t>(v)); }
inline void set_fb_id16(uint8_t* out, uint16_t v) {
out[2] = static_cast<uint8_t>(v & 0xFF);
out[3] = static_cast<uint8_t>((v >> 8) & 0xFF);
}
inline void set_cal_slot32(uint8_t* out, uint32_t v) {
out[4] = static_cast<uint8_t>(v & 0xFF);
out[5] = static_cast<uint8_t>((v >> 8) & 0xFF);
out[6] = static_cast<uint8_t>((v >> 16) & 0xFF);
out[7] = static_cast<uint8_t>((v >> 24) & 0xFF);
}
inline void set_off32_hi(uint8_t* out, int32_t v) { // SLOT_OFF:字节 7..10
const uint32_t u = static_cast<uint32_t>(v);
out[7] = static_cast<uint8_t>(u & 0xFF);
out[8] = static_cast<uint8_t>((u >> 8) & 0xFF);
out[9] = static_cast<uint8_t>((u >> 16) & 0xFF);
out[10] = static_cast<uint8_t>((u >> 24) & 0xFF);
}
inline void set_imm64(uint8_t* out, uint64_t v) {
for (int i = 0; i < 8; ++i) {
out[3 + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFF);
}
}
// ---- 便捷编码(vm_test 手拼映像用;输出 ≥16 字节缓冲)----
/// @brief 编码 RR 形态(MOVE/NOT/NEG 等),返回指令长度。
inline size_t enc_rr(uint8_t* out, const char* prefix, uint8_t op, uint8_t func,
uint8_t rd, uint8_t rs) {
set_byte0(out, prefix, op);
set_func(out, func);
set_rd(out, rd);
set_rs1(out, rs);
return instr_len(out);
}
/// @brief 编码 RRR 形态,返回指令长度。
inline size_t enc_rrr(uint8_t* out, const char* prefix, uint8_t op, uint8_t func,
uint8_t rd, uint8_t ra, uint8_t rb) {
set_byte0(out, prefix, op);
set_func(out, func);
set_rd(out, rd);
set_rs1(out, ra);
set_rs2(out, rb);
return instr_len(out);
}
/// @brief 编码 SLOT 形态(LOAD/STORE),返回指令长度。
inline size_t enc_slot(uint8_t* out, const char* prefix, uint8_t op, uint8_t func,
uint8_t rd, uint32_t slot) {
set_byte0(out, prefix, op);
set_func(out, func);
set_rd(out, rd);
set_slot32(out, slot);
return instr_len(out);
}
/// @brief 编码 IMM 形态(LOADK),返回指令长度。
inline size_t enc_imm(uint8_t* out, const char* prefix, uint8_t op, uint8_t func,
uint8_t rd, uint32_t imm) {
set_byte0(out, prefix, op);
set_func(out, func);
set_rd(out, rd);
set_imm32(out, imm);
return instr_len(out);
}
/// @brief 编码 JMP010010 区),返回指令长度。
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/JF010011 区),返回指令长度。
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 编码 CALL010100 区),返回指令长度。
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 编码 CAL010101 区),返回指令长度。
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_OFFLOAD_OFF/STORE_OFF0010 区),返回指令长度。
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_OFFLOAD_PTR/STORE_PTR0010 区),返回指令长度。
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 编码 RET10 区),返回指令长度。
inline size_t enc_ret(uint8_t* out) {
set_byte0(out, "10", static_cast<uint8_t>(Op::RET));
set_func(out, 0);
return instr_len(out);
}
}
+101 -126
View File
@@ -1,8 +1,13 @@
/**
* @file Op.h
* @brief
* @details .stb vm
* Doc/isa/.mdcompiler/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 <cstdint>
namespace isa {
/// 指令最大长度(字节)。
static constexpr int kMaxInstrLen = 16;
/**
* @brief
* @details
* Doc/isa/.mdcompiler/machine.toml
* @brief MOVE=0ADD=0LOADK=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(前缀 11op 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(前缀 10op 6 位)
NOP = 0, RET = 1,
// 8B RRR(前缀 011op 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/0011op 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 预留(前缀 0001op 4 位)
JTBL = 0, STR_CONCAT = 1, STR_FIND = 2, STR_REPLACE = 3,
ARR_LOAD = 4, ARR_STORE = 5,
};
/// 操作码总数(= 最大 opcode + 1kOpDefs 的下标上界)。
static const int kOpCount = 34;
/**
* @brief Doc/isa/.md
* @details a|b
* @brief
*/
enum class OpFormat : uint8_t {
RR, // 两寄存器:rd rsMOVE / NOT
RRR, // 三寄存器:rd ra rb(逻辑 / 算术 / 比较
IMM, // 常量:rd const_idLOADK
SLOT, // 槽:rd slotLOAD_* / STORE_*
JMP, // 偏移:off
JC, // 条件跳转:r offJT / JF
CALL, // 调用:fn_id
CAL, // 实例:instanceCAL_*
NONE, // 无操作数(RET
RR, ///< rd, rs4B
NONE, ///< 无操作数(4B
RRR, ///< rd, ra, rb8B
IMM, ///< rd, imm328B
SLOT, ///< rd, slot328B
JMP, ///< off328B
JC, ///< rd, off328B
CALL, ///< fn_id328B
CAL, ///< fb_id16, slot328B
SLOT_OFF, ///< rd, slot32, off3216B
RS_OFF, ///< rd, rs(基址寄存器), off3216B
IMM64, ///< rd, imm6416B
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/算术/CMPfunc 0..9 全矩阵
Float, ///< 浮点单目/舍入:func 8/9
Dst, ///< CONVfunc = 目标类型
};
/// 前缀字符串。
extern const char* kPrefixNames[12];
/**
* @brief
* @details Op
* @brief machine.toml [[op]]
*/
struct OpDef {
const char* name; ///< 助记符(与 .stb / machine.toml 一致)
const char* name; ///< 助记符(大写,与配置一致)
const char* prefix; ///< 形态前缀("11"/"010001" 等)
Op op; ///< 分区内编号
OpFormat format; ///< 操作数形态
OpClass cls; ///< 操作对象大类
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<int>(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<int>(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<int>(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];
}
+59 -47
View File
@@ -1,8 +1,12 @@
/**
* @file Types.h
* @brief
* @brief func V2
* @details compiler vm
* Doc/isa/.md
* Doc/isa/.mdV2
* - 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 tagLOADK func
* @author
* @date 2026-08-18
*/
@@ -14,47 +18,50 @@
namespace isa {
namespace types {
/**
* @brief 8 0/10=FALSE1=TRUE
*/
/// 布尔值:8 位,只允许 0/10=FALSE1=TRUE)。
using BOOL = uint8_t;
/**
* @brief IEC INT16
*/
/// 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=BOOL1=INT2=TIME
*
* @brief func
* @details 0..9 0..7 = 1/2/4/8B × /8=F329=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..71B无/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<int32_t>(a) + static_cast<int32_t>(b);
return static_cast<INT>(sum < std::numeric_limits<INT>::min() ? std::numeric_limits<INT>::min()
: sum > std::numeric_limits<INT>::max() ? std::numeric_limits<INT>::max()
template <typename T>
T sat_add(T a, T b) {
const __int128 sum = static_cast<__int128>(a) + static_cast<__int128>(b);
return static_cast<T>(sum < std::numeric_limits<T>::min()
? std::numeric_limits<T>::min()
: sum > std::numeric_limits<T>::max()
? std::numeric_limits<T>::max()
: sum);
}
@@ -62,13 +69,15 @@ namespace isa {
* @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<int32_t>(a) - static_cast<int32_t>(b);
return static_cast<INT>(diff < std::numeric_limits<INT>::min() ? std::numeric_limits<INT>::min()
: diff > std::numeric_limits<INT>::max() ? std::numeric_limits<INT>::max()
template <typename T>
T sat_sub(T a, T b) {
const __int128 diff = static_cast<__int128>(a) - static_cast<__int128>(b);
return static_cast<T>(diff < std::numeric_limits<T>::min()
? std::numeric_limits<T>::min()
: diff > std::numeric_limits<T>::max()
? std::numeric_limits<T>::max()
: diff);
}
@@ -76,31 +85,34 @@ namespace isa {
* @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<int32_t>(a) * static_cast<int32_t>(b);
return static_cast<INT>(prod < std::numeric_limits<INT>::min() ? std::numeric_limits<INT>::min()
: prod > std::numeric_limits<INT>::max() ? std::numeric_limits<INT>::max()
template <typename T>
T sat_mul(T a, T b) {
const __int128 prod = static_cast<__int128>(a) * static_cast<__int128>(b);
return static_cast<T>(prod < std::numeric_limits<T>::min()
? std::numeric_limits<T>::min()
: prod > std::numeric_limits<T>::max()
? std::numeric_limits<T>::max()
: prod);
}
/**
* @brief
* @brief A
* @param a
* @param b
* @return a/bb==0 1 aINT_MIN / -1 INT_MAX
* @details b==0 a compiler/VM
* @return a/bb==0 a
* MIN/-1 MAX
*/
inline INT sat_div(INT a, INT b) {
template <typename T>
T sat_div(T a, T b) {
if (b == 0) {
return a;
return a; // 除零返回被除数(Fail-safe
}
if (a == std::numeric_limits<INT>::min() && b == -1) {
return std::numeric_limits<INT>::max();
if (a == std::numeric_limits<T>::min() && b == T(-1)) {
return std::numeric_limits<T>::max(); // MIN/-1 溢出饱和
}
return static_cast<INT>(a / b);
return static_cast<T>(a / b);
}
}
}
+152 -45
View File
@@ -1,6 +1,6 @@
/**
* @file Encode.cpp
* @brief
* @brief V2
* @author
* @date 2026-08-19
*/
@@ -8,83 +8,190 @@
#include "isa/Encode.h"
#include <cstdio>
#include <cstring>
#include <string>
namespace isa {
/**
* @brief 32
* @param d
* @return encode(decode(w)) == w
namespace {
/**
* @brief func disasm
* @param func func 0..9
* @return "U8"/"I8"/.../"F64" "?"
*/
Instr encode(Decoded d) {
return pack(d.op, d.rd, d.a, d.b);
}
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 → imm32JMP/JC → offCALL → fn_id
const OpFormat f = format_of(d.prefix, d.op);
if (f == OpFormat::IMM || f == OpFormat::SLOT) {
d.imm32 = imm32_of(code);
} else if (f == OpFormat::JMP) {
d.imm32 = static_cast<uint32_t>(off32_of(code));
} else if (f == OpFormat::JC) {
d.imm32 = static_cast<uint32_t>(off32_jc_of(code));
} else if (f == OpFormat::CALL) {
d.imm32 = fn_id32_of(code);
} else if (f == OpFormat::CAL) {
d.imm32 = (cal_slot32_of(code) << 16) | fb_id16_of(code); // (slot<<16)|fb_id
} else if (f == OpFormat::SLOT_OFF) {
d.imm32 = slot32_of(code);
d.off_hi = off32_hi_of(code);
} else if (f == OpFormat::RS_OFF) {
d.off_hi = off32_hi_of(code);
}
return d;
}
/**
* @brief
* @param w
* @param out
* @param cap '\0'cap==0 out
* @details Doc/isa/.md
* `??? 0x<w>`
* @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<int32_t>(d.imm32));
} else if (f == OpFormat::JC) {
set_off32_jc(out, static_cast<int32_t>(d.imm32));
} else if (f == OpFormat::CALL) {
set_fn_id32(out, d.imm32);
} else if (f == OpFormat::CAL) {
set_fb_id16(out, static_cast<uint16_t>(d.imm32 & 0xFFFF));
set_cal_slot32(out, d.imm32);
} else if (f == OpFormat::SLOT_OFF) {
set_slot32(out, d.imm32);
set_off32_hi(out, d.off_hi);
} else if (f == OpFormat::RS_OFF) {
set_off32_hi(out, d.off_hi);
}
return instr_len(out);
}
/**
* @brief
*/
void disasm(const uint8_t* code, size_t len, char* out, size_t cap) {
if (cap == 0) {
return;
}
out[0] = '\0';
const Op o = op(w);
const int idx = static_cast<int>(o);
if (idx < 0 || idx >= kOpCount) {
snprintf(out, cap, "??? 0x%08x", static_cast<unsigned>(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<unsigned>(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<unsigned>(rd(w)), static_cast<unsigned>(a(w)));
snprintf(out, cap, "%s%s r%u, r%u", od.name, suffix.c_str(),
static_cast<unsigned>(d.rd), static_cast<unsigned>(d.rs1));
break;
case OpFormat::RRR:
snprintf(out, cap, "%s r%u, r%u, r%u", m,
static_cast<unsigned>(rd(w)), static_cast<unsigned>(a(w)),
static_cast<unsigned>(b(w)));
snprintf(out, cap, "%s%s r%u, r%u, r%u", od.name, suffix.c_str(),
static_cast<unsigned>(d.rd), static_cast<unsigned>(d.rs1),
static_cast<unsigned>(d.rs2));
break;
case OpFormat::IMM:
snprintf(out, cap, "%s%s r%u, %u", od.name, suffix.c_str(),
static_cast<unsigned>(d.rd), static_cast<unsigned>(d.imm32));
break;
case OpFormat::SLOT:
snprintf(out, cap, "%s r%u, %u", m,
static_cast<unsigned>(rd(w)), static_cast<unsigned>(imm16(w)));
snprintf(out, cap, "%s%s r%u, s%u", od.name, suffix.c_str(),
static_cast<unsigned>(d.rd), static_cast<unsigned>(d.imm32));
break;
case OpFormat::JMP:
snprintf(out, cap, "%s %+d", m, static_cast<int>(off16(w)));
snprintf(out, cap, "%s %+d", od.name, static_cast<int>(d.imm32));
break;
case OpFormat::JC:
snprintf(out, cap, "%s r%u, %+d", m,
static_cast<unsigned>(rd(w)), static_cast<int>(off16(w)));
snprintf(out, cap, "%s r%u, %+d", od.name, static_cast<unsigned>(d.rd),
static_cast<int>(d.imm32));
break;
case OpFormat::CALL:
case OpFormat::CAL:
snprintf(out, cap, "%s %u", m, static_cast<unsigned>(imm16(w)));
snprintf(out, cap, "%s %u", od.name, static_cast<unsigned>(d.imm32));
break;
case OpFormat::CAL: {
const uint16_t fb = static_cast<uint16_t>(d.imm32 & 0xFFFF);
const char* fbname = (fb < 15) ? kFbNames[fb] : "?";
snprintf(out, cap, "CAL.%s s%u", fbname, static_cast<unsigned>(d.imm32 >> 16));
break;
}
case OpFormat::SLOT_OFF:
snprintf(out, cap, "%s%s r%u, s%u, %d", od.name, suffix.c_str(),
static_cast<unsigned>(d.rd), static_cast<unsigned>(d.imm32),
static_cast<int>(d.off_hi));
break;
case OpFormat::RS_OFF:
snprintf(out, cap, "%s%s r%u, r%u, %d", od.name, suffix.c_str(),
static_cast<unsigned>(d.rd), static_cast<unsigned>(d.rs1),
static_cast<int>(d.off_hi));
break;
case OpFormat::IMM64:
case OpFormat::NONE:
snprintf(out, cap, "%s", m);
case OpFormat::RESERVED:
default:
snprintf(out, cap, "%s", od.name);
break;
}
}
+164
View File
@@ -0,0 +1,164 @@
/**
* @file Op.cpp
* @brief V2
* @details compiler/machine.toml 72 op
* uses_*
* @author
* @date 2026-08-19
*/
#include "isa/Op.h"
namespace isa {
const char* kPrefixNames[12] = {
"11", "10", "011", "010000", "010001", "010010",
"010011", "010100", "010101", "0010", "0011", "0001",
};
// 指令表(72 条;顺序与 machine.toml [[op]] 一致)
const OpDef kOps[] = {
// 4B RR(前缀 11
{"MOVE", "11", Op::MOVE, OpFormat::RR, FuncMode::Width},
{"NOT", "11", Op::NOT, OpFormat::RR, FuncMode::Uint},
{"NEG", "11", Op::NEG, OpFormat::RR, FuncMode::Full},
{"ABS", "11", Op::ABS, OpFormat::RR, FuncMode::Full},
{"INC", "11", Op::INC, OpFormat::RR, FuncMode::Full},
{"DEC", "11", Op::DEC, OpFormat::RR, FuncMode::Full},
{"SQRT", "11", Op::SQRT, OpFormat::RR, FuncMode::Float},
{"SIN", "11", Op::SIN, OpFormat::RR, FuncMode::Float},
{"COS", "11", Op::COS, OpFormat::RR, FuncMode::Float},
{"TAN", "11", Op::TAN, OpFormat::RR, FuncMode::Float},
{"ASIN", "11", Op::ASIN, OpFormat::RR, FuncMode::Float},
{"ACOS", "11", Op::ACOS, OpFormat::RR, FuncMode::Float},
{"ATAN", "11", Op::ATAN, OpFormat::RR, FuncMode::Float},
{"LN", "11", Op::LN, OpFormat::RR, FuncMode::Float},
{"LOG", "11", Op::LOG, OpFormat::RR, FuncMode::Float},
{"EXP", "11", Op::EXP, OpFormat::RR, FuncMode::Float},
{"ROUND", "11", Op::ROUND, OpFormat::RR, FuncMode::Float},
{"TRUNC", "11", Op::TRUNC, OpFormat::RR, FuncMode::Float},
{"FLOOR", "11", Op::FLOOR, OpFormat::RR, FuncMode::Float},
{"CEIL", "11", Op::CEIL, OpFormat::RR, FuncMode::Float},
{"CONV", "11", Op::CONV, OpFormat::RR, FuncMode::Dst},
// 4B NONE(前缀 10
{"NOP", "10", Op::NOP, OpFormat::NONE, FuncMode::None},
{"RET", "10", Op::RET, OpFormat::NONE, FuncMode::None},
// 8B RRR(前缀 011
{"ADD", "011", Op::ADD, OpFormat::RRR, FuncMode::Full},
{"SUB", "011", Op::SUB, OpFormat::RRR, FuncMode::Full},
{"MUL", "011", Op::MUL, OpFormat::RRR, FuncMode::Full},
{"DIV", "011", Op::DIV, OpFormat::RRR, FuncMode::Full},
{"MOD", "011", Op::MOD, OpFormat::RRR, FuncMode::Full},
{"EXPT", "011", Op::EXPT, OpFormat::RRR, FuncMode::Full},
{"AND", "011", Op::AND, OpFormat::RRR, FuncMode::Uint},
{"OR", "011", Op::OR, OpFormat::RRR, FuncMode::Uint},
{"XOR", "011", Op::XOR, OpFormat::RRR, FuncMode::Uint},
{"NAND", "011", Op::NAND, OpFormat::RRR, FuncMode::Uint},
{"NOR", "011", Op::NOR, OpFormat::RRR, FuncMode::Uint},
{"XNOR", "011", Op::XNOR, OpFormat::RRR, FuncMode::Uint},
{"SHL", "011", Op::SHL, OpFormat::RRR, FuncMode::Uint},
{"SHR", "011", Op::SHR, OpFormat::RRR, FuncMode::Full},
{"ROL", "011", Op::ROL, OpFormat::RRR, FuncMode::Uint},
{"ROR", "011", Op::ROR, OpFormat::RRR, FuncMode::Uint},
{"CMP_EQ", "011", Op::CMP_EQ, OpFormat::RRR, FuncMode::Full},
{"CMP_NE", "011", Op::CMP_NE, OpFormat::RRR, FuncMode::Full},
{"CMP_LT", "011", Op::CMP_LT, OpFormat::RRR, FuncMode::Full},
{"CMP_LE", "011", Op::CMP_LE, OpFormat::RRR, FuncMode::Full},
{"CMP_GT", "011", Op::CMP_GT, OpFormat::RRR, FuncMode::Full},
{"CMP_GE", "011", Op::CMP_GE, OpFormat::RRR, FuncMode::Full},
{"GETBIT", "011", Op::GETBIT, OpFormat::RRR, FuncMode::None},
{"SETBIT", "011", Op::SETBIT, OpFormat::RRR, FuncMode::None},
{"CLRBIT", "011", Op::CLRBIT, OpFormat::RRR, FuncMode::None},
{"TOGBIT", "011", Op::TOGBIT, OpFormat::RRR, FuncMode::None},
// 8B 010 区
{"LOADK", "010000", Op::LOADK, OpFormat::IMM, FuncMode::Full},
{"LOAD", "010001", Op::LOAD, OpFormat::SLOT, FuncMode::Full},
{"STORE", "010001", Op::STORE, OpFormat::SLOT, FuncMode::Full},
{"JMP", "010010", Op::JMP, OpFormat::JMP, FuncMode::None},
{"JT", "010011", Op::JT, OpFormat::JC, FuncMode::None},
{"JF", "010011", Op::JF, OpFormat::JC, FuncMode::None},
{"CALL", "010100", Op::CALL, OpFormat::CALL, FuncMode::None},
{"CAL", "010101", Op::CAL, OpFormat::CAL, FuncMode::None},
// 16B
{"LOAD_OFF", "0010", Op::LOAD_OFF, OpFormat::SLOT_OFF, FuncMode::Full},
{"STORE_OFF", "0010", Op::STORE_OFF, OpFormat::SLOT_OFF, FuncMode::Full},
{"STR_LEN", "0010", Op::STR_LEN, OpFormat::SLOT_OFF, FuncMode::None},
{"STR_CMP", "0010", Op::STR_CMP, OpFormat::SLOT_OFF, FuncMode::None},
{"STR_EQ", "0010", Op::STR_EQ, OpFormat::SLOT_OFF, FuncMode::None},
{"STR_MID", "0010", Op::STR_MID, OpFormat::SLOT_OFF, FuncMode::None},
{"STR_LEFT", "0010", Op::STR_LEFT, OpFormat::SLOT_OFF, FuncMode::None},
{"STR_RIGHT", "0010", Op::STR_RIGHT, OpFormat::SLOT_OFF, FuncMode::None},
{"LOAD_PTR", "0010", Op::LOAD_PTR, OpFormat::RS_OFF, FuncMode::Full},
{"STORE_PTR", "0010", Op::STORE_PTR, OpFormat::RS_OFF, FuncMode::Full},
{"LOADK64", "0011", Op::LOADK64, OpFormat::IMM64, FuncMode::Full},
// 32B 预留
{"JTBL", "0001", Op::JTBL, OpFormat::RESERVED, FuncMode::None},
{"STR_CONCAT", "0001", Op::STR_CONCAT, OpFormat::RESERVED, FuncMode::None},
{"STR_FIND", "0001", Op::STR_FIND, OpFormat::RESERVED, FuncMode::None},
{"STR_REPLACE", "0001", Op::STR_REPLACE, OpFormat::RESERVED, FuncMode::None},
{"ARR_LOAD", "0001", Op::ARR_LOAD, OpFormat::RESERVED, FuncMode::None},
{"ARR_STORE", "0001", Op::ARR_STORE, OpFormat::RESERVED, FuncMode::None},
};
int find_op(const char* prefix, uint8_t op) {
for (int i = 0; i < kOpCount; ++i) {
const char* p = kOps[i].prefix;
const char* q = prefix;
bool eq = true;
while (*p && *q) {
if (*p != *q) { eq = false; break; }
++p; ++q;
}
if (eq && *p == *q && static_cast<uint8_t>(kOps[i].op) == op) {
return i;
}
}
return -1;
}
int find_op_by_name(const char* name) {
for (int i = 0; i < kOpCount; ++i) {
const char* p = kOps[i].name;
const char* q = name;
while (*p && *q && *p == *q) { ++p; ++q; }
if (*p == '\0' && *q == '\0') {
return i;
}
}
return -1;
}
const char* mnemonic(const char* prefix, uint8_t op) {
const int i = find_op(prefix, op);
return i >= 0 ? kOps[i].name : "???";
}
OpFormat format_of(const char* prefix, uint8_t op) {
const int i = find_op(prefix, op);
return i >= 0 ? kOps[i].format : OpFormat::RESERVED;
}
FuncMode func_mode_of(const char* prefix, uint8_t op) {
const int i = find_op(prefix, op);
return i >= 0 ? kOps[i].func_mode : FuncMode::None;
}
bool uses_rd(OpFormat f) {
return f == OpFormat::RR || f == OpFormat::RRR || f == OpFormat::IMM ||
f == OpFormat::SLOT || f == OpFormat::JC || f == OpFormat::SLOT_OFF ||
f == OpFormat::RS_OFF || f == OpFormat::IMM64;
}
bool uses_rs1(OpFormat f) {
return f == OpFormat::RR || f == OpFormat::RRR || f == OpFormat::RS_OFF;
}
bool uses_rs2(OpFormat f) {
return f == OpFormat::RRR;
}
const char* kFbNames[15] = {
"TON", "TOF", "TP", "CTU", "DCTU", "CTD", "DCTD", "CTUD",
"DCTUD", "R_TRIG", "F_TRIG", "SR", "RS", "PWM", "RTC",
};
}
+2 -2
View File
@@ -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}")
+8
View File
@@ -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)
+4
View File
@@ -0,0 +1,4 @@
(* globals.st *)
VAR_GLOBAL
a : INT;
END_VAR
+12
View File
@@ -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
+18
View File
@@ -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
+11
View File
@@ -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"
@@ -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
+4
View File
@@ -0,0 +1,4 @@
(* globals.st *)
VAR_GLOBAL
a : INT;
END_VAR
+27
View File
@@ -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
@@ -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"
+10 -7
View File
@@ -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<uint8_t> img;
if (!codegen_project(p, units, link, cfg, &img, &err)) {
if (want == Outcome::CompileError) {
+219 -658
View File
@@ -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 <cstdio>
#include <cstring>
#include <cstdlib>
#include <filesystem>
#include <string>
#include <vector>
#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) {
namespace {
/**
* @brief
* @param dir tests/cases/<dir>
* @param img
* @return true false
*/
bool build_case(const char* dir, std::vector<uint8_t>* 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;
}
std::vector<SourceUnit> 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;
}
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;
}
/**
* @brief fn_id disasm
*/
std::vector<std::string> disasm_lines(const compiler::MachineConfig& cfg,
const compiler::StbView& v,
uint32_t fn_id) {
std::vector<std::string> 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<uint32_t>(ilen);
}
return lines;
}
/// 行列表是否包含指定前缀的指令(如 "LOADK.U8")。
bool has_line(const std::vector<std::string>& lines, const std::string& prefix) {
for (const std::string& l : lines) {
if (l.compare(0, prefix.size(), prefix) == 0) {
return true;
}
}
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 load machine.toml: %s\n", err.c_str());
return false;
std::printf("FAIL machine config: %s\n", err.c_str());
std::exit(1);
}
return cfg;
}();
return *kCfg;
}
return true;
}
static bool compile_case(const char* dir, std::vector<uint8_t>* image, std::string* err) {
} // namespace
// ---- 1. 01_empty_main:最小映像(RET----
static bool test_empty_main() {
using namespace compiler;
compiler::MachineConfig cfg;
if (!load_cfg(&cfg)) return false;
const std::string toml = std::string(REPO_ROOT) + "/tests/cases/" + dir + "/project.toml";
Project p;
if (!parse_project(toml, &p, err)) {
return false;
}
std::vector<SourceUnit> units;
for (const std::string& f : compile_files(p)) {
SourceUnit u;
if (!load_unit(p.base_dir + "/" + f, &u, err)) {
return false;
}
units.push_back(std::move(u));
}
LinkResult link;
if (!link_project(p, units, &link, err)) {
return false;
}
if (!check_project(p, units, link, err)) {
return false;
}
return codegen_project(p, units, link, cfg, image, err);
}
// 从临时工程(globals.st + main.st + 可含 io 绑定)走完整管线
static bool compile_src(const char* name, const char* io_extra, const char* globals_st,
const char* main_st, std::vector<uint8_t>* image,
std::string* err) {
using namespace compiler;
compiler::MachineConfig cfg;
if (!load_cfg(&cfg)) return false;
const std::string dir = std::string(REPO_ROOT) + "/build/cg_tmp_" + name;
std::filesystem::remove_all(dir);
std::filesystem::create_directories(dir);
const std::string toml_path = dir + "/project.toml";
std::FILE* f = std::fopen(toml_path.c_str(), "w");
std::fprintf(f, "[project]\nname = \"cg\"\nentry = \"program MAIN\"\n"
"cycle_limit = 1000\ndt_ms = 10\n"
"[files]\nst = [\"globals.st\", \"main.st\"]\n"
"[gvl]\nfile = \"globals.st\"\n%s", io_extra);
std::fclose(f);
f = std::fopen((dir + "/globals.st").c_str(), "w");
std::fputs(globals_st, f);
std::fclose(f);
f = std::fopen((dir + "/main.st").c_str(), "w");
std::fputs(main_st, f);
std::fclose(f);
Project p;
if (!parse_project(toml_path, &p, err)) {
return false;
}
std::vector<SourceUnit> units;
for (const std::string& file : compile_files(p)) {
SourceUnit u;
if (!load_unit(p.base_dir + "/" + file, &u, err)) {
return false;
}
units.push_back(std::move(u));
}
LinkResult link;
if (!link_project(p, units, &link, err)) {
return false;
}
if (!check_project(p, units, link, err)) {
return false;
}
return codegen_project(p, units, link, cfg, image, err);
}
// ---- 1. 用例 01:空 MAIN + RET ----
static bool test_case01() {
std::vector<uint8_t> img;
std::string err;
CHECK(compile_case("01_empty_main", &img, &err));
const compiler::StbView v = compiler::StbView::from(img);
CHECK(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); // 仅 RET4B
// 指令序列
const std::vector<std::string> lines = disasm_lines(cfg_global(), v, 0);
CHECK(lines.size() == 1);
CHECK(lines[0] == "RET");
return true;
}
// ---- 2. 用例 02BOOL 赋值(TRUE / FALSE → LOADK----
// ---- 2. 02_bool_assign:常量表 + LOADK ----
static bool test_case02() {
static bool test_bool_assign() {
using namespace compiler;
std::vector<uint8_t> img;
std::string err;
CHECK(compile_case("02_bool_assign", &img, &err));
const compiler::StbView v = compiler::StbView::from(img);
CHECK(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, br8 起)
CHECK(r.code_len == 3);
// 从字节码段取指令(按函数行 code_offset 定位)
const uint8_t* base = v.code_bytes();
char buf[64];
isa::disasm(reinterpret_cast<const uint32_t*>(base)[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r8, 0") == 0); // a := TRUE(常量 0
isa::disasm(reinterpret_cast<const uint32_t*>(base)[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r9, 1") == 0); // b := FALSE(常量 1
isa::disasm(reinterpret_cast<const uint32_t*>(base)[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "RET") == 0);
// 常量表:0 = BOOL 11 = 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<std::string> lines = disasm_lines(cfg_global(), v, 0);
CHECK(has_line(lines, "LOADK.U8"));
CHECK(has_line(lines, "RET"));
return true;
}
// ---- 3. 用例 09GVL + VAR_EXTERNAL(全局读取 LOAD_GLOBAL + 初值数据段----
// ---- 3. 17_ton:内建 FBCAL.TON + 字段访问----
static bool test_case09() {
static bool test_ton() {
using namespace compiler;
std::vector<uint8_t> img;
std::string err;
CHECK(compile_case("09_gvl_external", &img, &err));
const compiler::StbView v = compiler::StbView::from(img);
CHECK(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<const uint32_t*>(base)[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 0") == 0); // x := G1(槽 0 偏移 0
isa::disasm(reinterpret_cast<const uint32_t*>(base)[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "RET") == 0);
CHECK(v.n_slots() == 1); // 1 个 TON 实例
CHECK(v.values_size() == 32); // TON 实例 32Bin@0/pt@8/q@16/et@24
CHECK(v.n_consts() == 2); // in 初值 0、pt 初值 30T#30ms
const std::vector<std::string> 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_callCALL + max_stack ----
static bool test_io_ops() {
static bool test_function_call() {
using namespace compiler;
std::vector<uint8_t> img;
std::string err;
const char* io_extra =
"[[io.input]]\nvar = \"I0_0\"\nchannel = 0\nbit = 0\n"
"[[io.output]]\nvar = \"Q0_0\"\nchannel = 0\nbit = 0\n";
const char* globals_st =
"VAR_GLOBAL\n I0_0 : BOOL;\n Q0_0 : BOOL;\nEND_VAR\n";
const char* main_st =
"PROGRAM MAIN\nVAR\n q : BOOL;\nEND_VAR\n"
" q := I0_0;\n"
" Q0_0 := q;\n"
"END_PROGRAM\n";
CHECK(compile_src("io", io_extra, globals_st, main_st, &img, &err));
const compiler::StbView v = compiler::StbView::from(img);
CHECK(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<const uint32_t*>(base)[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_I r8, 0") == 0); // q := I0_0io.input → LOAD_I
isa::disasm(reinterpret_cast<const uint32_t*>(base)[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r9, r8") == 0); // 临时 ← q
isa::disasm(reinterpret_cast<const uint32_t*>(base)[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_Q r9, 1") == 0); // Q0_0 := 临时(io.output → STORE_Q
isa::disasm(reinterpret_cast<const uint32_t*>(base)[3], buf, sizeof buf);
CHECK(std::strcmp(buf, "RET") == 0);
return true;
}
// ---- 5. 混合类型布局:BOOL@0 INT@2 TIME@8,初值入数据段 ----
static bool test_layout() {
std::vector<uint8_t> img;
std::string err;
const char* globals_st =
"VAR_GLOBAL\n"
" B : BOOL;\n"
" I : INT := 300;\n"
" T : TIME := T#10ms;\n"
"END_VAR\n";
const char* main_st =
"PROGRAM MAIN\nVAR\n x : INT;\nEND_VAR\n"
" x := I;\n"
"END_PROGRAM\n";
CHECK(compile_src("layout", "", globals_st, main_st, &img, &err));
const compiler::StbView v = compiler::StbView::from(img);
CHECK(v.ok());
CHECK(v.n_globals() == 3);
CHECK(v.data_len() == 24); // 3 槽 × 8 字节定宽
CHECK(v.data_bytes()[0] == 0); // B 无初值
CHECK(v.data_bytes()[8] == 0x2C && v.data_bytes()[9] == 0x01); // I = 300(槽 1
CHECK(v.data_bytes()[16] == 10); // T = 10ms(槽 2
char buf[64];
isa::disasm(reinterpret_cast<const uint32_t*>(v.code_bytes())[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 1") == 0); // x := I(槽 1
return true;
}
// ---- 6. 写 io.input → codegen error ----
static bool test_write_input() {
std::vector<uint8_t> img;
std::string err;
const char* io_extra =
"[[io.input]]\nvar = \"I0_0\"\nchannel = 0\nbit = 0\n";
const char* globals_st =
"VAR_GLOBAL\n I0_0 : BOOL;\nEND_VAR\n";
const char* main_st =
"PROGRAM MAIN\n I0_0 := TRUE;\nEND_PROGRAM\n";
if (compile_src("winput", io_extra, globals_st, main_st, &img, &err)) {
std::printf("FAIL write_input: codegen passed\n");
return false;
}
CHECK(err.find("codegen error") == 0);
CHECK(err.find("cannot write to input") != std::string::npos);
return true;
}
// ---- 7. 用例 03:短路 AND / OR 必须编跳转 ----
static bool test_case03() {
std::vector<uint8_t> img;
std::string err;
CHECK(compile_case("03_short_circuit", &img, &err));
const compiler::StbView v = compiler::StbView::from(img);
CHECK(v.ok());
const compiler::StbView::FuncRow r = v.func_row(0);
CHECK(r.nregs == 12); // r8 起:a b x + 1 临时
CHECK(r.code_len == 9);
char buf[64];
const uint8_t* base = v.code_bytes();
const uint32_t* ins = reinterpret_cast<const uint32_t*>(base);
// x := a AND bMOVE 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 bMOVE r2,r0 / JT r2,+2 / MOVE r3,r1 / MOVE r2,r3
isa::disasm(ins[4], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r10, r8") == 0);
isa::disasm(ins[5], buf, sizeof buf);
CHECK(std::strcmp(buf, "JT r10, +2") == 0);
isa::disasm(ins[6], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r11, r9") == 0);
isa::disasm(ins[7], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r10, r11") == 0);
isa::disasm(ins[8], buf, sizeof buf);
CHECK(std::strcmp(buf, "RET") == 0);
return true;
}
// ---- 8. NOT ----
static bool test_not() {
std::vector<uint8_t> img;
std::string err;
const char* main_st =
"PROGRAM MAIN\nVAR\n a, x : BOOL;\nEND_VAR\n"
" x := NOT a;\n"
"END_PROGRAM\n";
CHECK(compile_src("not", "", "", main_st, &img, &err));
const compiler::StbView v = compiler::StbView::from(img);
CHECK(v.ok());
const compiler::StbView::FuncRow r = v.func_row(0);
CHECK(r.nregs == 11); // r8 起:a x + 1 临时
CHECK(r.code_len == 3);
char buf[64];
const uint8_t* base = v.code_bytes();
const uint32_t* ins = reinterpret_cast<const uint32_t*>(base);
isa::disasm(ins[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r10, r8") == 0); // 临时 ← a
isa::disasm(ins[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "NOT r9, r10") == 0); // x := NOT 临时
isa::disasm(ins[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "RET") == 0);
return true;
}
// ---- 9. 用例 04IF / ELSIF / ELSECMP + JF + JMP 回填)----
static bool test_case04() {
std::vector<uint8_t> img;
std::string err;
CHECK(compile_case("04_if_elsif_else", &img, &err));
const compiler::StbView v = compiler::StbView::from(img);
CHECK(v.ok());
const compiler::StbView::FuncRow r = v.func_row(0);
CHECK(r.nregs == 13); // r8 起:sel out + 3 临时
CHECK(r.code_len == 14);
char buf[64];
const uint8_t* base = v.code_bytes();
const uint32_t* ins = reinterpret_cast<const uint32_t*>(base);
// IF sel = 0 THENsel→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. 用例 05WHILE 回环(JF 出口 + JMP 回填)----
static bool test_case05() {
std::vector<uint8_t> img;
std::string err;
CHECK(compile_case("05_while_normal", &img, &err));
const compiler::StbView v = compiler::StbView::from(img);
CHECK(v.ok());
const compiler::StbView::FuncRow r = v.func_row(0);
CHECK(r.nregs == 12); // r8 起:n + 3 临时(条件结果 r9 + 两操作数)
CHECK(r.code_len == 10);
char buf[64];
const uint8_t* base = v.code_bytes();
const uint32_t* ins = reinterpret_cast<const uint32_t*>(base);
isa::disasm(ins[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r8, 0") == 0); // n := 0
// L_loop [1]n < 10 → r9
isa::disasm(ins[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r10, r8") == 0);
isa::disasm(ins[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r11, 1") == 0); // 常量 1 = 10
isa::disasm(ins[3], buf, sizeof buf);
CHECK(std::strcmp(buf, "CMP_LT r9, r10, r11") == 0);
isa::disasm(ins[4], buf, sizeof buf);
CHECK(std::strcmp(buf, "JF r9, +4") == 0); // 假 → RET[9]
// n := n + 1(体语句临时复用 r9/r10
isa::disasm(ins[5], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r9, r8") == 0);
isa::disasm(ins[6], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r10, 2") == 0); // 常量 2 = 1
isa::disasm(ins[7], buf, sizeof buf);
CHECK(std::strcmp(buf, "ADD r8, r9, r10") == 0);
isa::disasm(ins[8], buf, sizeof buf);
CHECK(std::strcmp(buf, "JMP -8") == 0); // 回 L_loop[1]
isa::disasm(ins[9], buf, sizeof buf);
CHECK(std::strcmp(buf, "RET") == 0);
return true;
}
// ---- 11. 用例 07:四则 + 比较 ----
static bool test_case07() {
std::vector<uint8_t> img;
std::string err;
CHECK(compile_case("07_int_arith", &img, &err));
const compiler::StbView v = compiler::StbView::from(img);
CHECK(v.ok());
const compiler::StbView::FuncRow r = v.func_row(0);
CHECK(r.nregs == 14); // r8 起:a b c eq + 2 临时
char buf[64];
const uint8_t* base = v.code_bytes();
const uint32_t* ins = reinterpret_cast<const uint32_t*>(base);
bool saw_mul = false, saw_div = false, saw_sub = false, saw_gt = false;
for (uint32_t i = 0; i < r.code_len; ++i) {
isa::disasm(ins[i], buf, sizeof buf);
if (std::strstr(buf, "MUL r8, ")) saw_mul = true;
if (std::strstr(buf, "DIV r9, ")) saw_div = true;
if (std::strstr(buf, "SUB r10, ")) saw_sub = true;
if (std::strstr(buf, "CMP_GT r11, ")) saw_gt = true;
}
CHECK(saw_mul && saw_div && saw_sub && saw_gt);
return true;
}
// ---- 12. 用例 08TIME 字面量进常量表 ----
static bool test_case08() {
std::vector<uint8_t> img;
std::string err;
CHECK(compile_case("08_time_literal", &img, &err));
const compiler::StbView v = compiler::StbView::from(img);
CHECK(v.ok());
CHECK(v.n_consts() == 2);
const compiler::ConstEntry c0 = v.const_entry(0);
CHECK(c0.tag == 2 && c0.value == 10); // T#10ms
const compiler::ConstEntry c1 = v.const_entry(1);
CHECK(c1.tag == 2 && c1.value == 1250); // T#1s250ms
const compiler::StbView::FuncRow r = v.func_row(0);
CHECK(r.nregs == 10); // r8 起
char buf[64];
const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes());
isa::disasm(ins[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r8, 0") == 0);
isa::disasm(ins[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r9, 1") == 0);
return true;
}
// ---- 13. 用例 13FUNCTION + CALL(调用约定 r0 结果 / r1.. 参数)----
static bool test_case13() {
std::vector<uint8_t> img;
std::string err;
CHECK(compile_case("13_function_call", &img, &err));
const compiler::StbView v = compiler::StbView::from(img);
CHECK(v.ok());
CHECK(v.n_funcs() == 2);
CHECK(v.entry_fn_id() == 1); // MAIN 是第二个 POU
char buf[64];
const uint8_t* base = v.code_bytes();
// Addfn_id 0):结果 r0、输入 r1(a) r2(b)、临时 r8/r9
const compiler::StbView::FuncRow fa = v.func_row(0);
CHECK(fa.nregs == 10);
CHECK(fa.code_len == 4);
const uint32_t* ia = reinterpret_cast<const uint32_t*>(base);
isa::disasm(ia[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r8, r1") == 0); // t ← a
isa::disasm(ia[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r9, r2") == 0); // t ← b
isa::disasm(ia[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "ADD r0, r8, r9") == 0); // 结果 r0
isa::disasm(ia[3], buf, sizeof buf);
CHECK(std::strcmp(buf, "RET") == 0);
// MAINfn_id 1):x 在 r8,实参临时 r9/r10
const compiler::StbView::FuncRow fm = v.func_row(1);
CHECK(fm.nregs == 11);
CHECK(fm.code_len == 7);
const uint32_t* im = reinterpret_cast<const uint32_t*>(base) + fm.code_offset / 4;
isa::disasm(im[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r9, 0") == 0); // 3
isa::disasm(im[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r10, 1") == 0); // 4
isa::disasm(im[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r1, r9") == 0); // 参数 1 ← 3
isa::disasm(im[3], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r2, r10") == 0); // 参数 2 ← 4
isa::disasm(im[4], buf, sizeof buf);
CHECK(std::strcmp(buf, "CALL 0") == 0);
isa::disasm(im[5], buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r8, r0") == 0); // x ← 结果
isa::disasm(im[6], buf, sizeof buf);
CHECK(std::strcmp(buf, "RET") == 0);
return true;
}
// ---- 14. 用例 15:用户 FB 内联展开 ----
static bool test_case15() {
std::vector<uint8_t> img;
std::string err;
CHECK(compile_case("15_fb_instance", &img, &err));
const compiler::StbView v = compiler::StbView::from(img);
CHECK(v.ok());
CHECK(v.n_funcs() == 2); // FB 占位 + MAIN
CHECK(v.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<const uint32_t*>(v.code_bytes()) + fm.code_offset / 4;
char buf[64];
// 实参写字段:start := TRUE / stop := FALSE
isa::disasm(im[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r9, 0") == 0);
isa::disasm(im[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 0") == 0);
isa::disasm(im[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r10, 1") == 0);
isa::disasm(im[3], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_GLOBAL r10, 1") == 0);
// 内联体:Q := start AND NOT stop(短路跳转)
isa::disasm(im[4], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 0") == 0);
isa::disasm(im[5], buf, sizeof buf);
CHECK(std::strcmp(buf, "JF r9, +3") == 0);
isa::disasm(im[9], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 2") == 0); // Q 字段
// q := starter.Q
isa::disasm(im[10], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 2") == 0);
const std::vector<std::string> 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. 用例 17TONCAL_TON + 字段偏移----
static bool test_case17() {
std::vector<uint8_t> img;
std::string err;
CHECK(compile_case("17_ton", &img, &err));
const compiler::StbView v = compiler::StbView::from(img);
CHECK(v.ok());
CHECK(v.data_len() == 32); // in@0 pt@8 q@16 et@24
const compiler::StbView::FuncRow r = v.func_row(0);
CHECK(r.nregs == 11);
const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes());
char buf[64];
isa::disasm(ins[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r9, 0") == 0);
isa::disasm(ins[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 0") == 0); // in
isa::disasm(ins[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r10, 1") == 0);
isa::disasm(ins[3], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_GLOBAL r10, 1") == 0); // pt(槽 1
isa::disasm(ins[4], buf, sizeof buf);
CHECK(std::strcmp(buf, "CAL_TON 0") == 0);
isa::disasm(ins[5], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 2") == 0); // t.Q(槽 2
return true;
}
// ---- 16. 用例 18TOF / CTU ----
static bool test_case18() {
std::vector<uint8_t> img;
std::string err;
CHECK(compile_case("18_tof_ctu", &img, &err));
const compiler::StbView v = compiler::StbView::from(img);
CHECK(v.ok());
CHECK(v.data_len() == 72); // tf 4 槽 + c 5 槽 = 9 槽 × 8
const compiler::StbView::FuncRow r = v.func_row(0);
CHECK(r.nregs == 14);
const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes());
char buf[64];
isa::disasm(ins[4], buf, sizeof buf);
CHECK(std::strcmp(buf, "CAL_TOF 0") == 0);
isa::disasm(ins[12], buf, sizeof buf);
CHECK(std::strcmp(buf, "CAL_CTU 4") == 0);
isa::disasm(ins[13], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 7") == 0); // c.Q(槽 7
isa::disasm(ins[14], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r10, 8") == 0); // c.CV(槽 8
return true;
}
// ---- 17. line1 全链路(MAIN + Motor FB + I/O + 全局)----
// ---- 5. 20_line1:全链路(全局 + FB 实例 + I/Q 语义----
static bool test_line1() {
std::vector<uint8_t> img;
std::string err;
using namespace compiler;
const std::string toml = std::string(REPO_ROOT) + "/examples/line1/project.toml";
Project p;
CHECK(parse_project(toml, &p, &err));
std::vector<SourceUnit> units;
for (const std::string& f : compile_files(p)) {
SourceUnit u;
CHECK(load_unit(p.base_dir + "/" + f, &u, &err));
units.push_back(std::move(u));
}
LinkResult link;
CHECK(link_project(p, units, &link, &err));
CHECK(check_project(p, units, link, &err));
compiler::MachineConfig cfg;
CHECK(load_cfg(&cfg));
CHECK(codegen_project(p, units, link, cfg, &img, &err));
const compiler::StbView v = compiler::StbView::from(img);
std::vector<uint8_t> 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
// MAINfn 1):LOADI 读)、STORE_OFF(实参写)、LOADK r1 + CALL(共享体)、LOAD_OFF(输出读)、STOREQ 写)
const std::vector<std::string> 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<std::string> 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<const uint32_t*>(v.code_bytes()) + fm.code_offset / 4;
char buf[64];
isa::disasm(im[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_I r8, 1") == 0); // I0_0io.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_0io.output
isa::disasm(im[16], buf, sizeof buf);
CHECK(std::strcmp(buf, "RET") == 0);
// ---- 6. 05_while_normal:跳转回填(字节单位)----
// 12.13 写侧自检:型号标识 + SHA-256compiler::StbView
const compiler::StbView self = compiler::StbView::from(img);
CHECK(self.ok());
CHECK(self.model_matches("STATOR", 1));
CHECK(self.sha_ok());
std::vector<uint8_t> tampered = img;
tampered[100] ^= 0x01; // 篡改代码段一字节
const compiler::StbView bad = compiler::StbView::from(tampered);
CHECK(bad.ok() && !bad.sha_ok()); // 结构可解析但 SHA 校验失败
static bool test_while_jumps() {
using namespace compiler;
std::vector<uint8_t> img;
CHECK(build_case("05_while_normal", &img));
const StbView v = StbView::from(img);
CHECK(v.ok());
const std::vector<std::string> 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<uint8_t> 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 : INT2 字节,初值 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;
}
+1 -1
View File
@@ -202,7 +202,7 @@ static bool test_all_cases() {
}
++n;
}
CHECK(n == 20);
CHECK(n == 22);
return true;
}
+225 -85
View File
@@ -1,15 +1,12 @@
/**
* @file isa_test.cpp
* @brief isa disasmsidecar
* @brief isa V2 / func / / / / disasm / uses_*
* @author
* @date 2026-08-19
*/
#include <cassert>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include "isa/Encode.h"
#include "isa/Instr.h"
@@ -27,110 +24,253 @@ static int g_checks = 0;
++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<int>(TypeTag::Bool) == 0);
CHECK(static_cast<int>(TypeTag::Int) == 1);
CHECK(static_cast<int>(TypeTag::Time) == 2);
// int8
CHECK(sat_add<int8_t>(100, 100) == 127);
CHECK(sat_add<int8_t>(-100, -100) == -128);
CHECK(sat_sub<int8_t>(-128, 1) == -128);
CHECK(sat_mul<int8_t>(20, 20) == 127);
CHECK(sat_div<int8_t>(-128, -1) == 127); // MIN/-1 饱和
CHECK(sat_div<int8_t>(7, 0) == 7); // 除零返回被除数
CHECK(sat_div<int8_t>(-7, 3) == -2); // 截断
// int16
CHECK(sat_add<int16_t>(30000, 30000) == 32767);
CHECK(sat_add<int16_t>(-30000, -30000) == -32768);
CHECK(sat_div<int16_t>(-32768, -1) == 32767);
CHECK(sat_div<int16_t>(10, 3) == 3);
CHECK(sat_div<int16_t>(7, 0) == 7);
// int32 / int64 边界
CHECK(sat_add<int32_t>(2147483000, 1000) == 2147483647);
CHECK(sat_div<int64_t>(INT64_MIN, -1) == INT64_MAX);
CHECK(sat_add<int64_t>(INT64_MAX, 1) == INT64_MAX);
return true;
}
// ---- 2. 操作码与助记符 ----
// ---- 2. func 表 ----
static bool test_op() {
using namespace isa;
CHECK(static_cast<int>(Op::MOVE) == 0);
CHECK(static_cast<int>(Op::CAL_TON) == 24);
CHECK(static_cast<int>(Op::CAL_F_TRIG) == 31); // 8 个 CAL_* 连续 24..31
CHECK(static_cast<int>(Op::CALL) == 32);
CHECK(static_cast<int>(Op::RET) == 33);
CHECK(kOpCount == 34);
CHECK(std::strcmp(mnemonic(Op::CAL_CTUD), "CAL_CTUD") == 0);
CHECK(std::strcmp(mnemonic(Op::CAL_F_TRIG), "CAL_F_TRIG") == 0);
CHECK(std::strcmp(mnemonic(static_cast<Op>(255)), "???") == 0);
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};
// RRMOVE 11 区)
enc_rr(buf, "11", static_cast<uint8_t>(isa::Op::MOVE), 0, 5, 3);
CHECK(op_of(buf) == static_cast<uint8_t>(isa::Op::MOVE));
CHECK(rd_of(buf) == 5);
CHECK(rs1_of(buf) == 3);
// RRRADD 011 区,func=3
enc_rrr(buf, "011", static_cast<uint8_t>(isa::Op::ADD), 3, 1, 2, 4);
CHECK(op_of(buf) == static_cast<uint8_t>(isa::Op::ADD));
CHECK(func_of(buf) == 3);
CHECK(rd_of(buf) == 1 && rs1_of(buf) == 2 && rs2_of(buf) == 4);
// SLOTLOAD 010001 区)
enc_slot(buf, "010001", static_cast<uint8_t>(isa::Op::LOAD), 0, 7, 300);
CHECK(rd_of(buf) == 7);
CHECK(slot32_of(buf) == 300);
// IMMLOADK
enc_imm(buf, "010000", static_cast<uint8_t>(isa::Op::LOADK), 3, 0, 0x12345678);
CHECK(imm32_of(buf) == 0x12345678);
// JMP010010
enc_jmp(buf, -3);
CHECK(off32_of(buf) == -3);
// JC010011off@55..24 不覆盖 rd
enc_jc(buf, static_cast<uint8_t>(isa::Op::JT), 2, -3);
CHECK(rd_of(buf) == 2);
CHECK(off32_jc_of(buf) == -3);
// CALL010100
enc_call(buf, 7);
CHECK(fn_id32_of(buf) == 7);
// CAL010101fb_id16 + slot32
enc_cal(buf, 3, 42);
CHECK(fb_id16_of(buf) == 3);
CHECK(cal_slot32_of(buf) == 42);
// SLOT_OFF0010off@87..56
enc_slot_off(buf, static_cast<uint8_t>(isa::Op::LOAD_OFF), 3, 1, 9, -12);
CHECK(slot32_of(buf) == 9);
CHECK(off32_hi_of(buf) == -12);
// RS_OFF0010 op=8/9rd@2、rs1@3、off@87..56
enc_rs_off(buf, static_cast<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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);
// RRMOVE
enc_rr(buf, "11", static_cast<uint8_t>(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<uint8_t>(isa::Op::ADD), 8, 1, 2, 3);
isa::disasm(buf, 8, out, sizeof out);
CHECK(std::strcmp(out, "ADD.F32 r1, r2, r3") == 0);
// SLOTLOAD.I16
enc_slot(buf, "010001", static_cast<uint8_t>(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<uint8_t>(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);
// CALfb 名
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<uint8_t>(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_OFFLOAD_PTR/STORE_PTR
enc_rs_off(buf, static_cast<uint8_t>(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<uint8_t>(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<uint8_t>(Op::MOVE)) == 0);
CHECK(find_op("011", static_cast<uint8_t>(Op::ADD)) >= 0);
CHECK(find_op("010001", static_cast<uint8_t>(Op::LOAD)) >= 0);
CHECK(find_op("010101", static_cast<uint8_t>(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<uint8_t>(Op::MOVE)), "MOVE") == 0);
CHECK(std::strcmp(mnemonic("011", static_cast<uint8_t>(Op::ADD)), "ADD") == 0);
CHECK(std::strcmp(mnemonic("99", 0), "???") == 0);
CHECK(format_of("010101", static_cast<uint8_t>(Op::CAL)) == OpFormat::CAL);
CHECK(func_mode_of("11", static_cast<uint8_t>(Op::MOVE)) == FuncMode::Width);
CHECK(func_mode_of("010001", static_cast<uint8_t>(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;
}
+11
View File
@@ -197,7 +197,18 @@ static bool test_line1() {
return true;
}
static bool test_var_temp_kw() {
using namespace compiler;
std::vector<Token> 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;
+81 -8
View File
@@ -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_TEMPFB 布局排除 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<SourceUnit> units;
units.push_back(std::move(u));
CHECK(link_project(p, units, cfg, &r, &err));
// FB 布局:字段 = input/output/内部 varVAR_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. 用例 09GVL + 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;
+215 -158
View File
@@ -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",
{
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")) {
}
// 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")) {
}
// 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")) {
}
// 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")) {
}
// 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")) {
}
// (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",
}
// 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;
}
}
// 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.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")) {
}
// 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 字段类型未命中 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",
}
// 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;
}
}
// 缺 CALop-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. 类型元数据(TypeInfoV2----
static bool test_type_meta() {
compiler::MachineConfig cfg;
std::string err;
CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err));
// BOOLbase=uint8 → 1 字节、无符号、非浮点、tag 0、range [0,1]
// BOOL1 字节、无符号、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);
// INTint16 → 2 字节、有符号、tag 1、无 range
// INT2 字节、有符号、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");
// TIMEint64 → 8 字节、有符号、tag 2
const compiler::TypeMeta t = compiler::type_meta(cfg, "TIME");
CHECK(t && t.width() == 8 && t.is_signed() && !t.is_float() && t.tag() == 2);
// REAL4 字节、浮点、func 8TOD4 字节、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);
+34 -3
View File
@@ -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;
+17 -7
View File
@@ -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
+176 -142
View File
@@ -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<uint32_t>(s));
const uint32_t b = m.slot_addr(static_cast<uint32_t>(s + 1));
if (b > a && b - a < 8) {
return b - a;
}
if (b == 0) {
const uint32_t rem = static_cast<uint32_t>(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<uint32_t>(s));
int64_t v = 0;
for (int i = 0; i < 8; ++i) {
v |= static_cast<int64_t>(m.data()[s * 8 + i]) << (8 * i);
for (uint32_t i = 0; i < slot_width(m, s); ++i) {
v |= static_cast<int64_t>(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<uint8_t>((v >> (8 * i)) & 0xFF);
const uint32_t addr = m.slot_addr(static_cast<uint32_t>(s));
for (uint32_t i = 0; i < slot_width(m, s); ++i) {
m.data()[addr + i] = static_cast<uint8_t>((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<uint8_t> 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<isa::Instr> code;
std::vector<uint8_t> code; ///< 指令字节流(isa::enc_* 追加)
uint32_t nregs = 8;
};
static std::vector<uint8_t> hand_image(const std::vector<vm::ConstEntry>& consts,
/// 发射一条指令到 HFuncisa::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<uint8_t> hand_image(const std::vector<uint64_t>& consts,
const std::vector<HFunc>& funcs,
uint32_t entry = 0,
uint32_t cycle_limit = 100000,
const std::vector<uint8_t>& data = {}) {
size_t code_total = 0;
const std::vector<uint8_t>& values = {},
const std::vector<uint32_t>& slots = {},
uint32_t max_stack = 4096) {
using namespace compiler;
uint32_t off_const = static_cast<uint32_t>(kHeaderSize);
uint32_t off_funcs = off_const + static_cast<uint32_t>(consts.size()) * kConstEntrySize;
uint32_t off_code = off_funcs + static_cast<uint32_t>(funcs.size()) * kFuncRowSize;
uint32_t code_total = 0;
for (const HFunc& f : funcs) {
code_total += f.code.size() * 4;
code_total += static_cast<uint32_t>(f.code.size());
}
const uint32_t off_const = 104;
const uint32_t off_funcs = off_const + static_cast<uint32_t>(consts.size()) * 12;
const uint32_t off_code = off_funcs + static_cast<uint32_t>(funcs.size()) * 12;
const uint32_t off_data = off_code + static_cast<uint32_t>(code_total);
const uint32_t off_end = off_data + static_cast<uint32_t>(data.size()) + 32; // SHA 尾占位
uint32_t off_slots = off_code + code_total;
uint32_t off_values = off_slots + static_cast<uint32_t>(slots.size()) * kSlotRowSize;
std::vector<uint8_t> b(off_values + values.size() + kSha256Size, 0);
std::vector<uint8_t> b(off_end, 0);
auto put32 = [&](size_t o, uint32_t v) {
b[o + 0] = static_cast<uint8_t>(v & 0xFFu);
b[o + 1] = static_cast<uint8_t>((v >> 8) & 0xFFu);
b[o + 2] = static_cast<uint8_t>((v >> 16) & 0xFFu);
b[o + 3] = static_cast<uint8_t>((v >> 24) & 0xFFu);
b[o] = static_cast<uint8_t>(v & 0xFF);
b[o + 1] = static_cast<uint8_t>((v >> 8) & 0xFF);
b[o + 2] = static_cast<uint8_t>((v >> 16) & 0xFF);
b[o + 3] = static_cast<uint8_t>((v >> 24) & 0xFF);
};
auto put64 = [&](size_t o, uint64_t v) {
for (int i = 0; i < 8; ++i) {
b[o + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFFu);
b[o + i] = static_cast<uint8_t>((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<uint32_t>(consts.size()));
put32(48, static_cast<uint32_t>(funcs.size()));
put32(52, off_const);
put32(56, off_funcs);
put32(60, off_code);
put32(64, off_data);
put32(68, off_data);
put32(kOffMagic, kMagic);
put32(kOffVersion, kVersion);
put32(kOffCycleLimit, cycle_limit);
put32(kOffDtMs, 10);
put32(kOffEntryFnId, entry);
put32(kOffNGlobals, static_cast<uint32_t>(slots.size()));
put32(kOffNI, 0);
put32(kOffNQ, 0);
put32(kOffNM, 0);
put32(kOffNConsts, static_cast<uint32_t>(consts.size()));
put32(kOffNFuncs, static_cast<uint32_t>(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<uint32_t>(slots.size()));
put32(kOffValuesSize, static_cast<uint32_t>(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<uint8_t>(mid[i]);
}
for (size_t i = 0; i < consts.size(); ++i) {
const size_t o = off_const + i * 12;
put32(o, static_cast<uint32_t>(consts[i].tag));
put64(o + 4, consts[i].value);
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<uint32_t>(acc));
put32(o + 8, static_cast<uint32_t>(funcs[i].code.size()));
acc += funcs[i].code.size() * 4;
const size_t o = off_funcs + i * kFuncRowSize;
put32(o + kFuncNregsOff, funcs[i].nregs);
put32(o + kFuncCodeOff, acc);
put32(o + kFuncLenOff, static_cast<uint32_t>(funcs[i].code.size()));
for (size_t j = 0; j < funcs[i].code.size(); ++j) {
b[off_code + acc + j] = funcs[i].code[j];
}
size_t c = off_code;
for (const HFunc& f : funcs) {
for (const isa::Instr in : f.code) {
put32(c, in);
c += 4;
acc += static_cast<uint32_t>(funcs[i].code.size());
}
for (size_t i = 0; i < slots.size(); ++i) {
put32(off_slots + i * kSlotRowSize + kSlotAddrOff, slots[i]);
put32(off_slots + i * kSlotRowSize + kSlotPadOff, 0);
}
for (size_t i = 0; i < data.size(); ++i) {
b[off_data + i] = data[i];
for (size_t i = 0; i < values.size(); ++i) {
b[off_values + i] = values[i];
}
// 型号标识(STATOR1+ 真实 SHA-256 尾(用 compiler 实现填)
const char* mid = "STATOR1";
for (size_t i = 0; i < 32; ++i) {
b[72 + i] = i < 7 ? static_cast<uint8_t>(mid[i]) : 0;
}
uint8_t digest[32];
compiler::sha256(b.data(), b.size() - 32, digest);
for (int i = 0; i < 32; ++i) {
b[b.size() - 32 + i] = digest[i];
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 := 5r9 := r8 + 3;槽 0 ← r9;槽 1 → r10;JT 跳过一条
const std::vector<vm::ConstEntry> consts = {
{1, 5},
{1, 3},
{1, 1},
};
const std::vector<HFunc> funcs = {{
{
isa::enc_imm(isa::Op::LOADK, 8, 0),
isa::enc_rr(isa::Op::MOVE, 9, 8),
isa::enc_imm(isa::Op::LOADK, 11, 1),
isa::enc_rrr(isa::Op::ADD, 9, 9, 11),
isa::enc_slot(isa::Op::STORE_GLOBAL, 9, 0),
isa::enc_slot(isa::Op::LOAD_GLOBAL, 10, 0),
isa::enc_jc(isa::Op::JT, 9, 1), // r9≠0 → 跳过下一条
isa::enc_imm(isa::Op::LOADK, 10, 2),
isa::enc_ret(),
},
12,
}};
const std::vector<uint8_t> img = hand_image(consts, funcs, 0, 100000,
std::vector<uint8_t>(16, 0)); // 2 槽
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<uint8_t>(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<HFunc> funcs = {hf};
const std::vector<uint64_t> consts = {5, 3, 1};
const std::vector<uint32_t> slots = {0, 8}; // 槽 0/1 → 值区 0/8
const std::vector<uint8_t> img =
hand_image(consts, funcs, 0, 100000, std::vector<uint8_t>(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 未写=0JT 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←r8CALL 1;结果 r0 → r8
// fn1r0 = r1 + r2r1/r2 来自调用约定区复制)
const std::vector<vm::ConstEntry> consts = {
{1, 5},
{1, 7},
};
const std::vector<HFunc> funcs = {
{{
isa::enc_imm(isa::Op::LOADK, 8, 0),
isa::enc_imm(isa::Op::LOADK, 2, 1),
isa::enc_rr(isa::Op::MOVE, 1, 8),
isa::enc_call(1),
isa::enc_rr(isa::Op::MOVE, 8, 0),
isa::enc_ret(),
},
12},
{{
isa::enc_rrr(isa::Op::ADD, 0, 1, 2),
isa::enc_ret(),
},
10},
};
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<HFunc> funcs = {main_f, add_f};
const std::vector<uint64_t> consts = {5, 7};
const std::vector<uint8_t> 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<HFunc> funcs = {{
{
isa::enc_call(0),
isa::enc_ret(),
},
8,
}};
const std::vector<uint8_t> 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<HFunc> funcs = {hf};
const std::vector<uint8_t> 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<HFunc> funcs = {{
{
isa::enc_slot(isa::Op::STORE_GLOBAL, 8, 60000), // 数据区 0 字节
isa::enc_ret(),
},
12,
}};
const std::vector<uint8_t> img = hand_image({}, funcs);
// 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<HFunc> funcs = {hf};
const std::vector<uint8_t> 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_TEMPtmp/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/c2a = 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 line1I 组合 → 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<HFunc> 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<HFunc> funcs = {hf};
const std::vector<uint8_t> 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<uint8_t> 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<vm::ConstEntry> consts;
const std::vector<HFunc> funcs = {{
{
isa::enc_ret(),
},
8,
}};
std::vector<uint8_t> img = hand_image(consts, funcs);
HFunc hf;
hf.nregs = 8;
EMIT(hf, isa::enc_ret);
const std::vector<HFunc> funcs = {hf};
std::vector<uint8_t> img = hand_image({}, funcs);
for (int i = 0; i < 5; ++i) {
img[72 + i] = "OTHER"[i];
}
+42 -27
View File
@@ -1,11 +1,12 @@
/**
* @file Image.h
* @brief vm .stb compiler
* @brief vm .stb compiler V2
* @author
* @date 2026-08-21
*
* @details Doc/isa/.md12.13 104 = 72 + [32]
* SHA-256[32] SHA-256
* @details Doc/isa/.mdV2 128Bversion=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 V2128 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; ///< 元数据段偏移(@1120 = 空段)
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 tagLOADK func
*/
struct ConstEntry {
uint32_t tag = 0; ///< 类型标记(isa::types::TypeTag0=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;
+67 -79
View File
@@ -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 RETMAIN 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 .stbvm::Imagecreate + SHA-256 + STATOR2
* - instr_len + func + uses_*
* - 访slot [slot].addr func /
* - Ccreate max_stack 12Bfn_id/ret_pc/ret_fn_id
* + regsnregs×8int64 r0..r7 + 64
* MAIN 退 sp MAIN
* - CAL 15 fb_id Doc/isa/.md §8.1
* RTC × dt_ms1970
* - 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 falseerr
* @details .stbImage::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<uint8_t>& 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<int64_t> regs; ///< 寄存器文件(大小 = 函数头 nregs
uint32_t ret_pc = 0; ///< CALL 的下一条(非 MAIN 帧有意义
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<uint8_t> data_; ///< 数据区工作副本(8 字节定宽槽)
std::vector<Frame> frames_; ///< 调用栈([0] = MAIN,跨周期保留)
/**
* @brief 沿 2 [slot*2] 沿[slot*2+1] 沿
* CTU/CTD/R_TRIG/F_TRIG CTUD
*/
std::vector<uint8_t> edge_prev_;
uint32_t pc_ = 0; ///< 当前 pc(相对本帧函数字节码段)
uint32_t cycle_count_ = 0; ///< 本周期指令数
Fault fault_ = Fault::None;///< 最近一次故障
bool ended_ = false; ///< step 后周期是否已结束
/// @brief 当前(栈顶)帧引用。
Frame& cur_frame();
/// @brief 当前(栈顶)帧常量引用。
const Frame& cur_frame() const;
/// @brief 译码执行一条;false = 故障或周期结束。
bool exec_one();
/// @brief 压帧(复制 r0..r7);fn_id 越界以 BadOp 表示。
Fault do_call(uint32_t fn_id);
/// @brief 弹帧 / 周期结束(复制 r0..r7 回调用方)。
Fault do_ret();
Fault do_cal(uint16_t fb_id, uint32_t slot);
/// @brief 执行内置 FBTON/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 <typename T>
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<uint8_t> data_; ///< 值区工作副本
std::vector<uint8_t> stack_; ///< 预分配栈区(max_stack 字节)
size_t sp_ = 0; ///< 栈顶(MAIN 帧之后)
std::vector<Frame> frames_; ///< 调用栈([0] = MAIN,跨周期保留)
std::vector<uint8_t> 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 后周期是否已结束
};
}
+110 -91
View File
@@ -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] @7212.13 修订)。
const size_t kHeaderSize = 104;
/// 常量表一行字节数(tag:4 + value:8)。
const size_t kConstEntrySize = 12;
/// 映像格式版本V2
const uint32_t kVersion = 2;
/// 映像头字节数(V2128)。
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; ///< 常量表行内:value8 字节)
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<uint32_t>(p[0])
| (static_cast<uint32_t>(p[1]) << 8)
@@ -37,7 +69,6 @@ namespace {
| (static_cast<uint32_t>(p[3]) << 24);
}
/// 小端读 64 位。
uint64_t get_le64(const uint8_t* p) {
uint64_t v = 0;
for (int i = 0; i < 8; ++i) {
@@ -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<uint32_t>(p[0]) << 24) | (static_cast<uint32_t>(p[1]) << 16) |
(static_cast<uint32_t>(p[2]) << 8) | static_cast<uint32_t>(p[3]);
}
};
/**
* @brief SHA-256
* @param data
* @param len
* @param out 32
*/
void sha256(const uint8_t* data, size_t len, uint8_t out[32]) {
Sha256 s;
s.update(data, len);
s.final(out);
}
/**
* @brief 32 name + version "STATOR1" '\0'
* @param name
* @param version
* @param out 32
*/
void fill_model_id(const std::string& name, uint32_t version, char out[32]) {
const std::string id = name + std::to_string(version);
for (size_t i = 0; i < 32; ++i) {
@@ -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<size_t>(h.offset_data) + kSha256Size) {
// 段校验(V2:值段之后是 SHA-256 文件尾;元数据段 0 = 空段
if (len < static_cast<size_t>(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<uint64_t>(h.n_slots) * kSlotRowSize) {
v.err_ = "slot table size mismatch";
return v;
}
// 值段大小:meta 段为 0(空段)时值段到 SHA 尾;否则到 meta 起点
if (h.offset_meta != 0) {
if (static_cast<uint64_t>(h.offset_meta) - offs[4] != h.values_size) {
v.err_ = "values segment size mismatch";
return v;
}
} else if (static_cast<uint64_t>(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<uint8_t>& buf) {
return from(buf.data(), buf.size());
@@ -288,68 +302,77 @@ Image Image::from(const std::vector<uint8_t>& 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<const char*>(buf_ + 72), kModelIdSize);
std::string s(reinterpret_cast<const char*>(buf_ + kOffModelId), kModelIdSize);
const size_t z = s.find('\0');
if (z != std::string::npos) {
s.resize(z);
@@ -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_) {
+586 -331
View File
File diff suppressed because it is too large Load Diff