Files
Interpreter/Doc/vm/扫描周期.md
T
Admin 6873b5ee0b 文档同步:machine.toml 配置驱动架构收尾。
- README.md:依赖方向(STCompiler→compiler 不链 isa)、模块表、isa 归执行器侧、
  数据流图(machine.toml + 型号/SHA)
- isa/README.md:重写——只管指令定义(Types/Op/Instr/Encode),删 Image 部分
- 指令与映像.md:依赖图与 CMake(compiler 不链 isa)
- 编译管线.md:管线首步加载 machine.toml、产物含型号/SHA
- 初步计划.md:12.10/12.13 修订(--machine、依赖方向)
- 扫描周期.md / 指令执行.md:12.9 步骤标注已落地、vm 读校验说明
- compiler/vm/executor README:依赖与边界同步
2026-08-21 22:39:27 +08:00

6.0 KiB
Raw Permalink Blame History

扫描周期

vm 模块:寄存器虚拟机。CMake 目标:vmSTATIC),只依赖 指令与映像.md

运行时是 一台 VM、一个入口、一份映像。不链接 compiler。扫描周期与确定性见 初步计划.md 第 2、8 节。

扫描周期

1. 按 sidecar 的 io 绑定,采样 → I / 输入全局
2. 从 PROGRAM MAIN 的 pc=0 执行到 RET
3. 写回 Q / 输出全局
4. 用本周期 Δt 推进内置 FB(不用 wall clock

边界

  • 只认 .stb 映像(vm 自带读实现);读取时型号标识匹配 + SHA-256 校验,不符/篡改 → 拒绝
  • 类型与溢出按 指令与映像.mdINT 饱和);指令定义归 isa
  • 局部 / VAR = 固定寄存器;nregs 在函数头
  • CALL 目标是立即数 fn_id,有界调用栈
  • 无 GC、无堆、无线程;每周期受 cycle_limit 限制
  • I/O 从哪来、Δt 谁给、回放怎么喂,属于 BytecodeExecutor(读 .stb + sidecar

可观察性接口(v1 预留)

TUI 是执行器(BytecodeExecutor)的界面,用途是方便观察 VM 运行状态。v1 不做 TUI,只预留接口,避免后续改 VM 的观察面。原则:

  • 数据与展示分离vm 只提供只读观察 API(数据);TUI 渲染与文本输出属于 executor(展示)。vm 不画任何界面。
  • 只读轮询:TUI 通过快照轮询,绝不写 VM 状态;"继续 / 单步"是 executor 侧命令,不改变确定性。
  • 快照 POD 化:快照保持简单数据(将来跨进程 TUI 可序列化)。

建议的 API 形态(12.9 实现时冻结签名,本节只定方向):

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;
  • 寄存器 ≤256、映像很小,快照拷贝成本可忽略。
  • 单步、回放、dump 都建立在同一套观察 API 上。

12.9 执行计划

指令执行语义(帧/调用栈、指令表、定时器、故障、line1 推演)见 指令执行.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文件格式.mdline1 数据段 16B → 56B
  • Codegen.cpp 简化:width_of/align_up 删除、layout_data/layout_fb_instances 按槽号布局、指令 slot = 符号表槽号
  • codegen_test 断言更新(slot 从字节偏移变槽号、数据区字节数变化)

步骤 1Machine.h(已落地 vm/include/vm/Machine.h

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 读)

步骤 2Machine.cpp 译码 switch(已落地)

  • 取指:函数表 → code_offset(字节)→ 读 u32 → 拆 op/rd/a/b
  • 标量:MOVE / LOADK(常量表 tag 定值表示)/ NOT / AND / OR(布尔)/ ADD/SUB/MUL/DIVsat_*/ CMP_xx(全宽比较)
  • 跳转:JMP / JT / JFpc += off,相对下一条语义)
  • 数据区:LOAD_I/LOAD_M/LOAD_GLOBALSTORE_Q/STORE_M/STORE_GLOBAL(同一实现,slot × 8

步骤 3CALL / RET 帧栈 + 故障(已落地)

  • CALL fn_id:压新帧(复制 r0..r7)→ pc=0RET:复制 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+1;r → 复位;q = cv ≥ pv

步骤 5:可观察性基础(已落地)

  • step() / pc / fn_id / cycle_count / call_depth / reg 落基础实现;snapshot() 留 12.11 补全

步骤 6vm_test(已落地)

  • 手工拼小映像(isa 编码函数):赋值、跳转、CALL/RET 帧复制、cycle_limit
  • 编译器产物全链路:用例 01/02/04/05/07/09/13/15/17/18/20 跑周期断言寄存器与数据区
  • 用例 06cycle_limit 打满 → CycleLimit 故障
  • TONdt=10、pt=30 → 第 3 周期 q=1CTU:上升沿计数
  • 确定性:同一 I 序列跑两遍,数据区完全一致

步骤 7:验证 + 提交(已落地,ctest 14/14

  • cmake --build + ctest(新增 vm_cycles 后 10/10 全绿);20 用例扫描:14 正例执行通过