Skip to content

课题4 W2:统一 Pass 接口与 PassManager - #48

Open
DzSexton wants to merge 4 commits into
ScratchV-Compiler:mainfrom
DzSexton:feat/topic-04-w2-pass-manager
Open

课题4 W2:统一 Pass 接口与 PassManager#48
DzSexton wants to merge 4 commits into
ScratchV-Compiler:mainfrom
DzSexton:feat/topic-04-w2-pass-manager

Conversation

@DzSexton

Copy link
Copy Markdown

内容

  • 新增 IR 专用的 OptimizationPass.optimize(program) -> int 抽象接口。
  • 重构 PassManager:按注册顺序执行、记录逐 Pass 变更数与耗时、支持空/重复管线,并在异常或非法计数时立即停止。
  • 提供唯一的 none/basic/all 管线工厂,统一 CompilerDriver 与 benchmark 的优化级别映射。
  • 将 5 个既有 IR Pass 机械迁移到统一接口,删除默认 _PassAdapter;不修改其优化算法。
  • 主 CLI 新增 --opt-level,保留 --optimize 兼容别名。
  • CompilerDriver 输出结构化 optimization 统计;优化失败时不进入 codegen,也不写输出文件。
  • 同步仓库内测试、benchmark、示例和直接相关文档。

范围

本 PR 仅实现课题 4 的 W2「统一 Pass 接口与 PassManager」。没有提前实现 W3–W9 的常量折叠、不动点、use-def、规则引擎、融合或 LICM 算法增强。

验证

  • W2/优化器/benchmark 专项:82 passed
  • 可运行全量测试:382 passed, 4 skipped, 1 deselected
  • benchmark 回归:25 passed
  • Python 语法编译与 git diff --check 通过
  • Standards 与 W2 Spec 双轴复审均无剩余问题

草稿 PR,等待维护者确认 W2 接口与兼容策略。

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown

🤖 AI Code Review

共审查 10 个变更文件
⚠️ 另有 12 个文件超过上限(最多 10 个)未审查

📁 CONTRIBUTING.md

🟡 文档歧义name 要求 "stable, lowercase, kebab-case",但 "stable" 含义模糊。建议明确:不应在运行时改变,且在管理器内唯一(用作报告 key)。可改为 "unique, lowercase, kebab-case" 或补充说明。

💭 测试覆盖不足 — 步骤 5 仅提及正/负测试,未要求验证 name 属性和 optimize 返回值语义(非负、非累计)。建议增加:验证 name 符合格式,返回值小于等于实际可优化点数量。


📁 benchmarks/run_benchmark.py

🔴 API 契约未验证_optimize 假定 create_optimization_pass_manager(level) 返回的 manager.run(program) 会原地修改 program 且返回带 elapsed_seconds 属性的对象。若 run 返回新程序或 report 无此属性,将导致 AttributeError 或优化未生效(run_benchmark 后续仍用原 program)。建议确认实现,并考虑 program = manager.run(program).program 之类的赋值。

🟡 计时语义变化 — 原实现用 time.perf_counter() 测量墙钟时间,现改为依赖流水线内部 elapsed_seconds。若该值只统计 pass 自身耗时(不含调度、内存分配等),基准数据与旧版本不可比。建议保留外部计时,或明确 elapsed_seconds 的定义。

🟡 "none" 级别行为可能改变 — 新代码无条件调用 manager.run(program)。若 "none" 不是真正的空流水线(例如包含默认清理 pass),ir_opt_inst_count 将与 ir_inst_count 不一致,破坏原有的基准基线。建议确认 "none" 的实现,或保留条件分支。

💭 缺少异常处理 — 若 level 无效或 create_optimization_pass_manager 未实现,脚本会直接崩溃。可捕获异常并给出清晰错误信息,便于调试。


📁 benchmarks/test_benchmark.py

🔴 可能故障:inst_after <= inst_before 假设过强 — 如果 "all" 包含循环展开、内联等可能增大指令数的 pass,断言会失败。优化并不总是减少指令数。建议改为只断言 inst_after > 0,或明确 "all" 只包含减小 IR 的 pass。

🟡 测试覆盖变化 — 原先明确调用 ConstantFolderDeadCodeEliminatorIRPeepholeOptimizer,现在依赖 create_optimization_pass_manager("all") 的语义。如果该 pass manager 不包含 IRPeepholeOptimizer(或包含额外行为),测试就不能覆盖原意图。建议为 pass manager 添加单元测试,或在测试中明确 pass 列表。

🟡 inst_after > 0 可能太严格 — 如果某个 benchmark 模型被完整折叠/删除(例如常量模型),优化后 IR 为空是合法的,但测试会失败。原断言 >= 0 允许这种情况。若这种情况可能出现,请调整断言或跳过空结果。

💭 导入位置create_optimization_pass_manager 在两个测试函数中重复导入,可考虑移到模块顶部。不过与原文件内部导入风格一致,非必须。


📁 docs/optimization_guide.md

🟡 Missing name in examples — The text says every pass defines a stable name, but neither PeepholeOptimizer nor LICM shows one. Add e.g. name = "peephole" to each class so the docs are self-consistent.

🟡 Ambiguous --optimize alias — “Compatibility alias” is unclear. Does --optimize take a value (--optimize all) or is it a legacy boolean flag? If the latter, specify what it maps to (e.g. --optimize == --opt-level all).

🟡 LICM example is still a skeleton — The snippet returns hoisted_count but never increments it; the actual hoisting logic is still comments. Label it as “skeleton/pseudocode” or add concrete increment examples, otherwise readers may copy it and get an always-zero report.

💭 CLI flag name collision — A separate numeric --opt-level for the LLVM tool is easy to confuse with the main CLI’s string --opt-level. Consider naming it --llvm-opt-level or explicitly warning that the two are not interchangeable.

💭 Terminology consistency — “non-negative number of transformations” is fine, but “number of changes” is simpler and matches the total_changes field used elsewhere.


📁 docs/topics/04-IR优化器框架.md

🔴 不一致的 name 属性定义 — 父类 OptimizationPassname 定义为 @property 抽象方法,但子类示例 ConstantFolder 却用类属性 name = "constant-folding" 实现。Python 中 property 是数据描述符,优先级高于子类类属性,因此子类类属性不会覆盖父类的 property,导致子类实例访问 name 时仍会触发父类 property 的 getter(返回 ...),而非子类赋值的字符串。建议统一为一种风格:要么所有子类都实现 @property 方法,要么父类只定义 name 为类属性(甚至去掉 @property),让子类直接覆盖。

🟡 工厂函数命名未反映管线含义create_optimization_pass_manager("basic") 返回的是 manager,而参数 "basic" 应表示预设管线名称。若管理器内部按注册顺序执行,文档应明确说明 "basic" 管线包含哪些 pass,或至少提示用户如何查阅预设列表。

💭 命令行别名预期行为未澄清--opt-level--optimize 同时提供时,以哪个为准?文档说 --optimize 是兼容别名,但未说明冲突处理策略(如最后出现者优先或报错)。建议补充一句“若两者同时出现,以 --opt-level 为准”。

💭 命令名 scratchv 是否需要确认 — 文档中统一使用 scratchv,若项目真实命令与此一致则无问题,否则需修正为实际名称(如 scratcher)。


📁 docs/topics/archive/optimizer_framework.md

🟡 Suggestion: 缺少 program 类型说明
示例中 manager.run(program)program 未定义,建议明确其应为 Program 实例(或 ir.Module),避免读者困惑。

💭 Nit: --opt-level--optimize 并存时的优先级
文档未说明两者同时指定时谁优先,建议明确 --opt-level 优先并标记 --optimize 为即将废弃。


📁 docs/verification.md

💭 Language inconsistency — The new note (lines 221-223) is in Chinese, but the rest of the document (including the changed commands) is in English.
Suggestion: Keep documentation language consistent. If the document is English, translate the note to English.
Example: --optimize all remains a compatibility alias; prefer the canonical form --opt-level all in new documentation.

🟡 Potential confusion about the alias — The note says --optimize all is an alias, but the original command used --optimize without arguments. If --optimize (no value) is also an alias, clarify. If it's deprecated, mention that.
Suggestion: Update the note to clearly state the relationship:

  • --optimize (alias for --opt-level default? Or --optimize all is the alias for --opt-level all?).
  • Ensure the command examples are consistent with the actual CLI behavior.

💭 Missing consistency check — The diff only updates two occurrences of --optimize. If the file or other docs still use --optimize elsewhere, they should be updated too.
Suggestion: Search the entire repository for remaining uses of --optimize and update them to the canonical form.


📁 examples/end_to_end_pipeline.py

🔴 确认 run 的修改语义 — 第二个 hunk 中 create_optimization_pass_manager("all").run(program2) 之后直接 LLVMCodegen(program2)。如果 run() 返回的是新 IR 而不是原地修改 program2,codegen 会生成未优化代码。同理第一个 hunk 后续也继续使用 program。请确认 API 行为,必要时改为 program2 = manager.run(program2) 或把返回值传给 codegen。

🟡 pass 名字是魔法字符串changes_by_name["constant-folding"]changes_by_name["dead-code-elim"] 依赖 pass 管理器的内部命名。只要名称变化、或 "basic" 配置里没有该 pass,就会 KeyError。建议用 report.get_changes("constant-folding", 0) 之类的安全访问,或从 compiler 模块导出 pass 名常量。

🟡 report.executions 等属性是隐式假设execution.nameexecution.changesreport.total_changes 都是对 API 的猜测。如果实际字段名是 results/pass_name/changes_count,会直接 AttributeError。建议在例子里加一个 assert 或先打印 report 的字段,让错误更早暴露。

🟡 第一个 hunk 的 dict 构建可能掩盖问题 — 如果 report.executions 为空,changes_by_name 为空 dict,下面取 ["constant-folding"] 会抛 KeyError,但错误信息不直观。可以改成直接遍历 report 并打印每个 pass 的 name/changes,或者用 .get 提供默认值。

💭 输出文案过时 — 第二个 hunk 的 print("After optimization (fold + dce + peephole):")"all" 实际执行的 pass 集合可能不一致。建议改为 f"After optimization ({report.total_changes} changes)",或动态列出 pass 名。

💭 重复导入 — 两个函数内部都 from scratchv.compiler import create_optimization_pass_manager。既然是同一个示例文件,可以移到文件顶部,减少重复。


📁 examples/llvm_optimization_pipeline.py

🔴 行为改变:program3 的优化结果统计 — 原代码只打印 peephole 优化次数,新代码打印所有 pass 的总优化次数。如果这是有意为之,请确认;否则需要保持原有行为,或者只打印特定的 pass 结果。

🔴 潜在 KeyError — 若 report.executions 中不包含 "constant-folding""dead-code-elim"(例如 pass 名称变更或未注册),访问 changes_by_name[name] 会引发未捕获异常。建议使用 changes_by_name.get(name) 或在访问前进行检查。

🟡 硬编码 pass 名称"constant-folding""dead-code-elim" 是字符串字面量,依赖内部实现。建议定义为常量(如 PASS_CONSTANT_FOLDING = "constant-folding")或使用枚举,避免未来改名导致运行错误。

🟡 缺少错误处理 — 若 create_optimization_pass_manager 抛出异常(如无效的 profile 名称 "basic""all"),当前代码未处理,会直接崩溃。建议增加 try-except 或输入验证。

💭 命名一致性 — program3 的打印信息从 "Folded+DCE+Peephole" 改为 "Full pipeline",虽然更简洁,但可能丢失了具体 pass 的可见性。如果希望保留调试信息,可考虑在 report 中额外输出每个 pass 的贡献。


📁 examples/onnx_llvm_verification.py

🔴 Bug: KeyError if pass names differ — The code assumes report.executions contains exactly "constant-folding" and "dead-code-elim". If the pass manager names are different (e.g., "constant_folding" or "dce"), this will crash with KeyError. Check the actual pass names or use .get() with a fallback.

🔴 Bug: "basic" pass manager may not run both passes — If create_optimization_pass_manager("basic") doesn't include both constant folding and DCE, the lookup will fail. Verify the pass configuration includes both, or specify a more explicit pass list.

🟡 Suggestion: Handle empty/missing report — If report.executions is empty or the pass manager doesn't record executions, the dict comprehension will succeed but the lookups will fail. Add a guard:

if "constant-folding" not in changes_by_name or "dead-code-elim" not in changes_by_name:
    raise RuntimeError("Pass manager did not execute expected passes")

🟡 Suggestion: Use next with default for clarity — Instead of building a dict, consider:

folded = next((e.changes for e in report.executions if e.name == "constant-folding"), None)

This avoids dict allocation and makes the assumed pass names explicit.

💭 Nit: Duplicate names — If two executions share the same name, the dict comprehension silently keeps the last. Use a list or check for duplicates if order matters.

💭 Nit: Verify changes type — The old code returned counts from .run(). The report's changes field might be a list of changed nodes or a count. Confirm it's an integer for the print statement.



⚠️ 未审查的文件

  • examples/verify_with_tinyfive.py
  • scratchv/compiler.py
  • scratchv/main.py
  • scratchv/optimizer/constant_folding.py
  • scratchv/optimizer/dead_code.py
  • scratchv/optimizer/licm.py
  • scratchv/optimizer/muladd_fusion.py
  • scratchv/optimizer/peephole.py
  • scratchv/pass_interface.py
  • tests/test_optimizer.py
  • tests/test_optimizer_advanced.py
  • tests/test_pass_manager.py

@DzSexton
DzSexton marked this pull request as ready for review August 12, 2026 14:05
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