Skip to content

feat: topic17 v1.3 regression allocator optimization + scenario analysis + docs - #37

Open
Cynthia-Xichen wants to merge 1 commit into
ScratchV-Compiler:mainfrom
Cynthia-Xichen:feat/topic17-v1.3-clean
Open

feat: topic17 v1.3 regression allocator optimization + scenario analysis + docs#37
Cynthia-Xichen wants to merge 1 commit into
ScratchV-Compiler:mainfrom
Cynthia-Xichen:feat/topic17-v1.3-clean

Conversation

@Cynthia-Xichen

Copy link
Copy Markdown

Summary

topic17 v1.3 回归分配器优化 + 瓶颈场景分析 + 文档

Changes

  • regalloc_linear_v1.3.py: 回归分配器 v1.3 版本,优化了 eviction/scratch/pressure 策略
  • topic17_bottleneck_scenarios_v1.3.py: 瓶颈场景分析框架 v1.3
  • 开发文档: topic17 v1.3 开发过程记录
  • 设计文档: topic17 v1.3 设计方案说明

Test Plan

  • 单元测试通过
  • 回归分析覆盖

Closes #

@github-actions

Copy link
Copy Markdown

🤖 AI Code Review

共审查 4 个变更文件

📁 docs/topic17_v1.3开发文档.md

🟡 核心算法遗漏自溢分支 — 2.2 节步骤 4.c 只描述“溢出 end 最晚的区间”,但未说明当前区间自身就是最晚结束时应如何处理(v1.1 优化中已实现自溢)。建议补充该分支,否则读者可能误解溢出策略全貌。

🟡 上游接口转换未说明 — 2.3 节指出上游依赖 MachineInstr,但 API 示例直接使用 LsInstruction。缺少从 MachineInstr 到 LsInstruction 的转换逻辑或接口约定,可能导致集成时歧义。

💭 栈槽对齐约束未提及 — 5. 风险评估中栈槽分配使用独立计数器,但 RISC-V 的 sw/lw 要求地址字对齐(4 字节)。建议在栈槽管理部分明确对齐策略(如 offset = -4 * next_slot 天然对齐,但需显式说明)。

💭 术语一致性 — 文档混用“溢出”/“spill”、“eviction”/“victim”,未定义或统一。建议在首次出现时给出英文对照(如“溢出(spill)”、“驱逐(eviction)”),并保持全文一致。

💭 peak_real_pressure 缺少测试验证 — 4.1 单元测试覆盖了基本场景,但未包含对新增指标 peak_real_pressure 的验证。建议添加测试用例,确保该指标在自溢场景下正确反映真实压力。

💭 历史版本节标题易混淆 — 7.3/7.4/7.5 节标题为“v1.1 代码优化”、“v1.2 peak_active 统计指标”、“v1.3 Bug 修复”,但文档整体为 v1.3,易误解为当前版本。建议在节标题中加注“历史:”或“v1.1 已修复内容”。


📁 docs/topic17_v1.3设计文档.md

🔴 活跃区间计算忽略 live-in/live-outcompute_live_intervals 仅基于块内 defines/uses,未考虑基本块入口活跃的虚拟寄存器(live-in)和出口活跃(live-out)。这会导致块开始时错误分配(如假定所有 vreg 首次使用前无定义)或块结束时寄存器被提前释放(live-out 变量未保持)。线性扫描需要全局 liveness 信息。

🔴 同一 vreg 多次定义时的区间计算错误 — 代码只记录 first_def,若某 vreg 在块内被重新定义(如 v1 = ...; v1 = ...),则区间会合并为 [first_def, last_use),实际上每次定义应开始新区间。当前实现会错误地认为 vreg 在整个区间内活跃,可能造成寄存器冲突。

🟡 spill() 函数未定义,核心溢出逻辑缺失 — 伪代码调用了 spill() 并依赖其返回值,但文档未给出该函数的实现(如何选择溢出目标、如何返回寄存器或 None)。读者无法理解完整的溢出决策流程。

🟡 active 列表未维护有序,与描述矛盾 — 文档声称 active 按 end 升序维护,但伪代码中仅 active.append(...) 而不排序。_expire_old_intervals 通常依赖有序性来提前终止,未给出实现细节,可能导致过期检查错误或性能低下。

🟡 _pick_scratch 的冲突避免策略未描述 — 自溢变量每次使用前需加载到临时寄存器,但如何确保临时寄存器不与当前指令的其他操作数寄存器冲突?文档仅提到 _scratch_cache 缓存,缺少冲突解决算法(如遍历操作数集选择可用寄存器)。

💭 inst.id 与定义中的 inst.index 不一致 — 在 compute_live_intervals 代码片段中使用了 inst.id,但数据结构 LsInstruction 字段名为 index,应统一以避免混淆。

💭 peak_real_pressure 未在报告示例中体现 — v1.3 新增该指标,但 2.5 节 report() 示例仍只输出 Peak simultaneously active,可能造成使用者不知道新字段的存在。

💭 物理寄存器池表格未明确排除 x1(ra) — 文字提到排除 ra、sp、gp、tp,但表格中未列出 x1,容易让读者误以为 x1 包含在池中(尽管按计数是正确的)。建议在表格中加注“排除 x1-x4”。


📁 scratchv/backend/regalloc_linear_v1.3.py

🔴 Bug: 寄存器冲突spill() 方法中 self-spill 时使用固定 phys_regs[0] 但未检查该寄存器是否已被占用。此时 free_regs 为空,所有物理寄存器均被活跃间隔占用,直接写入 temp_reg 会导致数据损坏。
🔴 Bug: alloc_map 映射不一致spill() 中 self-spill 将 alloc_map[current.vreg] 设为物理寄存器,但 get_allocated_code() 期望 spilled vreg 的映射以 "SPILL_" 开头才能触发 scratch 选择。该定义指令会直接使用该物理寄存器,但后续 reload 可能用不同寄存器,且未考虑该寄存器是否已被其他 vreg 占用。

🟡 逻辑冗余compute_live_intervals() 中第三个 if vreg in inst.defines and vreg in inst.uses: 块完全重复了前两个 if 的处理,应移除。

🟡 潜在性能/维护machine_instrs_from_block()block_from_machine_instrs() 在函数内部 import MachineInstr,应移到文件顶部以避免重复导入开销。

🟡 容错欠妥machine_instrs_from_block()MachineOp(opcode) 失败时 fallback 为 MachineOp.MV 会静默吞掉未知指令,建议显式抛出异常或记录。

🟡 边界情况_pick_reload_reg() 中如果所有寄存器均被占用且 _evict_for_reload() 找不到可 evict 的 vreg(例如所有 vreg 均受保护),返回 phys_regs[0] 可能仍然被占用,应重试或报错。

💭 命名小细节 — 变量名 farthest_end 拼写正确,但注释中误写为 "farthest end"(应为 farthest),不过不影响理解。

💭 非 SSA 假设 — 活区间计算假设每个 vreg 仅定义一次,若存在多定义(如循环内重新赋值)会导致区间不准确,建议文档注明或增加断言。


📁 scratchv/backend/topic17_bottleneck_scenarios_v1.3.py

🔴 Eviction / self-spill 计数可能不准确 — 第 70-74 行:evicted_sw_count 统计 spill_code'sw' 出现次数,但 spill() 同时为自溢和 eviction 生成 sw,无法区分。self_spill = max(0, len(alloc._spill_slots) - evicted_sw_count) 假设每个 spill slot 对应一次自溢 sw,但实际一个 slot 可能被多次写(eviction 后重写)。建议在 LinearScanAllocator 中直接暴露 self_spillevicted 计数器,或修改 spill_code 条目标记类型。

🟡 依赖注释格式解析脆弱 — 第 88-98 行和 104-112 行:用 # reload vXX# spill vXX 注释字符串提取 vreg 名称。如果 LinearScanAllocator 改变注释格式,测试会静默错误。建议将 spill_code 改为结构化数据(如 (pos, op, src_reg, offset, vreg, kind)),或通过 alloc.alloc_map 等内部状态直接获取。

🟡 redundant_reloads 统计不精确 — 第 102 行:sum(len(regs) - 1) 只统计了卸载到不同 scratch 寄存器的种类数,而非实际多余 reload 指令数。例如,同一 vreg 被 reload 五次到同一个寄存器,len(regs)=1,不计数,但实际有 4 次冗余 reload。建议改为统计 spill_codelw 条目数减去预期最小 reload 次数(或直接报告 reload 总数)。

🟡 'sw' in op 可能误匹配 — 第 71 行:'sw' in op 会匹配 'sw''sw_' 等。若 LinearScanAllocator 使用 'sw ''sw\t' 则无问题,但更安全的检查是 op.startswith('sw')op == 'sw''lw' 同理。

💭 import inspect 在循环内 — 第 635 行:应在文件顶部统一导入,避免每次循环重复导入(虽然开销小,但风格不统一)。

💭 长行可换行 — 如第 80 行 redundant_reloads = sum(...) 和多个 print 语句,超过 120 字符,建议适当换行提高可读性。

💭 PHYS_REGS 包含 s8-s11 — 标准 RISC-V 调用约定中 s8-s11 通常不存在(s0-s11 共 12 个,但实际 s8-s11t0-t3 的别名?)。若模拟环境无限制则无妨,但建议明确注释。


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant