Skip to content

feat(ir): add UseAfterDef structural property verifier#574

Open
huxinyuan1215 wants to merge 7 commits intohw-native-sys:mainfrom
huxinyuan1215:feat/structural-property-use-after-def
Open

feat(ir): add UseAfterDef structural property verifier#574
huxinyuan1215 wants to merge 7 commits intohw-native-sys:mainfrom
huxinyuan1215:feat/structural-property-use-after-def

Conversation

@huxinyuan1215
Copy link
Contributor

Summary

Add `IRProperty::UseAfterDef` as a structural property with a corresponding `UseAfterDefVerifier` that checks all variable uses occur after their definitions
in the IR.

  • Add `UseAfterDef` to `IRProperty` enum
  • Implement `UseAfterDefVerifier` walking the IR and tracking in-scope variables (pushed on entry via `LetStmt`, `ForStmt` loop var, function params,
    `AllocateStmt`; popped on exit)
  • Register in `PropertyVerifierRegistry` and add to `GetStructuralProperties()` — runs automatically before/after every pass when verification is enabled
  • Expose `IRProperty::UseAfterDef` in Python bindings and type stubs
  • Add tests in `tests/ut/ir/transforms/`

Testing

  • All tests pass
  • Code review completed
  • Documentation updated

Related Issues

Closes #(issue number)

@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

This PR adds a UseAfterDef structural property verifier: new enum value, verifier implementation and factory, registry registration, error type and bindings, docs, and unit tests; it enforces variable-use-before-definition rules across function and control-flow scopes.

Changes

Cohort / File(s) Summary
Build
CMakeLists.txt
Added verify_use_after_def.cpp to verifier sources.
Docs
docs/en/dev/passes/99-verifier.md, docs/zh-cn/dev/passes/99-verifier.md
Documented new UseAfterDef structural property, UseAfterDefCheck rule, error semantics, and test guidance.
IR Property
include/pypto/ir/transforms/ir_property.h, src/ir/transforms/ir_property.cpp
Added IRProperty::UseAfterDef, string mapping, and included it in GetStructuralProperties() / GetDefaultVerifyProperties().
Verification Errors
include/pypto/ir/verifier/verification_error.h
Added pypto::ir::use_after_def::ErrorType (USE_BEFORE_DEF = 401) and ErrorTypeToString declaration.
Verifier API
include/pypto/ir/verifier/verifier.h
Declared CreateUseAfterDefPropertyVerifier() factory.
Verifier Registry
src/ir/verifier/property_verifier_registry.cpp
Registered CreateUseAfterDefPropertyVerifier() in the registry constructor.
Verifier Impl
src/ir/verifier/verify_use_after_def.cpp
New verifier implementation: UseAfterDefChecker visitor, scoping rules (params, assign RHS/LHS, loops, if-branches), diagnostics emission, and factory function.
Bindings / Stubs
python/bindings/modules/passes.cpp, python/pypto/pypto_core/passes.pyi
Exposed IRProperty::UseAfterDef and UseAfterDefErrorType (USE_BEFORE_DEF) in Python bindings/stubs.
Tests
tests/ut/ir/transforms/test_verify_use_after_def.py
Added comprehensive tests for valid/invalid use-before-def cases, loop/branch scoping, error code and rule metadata, and registry presence.
Loop Unroll (related)
src/ir/transforms/unroll_loops_pass.cpp
Significant unroll logic changes to track per-iteration carried defs, shadowing, and substitutions — review recommended for def propagation correctness.
Parser Scope Fix
python/pypto/language/parser/ast_parser.py
Snapshot/restore of outer scope for if/else to avoid unintended leakage into else branch when leaking is possible.
Memory / TileType API
python/pypto/ir/builder.py, tests/ut/ir/..., python/pypto/ir/*
Added memory_space parameter to TileType / IRBuilder.tile_type and updated tests to pass explicit memory_space values.
MemRef Remap Memoization
src/ir/transforms/allocate_memory_addr_pass.cpp
Added memoization map for remapping Vars/IterArgs to ensure consistent replacements.

Sequence Diagram

sequenceDiagram
    participant Registry as PropertyVerifierRegistry
    participant Factory as CreateUseAfterDefPropertyVerifier
    participant Verifier as UseAfterDefPropertyVerifier
    participant Checker as UseAfterDefChecker
    participant IR as FunctionBody
    participant Diag as Diagnostics

    Registry->>Factory: register(CreateUseAfterDefPropertyVerifier)
    Factory->>Verifier: create verifier instance
    Verifier->>Checker: instantiate visitor

    loop for each Function in Program
        Verifier->>IR: request traversal
        IR->>Checker: traverse nodes
        Checker->>Checker: manage scopes (params, assigns, loops, if-branches)
        Checker->>Checker: lookup variable uses
        alt use before def detected
            Checker->>Diag: emit USE_BEFORE_DEF (401)
        end
    end

    Verifier->>Diag: return diagnostics
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • lyfne123

Poem

🐇
I bound through scopes both near and far,
I count each def, each use, each var,
With UseAfterDef I stand on guard,
No early use will pass my yard —🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 67.07% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a UseAfterDef structural property verifier to the IR system.
Description check ✅ Passed The description is directly related to the changeset, explaining the purpose, implementation details, and testing of the UseAfterDef verifier feature.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new structural property, UseAfterDef, to the IR verification system. This property ensures that all variables are used only after they have been properly defined within their respective scopes, thereby enhancing the correctness and reliability of the Intermediate Representation. The new verifier integrates seamlessly into the existing pass pipeline, providing automatic checks during IR transformations.

Highlights

  • New Structural Property: Added IRProperty::UseAfterDef to the IRProperty enum, introducing a new structural property for the Intermediate Representation.
  • UseAfterDef Verifier Implementation: Implemented UseAfterDefVerifier, which checks that all variable uses occur after their definitions in the IR, tracking in-scope variables through various statement types like LetStmt, ForStmt, function parameters, and AllocateStmt.
  • Automatic Verification Integration: Registered the UseAfterDefVerifier in the PropertyVerifierRegistry and added it to GetStructuralProperties(), ensuring it runs automatically before and after every pass when verification is enabled.
  • Python Bindings and Type Stubs: Exposed IRProperty::UseAfterDef and its associated error types in Python bindings and type stubs, making the new property accessible from Python.
  • Comprehensive Testing: Added new unit tests in tests/ut/ir/transforms/ to cover various valid and invalid scenarios for the UseAfterDef verifier, including scoping rules for loops and conditional statements.
Changelog
  • CMakeLists.txt
    • Added the new verify_use_after_def.cpp source file to the build system.
  • docs/en/dev/passes/99-verifier.md
    • Updated the English documentation to include UseAfterDef as a structural property, detailing its error types and scoping rules.
  • docs/zh-cn/dev/passes/99-verifier.md
    • Updated the Chinese documentation with similar details for the new UseAfterDef structural property.
  • include/pypto/ir/transforms/ir_property.h
    • Extended the IRProperty enum to include UseAfterDef and updated GetStructuralProperties() to return this new property.
  • include/pypto/ir/verifier/verification_error.h
    • Introduced a new use_after_def namespace containing ErrorType enum (with USE_BEFORE_DEF) and its string conversion utility.
  • include/pypto/ir/verifier/verifier.h
    • Declared the factory function CreateUseAfterDefPropertyVerifier for creating instances of the new verifier.
  • python/bindings/modules/passes.cpp
    • Modified Python bindings to expose IRProperty::UseAfterDef and the UseAfterDefErrorType enum.
  • python/pypto/pypto_core/passes.pyi
    • Updated Python type stubs to include IRProperty.UseAfterDef and UseAfterDefErrorType.
  • src/ir/transforms/ir_property.cpp
    • Implemented the string conversion for IRProperty::UseAfterDef and updated the GetStructuralProperties set.
  • src/ir/verifier/property_verifier_registry.cpp
    • Registered the CreateUseAfterDefPropertyVerifier factory with the property verifier registry.
  • src/ir/verifier/verify_use_after_def.cpp
    • Added the core implementation of the UseAfterDefChecker visitor and UseAfterDefPropertyVerifierImpl, defining the logic for variable definition and usage tracking.
  • tests/ut/ir/transforms/test_verify_use_after_def.py
    • Added a new test file containing unit tests for the UseAfterDef verifier, covering various valid and invalid variable scoping scenarios.
Activity
  • All existing tests passed.
  • Code review has been completed.
  • Documentation in both English and Chinese has been updated to reflect the new feature.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request successfully introduces the UseAfterDef structural property verifier, which is a valuable addition to the IR verification system. The implementation correctly handles complex scoping rules for various IR statement types, and the Python bindings and documentation have been updated accordingly. The comprehensive unit tests provide good coverage for both valid and invalid scenarios.

Comment on lines +161 to +171
auto saved_scope = in_scope_;

if (op->then_body_) VisitStmt(op->then_body_);

in_scope_ = saved_scope;
if (op->else_body_.has_value() && *op->else_body_) {
VisitStmt(*op->else_body_);
}

// Restore: branch-local defs do not propagate outward.
in_scope_ = saved_scope;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current approach to managing variable scope in VisitStmt_(const IfStmtPtr& op) involves copying the in_scope_ unordered_set before and after visiting each branch. While correct, for deeply nested IR structures or functions with a large number of variables, this repeated copying could introduce performance overhead. Consider optimizing this by using a stack-based approach to manage scope changes, where only additions/removals for the current scope are pushed/popped, rather than copying the entire set. This could improve efficiency for large programs.

References
  1. This suggestion to use a stack-based approach for managing scope changes in nested IR structures aligns with the general principle of employing scope-aware mechanisms for nested scopes. Rule 3, for instance, emphasizes using a stack of substitution maps or save/restore semantics for correctly handling variable shadowing during AST transformations. Although the immediate concern here is performance optimization for scope management rather than variable substitution correctness, the recommended stack-based pattern is a robust solution for efficiently handling variable availability across nested scopes.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/ir/transforms/unroll_loops_pass.cpp (1)

247-259: CollectLastDefsByName should also recurse into IfStmt branches for consistency.

Similar to collect_defs, this misses definitions inside conditional branches. For robustness with complex loop bodies:

♻️ Suggested enhancement
   static void CollectLastDefsByName(const StmtPtr& s, std::unordered_map<std::string, VarPtr>& defs) {
     if (!s) return;
     if (auto assign = std::dynamic_pointer_cast<const AssignStmt>(s)) {
       if (assign->var_) defs[assign->var_->name_hint_] = assign->var_;
     } else if (auto seq = std::dynamic_pointer_cast<const SeqStmts>(s)) {
       for (const auto& child : seq->stmts_) CollectLastDefsByName(child, defs);
     } else if (auto for_s = std::dynamic_pointer_cast<const ForStmt>(s)) {
       if (for_s->body_) CollectLastDefsByName(for_s->body_, defs);
+    } else if (auto if_s = std::dynamic_pointer_cast<const IfStmt>(s)) {
+      CollectLastDefsByName(if_s->then_, defs);
+      CollectLastDefsByName(if_s->else_, defs);
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ir/transforms/unroll_loops_pass.cpp` around lines 247 - 259,
CollectLastDefsByName currently misses definitions inside conditional branches;
add an else-if that dynamic_pointer_casts s to const IfStmt and, if present,
call CollectLastDefsByName on the IfStmt's then and else bodies (e.g.,
then_body_ and else_body_ or the actual field names used for the IfStmt's
branches) so last defs inside both branches are recorded; update
CollectLastDefsByName to check each branch for null before recursing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/ir/transforms/unroll_loops_pass.cpp`:
- Around line 247-259: CollectLastDefsByName currently misses definitions inside
conditional branches; add an else-if that dynamic_pointer_casts s to const
IfStmt and, if present, call CollectLastDefsByName on the IfStmt's then and else
bodies (e.g., then_body_ and else_body_ or the actual field names used for the
IfStmt's branches) so last defs inside both branches are recorded; update
CollectLastDefsByName to check each branch for null before recursing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ac497ba6-0246-4d80-905c-c25eabe173e7

📥 Commits

Reviewing files that changed from the base of the PR and between 264124a and ee61e3e.

📒 Files selected for processing (2)
  • src/ir/transforms/unroll_loops_pass.cpp
  • src/ir/verifier/verify_use_after_def.cpp

@huxinyuan1215 huxinyuan1215 force-pushed the feat/structural-property-use-after-def branch 3 times, most recently from 77ebc82 to ff4e830 Compare March 17, 2026 13:37
Add IRProperty::UseAfterDef and a corresponding PropertyVerifier that
checks every Var reference in an expression is dominated by a definition.
Register it as a structural property so it is verified automatically
before and after every pass alongside TypeChecked, BreakContinueValid,
and NoRedundantBlocks.

Scoping rules implemented:
- Function parameters: in scope for entire function body
- AssignStmt: LHS enters scope after RHS is evaluated
- ForStmt: loop_var and iter_args scoped to body; return_vars enter
  enclosing scope after the loop
- WhileStmt: iter_args scoped to condition and body; return_vars enter
  enclosing scope after the loop
- IfStmt: branch-local definitions do not propagate outward; return_vars
  enter enclosing scope after the if

Closes hw-native-sys#548
…nused includes

The UnrollLoops pass was producing invalid IR after the UseAfterDef
structural property verifier was added: all unrolled iterations used
the original outer variable instead of chaining outputs as inputs to
the next iteration.

Fix by building a shadow_map that identifies AssignStmt targets in the
loop body that shadow outer-scope vars by name, then using carry_map to
chain each iteration's output as the next iteration's input. The final
carry is propagated to subsequent statements (e.g. return) via
pending_subst in VisitStmt_(SeqStmtsPtr). Zero-trip loops are handled
by mapping body def vars back to their outer vars. Nested unroll loops
inside regular for loops are handled via CollectLastDefsByName which
compares pre/post def vars to build the substitution.

Also removes unused includes (function.h, program.h, span.h) from
verify_use_after_def.cpp that were flagged by clang-tidy.

Fixes 6 failing tests in test_unroll_loops_pass.py.
- Move UseAfterDef from GetStructuralProperties() to GetDefaultVerifyProperties()
  so it is checked on explicit request rather than before every pass, fixing
  85 unit test failures caused by manually-constructed test IR that intentionally
  has undefined variables
- Change pointer-keyed unordered_map to std::map in unroll_loops_pass.cpp
  (shadow_map, carry_map, final_carry, carry locals) to fix
  bugprone-nondeterministic-pointer-iteration-order clang-tidy errors
- Add missing #include "pypto/ir/program.h" to verify_use_after_def.cpp
  to fix misc-include-cleaner clang-tidy error
- Update Doxygen comments in ir_property.h and docs (EN + ZH) to reflect
  the new property set assignments
@huxinyuan1215 huxinyuan1215 force-pushed the feat/structural-property-use-after-def branch from ff4e830 to 40d0a22 Compare March 17, 2026 14:00
…perty-use-after-def

# Conflicts:
#	include/pypto/ir/transforms/ir_property.h
#	src/ir/transforms/ir_property.cpp
#	tests/ut/ir/transforms/test_pass_manager.py
#	tests/ut/ir/transforms/test_pass_pipeline.py
- Fix MemRefUpdateMutator in allocate_memory_addr_pass to store new
  Var/IterArg pointers in var_remap_ so subsequent uses map to the
  same new pointer, preventing false use-before-def reports
- Fix UseAfterDefChecker IfStmt handling: in leak mode (no return_vars),
  union both branch scopes into the enclosing scope so variables defined
  in both branches are visible after the if statement
- Update test_verify_ssa_pass scope violation tests to accept either
  UseAfterDef or SSA verifier error messages (both are correct)
- Restore test_verify_no_nested_call_pass to upstream's simpler IR
  that doesn't trigger NormalizedStmtStructure post-verification
- Fix test_pass_manager merge artifact: restore missing pm/result lines
hw-native-sys#548)

Fix the UseAfterDef verifier to correctly handle variables that leak out
of if-branches and for-loops in non-SSA (leak) mode. Previously, the
verifier incorrectly reported use-after-def errors for variables defined
in then-only branches or loop bodies when used after the construct.

Also fix upstream-missed test updates: add explicit memory_space argument
to ir.TileType() calls in test files that were broken after commit 45a3110
moved memory_space from MemRef to TileType. Add memory_space parameter to
IRBuilder.tile_type() helper for consistency.
@Hzfengsy
Copy link
Member

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new UseAfterDef structural property verifier, which is a significant improvement for IR correctness. The changes correctly integrate the new verifier into the build system, documentation, and Python bindings. The LoopUnrollMutator and parse_if_statement logic have been appropriately updated to ensure correct variable scoping and substitution, which is crucial for the UseAfterDef analysis. The addition of memory_space to TileType constructors across various test files ensures consistency and proper type inference. The new unit tests for UseAfterDef cover both valid and invalid scenarios, demonstrating the effectiveness of the verifier. Overall, the changes are well-implemented and enhance the robustness of the IR.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new IR structural verifier (UseAfterDef) to ensure variable uses occur only after a dominating definition, integrating it into the verification pipeline (C++), Python bindings/stubs, docs, and unit tests.

Changes:

  • Introduce IRProperty::UseAfterDef and implement/register UseAfterDefCheck verifier.
  • Expose the new property + error enum in Python bindings/type stubs and document the rule.
  • Add focused unit tests and adjust existing tests affected by the stricter verification.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
tests/ut/ir/transforms/test_verify_use_after_def.py New unit tests for the UseAfterDef verifier (valid/invalid cases + error code/rule name).
tests/ut/ir/transforms/test_verify_ssa_pass.py Relaxed regex expectations to allow new “used before definition” failure mode.
tests/ut/ir/transforms/test_python_printer.py Update TileType construction to include explicit memory_space when printing/round-tripping.
tests/ut/ir/transforms/test_insert_sync.py Update TileType creation sites to pass memory_space.
tests/ut/ir/transforms/test_basic_memory_reuse.py Update TileType construction for new signature/semantics.
tests/ut/ir/memory/test_memref.py Update TileType construction sites to include memory_space.
src/ir/verifier/verify_use_after_def.cpp New C++ verifier implementation (UseAfterDefCheck).
src/ir/verifier/property_verifier_registry.cpp Register the new verifier in the registry.
src/ir/transforms/unroll_loops_pass.cpp Update unroll logic to perform var substitution/carry across unrolled iterations.
src/ir/transforms/ir_property.cpp Add UseAfterDef to stringification, structural props, and default verify set.
src/ir/transforms/allocate_memory_addr_pass.cpp Attempt to cache remapped Vars/IterArgs during MemRef address updates.
python/pypto/pypto_core/passes.pyi Add IRProperty.UseAfterDef and UseAfterDefErrorType to Python stubs.
python/pypto/language/parser/ast_parser.py Fix if/else parsing so else branch doesn’t see then-branch leaked vars in leak mode.
python/pypto/ir/builder.py Extend tile_type(...) builder helper to accept/pass through memory_space.
python/bindings/modules/passes.cpp Bind IRProperty.UseAfterDef and UseAfterDefErrorType to Python.
include/pypto/ir/verifier/verifier.h Declare CreateUseAfterDefPropertyVerifier() factory.
include/pypto/ir/verifier/verification_error.h Define use_after_def::ErrorType (401) and declare ErrorTypeToString.
include/pypto/ir/transforms/ir_property.h Add UseAfterDef to the IRProperty enum and update structural/default property docs.
docs/zh-cn/dev/passes/99-verifier.md Document UseAfterDef rule and add it to structural/default property sets.
docs/en/dev/passes/99-verifier.md Document UseAfterDef rule and add it to structural/default property sets.
CMakeLists.txt Add new verifier source to the build.

Comment on lines +250 to +258
/// Recurses into ForStmt bodies and SeqStmts.
static void CollectLastDefsByName(const StmtPtr& s, std::unordered_map<std::string, VarPtr>& defs) {
if (!s) return;
if (auto assign = std::dynamic_pointer_cast<const AssignStmt>(s)) {
if (assign->var_) defs[assign->var_->name_hint_] = assign->var_;
} else if (auto seq = std::dynamic_pointer_cast<const SeqStmts>(s)) {
for (const auto& child : seq->stmts_) CollectLastDefsByName(child, defs);
} else if (auto for_s = std::dynamic_pointer_cast<const ForStmt>(s)) {
if (for_s->body_) CollectLastDefsByName(for_s->body_, defs);
- `AssignStmt`: LHS variable enters scope after RHS is evaluated
- `ForStmt`: `loop_var` and `iter_args` are in scope inside the loop body only; `return_vars` enter the enclosing scope after the loop
- `WhileStmt`: `iter_args` are in scope for the condition and body; `return_vars` enter the enclosing scope after the loop
- `IfStmt`: definitions inside then/else branches do **not** propagate to the outer scope; `return_vars` enter the enclosing scope after the if
- `AssignStmt`:LHS 变量在 RHS 求值后进入作用域
- `ForStmt`:`loop_var` 和 `iter_args` 仅在循环体内可见;`return_vars` 在循环结束后进入外层作用域
- `WhileStmt`:`iter_args` 在条件和循环体内可见;`return_vars` 在循环结束后进入外层作用域
- `IfStmt`:then/else 分支内的定义**不**传播到外层作用域;`return_vars` 在 if 结束后进入外层作用域
static_cast<int>(use_after_def::ErrorType::USE_BEFORE_DEF), msg.str(),
op->span_);
}
// Leaf node — no children to recurse into.
Comment on lines +104 to +108
auto saved_scope = in_scope_;

// loop_var and iter_args are in scope only inside the body.
if (op->loop_var_) in_scope_.insert(op->loop_var_.get());
for (const auto& iter_arg : op->iter_args_) {
Comment on lines +110 to +131
// Collect body def vars that shadow outer-scope vars by name.
// shadow_map: body_def_var* → outer_var VarPtr (the var it shadows)
std::map<const Var*, VarPtr> shadow_map;
{
std::unordered_map<std::string, const Var*> def_by_name;
std::function<void(const StmtPtr&)> collect_defs = [&](const StmtPtr& s) {
if (!s) return;
if (auto assign = std::dynamic_pointer_cast<const AssignStmt>(s)) {
if (assign->var_) def_by_name[assign->var_->name_hint_] = assign->var_.get();
} else if (auto seq = std::dynamic_pointer_cast<const SeqStmts>(s)) {
for (const auto& child : seq->stmts_) collect_defs(child);
}
};
collect_defs(op->body_);

std::function<void(const ExprPtr&)> collect_uses = [&](const ExprPtr& e) {
if (!e) return;
if (auto var = std::dynamic_pointer_cast<const Var>(e)) {
auto it = def_by_name.find(var->name_hint_);
if (it != def_by_name.end() && var.get() != it->second) {
shadow_map[it->second] = std::const_pointer_cast<Var>(var);
}
Comment on lines +95 to +104
// Check if already remapped (same old pointer seen again).
auto it = var_remap_.find(op.get());
if (it != var_remap_.end()) {
return it->second;
}
TypePtr new_type = UpdateTypeMemRef(op->GetType());
if (new_type != op->GetType()) {
return std::make_shared<Var>(op->name_hint_, new_type, op->span_);
auto new_var = std::make_shared<Var>(op->name_hint_, new_type, op->span_);
var_remap_[op.get()] = new_var;
return new_var;
* - WhileStmt::iter_args_: in scope inside the loop body (including condition)
* - WhileStmt::return_vars_: defined in the enclosing scope after the loop
* - IfStmt::return_vars_: defined in the enclosing scope after the if
* - Definitions inside if/else branches do NOT propagate to the outer scope
Comment on lines +144 to +160
// iter_args are in scope for condition and body.
for (const auto& iter_arg : op->iter_args_) {
if (iter_arg) in_scope_.insert(iter_arg.get());
}

if (op->condition_) VisitExpr(op->condition_);
if (op->body_) VisitStmt(op->body_);

for (const auto& iter_arg : op->iter_args_) {
if (iter_arg) in_scope_.erase(iter_arg.get());
}

// return_vars are defined in the enclosing scope after the loop.
for (const auto& rv : op->return_vars_) {
if (rv) in_scope_.insert(rv.get());
}
}
Comment on lines +125 to +145
std::function<void(const ExprPtr&)> collect_uses = [&](const ExprPtr& e) {
if (!e) return;
if (auto var = std::dynamic_pointer_cast<const Var>(e)) {
auto it = def_by_name.find(var->name_hint_);
if (it != def_by_name.end() && var.get() != it->second) {
shadow_map[it->second] = std::const_pointer_cast<Var>(var);
}
} else if (auto call = std::dynamic_pointer_cast<const Call>(e)) {
for (const auto& arg : call->args_) collect_uses(arg);
}
};
std::function<void(const StmtPtr&)> collect_rhs = [&](const StmtPtr& s) {
if (!s) return;
if (auto assign = std::dynamic_pointer_cast<const AssignStmt>(s)) {
collect_uses(assign->value_);
} else if (auto seq = std::dynamic_pointer_cast<const SeqStmts>(s)) {
for (const auto& child : seq->stmts_) collect_rhs(child);
}
};
collect_rhs(op->body_);
}
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
python/pypto/ir/builder.py (1)

714-721: ⚠️ Potential issue | 🟠 Major

tile_type() introduces a positional-argument breaking change.

By inserting memory_space before span (Line 714-Line 721), previous positional calls that passed span as the 5th argument now bind incorrectly.

Compatibility-safe signature update
     def tile_type(
         self,
         shape: Sequence[int | ir.Expr],
         dtype: DataType,
         memref: ir.MemRef | None = None,
         tile_view: ir.TileView | None = None,
-        memory_space: ir.MemorySpace | None = None,
         span: ir.Span | None = None,
+        *,
+        memory_space: ir.MemorySpace | None = None,
     ) -> ir.TileType:

Also applies to: 746-746

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/pypto/ir/builder.py` around lines 714 - 721, The tile_type() signature
change moved memory_space before span and breaks existing positional calls;
revert the breaking change by making memory_space a keyword-only parameter
(e.g., insert a positional-only separator so span stays as the 5th positional
arg) or else restore the original parameter order so span remains the 5th
positional argument; apply the same fix to the other affected function(s)
mentioned around line 746 to preserve backward compatibility.
🧹 Nitpick comments (1)
python/pypto/ir/builder.py (1)

730-730: Docstring contract and runtime behavior are inconsistent.

Line 730 says memory_space is required with memref, but the method currently accepts memref with memory_space=None. Either enforce it in code or soften the docstring.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/pypto/ir/builder.py` at line 730, The docstring states memory_space is
required when memref is provided but the method currently allows
memory_space=None; modify the function that accepts the parameters memref and
memory_space to validate this contract at runtime by adding a guard like: if
memref is not None and memory_space is None: raise ValueError("memory_space is
required when memref is provided"), or alternatively update the docstring to
indicate memory_space is optional—ensure the change is applied in the
function/method that declares the memref and memory_space parameters so callers
get consistent behavior and messaging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/en/dev/passes/99-verifier.md`:
- Around line 121-128: The docs state that IfStmt branch-local definitions do
not propagate, but the UseAfterDef test
test_valid_then_only_leak_visible_after_if and the verifier accept a then-only
definition leaking out; update the IfStmt scoping rule text to reflect the
implemented contract: indicate that variables defined in a then/else branch may
propagate to the enclosing scope (or specifically that then-only definitions are
visible after the IfStmt) and clarify how return_vars behave, so the
documentation for IfStmt matches the verifier behavior (refer to IfStmt,
UseAfterDef, and test_valid_then_only_leak_visible_after_if to validate
wording).

In `@docs/zh-cn/dev/passes/99-verifier.md`:
- Around line 121-128: 文档中关于 IfStmt 的作用域规则描述不匹配测试实现:当前说“then/else
分支内的定义不传播到外层”,但 tests/ut/ir/transforms/test_verify_use_after_def.py 中的
test_valid_then_only_leak_visible_after_if 将“只有 then 分支定义的变量在 if 结束后可见”视为合法;请将
docs/zh-cn/dev/passes/99-verifier.md 中 IfStmt 条目改为与实现/英文文档保持一致,明确说明哪些
return_vars/分支内定义会在 if 结束后进入外层作用域(例如 then-only leak 情形),使中文文档与
test_valid_then_only_leak_visible_after_if 和 docs/en/dev 的语义一致。

In `@src/ir/transforms/ir_property.cpp`:
- Around line 115-124: GetStructuralProperties now includes
IRProperty::UseAfterDef but GetVerifiedProperties still omits it, causing
structural-start verification to skip UseAfterDef; update the implementation of
GetVerifiedProperties to include IRProperty::UseAfterDef in its returned
IRPropertySet (so the intersection
GetStructuralProperties().Intersection(GetVerifiedProperties()) will include
UseAfterDef), referencing the existing functions GetStructuralProperties,
GetDefaultVerifyProperties and GetVerifiedProperties to locate where to add
IRProperty::UseAfterDef.

In `@src/ir/transforms/unroll_loops_pass.cpp`:
- Around line 156-164: The carry mapping is captured from def_var_map computed
from DeepClone before recursive rewriting, so nested VisitStmt may rewrite those
defs and leave carry_map pointing to stale vars; move the carry derivation to
after processing the cloned body (i.e., call VisitStmt(cloned_body) first and
then obtain the final def remap to update carry_map), or change VisitStmt to
return both the rewritten stmt and its def remap and use that remap to populate
carry_map (reference symbols: DeepClone, cloned_body, def_var_map, VisitStmt,
carry_map, shadow_map, unrolled).

In `@src/ir/verifier/verify_use_after_def.cpp`:
- Around line 66-77: The VisitVarLike_ implementation flags a use-before-def but
never continues the IR traversal, so nested uses in var-like metadata are not
visited; update VisitVarLike_ to still call the base traversal
(IRVisitor::VisitVarLike_(op)) after performing the in_scope_ check and
recording the diagnostic (i.e., keep the scope check and diagnostics as-is, then
invoke IRVisitor::VisitVarLike_(op) to recurse into any metadata/children),
ensuring you reference the VisitVarLike_ method and IRVisitor::VisitVarLike_
when making the change.
- Around line 134-159: The VisitStmt_(const WhileStmtPtr& op) leaves loop-local
definitions from op->body_ in in_scope_ when op->return_vars_ are inserted, so
restore the original scope before adding the return_vars_; capture the in_scope_
state (or remove only loop-local symbols) after visiting op->body_ and before
inserting op->return_vars_, then insert the return_vars_ into the
restored/enclosing scope so loop-local defs are not visible after the loop;
refer to VisitStmt_(const WhileStmtPtr& op), in_scope_, op->iter_args_,
op->condition_, op->body_, and op->return_vars_ to locate where to
snapshot/restore or selectively erase loop-local entries.

In `@tests/ut/ir/transforms/test_verify_ssa_pass.py`:
- Line 307: Update the pytest.raises calls that pass regex patterns as normal
strings to use raw-string literals so backslashes and regex metacharacters are
not interpreted by Python string escapes; specifically change the match argument
in the pytest.raises call that currently reads match="used before
definition|used outside its defining scope" (and the similar patterns at the
other occurrences) to use a raw string (r"...") in the test_verify_ssa_pass.py
tests (look for the pytest.raises(...) occurrences around the failing
assertions).

---

Outside diff comments:
In `@python/pypto/ir/builder.py`:
- Around line 714-721: The tile_type() signature change moved memory_space
before span and breaks existing positional calls; revert the breaking change by
making memory_space a keyword-only parameter (e.g., insert a positional-only
separator so span stays as the 5th positional arg) or else restore the original
parameter order so span remains the 5th positional argument; apply the same fix
to the other affected function(s) mentioned around line 746 to preserve backward
compatibility.

---

Nitpick comments:
In `@python/pypto/ir/builder.py`:
- Line 730: The docstring states memory_space is required when memref is
provided but the method currently allows memory_space=None; modify the function
that accepts the parameters memref and memory_space to validate this contract at
runtime by adding a guard like: if memref is not None and memory_space is None:
raise ValueError("memory_space is required when memref is provided"), or
alternatively update the docstring to indicate memory_space is optional—ensure
the change is applied in the function/method that declares the memref and
memory_space parameters so callers get consistent behavior and messaging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 60549225-8658-44d2-8bc1-2473d6b8b3e7

📥 Commits

Reviewing files that changed from the base of the PR and between 264124a and afa1201.

📒 Files selected for processing (21)
  • CMakeLists.txt
  • docs/en/dev/passes/99-verifier.md
  • docs/zh-cn/dev/passes/99-verifier.md
  • include/pypto/ir/transforms/ir_property.h
  • include/pypto/ir/verifier/verification_error.h
  • include/pypto/ir/verifier/verifier.h
  • python/bindings/modules/passes.cpp
  • python/pypto/ir/builder.py
  • python/pypto/language/parser/ast_parser.py
  • python/pypto/pypto_core/passes.pyi
  • src/ir/transforms/allocate_memory_addr_pass.cpp
  • src/ir/transforms/ir_property.cpp
  • src/ir/transforms/unroll_loops_pass.cpp
  • src/ir/verifier/property_verifier_registry.cpp
  • src/ir/verifier/verify_use_after_def.cpp
  • tests/ut/ir/memory/test_memref.py
  • tests/ut/ir/transforms/test_basic_memory_reuse.py
  • tests/ut/ir/transforms/test_insert_sync.py
  • tests/ut/ir/transforms/test_python_printer.py
  • tests/ut/ir/transforms/test_verify_ssa_pass.py
  • tests/ut/ir/transforms/test_verify_use_after_def.py
✅ Files skipped from review due to trivial changes (1)
  • src/ir/transforms/allocate_memory_addr_pass.cpp
🚧 Files skipped from review as they are similar to previous changes (5)
  • CMakeLists.txt
  • python/bindings/modules/passes.cpp
  • include/pypto/ir/verifier/verification_error.h
  • include/pypto/ir/transforms/ir_property.h
  • include/pypto/ir/verifier/verifier.h

Comment on lines +121 to +128
**Scoping rules:**

- Function parameters are in scope for the entire function body
- `AssignStmt`: LHS variable enters scope after RHS is evaluated
- `ForStmt`: `loop_var` and `iter_args` are in scope inside the loop body only; `return_vars` enter the enclosing scope after the loop
- `WhileStmt`: `iter_args` are in scope for the condition and body; `return_vars` enter the enclosing scope after the loop
- `IfStmt`: definitions inside then/else branches do **not** propagate to the outer scope; `return_vars` enter the enclosing scope after the if

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

IfStmt scoping rule documentation conflicts with new UseAfterDef test behavior.

Line 127 says branch-local definitions do not propagate, but test_valid_then_only_leak_visible_after_if in tests/ut/ir/transforms/test_verify_use_after_def.py (Lines 188-210) expects then-only definition leakage to be accepted by UseAfterDef. Please reconcile the documented rule with the implemented/tested contract.

As per coding guidelines: English docs in docs/en/dev/ are the ground truth and should be updated when behavior changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/en/dev/passes/99-verifier.md` around lines 121 - 128, The docs state
that IfStmt branch-local definitions do not propagate, but the UseAfterDef test
test_valid_then_only_leak_visible_after_if and the verifier accept a then-only
definition leaking out; update the IfStmt scoping rule text to reflect the
implemented contract: indicate that variables defined in a then/else branch may
propagate to the enclosing scope (or specifically that then-only definitions are
visible after the IfStmt) and clarify how return_vars behave, so the
documentation for IfStmt matches the verifier behavior (refer to IfStmt,
UseAfterDef, and test_valid_then_only_leak_visible_after_if to validate
wording).

Comment on lines +121 to +128
**作用域规则:**

- 函数参数在整个函数体内可见
- `AssignStmt`:LHS 变量在 RHS 求值后进入作用域
- `ForStmt`:`loop_var` 和 `iter_args` 仅在循环体内可见;`return_vars` 在循环结束后进入外层作用域
- `WhileStmt`:`iter_args` 在条件和循环体内可见;`return_vars` 在循环结束后进入外层作用域
- `IfStmt`:then/else 分支内的定义**不**传播到外层作用域;`return_vars` 在 if 结束后进入外层作用域

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

IfStmt 作用域规则描述与新用例语义不一致。

Line 127 写明“then/else 内定义不传播到外层”,但 tests/ut/ir/transforms/test_verify_use_after_def.pytest_valid_then_only_leak_visible_after_if(Lines 188-210)明确将该场景视为 UseAfterDef 合法。建议统一文档与实现/测试语义,避免读者误解。

As per coding guidelines: docs/zh-cn/dev/ must stay aligned with English docs when docs/en/dev/ changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/zh-cn/dev/passes/99-verifier.md` around lines 121 - 128, 文档中关于 IfStmt
的作用域规则描述不匹配测试实现:当前说“then/else 分支内的定义不传播到外层”,但
tests/ut/ir/transforms/test_verify_use_after_def.py 中的
test_valid_then_only_leak_visible_after_if 将“只有 then 分支定义的变量在 if 结束后可见”视为合法;请将
docs/zh-cn/dev/passes/99-verifier.md 中 IfStmt 条目改为与实现/英文文档保持一致,明确说明哪些
return_vars/分支内定义会在 if 结束后进入外层作用域(例如 then-only leak 情形),使中文文档与
test_valid_then_only_leak_visible_after_if 和 docs/en/dev 的语义一致。

Comment on lines 115 to +124
const IRPropertySet& GetStructuralProperties() {
static const IRPropertySet props{IRProperty::TypeChecked, IRProperty::BreakContinueValid,
IRProperty::NoRedundantBlocks};
IRProperty::NoRedundantBlocks, IRProperty::UseAfterDef};
return props;
}

const IRPropertySet& GetDefaultVerifyProperties() {
static const IRPropertySet props{IRProperty::SSAForm, IRProperty::TypeChecked, IRProperty::NoNestedCalls,
IRProperty::BreakContinueValid, IRProperty::NoRedundantBlocks};
static const IRPropertySet props{IRProperty::SSAForm, IRProperty::TypeChecked,
IRProperty::NoNestedCalls, IRProperty::BreakContinueValid,
IRProperty::NoRedundantBlocks, IRProperty::UseAfterDef};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

UseAfterDef is structural here, but still not in auto-verified set.

Line 115-Line 124 adds UseAfterDef to structural/default verification sets, but GetVerifiedProperties() still omits it. In the current pipeline behavior, that means structural-start verification can skip UseAfterDef.

Suggested fix (outside this hunk)
 const IRPropertySet& GetVerifiedProperties() {
   static const IRPropertySet props{IRProperty::SSAForm, IRProperty::TypeChecked,
                                    IRProperty::AllocatedMemoryAddr, IRProperty::BreakContinueValid,
-                                   IRProperty::NoRedundantBlocks};
+                                   IRProperty::NoRedundantBlocks, IRProperty::UseAfterDef};
   return props;
 }
Based on learnings: structural properties are checked at pipeline start via `GetStructuralProperties().Intersection(GetVerifiedProperties())` in this codebase.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ir/transforms/ir_property.cpp` around lines 115 - 124,
GetStructuralProperties now includes IRProperty::UseAfterDef but
GetVerifiedProperties still omits it, causing structural-start verification to
skip UseAfterDef; update the implementation of GetVerifiedProperties to include
IRProperty::UseAfterDef in its returned IRPropertySet (so the intersection
GetStructuralProperties().Intersection(GetVerifiedProperties()) will include
UseAfterDef), referencing the existing functions GetStructuralProperties,
GetDefaultVerifyProperties and GetVerifiedProperties to locate where to add
IRProperty::UseAfterDef.

Comment on lines +156 to 164
auto [cloned_body, def_var_map] = DeepClone(op->body_, sub_map);
// Update carry: outer_var → this iteration's fresh output var
for (const auto& [body_def, outer_var] : shadow_map) {
auto it = def_var_map.find(body_def);
if (it != def_var_map.end()) {
carry_map[outer_var.get()] = it->second;
}
}
unrolled.push_back(VisitStmt(cloned_body));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Carry state is captured before recursive mutation, which can leave stale carried vars.

Line 156 computes def_var_map from the raw clone and Line 161 stores carry from it, but Line 164 then runs VisitStmt(cloned_body), which may rewrite defs (e.g., nested unroll/substitution). Next iterations can therefore substitute with a var that is no longer the final emitted def. Carry should be derived from the post-VisitStmt body, or VisitStmt should return a def remap alongside the new stmt.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ir/transforms/unroll_loops_pass.cpp` around lines 156 - 164, The carry
mapping is captured from def_var_map computed from DeepClone before recursive
rewriting, so nested VisitStmt may rewrite those defs and leave carry_map
pointing to stale vars; move the carry derivation to after processing the cloned
body (i.e., call VisitStmt(cloned_body) first and then obtain the final def
remap to update carry_map), or change VisitStmt to return both the rewritten
stmt and its def remap and use that remap to populate carry_map (reference
symbols: DeepClone, cloned_body, def_var_map, VisitStmt, carry_map, shadow_map,
unrolled).

Comment on lines +66 to +77
void VisitVarLike_(const VarPtr& op) override {
if (!op) return;
if (in_scope_.find(op.get()) == in_scope_.end()) {
std::ostringstream msg;
msg << "Variable '" << op->name_hint_ << "' used before definition"
<< " in function '" << func_name_ << "'";
diagnostics_.emplace_back(DiagnosticSeverity::Error, "UseAfterDefCheck",
static_cast<int>(use_after_def::ErrorType::USE_BEFORE_DEF), msg.str(),
op->span_);
}
// Leaf node — no children to recurse into.
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

VisitVarLike_ should continue base traversal after the scope check.

Line 66-Line 77 records the diagnostic but never calls IRVisitor::VisitVarLike_(op), so nested uses reachable through var-like metadata aren’t traversed.

Suggested fix
   void VisitVarLike_(const VarPtr& op) override {
     if (!op) return;
     if (in_scope_.find(op.get()) == in_scope_.end()) {
       std::ostringstream msg;
       msg << "Variable '" << op->name_hint_ << "' used before definition"
           << " in function '" << func_name_ << "'";
       diagnostics_.emplace_back(DiagnosticSeverity::Error, "UseAfterDefCheck",
                                 static_cast<int>(use_after_def::ErrorType::USE_BEFORE_DEF), msg.str(),
                                 op->span_);
     }
-    // Leaf node — no children to recurse into.
+    IRVisitor::VisitVarLike_(op);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void VisitVarLike_(const VarPtr& op) override {
if (!op) return;
if (in_scope_.find(op.get()) == in_scope_.end()) {
std::ostringstream msg;
msg << "Variable '" << op->name_hint_ << "' used before definition"
<< " in function '" << func_name_ << "'";
diagnostics_.emplace_back(DiagnosticSeverity::Error, "UseAfterDefCheck",
static_cast<int>(use_after_def::ErrorType::USE_BEFORE_DEF), msg.str(),
op->span_);
}
// Leaf node — no children to recurse into.
}
void VisitVarLike_(const VarPtr& op) override {
if (!op) return;
if (in_scope_.find(op.get()) == in_scope_.end()) {
std::ostringstream msg;
msg << "Variable '" << op->name_hint_ << "' used before definition"
<< " in function '" << func_name_ << "'";
diagnostics_.emplace_back(DiagnosticSeverity::Error, "UseAfterDefCheck",
static_cast<int>(use_after_def::ErrorType::USE_BEFORE_DEF), msg.str(),
op->span_);
}
IRVisitor::VisitVarLike_(op);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ir/verifier/verify_use_after_def.cpp` around lines 66 - 77, The
VisitVarLike_ implementation flags a use-before-def but never continues the IR
traversal, so nested uses in var-like metadata are not visited; update
VisitVarLike_ to still call the base traversal (IRVisitor::VisitVarLike_(op))
after performing the in_scope_ check and recording the diagnostic (i.e., keep
the scope check and diagnostics as-is, then invoke IRVisitor::VisitVarLike_(op)
to recurse into any metadata/children), ensuring you reference the VisitVarLike_
method and IRVisitor::VisitVarLike_ when making the change.

Comment on lines +134 to +159
void VisitStmt_(const WhileStmtPtr& op) override {
if (!op) return;

// IterArg initial values are evaluated in the outer scope.
for (const auto& iter_arg : op->iter_args_) {
if (iter_arg && iter_arg->initValue_) {
VisitExpr(iter_arg->initValue_);
}
}

// iter_args are in scope for condition and body.
for (const auto& iter_arg : op->iter_args_) {
if (iter_arg) in_scope_.insert(iter_arg.get());
}

if (op->condition_) VisitExpr(op->condition_);
if (op->body_) VisitStmt(op->body_);

for (const auto& iter_arg : op->iter_args_) {
if (iter_arg) in_scope_.erase(iter_arg.get());
}

// return_vars are defined in the enclosing scope after the loop.
for (const auto& rv : op->return_vars_) {
if (rv) in_scope_.insert(rv.get());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

WhileStmt scope restoration is incomplete in SSA mode.

In Line 134-Line 159, defs created inside op->body_ remain in in_scope_ even when return_vars_ is present. That makes loop-local defs incorrectly visible after the while loop.

Suggested fix
   void VisitStmt_(const WhileStmtPtr& op) override {
     if (!op) return;
+    auto saved_scope = in_scope_;

     // IterArg initial values are evaluated in the outer scope.
     for (const auto& iter_arg : op->iter_args_) {
       if (iter_arg && iter_arg->initValue_) {
         VisitExpr(iter_arg->initValue_);
       }
     }

     // iter_args are in scope for condition and body.
     for (const auto& iter_arg : op->iter_args_) {
       if (iter_arg) in_scope_.insert(iter_arg.get());
     }

     if (op->condition_) VisitExpr(op->condition_);
     if (op->body_) VisitStmt(op->body_);

     for (const auto& iter_arg : op->iter_args_) {
       if (iter_arg) in_scope_.erase(iter_arg.get());
     }

-    // return_vars are defined in the enclosing scope after the loop.
-    for (const auto& rv : op->return_vars_) {
-      if (rv) in_scope_.insert(rv.get());
-    }
+    if (!op->return_vars_.empty()) {
+      // SSA mode: only return_vars are visible after the loop.
+      in_scope_ = saved_scope;
+      for (const auto& rv : op->return_vars_) {
+        if (rv) in_scope_.insert(rv.get());
+      }
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ir/verifier/verify_use_after_def.cpp` around lines 134 - 159, The
VisitStmt_(const WhileStmtPtr& op) leaves loop-local definitions from op->body_
in in_scope_ when op->return_vars_ are inserted, so restore the original scope
before adding the return_vars_; capture the in_scope_ state (or remove only
loop-local symbols) after visiting op->body_ and before inserting
op->return_vars_, then insert the return_vars_ into the restored/enclosing scope
so loop-local defs are not visible after the loop; refer to VisitStmt_(const
WhileStmtPtr& op), in_scope_, op->iter_args_, op->condition_, op->body_, and
op->return_vars_ to locate where to snapshot/restore or selectively erase
loop-local entries.


verify_pass = passes.run_verifier()
with pytest.raises(Exception, match="used outside its defining scope"):
with pytest.raises(Exception, match="used before definition|used outside its defining scope"):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use raw-string regex literals in pytest.raises(..., match=...).

Line 307, Line 341, Line 372, and Line 396 use regex metacharacters in normal strings, which triggers RUF043 and is easy to avoid with raw strings.

Suggested fix
-        with pytest.raises(Exception, match="used before definition|used outside its defining scope"):
+        with pytest.raises(Exception, match=r"used before definition|used outside its defining scope"):
             verify_pass(program)
...
-        with pytest.raises(Exception, match="used before definition|used outside its defining scope"):
+        with pytest.raises(Exception, match=r"used before definition|used outside its defining scope"):
             verify_pass(program)
...
-        with pytest.raises(Exception, match="used before definition|used outside its defining scope"):
+        with pytest.raises(Exception, match=r"used before definition|used outside its defining scope"):
             verify_pass(program)
...
-        with pytest.raises(Exception, match="used before definition|used outside its defining scope"):
+        with pytest.raises(Exception, match=r"used before definition|used outside its defining scope"):
             verify_pass(program)

Also applies to: 341-341, 372-372, 396-396

🧰 Tools
🪛 Ruff (0.15.6)

[warning] 307-307: Pattern passed to match= contains metacharacters but is neither escaped nor raw

(RUF043)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/ut/ir/transforms/test_verify_ssa_pass.py` at line 307, Update the
pytest.raises calls that pass regex patterns as normal strings to use raw-string
literals so backslashes and regex metacharacters are not interpreted by Python
string escapes; specifically change the match argument in the pytest.raises call
that currently reads match="used before definition|used outside its defining
scope" (and the similar patterns at the other occurrences) to use a raw string
(r"...") in the test_verify_ssa_pass.py tests (look for the pytest.raises(...)
occurrences around the failing assertions).

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.

3 participants