From ce948451e02e2cfaf2595811b223231eff64c414 Mon Sep 17 00:00:00 2001 From: chentianya Date: Fri, 21 Aug 2026 20:01:21 +0800 Subject: [PATCH] =?UTF-8?q?12.9=20=E6=AD=A5=E9=AA=A4=200=EF=BC=9A=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=AE=B5=E6=94=B9=208=20=E5=AD=97=E8=8A=82=E5=AE=9A?= =?UTF-8?q?=E5=AE=BD=E6=A7=BD=EF=BC=88=E6=96=B9=E6=A1=88=20a=EF=BC=89?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 指令与映像.md / 寄存器码.md:slot 语义 = 槽号(数据偏移 = 槽号 × 8),取消变宽对齐与 global_offset 映射 - Codegen.cpp:删除 width_of/align_up/global_offsets_;layout_data/layout_fb_instances 按槽号布局(字段 = 槽序号) - stb文件格式.md:line1 真实字节更新(224B、56B 数据段、starter 槽 4/5/6) - codegen_test:断言更新(槽号化 + 数据区字节数),189 断言 9/9 全绿 --- Doc/compiler/寄存器码.md | 6 +- Doc/isa/stb文件格式.md | 35 ++++++----- Doc/isa/指令与映像.md | 4 +- compiler/src/Codegen.cpp | 116 ++++++++++++------------------------- tests/src/codegen_test.cpp | 34 +++++------ 5 files changed, 78 insertions(+), 117 deletions(-) diff --git a/Doc/compiler/寄存器码.md b/Doc/compiler/寄存器码.md index a50d4ed..687f402 100644 --- a/Doc/compiler/寄存器码.md +++ b/Doc/compiler/寄存器码.md @@ -25,9 +25,9 @@ compiler 模块的代码生成。输入:`Project` + `Unit`(AST)+ `LinkResu | FB 实例(任意 POU 内声明) | **数据区实例块**(绝对地址) | 状态跨周期持久 | | 全局(含 I/Q/M) | 数据区(声明序) | 12.6 已定槽号 | -- **数据区** = 全局块(声明序,按对齐规则:BOOL 1B / INT 2B / TIME 8B,起点对齐自身宽度)→ FB 实例块(按实例声明序,各块按类型布局大小、8 字节对齐) -- **指令 slot = 数据区字节偏移**(u16);符号表 `address` 保持逻辑槽号(12.6 冻结),编译时做 槽号 → 偏移 映射 -- 字段访问 `fb.Q`:`LOAD_GLOBAL rd, <实例基址+字段偏移>`(编译期算死);I/Q/M 用 `LOAD_I` / `STORE_Q` / `LOAD_M` / `STORE_M` 同槽号 +- **数据区** = 全局块(声明序,**每槽 8 字节定宽**,见 [`指令与映像.md`](../isa/指令与映像.md))→ FB 实例块(按实例声明序,每字段一槽) +- **指令 slot = 槽号**(u16,0..65535);数据偏移 = 槽号 × 8;符号表 `address` 即指令 slot,无映射 +- 字段访问 `fb.Q`:`LOAD_GLOBAL rd, <实例基槽+字段序号>`(编译期算死);I/Q/M 用 `LOAD_I` / `STORE_Q` / `LOAD_M` / `STORE_M` 同槽号 - 函数帧寄存器:`FUNCTION` 结果固定 **r0**,`VAR_INPUT` 从 r1 起按声明序,`VAR` 续后;PROGRAM / FB 变量从 r0 起按声明序 ### 调用约定(v1 冻结,VM 12.9 按此实现) diff --git a/Doc/isa/stb文件格式.md b/Doc/isa/stb文件格式.md index 7474d44..7a32781 100644 --- a/Doc/isa/stb文件格式.md +++ b/Doc/isa/stb文件格式.md @@ -1,6 +1,6 @@ # .stb 映像文件结构 -编译产物 `.stb` 的**面向使用者**的结构说明。规范以 [`指令与映像.md`](指令与映像.md) 为准,本文用 `line1.stb`(184 字节)做真实拆解。 +编译产物 `.stb` 的**面向使用者**的结构说明。规范以 [`指令与映像.md`](指令与映像.md) 为准,本文用 `line1.stb`(224 字节)做真实拆解。 ## 概览 @@ -74,15 +74,15 @@ line1(2 行): - `CALL` 的 `fn_id` = 函数表行下标 - 调用约定:结果 r0、参数 r1..r7、变量与临时 r8 起 -line1 `MAIN` 前 5 条(`0x60` 起): +line1 `MAIN` 前 5 条(`0x60` 起,slot 为 8 字节定宽槽号): | 偏移 | 字节 | 反汇编 | 含义 | |---|---|---|---| -| 0x60 | `12 08 01 00` | `LOAD_I r8, 1` | I0_0(io.input 绑定 → LOAD_I) | -| 0x64 | `17 08 08 00` | `STORE_GLOBAL r8, 8` | starter.start ← I0_0 | -| 0x68 | `16 09 02 00` | `LOAD_GLOBAL r9, 2` | I0_1 | -| 0x6C | `17 09 09 00` | `STORE_GLOBAL r9, 9` | starter.stop ← I0_1 | -| 0x70 | `16 08 08 00` | `LOAD_GLOBAL r8, 8` | 内联体:读 starter.start | +| 0x64 | `12 08 01 00` | `LOAD_I r8, 1` | I0_0(io.input 绑定 → LOAD_I,槽 1) | +| 0x68 | `17 08 04 00` | `STORE_GLOBAL r8, 4` | starter.start(槽 4)← I0_0 | +| 0x6C | `16 09 02 00` | `LOAD_GLOBAL r9, 2` | I0_1(槽 2) | +| 0x70 | `17 09 05 00` | `STORE_GLOBAL r9, 5` | starter.stop(槽 5)← I0_1 | +| 0x74 | `16 08 04 00` | `LOAD_GLOBAL r8, 4` | 内联体:读 starter.start(槽 4) | ## FB 布局段(`offset_fb` 起) @@ -90,18 +90,21 @@ v1 为空(用户 FB 内联展开,字段偏移已算进数据段;内建 TON ## 数据段(`offset_data` 起) -**全局块**(`n_globals` 个,声明序)→ **FB 实例块**(按 POU 收集序、实例声明序,块间 8 字节对齐)。 +**每槽 8 字节定宽**(12.9 方案 a):`BOOL` 用低 1 字节、`INT` 用低 2 字节(小端)、`TIME` 全 8 字节。指令 `slot` 操作数即**槽号**,数据偏移 = 槽号 × 8。 -槽宽与对齐:`BOOL` 1 字节 / `INT` 2 字节 / `TIME` 8 字节,每个槽起点对齐自身宽度。 +槽序:**全局块**(`n_globals` 个,声明序)→ **FB 实例块**(POU 收集序、实例声明序,每字段一槽)。 -line1 数据段(16 字节,`0xA8` 起): +line1 数据段(56 字节 = 7 槽 × 8B,`0xA8` 起): -| 偏移 | 内容 | -|---|---| -| 0xA8..0xAB | 全局:EmergencyStop、I0_0、I0_1、Q0_0(BOOL,初值 0) | -| 0xAC..0xAF | 填充(实例块 8 对齐) | -| 0xB0..0xB2 | starter 实例:start、stop、q(BOOL) | -| 0xB3..0xB7 | 填充 | +| 槽号 | 偏移 | 内容 | +|---|---|---| +| 0 | 0xA8 | EmergencyStop(BOOL,初值 0) | +| 1 | 0xB0 | I0_0(BOOL) | +| 2 | 0xB8 | I0_1(BOOL) | +| 3 | 0xC0 | Q0_0(BOOL) | +| 4 | 0xC8 | starter.start(BOOL) | +| 5 | 0xD0 | starter.stop(BOOL) | +| 6 | 0xD8 | starter.q(BOOL) | ## 用工具验证 diff --git a/Doc/isa/指令与映像.md b/Doc/isa/指令与映像.md index 082d43f..a196a7d 100644 --- a/Doc/isa/指令与映像.md +++ b/Doc/isa/指令与映像.md @@ -76,7 +76,7 @@ C++20 定宽: - 有符号:`TOF` 剩余时间、两个时间点相减会出负值,免去无符号边界处理 - 64 位:为将来 `DATE_AND_TIME`(毫秒时间戳)与 `TON` / `TOF` / `TP` 的 64 位定时器预留;毫秒量程约 2.9 亿年,扫描周期场景永不溢出 -- 代价:槽位 8 字节对齐。**槽位对齐规则**:`BOOL` 1 字节、`INT` 2 字节、`TIME` 8 字节;连续排列按类型对齐,需要时插 padding,偏移以本规则为准。 +- 代价:槽位 8 字节对齐。**槽位规则**(12.9 方案 a):每槽 8 字节定宽,`BOOL` 用低 1 字节、`INT` 用低 2 字节(小端)、`TIME` 全 8 字节;槽号 × 8 = 数据偏移,无 padding。 --- @@ -180,7 +180,7 @@ FB 实例固定布局,字段偏移编译期算死。 ### 数据段 -槽初值按序排:全局(`n_globals`)→ I → Q → M。槽宽与对齐:`BOOL` 1 字节 / `INT` 2 字节 / `TIME` 8 字节,每个槽起点对齐到自身宽度(需要时插 padding)。 +槽初值按序排:全局(`n_globals`,v1 不拆 I/Q/M)→ FB 实例块(每字段一槽)。**每槽 8 字节定宽**(12.9 方案 a):`BOOL` 用低 1 字节、`INT` 用低 2 字节(小端)、`TIME` 全 8 字节。指令 `slot` 操作数即**槽号**,数据偏移 = 槽号 × 8;符号表 `address` 与指令 slot 一致,无映射。 工程哈希:收录源文件路径排序后对内容做 **FNV-1a 64**,只保证回放槽位一致,不是密码学哈希。FNV-1a 64:basis `0xcbf29ce484222325`,prime `0x100000001b3`。 diff --git a/compiler/src/Codegen.cpp b/compiler/src/Codegen.cpp index 29bb880..9d63b6e 100644 --- a/compiler/src/Codegen.cpp +++ b/compiler/src/Codegen.cpp @@ -7,15 +7,14 @@ * @details 设计说明(详见 Doc/compiler/寄存器码.md): * - 切片 1~6:帧/字面量/MOVE/RET;全局数据区;短路 AND/OR;CMP 与 IF;WHILE 与四则; * FUNCTION 与 CALL(调用约定 r0/r1..r7/r8+)。 - * - 切片 7:FB 实例 → 数据区实例块(跨周期持久);字段地址 = 实例基址 + 字段偏移 + * - 切片 7:FB 实例 → 数据区实例块(跨周期持久);字段槽号 = 实例基槽 + 字段序号 * (编译期算死);字段读写走 LOAD_GLOBAL / STORE_GLOBAL; - * 内建 TON/TOF/CTU → 实参写字段后 CAL_* <实例偏移>; + * 内建 TON/TOF/CTU → 实参写字段后 CAL_* <实例基槽>; * 用户 FB → 调用点内联展开(实参写字段 → FB 体以实例字段为变量编译) - * - 其余构造报 codegen error + * - 方案 a(12.9 前置):数据段**每槽 8 字节定宽**,指令 slot = 槽号(无映射) * * 函数清单: * - put_le32 / put_le64 小端写入映像缓冲 - * - align_up / width_of 对齐与槽宽 * - Builder::Builder (构造)存工程/源文件/链接结果/输出,收集 io 绑定分类 * - Builder::run 布局数据区(全局 + 实例)→ 逐 POU 建函数 → 拼映像 * - Builder::fail 组装 "codegen error: " 返回 false @@ -29,9 +28,10 @@ * - store_target 赋值左值:STORE_* 到全局槽(或内联模式实例字段) * - compile_expr 表达式编译到寄存器(含 Field 字段读) * - cmp_op / arith_op / load_op / store_op 操作码选择 - * - global_offset / instance_of 槽号 → 偏移 / 实例 → 字段地址 + * - global_offset 槽号恒等(方案 a 无映射) + * - instance_of 实例 → 字段槽号表 * - patch_jump 回填跳转偏移(相对下一条指令) - * - layout_data / layout_fb_instances / init_of 数据区布局 + * - layout_data / layout_fb_instances / init_of 数据区布局(8 字节定宽槽) * - const_id 取常量表 id(无则追加) * - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + 数据段 * - codegen_project 对外入口 @@ -68,38 +68,6 @@ namespace { } } - /** - * @brief 按 2 的幂宽度向上对齐 - * @param v 当前偏移 - * @param w 对齐宽度(1/2/8) - * @return 对齐后的偏移 - */ - uint32_t align_up(uint32_t v, uint32_t w) { - return (v + w - 1) & ~(w - 1); - } - - /** - * @brief 类型名 → 槽宽(对齐规则见 Doc/isa/指令与映像.md) - * @param name "bool" / "int" / "time" - * @return 1 / 2 / 8;未知返回 1 - */ - uint32_t width_of(const std::string& name) { - if (name == "int") return 2; - if (name == "time") return 8; - return 1; // bool 及未知 - } - - /** - * @brief TypeKind → 槽宽(FB 字段布局用) - * @param k 类型种类 - * @return 1 / 2 / 8;未知返回 1 - */ - uint32_t width_of(TypeKind k) { - if (k == TypeKind::Int) return 2; - if (k == TypeKind::Time) return 8; - return 1; - } - /** * @brief 代码生成器(切片 1) */ @@ -715,58 +683,56 @@ namespace { } /** - * @brief 逻辑槽号 → 数据区字节偏移(layout_data 先行) - * @param slot 全局逻辑槽号(符号表 address) - * @return 数据区字节偏移(u16 可容) + * @brief 逻辑槽号(符号表 address)即指令 slot(方案 a:8 字节定宽,无映射) + * @param slot 全局逻辑槽号 + * @return 同值(u16 可容,layout_data 已校验) */ uint16_t global_offset(uint32_t slot) const { - return static_cast(global_offsets_[slot]); + return static_cast(slot); } /** - * @brief 布局全局数据区:对齐 + 初值字节 - * @details 槽序 = 声明序(12.6);对齐规则 BOOL 1B / INT 2B / TIME 8B, - * 起点对齐自身宽度;初值取自 GVL 声明的 has_init(无则 0) - * @return true 成功;false(槽溢出等,err 已写) + * @brief 布局全局数据区:每槽 8 字节定宽(方案 a) + * @details 槽号 = 符号表 address(声明序,12.6); + * BOOL 用低 1 字节、INT 用低 2 字节(小端)、TIME 全 8 字节; + * 初值取自 GVL 声明的 has_init(无则 0) + * @return true 成功;false(槽号越界,err 已写) */ bool layout_data() { - uint32_t cur = 0; for (const Symbol& s : link_.globals) { - const uint32_t w = width_of(s.type_name); - cur = align_up(cur, w); - if (cur + w > 0xFFFF) { + if (s.address > 0xFFFF) { return fail("data area exceeds slot range"); } - global_offsets_.push_back(cur); bool has_init = false; int64_t init = 0; init_of(s.name, &has_init, &init); - data_.resize(cur + w, 0); - if (s.type_name == "bool") { - data_[cur] = static_cast(has_init ? init : 0); - } else if (s.type_name == "int") { - const uint16_t v = static_cast(has_init ? init : 0); - data_[cur] = static_cast(v & 0xFFu); - data_[cur + 1] = static_cast((v >> 8) & 0xFFu); + const int64_t v = has_init ? init : 0; + const size_t off = static_cast(s.address) * 8; + data_.resize(off + 8, 0); + if (s.type_name == "int") { + const uint16_t iv = static_cast(v); + data_[off] = static_cast(iv & 0xFFu); + data_[off + 1] = static_cast((iv >> 8) & 0xFFu); } else if (s.type_name == "time") { - const uint64_t v = static_cast(has_init ? init : 0); + const uint64_t tv = static_cast(v); for (int i = 0; i < 8; ++i) { - data_[cur + i] = static_cast((v >> (8 * i)) & 0xFFu); + data_[off + i] = static_cast((tv >> (8 * i)) & 0xFFu); } + } else { // bool + data_[off] = v ? 1 : 0; } - cur += w; } return true; } /** * @brief 布局 FB 实例块:数据区全局之后,按 POU 收集序 / 实例声明序 - * @details 每实例:字段按布局段序对齐排布(BOOL 1 / INT 2 / TIME 8), - * 字段地址 = 实例基址 + 字段偏移;实例块间 8 字节对齐,块内容初值 0 + * @details 每实例:字段按布局段序各占一槽(8 字节定宽), + * 字段地址 = 实例基槽 + 字段序号;实例基槽 = 累计槽数 * @return true 成功;false(err 已写) */ bool layout_fb_instances() { - uint32_t cur = align_up(static_cast(data_.size()), 8); + uint32_t cur = static_cast(data_.size() / 8); // 槽号起点 bool any = false; for (const LinkResult::PouScope& sc : link_.scopes) { for (const Symbol& s : sc.syms) { @@ -777,27 +743,20 @@ namespace { if (lay == sc.fb_instances.end()) { return fail("no layout for instance '" + s.name + "'"); } - cur = align_up(cur, 8); InstFields inst; - inst.base = cur; + inst.base = cur; // 实例基槽(CAL_* 操作数) inst.type_name = s.type_name; - uint32_t off = 0; - for (const FbField& fd : lay->second.fields) { - const uint32_t w = width_of(fd.type); - off = align_up(off, w); - inst.field_addr[fd.name] = cur + off; - off += w; + for (size_t i = 0; i < lay->second.fields.size(); ++i) { + inst.field_addr[lay->second.fields[i].name] = cur + i; } - cur += align_up(off, 8); + cur += static_cast(lay->second.fields.size()); instances_[sc.name + "/" + s.name] = inst; any = true; } } // 仅在有实例时把数据区补到实例区终点(无实例则保持全局区原样) if (any) { - while (data_.size() < cur) { - data_.push_back(0); - } + data_.resize(static_cast(cur) * 8, 0); } return true; } @@ -945,9 +904,8 @@ namespace { std::vector consts_; std::map io_input_; // io.input 绑定名(小写) std::map io_output_; // io.output 绑定名(小写) - std::vector global_offsets_; // 逻辑槽号 → 数据区字节偏移 - std::vector data_; // 数据区(全局初值 + FB 实例块) - std::map instances_; // "POU/实例名" → 字段地址 + std::vector data_; // 数据区(8 字节定宽槽:全局 + FB 实例块) + std::map instances_; // "POU/实例名" → 字段槽号 const InstFields* inline_fields_ = nullptr; // 内联 FB 体字段上下文(可空) }; diff --git a/tests/src/codegen_test.cpp b/tests/src/codegen_test.cpp index aab5a73..5090cab 100644 --- a/tests/src/codegen_test.cpp +++ b/tests/src/codegen_test.cpp @@ -174,7 +174,7 @@ static bool test_case09() { const isa::ImageView v = isa::ImageView::from(img); CHECK(v.ok()); CHECK(v.header().n_globals == 1); - CHECK(v.data_len() == 2); // INT 2 字节 + CHECK(v.data_len() == 8); // 8 字节定宽槽 CHECK(v.data_bytes()[0] == 5); // G1 初值 5(小端低位) CHECK(v.data_bytes()[1] == 0); @@ -248,14 +248,14 @@ static bool test_layout() { const isa::ImageView v = isa::ImageView::from(img); CHECK(v.ok()); CHECK(v.header().n_globals == 3); - CHECK(v.data_len() == 16); // 0 + 2 + 8(对齐)= 16 + CHECK(v.data_len() == 24); // 3 槽 × 8 字节定宽 CHECK(v.data_bytes()[0] == 0); // B 无初值 - CHECK(v.data_bytes()[2] == 0x2C && v.data_bytes()[3] == 0x01); // I = 300 - CHECK(v.data_bytes()[8] == 10); // T = 10ms + CHECK(v.data_bytes()[8] == 0x2C && v.data_bytes()[9] == 0x01); // I = 300(槽 1) + CHECK(v.data_bytes()[16] == 10); // T = 10ms(槽 2) char buf[64]; isa::disasm(reinterpret_cast(v.code_bytes())[0], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 2") == 0); // x := I(INT 偏移 2) + CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 1") == 0); // x := I(槽 1) return true; } @@ -552,7 +552,7 @@ static bool test_case15() { CHECK(v.ok()); CHECK(v.header().n_funcs == 2); // FB 占位 + MAIN CHECK(v.header().entry_fn_id == 1); - CHECK(v.data_len() == 8); // 实例 3B(start@0 stop@1 q@2),8 对齐 + CHECK(v.data_len() == 24); // 实例 3 槽 × 8 字节定宽 const isa::FuncRow fm = v.func_row(1); CHECK(fm.nregs == 12); @@ -602,11 +602,11 @@ static bool test_case17() { 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, 8") == 0); // pt(TIME 8 对齐) + 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, 16") == 0); // t.Q + CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 2") == 0); // t.Q(槽 2) return true; } @@ -619,7 +619,7 @@ static bool test_case18() { const isa::ImageView v = isa::ImageView::from(img); CHECK(v.ok()); - CHECK(v.data_len() == 40); // tf 块 0..31(in@0 pt@8 q@16 et@24)、c 块 32.. + CHECK(v.data_len() == 72); // tf 4 槽 + c 5 槽 = 9 槽 × 8 const isa::FuncRow r = v.func_row(0); CHECK(r.nregs == 14); const uint32_t* ins = reinterpret_cast(v.code_bytes()); @@ -627,11 +627,11 @@ static bool test_case18() { 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 32") == 0); + CHECK(std::strcmp(buf, "CAL_CTU 4") == 0); isa::disasm(ins[13], buf, sizeof buf); - CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 36") == 0); // c.Q + 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, 38") == 0); // c.CV(INT 2 对齐) + CHECK(std::strcmp(buf, "LOAD_GLOBAL r10, 8") == 0); // c.CV(槽 8) return true; } @@ -660,7 +660,7 @@ static bool test_line1() { CHECK(v.header().n_globals == 4); CHECK(v.header().n_funcs == 2); CHECK(v.header().entry_fn_id == 1); - CHECK(v.data_len() == 16); // 全局 4B + 实例块 8..10(8 对齐) + CHECK(v.data_len() == 56); // 7 槽 × 8 字节定宽 const isa::FuncRow fm = v.func_row(1); CHECK(fm.nregs == 13); @@ -670,15 +670,15 @@ static bool test_line1() { isa::disasm(im[0], buf, sizeof buf); CHECK(std::strcmp(buf, "LOAD_I r8, 1") == 0); // I0_0(io.input) isa::disasm(im[1], buf, sizeof buf); - CHECK(std::strcmp(buf, "STORE_GLOBAL r8, 8") == 0); // starter.start + 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, 9") == 0); // starter.stop + 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, 10") == 0); // starter.Q + 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, 10") == 0); // starter.Q 读回 + CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 6") == 0); // starter.Q 读回 isa::disasm(im[15], buf, sizeof buf); CHECK(std::strcmp(buf, "STORE_Q r8, 3") == 0); // Q0_0(io.output) isa::disasm(im[16], buf, sizeof buf);