diff --git a/Doc/compiler/用例反汇编对照.md b/Doc/compiler/用例反汇编对照.md new file mode 100644 index 0000000..664de0e --- /dev/null +++ b/Doc/compiler/用例反汇编对照.md @@ -0,0 +1,129 @@ +# 用例反汇编对照(源码 ↔ 指令验证) + +> 状态:**13 个正例全部对照通过(2026-08-26)**。方法:`STCompiler` 编译 → `--dump-map` +> 取变量/槽映射 → `--disasm` 取指令序列 → 逐条对照源码语义。 +> 每用例记录「源码语句 ↔ 指令序列 ↔ 结论」;完整输出见 build/audit/(临时)。 + +## 01_empty_main:最小映像 + +```text +PROGRAM MAIN → fn0: RET(nregs=8, 4 字节)✓ +``` + +## 02_bool_assign:常量表 + BOOL 字面量 + +| 源码 | 指令 | 结论 | +|---|---|---| +| a := TRUE | LOADK.U8 r8, 0(常量[0]=1) | ✓ | +| b := FALSE | LOADK.U8 r9, 1(常量[1]=0) | ✓ | + +常量表 8B 无 tag、BOOL func=U8 ✓ + +## 03_short_circuit:短路 AND/OR + +| 源码 | 指令 | 结论 | +|---|---|---| +| x := a AND b | MOVE r10,r8 / JF r10,+8 / MOVE r11,r9 / MOVE r10,r11 | ✓ 短路跳过 rhs | +| x := a OR b | MOVE r10,r8 / JT r10,+8 / ... | ✓ | + +## 04_if_elsif_else:分支跳转 + +| 源码 | 指令 | 结论 | +|---|---|---| +| IF sel=0 THEN out:=10 | MOVE/LOADK(0)/CMP_EQ.I16/JF +16/LOADK(10)/JMP +52 | ✓ | +| ELSIF sel=1 THEN out:=20 | MOVE/LOADK(1)/CMP_EQ/JF/LOADK(20)/JMP +8 | ✓ | +| ELSE out:=30 | LOADK(30) | ✓ | + +跳转回填、常量正确 ✓ + +## 05_while_normal:WHILE 回填 + +| 源码 | 指令 | 结论 | +|---|---|---| +| n := 0 | LOADK.I16 r8, 0 | ✓ | +| WHILE n<10 | MOVE/LOADK(10)/CMP_LT.I16/JF +28 | ✓ | +| n := n+1 | MOVE/LOADK(1)/ADD.I16 r8 | ✓ | +| 回跳 | JMP -56 | ✓ | + +## 07_int_arith:算术 func + +| 源码 | 指令 | 结论 | +|---|---|---| +| a := 7*6 | LOADK(7)/LOADK(6)/MUL.I16 r8 | ✓ | +| b := a/4 | MOVE/LOADK(4)/DIV.I16 r9 | ✓ | +| c := a-b | MOVE/MOVE/SUB.I16 r10 | ✓ | +| eq := c>30 | MOVE/LOADK(30)/CMP_GT.I16 | ✓ | + +## 08_time_literal:TIME 字面量 + +| 源码 | 指令 | 结论 | +|---|---|---| +| t1 := T#10ms | LOADK.I64 r8, 0(常量 10) | ✓ | +| t2 := T#1s250ms | LOADK.I64 r9, 1(常量 1250) | ✓ | + +## 09_gvl_external:全局 + 初值 + +| 项 | 值 | 结论 | +|---|---|---| +| G1 槽 | slot=0 off=0 type=int func=3 | ✓ | +| G1 初值 | 值段 `05 00`(5 小端) | ✓ | +| x := G1 | LOAD.I16 r8, s0 | ✓ | + +## 13_function_call:调用约定 + max_stack + +| 源码 | 指令 | 结论 | +|---|---|---| +| Add: a+b | MOVE r8,r1 / MOVE r9,r2 / ADD.I16 r0 | ✓ r0 结果 | +| x := Add(3,4) | LOADK(3)/LOADK(4)/MOVE r1/MOVE r2/CALL 0/MOVE r8,r0 | ✓ 实参 r1..r7 | +| max_stack | 192 = (12+11×8) + (12+10×8) | ✓ 编译期 DAG | +| 映射 | add: a=r1 b=r2 result=r0;main: x=r8 | ✓ | + +## 15_fb_instance:用户 FB 内联 + +| 源码 | 指令 | 结论 | +|---|---|---| +| starter(start:=TRUE,...) | LOADK(TRUE)/STORE_OFF r9,s0,0;LOADK(FALSE)/STORE_OFF r10,s0,1 | ✓ 字段@0/1 | +| Q := start AND NOT stop | LOAD_OFF s0,0 / JF / LOAD_OFF s0,1 / NOT / MOVE / STORE_OFF s0,2 | ✓ 内联+短路 | +| q := starter.Q | LOAD_OFF r8, s0, 2 | ✓ | + +值段 3B、映射 start@0 stop@1 q@2 ✓ + +## 17_ton:内建 TON + +| 源码 | 指令 | 结论 | +|---|---|---| +| t(in:=TRUE, pt:=T#30ms) | STORE_OFF.U8 s0,0;STORE_OFF.I64 s0,8 | ✓ pt=TIME→I64 | +| 调用 | CAL.TON s0 | ✓ fb 名显示 | +| q := t.Q | LOAD_OFF.U8 s0,16 | ✓ 偏移 16 | + +值段 32B(in@0/pt@8/q@16/et@24)✓ + +## 18_tof_ctu:双实例 + 字段偏移 + +| 源码 | 指令 | 结论 | +|---|---|---| +| tf(in:=FALSE, pt:=T#20ms) | STORE_OFF s0,0/8;CAL.TOF s0;LOAD_OFF s0,16 | ✓ | +| c(cu:=TRUE,r:=FALSE,pv:=3) | STORE_OFF s1,0/1;STORE_OFF.I16 s1,2;CAL.CTU s1 | ✓ pv=INT 2B | +| q2 := c.Q / cnt := c.CV | LOAD_OFF.U8 s1,4;LOAD_OFF.I16 s1,6 | ✓ q@4 cv@6 | + +槽表 2 条(addr 0/32)、值段 40B ✓ + +## 20_line1:全链路 + +| 源码 | 指令 | 结论 | +|---|---|---| +| starter(start:=I0_0, stop:=I0_1) | LOAD.U8 r8,s1 → STORE_OFF s4,0;LOAD.U8 r9,s2 → STORE_OFF s4,1 | ✓ I 读 | +| Q := start AND NOT stop AND NOT EmergencyStop | LOAD_OFF s4,0/JF/LOAD_OFF s4,1/NOT/MOVE/JF/LOAD s0(急停)/NOT/MOVE/STORE_OFF s4,2 | ✓ 双短路+全局 | +| Q0_0 := starter.Q | LOAD_OFF s4,2 / STORE.U8 r8,s3 | ✓ Q 写 | + +槽表 5 条(addr 0..4)、值段 7B ✓ + +## 结构自检(stbcheck,13 用例) + +寄存器越界 / func 非法 / 跳转越界 / 槽号越界 / 指令长度越界:**全部通过**。 + +## 结论 + +- 编译器生成正确性:**13 正例全部对照通过** +- 结构自检:**全部通过** +- 已知缺陷(未修):LREAL 字面量装载按 REAL(LOADK.F32),F64 运算位模式错位(TODO) diff --git a/Doc/索引.md b/Doc/索引.md index 5df26e8..38af499 100644 --- a/Doc/索引.md +++ b/Doc/索引.md @@ -23,6 +23,7 @@ doc/ compiler/寄存器码.md compiler/编译器优化.md compiler/阶段3-语言层V2.md + compiler/用例反汇编对照.md vm/扫描周期.md vm/指令执行.md executor/执行器入口.md @@ -48,6 +49,7 @@ doc/ | [`compiler/寄存器码.md`](compiler/寄存器码.md) | 12.8 寄存器码:布局、指令模式、内联 FB、8 片计划 | | [`compiler/编译器优化.md`](compiler/编译器优化.md) | **V2 草案**:编译策略现状(FB 内联/短路/寄存器分配)+ V2 方向(FB 共享体/类型驱动/变长发射) | | [`compiler/阶段3-语言层V2.md`](compiler/阶段3-语言层V2.md) | **阶段 3 计划**:语言层 V2(Lexer/Parser/Typecheck/Linker)+ 待定决策点 L1..L8 | +| [`compiler/用例反汇编对照.md`](compiler/用例反汇编对照.md) | **编译器验证**:13 正例源码 ↔ 反汇编逐条对照结论 | | [`vm/扫描周期.md`](vm/扫描周期.md) | 扫描周期与 VM 边界 | | [`vm/指令执行.md`](vm/指令执行.md) | 12.9 指令执行流程:帧/调用栈、指令表、定时器、故障 | | [`executor/执行器入口.md`](executor/执行器入口.md) | 可执行入口:加载 `.stb` + sidecar,跑扫描周期 | diff --git a/compiler/include/compiler/Codegen.h b/compiler/include/compiler/Codegen.h index 1a52a78..6eab3f0 100644 --- a/compiler/include/compiler/Codegen.h +++ b/compiler/include/compiler/Codegen.h @@ -11,6 +11,7 @@ #pragma once #include +#include #include #include @@ -21,6 +22,18 @@ namespace compiler { + /** + * @brief 变量/槽映射(--dump-map 用;验证源码 ↔ 反汇编对照)。 + */ + struct CodegenMap { + /// 每函数:{ 函数名, { 局部变量名 → "寄存器号" } } + std::vector>> funcs; + /// 全局:{ 变量名 → "slot=.. off=.. type=.." } + std::map globals; + /// 实例:{ "POU名/实例名" → "slot=.. off=.. fb=.. size=.. 字段:名@off.." } + std::map instances; + }; + /** * @brief 编译工程为 .stb 映像字节(在链接 + 类型检查成功后调用)。 * @param proj 工程定义(cycle_limit / dt_ms / 哈希) @@ -34,5 +47,6 @@ namespace compiler { */ bool codegen_project(const Project& proj, const std::vector& units, const LinkResult& link, const MachineConfig& cfg, - std::vector* image, std::string* err); + std::vector* image, std::string* err, + CodegenMap* map = nullptr); } diff --git a/compiler/src/Codegen.cpp b/compiler/src/Codegen.cpp index adf92a6..f1ac473 100644 --- a/compiler/src/Codegen.cpp +++ b/compiler/src/Codegen.cpp @@ -53,8 +53,9 @@ namespace { public: Builder(const Project& proj, const std::vector& units, const LinkResult& link, const MachineConfig& cfg, - std::vector* image, std::string* err) - : proj_(proj), units_(units), link_(link), cfg_(cfg), image_(image), err_(err) { + std::vector* image, std::string* err, CodegenMap* map) + : proj_(proj), units_(units), link_(link), cfg_(cfg), image_(image), err_(err), + map_(map) { for (const IoBinding& b : proj_.io) { std::string key = b.var; for (char& ch : key) { @@ -307,6 +308,18 @@ namespace { } f->nlocals = r; f->nregs = r; + if (map_ != nullptr) { + std::map vars; + for (const auto& kv : f->regs) { + std::string tn = "int"; + const auto tf = f->reg_func.find(kv.first); + if (tf != f->reg_func.end()) { + tn = std::to_string(tf->second); + } + vars[kv.first] = "r" + std::to_string(kv.second) + " (func " + tn + ")"; + } + map_->funcs.emplace_back(f->name, vars); + } for (const Stmt& st : pou.body) { begin_stmt(*f); if (!compile_stmt(*f, st)) { @@ -755,6 +768,12 @@ namespace { const int64_t v = has_init ? init : 0; write_value(values_, cur_addr, v, tm.width()); global_slot_[s.name] = static_cast(slots_.size() - 1); + if (map_ != nullptr) { + map_->globals[s.name] = "slot=" + std::to_string(slots_.size() - 1) + + " off=" + std::to_string(cur_addr) + + " type=" + s.type_name + + " func=" + std::to_string(tm.func()); + } cur_addr += tm.width(); } for (const LinkResult::PouScope& sc : link_.scopes) { @@ -790,6 +809,16 @@ namespace { slots_.push_back(cur_addr); values_.resize(cur_addr + static_cast(off), 0); instances_[sc.name + "/" + s.name] = inst; + if (map_ != nullptr) { + std::string desc = "slot=" + std::to_string(inst.slot) + + " off=" + std::to_string(cur_addr) + + " fb=" + inst.type_name + + " size=" + std::to_string(off) + " "; + for (const auto& fo : inst.field_off) { + desc += fo.first + "@" + std::to_string(fo.second) + " "; + } + map_->instances[sc.name + "/" + s.name] = desc; + } cur_addr += static_cast(off); } } @@ -1036,6 +1065,7 @@ namespace { std::map instances_; ///< "POU名/实例名" → 实例布局 const InstFields* inline_fields_ = nullptr; ///< 内联 FB 字段表 uint32_t max_stack_ = 0; ///< 栈区字节数 + CodegenMap* map_ = nullptr; ///< 变量/槽映射输出(可空) }; } // namespace @@ -1052,8 +1082,8 @@ namespace { */ bool codegen_project(const Project& proj, const std::vector& units, const LinkResult& link, const MachineConfig& cfg, - std::vector* image, std::string* err) { - Builder b(proj, units, link, cfg, image, err); + std::vector* image, std::string* err, CodegenMap* map) { + Builder b(proj, units, link, cfg, image, err, map); return b.run(); } diff --git a/compiler/src/main.cpp b/compiler/src/main.cpp index d2e1180..41a874f 100644 --- a/compiler/src/main.cpp +++ b/compiler/src/main.cpp @@ -34,6 +34,7 @@ namespace { " -o .stb 编译并写出映像文件\n" " --machine 机器定义(machine.toml,编译路径必填)\n" " --disasm 反汇编一个已编译的 .stb 映像(需要 --machine)\n" + " --dump-map 编译后打印变量/槽映射(对照源码↔反汇编用)\n" " --help 打印本帮助\n"); } @@ -167,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) { @@ -253,10 +259,27 @@ int main(int argc, char** argv) { return 1; } std::vector image; - if (!compiler::codegen_project(proj, units, link, cfg, &image, &err)) { + compiler::CodegenMap cmap; + compiler::CodegenMap* cmap_ptr = dump_map ? &cmap : nullptr; + if (!compiler::codegen_project(proj, units, link, cfg, &image, &err, cmap_ptr)) { std::fprintf(stderr, "error: %s\n", err.c_str()); return 1; } + if (dump_map) { + std::printf("map:\n"); + for (const auto& f : cmap.funcs) { + std::printf(" function %s:\n", f.first.c_str()); + for (const auto& v : f.second) { + std::printf(" %-24s %s\n", v.first.c_str(), v.second.c_str()); + } + } + for (const auto& g : cmap.globals) { + std::printf(" global %-24s %s\n", g.first.c_str(), g.second.c_str()); + } + for (const auto& i : cmap.instances) { + std::printf(" instance %-24s %s\n", i.first.c_str(), i.second.c_str()); + } + } if (!compiler::write_stb_file(out_path.c_str(), image, &err)) { std::fprintf(stderr, "error: %s\n", err.c_str()); return 1;