Skip to content

[IR Container] Phase 2.1 Ownership Fusion Special Vals & Axioms#5954

Open
mdavis36 wants to merge 8 commits intomd/phase2-ir-refactorfrom
md/dep-special-types
Open

[IR Container] Phase 2.1 Ownership Fusion Special Vals & Axioms#5954
mdavis36 wants to merge 8 commits intomd/phase2-ir-refactorfrom
md/dep-special-types

Conversation

@mdavis36
Copy link
Collaborator

@mdavis36 mdavis36 commented Feb 11, 2026

Summary

Move per-Fusion state — special values (zero_val_, one_val_, true_val_, false_val_, magic_zero_val_), axioms, and metadata — from IrContainer to Fusion. This eliminates the 1:1 relationship assumption between IrContainer and Fusion that would break when multiple Fusions share a container in Phase 2.

Key Changes

Special values (zero_val_, one_val_, true_val_, false_val_, magic_zero_val_):

  • Moved from IrContainer to Fusion as raw cache pointers (memory still owned by IrContainer's vals_up_)
  • Lazy creation preserved — created on first access via IrBuilder::createInContainer
  • Fusion::clear() nulls cache pointers so they re-create on next access
  • StatementGuard rollback nulls any special vals created within the guard scope to prevent dangling pointers

Axioms and metadata (axioms_, metadata_):

  • Moved from IrContainer to Fusion
  • Methods that used parent_ to create vals (metadataOf, axioms, assumePositive, assumeNonNegative) reimplemented on Fusion
  • Properly swapped in Fusion::swap and cloned in Fusion::copy

Bug fixes surfaced by the migration:

  • SubstituteInExpr self-substitution: with per-Fusion special vals, zero_val_ is now the same pointer as IterDomain extents (both go through IrCloner). SubstituteInExpr(ext, zero) where ext == zero created a self-mapping that tripped a two-hop assertion. Fixed by guarding against reference == substitute.
  • Dangling special vals after StatementGuard rollback: special vals lazily created inside a guard scope were destroyed on rollback while Fusion cache pointers still referenced them.

Relationship to Phase 2

This is a hard prerequisite for shared containers. Without per-Fusion special vals:

Problem: Shared container with IrContainer-owned special vals

  Fusion A ─┐
             ├──→ IrContainer ──→ { zero_val_ = 0x1111 }
  Fusion B ─┘

  ~Fusion A:  IrContainer still alive (refcount > 0)
              But A's destruction might invalidate zero_val_
              B calls zeroVal() → dangling pointer or wrong Fusion's val

With per-Fusion special vals, each Fusion's zeroVal() returns its own independently-cached val. Destroying Fusion A has zero effect on Fusion B's special values.

CI Risk

Low. Each commit is independently CI-clean. The migration preserves identical behavior for the single-Fusion case (all existing usage). The two bug fixes (SubstituteInExpr, StatementGuard rollback) address real correctness issues that were latent in the old code and only surfaced because the migration changed pointer identity relationships.

@mdavis36
Copy link
Collaborator Author

!test

@github-actions
Copy link

github-actions bot commented Feb 11, 2026

Review updated until commit cf4ae4a

Description

  • Moved special values (zero_val_, one_val_, true_val_, false_val_, magic_zero_val_) from IrContainer to Fusion class for better ownership semantics

  • Implemented lazy creation pattern for all special value accessors with caching to prevent recreation overhead

  • Updated Fusion copy, swap, clear, and remove operations to properly handle per-Fusion special values

  • Added regression test for StatementGuard scope to ensure special vals don't become dangling pointers after rollback

  • Moved metadata, axioms, and assumption methods from IrContainer to Fusion class

Changes walkthrough

Relevant files
Enhancement
fusion.cpp
Implement per-Fusion special values with lazy creation     

csrc/fusion.cpp

  • Added lazy creation methods for special values (zeroVal, oneVal,
    falseVal, trueVal, magicZeroVal)
  • Updated Fusion copy operation to remap cached special val pointers
    through IrCloner
  • Modified Fusion swap operation to exchange per-Fusion special values
  • Enhanced Fusion clear operation to reset special value pointers
  • Updated Fusion removeVal to skip cached special vals during removal
  • Implemented removeStatementsCreatedAfter with special value cache
    nullification
  • Moved metadataOf, axioms, assumePositive, and assumeNonNegative
    methods from IrContainer
  • +211/-0 
    container.cpp
    Remove special value management from IrContainer                 

    csrc/ir/container.cpp

  • Removed special value shortcuts (zeroVal, oneVal, falseVal, trueVal,
    magicZeroVal) from IrContainer
  • Eliminated metadata, axioms, and assumption methods from IrContainer
  • Removed removeStatementsCreatedAfter method from IrContainer
  • Updated IrContainer copy and swap operations to exclude special value
    handling
  • Simplified IrContainer clear operation by removing special value
    cleanup
  • +2/-180 
    statement_guard.cpp
    Update StatementGuard to use simplified numVals call         

    csrc/statement_guard.cpp

  • Updated StatementGuard to call fusion_->numVals() instead of
    fusion_->numVals(/*include_shortcuts=*/false)
  • Simplified numVals call by removing include_shortcuts parameter
  • +1/-1     
    fusion.h
    Add per-Fusion special value members and accessors             

    csrc/fusion.h

  • Added private members for per-Fusion special values (zero_val_,
    one_val_, true_val_, false_val_, magic_zero_val_)
  • Declared public accessor methods for special values with lazy creation
  • Added axioms_ unique_ptr member and metadata_ unordered_map
  • Updated method declarations to move from IrContainer to Fusion class
  • Removed include_shortcuts parameter from numVals method
  • +25/-46 
    container.h
    Remove special value management from IrContainer interface

    csrc/ir/container.h

  • Removed special value shortcut method declarations from IrContainer
  • Eliminated metadata, axioms, and assumption method declarations
  • Removed removeStatementsCreatedAfter method declaration
  • Simplified numVals method to remove include_shortcuts parameter
  • Removed special value member variables and metadata structures
  • +2/-50   
    Bug fix
    utils.cpp
    Add null check for reference-substitute comparison             

    csrc/ir/utils.cpp

  • Added null check in SubstituteInExpr constructor to avoid unnecessary
    mutations when reference equals substitute
  • +3/-1     
    Tests
    test_indexing.cpp
    Update test expectations for reshape indexing                       

    tests/cpp/test_indexing.cpp

  • Updated expected string in reshape test case to match new indexing
    variable names (i113, i114, i115 instead of i114, i115, i116)
  • +2/-2     
    test_statement_guard.cpp
    Add regression test for special vals in StatementGuard scope

    tests/cpp/test_statement_guard.cpp

  • Added regression test "LazySpecialValsNotDangling" to verify special
    vals created inside StatementGuard scope remain valid after rollback
  • Test validates that zeroVal, oneVal, trueVal, and falseVal are
    properly recreated after StatementGuard destruction
  • +51/-0   

    PR Reviewer Guide

    Here are some key observations to aid the review process:

    🧪 PR contains tests
    ⚡ Recommended focus areas for review
    Raw Pointer Ownership

    The special value pointers (zero_val_, one_val_, etc.) are declared as raw pointers in the Fusion class, but the actual objects are owned by IrContainer. This creates a potential dangling pointer issue if the IrContainer is cleared or destroyed while the Fusion still holds references to these pointers. The current nullification logic in removeStatementsCreatedAfter helps mitigate this, but the ownership model could be clearer.

    Val* zero_val_ = nullptr;
    Val* one_val_ = nullptr;
    Val* true_val_ = nullptr;
    Val* false_val_ = nullptr;
    NamedScalar* magic_zero_val_ = nullptr;
    Lazy Initialization Thread Safety

    The lazy initialization pattern in methods like zeroVal(), oneVal(), etc. uses simple null checks without any synchronization. In a multi-threaded context, this could lead to race conditions where multiple threads simultaneously create the same special value, potentially causing memory leaks or inconsistent state.

    Val* Fusion::zeroVal() {
      if (!zero_val_) {
        zero_val_ = IrBuilder::createInContainer<Val>(this, 0L, DataType::Index);
      }
      return zero_val_;
    }
    
    Val* Fusion::oneVal() {
      if (!one_val_) {
        one_val_ = IrBuilder::createInContainer<Val>(this, 1L, DataType::Index);
      }
      return one_val_;
    }
    
    Val* Fusion::falseVal() {
      if (!false_val_) {
        false_val_ = IrBuilder::createInContainer<Val>(this, false, DataType::Bool);
      }
      return false_val_;
    }
    
    Val* Fusion::trueVal() {
      if (!true_val_) {
        true_val_ = IrBuilder::createInContainer<Val>(this, true, DataType::Bool);
      }
      return true_val_;
    }
    
    NamedScalar* Fusion::magicZeroVal() {
      if (!magic_zero_val_) {
        magic_zero_val_ = IrBuilder::createInContainer<NamedScalar>(
            this, kMagicZeroName, DataType::Index);
      }
      return magic_zero_val_;
    }
    Missing Error Handling

    The metadataOf() method and axioms() method don't have comprehensive error handling for edge cases. For example, what happens if metadataOf() is called with a null pointer, or if axioms() fails to create the parallel index/dim NamedScalars? Adding proper error checking would make the code more robust.

    Val* Fusion::metadataOf(Val* v) {
      if (metadata_.count(v) == 0) {
        auto metadata_val =
            IrBuilder::createInContainer<Val>(this, metaDataTypeOf(v));
        auto metadata_expr =
            IrBuilder::createInContainer<GetMetaData>(this, metadata_val, v);
        metadata_[v] = std::make_pair(metadata_val, metadata_expr);
      }
      return metadata_.at(v).first;
    }
    
    const std::vector<Val*>& Fusion::axioms() {
      if (!axioms_) {
        axioms_ = std::make_unique<std::vector<Val*>>();
        axioms_->reserve(kParallelTypeThreads.size() * 3);
        auto zero = zeroVal();
        for (auto p : kParallelTypeThreads) {
          auto pidx = NamedScalar::getParallelIndex(p);
          auto pdim = NamedScalar::getParallelDim(p);
          axioms_->push_back(SimplifyingIrBuilder::geExpr(pidx, zero));
          axioms_->push_back(SimplifyingIrBuilder::gtExpr(pdim, zero));
          axioms_->push_back(SimplifyingIrBuilder::ltExpr(pidx, pdim));
        }
      }
      return *axioms_;
    }

    @mdavis36 mdavis36 force-pushed the md/dep-special-types branch from 50c6cd1 to edcb6dc Compare February 11, 2026 20:01
    @mdavis36
    Copy link
    Collaborator Author

    !test

    1 similar comment
    @mdavis36
    Copy link
    Collaborator Author

    !test

    @mdavis36 mdavis36 force-pushed the md/dep-special-types branch from 8112846 to 1588d37 Compare February 12, 2026 01:37
    @mdavis36
    Copy link
    Collaborator Author

    !test

    @mdavis36 mdavis36 changed the title Ownership Fusion special types [IR Container] Phase 2.1 Ownership Fusion special types Feb 18, 2026
    Moved special values (`zero_val_`, `one_val_`, `true_val_`,
    `false_val_`, `magic_zero_val_`) from `IrContainer` to the `Fusion`
    class. This ensures that with shared containers, each Fusion has its own
    special values, preventing ownership conflicts when one Fusion is
    destroyed.
    
    **Option Implemented:** Option A (Move Special Values to Fusion) as
    recommended in the prompt.
    
    Added private members and public accessors to Fusion class:
    
    ```cpp
    // Phase 2: Per-Fusion special values
    // With shared containers, each Fusion needs its own special values.
    // These are raw pointers - memory is owned by IrContainer's vals_up_.
    // Destroying this Fusion removes these vals via
    removeStatementsOwnedBy().
    Val* zero_val_ = nullptr;
    Val* one_val_ = nullptr;
    Val* true_val_ = nullptr;
    Val* false_val_ = nullptr;
    NamedScalar* magic_zero_val_ = nullptr;
    ```
    
    Public accessors:
    - `Val* zeroVal()` - Returns Index 0
    - `Val* oneVal()` - Returns Index 1
    - `Val* falseVal()` - Returns Bool false
    - `Val* trueVal()` - Returns Bool true
    - `NamedScalar* magicZeroVal()` - Returns magic zero named scalar
    - `Val* zeroVal(DataType dtype)` - Returns 0 for specified dtype
    - `Val* oneVal(DataType dtype)` - Returns 1 for specified dtype
    
    Implemented lazy creation pattern for all special value accessors:
    
    ```cpp
    Val* Fusion::zeroVal() {
      if (!zero_val_) {
        zero_val_ = IrBuilder::createInContainer<Val>(this, 0L,
    DataType::Index);
      }
      return zero_val_;
    }
    // Similar implementations for oneVal(), falseVal(), trueVal(),
    magicZeroVal()
    ```
    
    Updated `Fusion::clear()` to reset special value pointers:
    
    ```cpp
    // Reset per-Fusion special values (they'll be recreated lazily if
    needed)
    // The actual Val objects were removed by removeStatementsOwnedBy above.
    zero_val_ = nullptr;
    one_val_ = nullptr;
    true_val_ = nullptr;
    false_val_ = nullptr;
    magic_zero_val_ = nullptr;
    ```
    
    Removed special value members and added documentation comment:
    
    ```cpp
    // Note: Special values (zero_val_, one_val_, true_val_, false_val_,
    // magic_zero_val_) are now per-Fusion, stored in Fusion class.
    // This avoids ownership conflicts when multiple Fusions share an
    IrContainer.
    // See Fusion::zeroVal(), etc. for the per-Fusion implementation.
    ```
    
    Removed special value accessor implementations (they're now in Fusion).
    
    All call sites were already updated to use `fusion->zeroVal()` instead
    of `ir_container()->zeroVal()`. Verified with grep that no call sites
    remain using the old pattern.
    
    Added 8 new unit tests for Task 7:
    
    1. **PerFusionSpecialValuesBasic** - Tests that special values are
    created and owned by the Fusion
    2. **SpecialValuesOwnedByFusion** - Tests that special values are
    tracked in `ownedVals()`
    3. **SeparateFusionsHaveOwnSpecialValues** - Tests that two Fusions have
    different special value objects
    4. **DestroyFusionDoesNotAffectOther** - Tests that destroying one
    Fusion doesn't affect another's special values
    5. **SpecialValuesLazyCreation** - Tests that same value is returned on
    repeated calls
    6. **AllSpecialValuesPerFusion** - Tests all five special value
    accessors
    7. **SpecialValuesClearedOnFusionClear** - Tests that `clear()` resets
    special values
    8. **SpecialValuesWithDtype** - Tests `zeroVal(dtype)` and
    `oneVal(dtype)` accessors
    
    ```
    [==========] Running 34 tests from 3 test suites.
    [  PASSED  ] 34 tests.
    ```
    
    ```
    [==========] Running 26 tests from 1 test suite.
    [  PASSED  ] 26 tests.
    ```
    
    Including 8 new Task 7 tests:
    - `Phase2ContainerTest.PerFusionSpecialValuesBasic` - PASSED
    - `Phase2ContainerTest.SpecialValuesOwnedByFusion` - PASSED
    - `Phase2ContainerTest.SeparateFusionsHaveOwnSpecialValues` - PASSED
    - `Phase2ContainerTest.DestroyFusionDoesNotAffectOther` - PASSED
    - `Phase2ContainerTest.SpecialValuesLazyCreation` - PASSED
    - `Phase2ContainerTest.AllSpecialValuesPerFusion` - PASSED
    - `Phase2ContainerTest.SpecialValuesClearedOnFusionClear` - PASSED
    - `Phase2ContainerTest.SpecialValuesWithDtype` - PASSED
    
    - `csrc/fusion.h` - Added special value members and accessors
    - `csrc/fusion.cpp` - Added accessor implementations, updated `clear()`
    - `csrc/ir/container.h` - Removed special values, added comment
    - `csrc/ir/container.cpp` - Removed accessor implementations
    - `tests/cpp/test_phase2_container_sharing.cpp` - Added 8 unit tests
    
    - [x] Each Fusion has its own special values
    - [x] Destroying Fusion A doesn't affect Fusion B's special values
    - [x] Special value accessors (`zeroVal()`, `oneVal()`, etc.) return
    this Fusion's values
    - [x] Lazy creation still works (create on first access)
    - [x] Smoke tests pass (34/34)
    - [x] Unit tests added (8 tests)
    - [x] Unit tests pass (26/26 Phase 2 tests)
    - [x] Code compiles without errors
    - [x] REPORT.md delivered
    
    1. **Memory ownership:** Special values are raw pointers stored in
    Fusion, but the actual memory is owned by IrContainer's `vals_up_`. When
    a Fusion is destroyed, `removeStatementsOwnedBy()` cleans up these vals.
    
    2. **Lazy creation pattern:** Special values are created on first
    access. This matches the original IrContainer behavior and avoids
    creating values that aren't needed.
    
    3. **Clear handling:** `Fusion::clear()` now resets special value
    pointers to nullptr after `removeStatementsOwnedBy()` removes the actual
    Val objects. This ensures lazy recreation works correctly after clear.
    
    4. **Copy/move handling:** Will be addressed in Tasks 5 and 6. This task
    just moves the members and accessors.
    Moved `axioms_` and `metadata_` from `IrContainer` to the `Fusion` class.
    This completes the deprecation of `parent_` usage for val-creating methods,
    which was necessary because `parent_` implies a 1-1 relationship
    (container → Fusion), but Phase 2 has 1-many (shared containers).
    
    Methods that used `parent_` to create vals were moved to Fusion:
    - `metadataOf(Val*)` - Now uses `v->container()` to get owning Fusion
    - `axioms()` - Now creates axiom vals owned by `this` Fusion
    - `assumePositive/assumeNonNegative` - Now adds to `this` Fusion's axioms
    
    - Added `axioms_` and `metadata_` private members
    - Changed method declarations from forwarding to actual implementations
    
    - Added includes for `ir/builder.h` and `ir/internal_nodes.h`
    - Implemented `metadataOf()`, `axioms()`, `assumePositive()`,
      `assumeNonNegative()` methods
    - Updated `clear()` to reset `axioms_` and `metadata_`
    
    - Removed `metadataOf()`, `axioms()`, `assumePositive()`,
      `assumeNonNegative()` declarations
    - Removed `lazyInitAxioms()` declaration
    - Removed `axioms_` and `metadata_` members
    
    - Removed implementations of above methods
    - Updated `IrContainer::swap` to remove axioms_/metadata_ swapping
    - Updated `IrContainer::copy` to remove axioms_/metadata_ handling
    - Updated `IrContainer::clear` to remove axioms_/metadata_ clearing
    
    Each Fusion now has its own axioms and metadata cache. This ensures:
    1. No ownership conflicts when multiple Fusions share an IrContainer
    2. Correct behavior when one Fusion is destroyed (doesn't affect others)
    3. Lazy creation pattern preserved (create on first access)
    
    This is a prerequisite for the copy/move semantics changes which will
    swap/transfer these per-Fusion members.
    - Add missing swap of axioms_ and metadata_ in Fusion::swap to prevent
      dangling pointers after move/assignment
    - Add missing cloning of axioms_ and metadata_ in Fusion::copy to
      preserve custom assumptions and metadata cache across copies
    - Guard Fusion::removeVal against removing cached special vals
    - Use std::unique_ptr for special vals and steal from vals_up_ to
      preserve the original invariant (shortcuts in vals_ but not vals_up_)
    - Fix metadataOf to use 'this' instead of v->container()
    The old IrContainer approach popped special vals (zeroVal, oneVal, etc.)
    from vals_up_ after creation. During Fusion::copy, these vals were not
    cloned through the normal deterministic_vals() path. Instead, they were
    first cloned during axiom cloning, which happened AFTER
    val_type_name_map_
    was overridden from the source — causing the name counter to be
    incremented 1 past the source value.
    
    Now that special vals remain in vals_up_, they are properly cloned
    before
    the counter override, so the counter stays accurate. This shifts loop
    index val names down by 1 (e.g., i113 instead of i114). The index
    expression structure is unchanged.
    Special vals (trueVal, falseVal, oneVal, etc.) can be lazily created
    inside a StatementGuard scope (e.g. by simplifyExpr called from
    haveDifferentShardings). When the guard rolls back, it pops vals_up_
    back to the snapshot, destroying those vals while the Fusion cache
    pointers still reference them. Subsequent calls return dangling pointers
    causing UB — this manifested as LoopShardedSplitReshapeIds incorrectly
    classifying a reshape as resharding on CI.
    
    Fusion::removeStatementsCreatedAfter now nulls out any special val cache
    pointers that are about to be destroyed, so they get re-created on next
    access.
    SubstituteInExpr directly sets mutations_[reference] = substitute
    without checking reference == substitute, unlike registerMutation
    which guards against this. With per-Fusion special vals, Fusion::copy
    now maps zero_val_ through the cloner so that IterDomain extents and
    zero_val_ share the same pointer. When concretizeEmptyExtents finds
    an extent that IS zero_val_, SubstituteInExpr created a self-mapping
    that tripped the two-hop assertion in maybeMutated.
    
    Why this didn't happen before:
    
      Old code (main):
        zero_val_ was stored in a separate unique_ptr, popped from
        vals_up_. Fusion::copy didn't wire it up — B->zeroVal() lazily
        created a brand new Val, so ext != zero always held.
    
      New code (this branch):
        zero_val_ lives in vals_up_ like any other Val. Fusion::copy
        remaps it via ir_cloner.clone(), so B->zero_val_ IS the same
        cloned Val that IterDomain extents reference:
    
          Fusion A                         Fusion B (clone)
          ┌─────────────────┐             ┌──────────────────┐
          │ zero_val_ ─► 0x1111           │ zero_val_ ─► 0x2222
          │ id->extent() ─► 0x1111        │ id->extent() ─► 0x2222
          └─────────────────┘             └──────────────────┘
                                clone maps 0x1111 → 0x2222
    
        So ext == zero, and SubstituteInExpr(ext, zero) created:
          mutations_[0x2222] = 0x2222  (self-mapping)
        Then maybeMutated looked up 0x2222, found itself, treated
        it as a two-hop chain, and asserted.
    @mdavis36 mdavis36 force-pushed the md/dep-special-types branch from 1588d37 to cf4ae4a Compare February 18, 2026 03:13
    @mdavis36 mdavis36 changed the title [IR Container] Phase 2.1 Ownership Fusion special types [IR Container] Phase 2.1 Ownership Fusion Special Vals & Axioms Feb 18, 2026
    @mdavis36
    Copy link
    Collaborator Author

    !test

    @mdavis36 mdavis36 marked this pull request as ready for review February 18, 2026 06:36
    @mdavis36 mdavis36 changed the base branch from main to md/phase2-ir-refactor February 18, 2026 06:38
    @greptile-apps
    Copy link
    Contributor

    greptile-apps bot commented Feb 18, 2026

    Greptile Summary

    This PR migrates per-Fusion state — special values (zero_val_, one_val_, true_val_, false_val_, magic_zero_val_), axioms, and metadata — from IrContainer to Fusion, eliminating the 1:1 coupling between them. This is a prerequisite for Phase 2's shared-container model where multiple Fusions share a single IrContainer.

    • Ownership model change: Special vals move from unique_ptr members in IrContainer (stored outside vals_up_) to raw cache pointers in Fusion (with memory owned by IrContainer::vals_up_). This simplifies the numVals API by removing the include_shortcuts parameter.
    • Bug fix — SubstituteInExpr self-substitution: With per-Fusion special vals, cloned zero_val_ can be pointer-identical to IterDomain extents, causing a self-mapping that tripped a two-hop assertion. Fixed by guarding against reference == substitute.
    • Bug fix — dangling special vals after StatementGuard rollback: Special vals lazily created inside a guard scope were destroyed on rollback while Fusion cache pointers still referenced them. removeStatementsCreatedAfter now nulls any special val caches pointing to vals about to be destroyed.
    • Proper clone/swap/clear: Fusion::copy remaps special val cache pointers through IrCloner, Fusion::swap swaps all new fields, and Fusion::clear resets them. Axioms and metadata are also properly cloned and swapped.
    • Regression test added for the StatementGuard dangling pointer scenario.

    Confidence Score: 4/5

    • This PR is safe to merge — it's a well-structured ownership migration with bug fixes, regression tests, and no identified logic issues.
    • The migration is clean and methodical: all call sites already went through Fusion (via Statement::container() which returns Fusion*), so the API surface is unchanged for callers. The two bug fixes address real correctness issues with proper guards and a regression test. The only reason for not giving a 5 is the inherent complexity of ownership changes in a large IR system — there could be edge cases in rarely-exercised paths that are hard to verify statically.
    • csrc/fusion.cpp deserves the most attention as it contains the core migration logic, especially removeStatementsCreatedAfter (dangling pointer prevention) and Fusion::copy (clone correctness).

    Important Files Changed

    Filename Overview
    csrc/fusion.cpp Core of the migration: moves special vals, axioms, metadata from IrContainer to Fusion. Adds removeStatementsCreatedAfter with dangling-pointer prevention, removeVal guard for cached special vals, lazy creation methods, and proper clone/swap/clear handling. Well-structured with clear separation of concerns.
    csrc/fusion.h Declares the new per-Fusion special val caches (raw pointers), axioms, and metadata members. Removes delegating wrappers that forwarded to IrContainer. Simplifies numVals API by removing the include_shortcuts parameter.
    csrc/ir/container.cpp Removes all special val, axiom, metadata, and removeStatementsCreatedAfter implementations from IrContainer. Removes the special-val guard from removeVal. Clean deletion of ~150 lines of now-dead code.
    csrc/ir/container.h Removes special val unique_ptrs, axioms, metadata members, public API methods, and removeStatementsCreatedAfter declaration. Simplifies numVals by removing the include_shortcuts parameter. IrContainer is now purely a storage layer.
    csrc/ir/utils.cpp Bug fix: guards SubstituteInExpr against self-substitution (reference == substitute) which would create a self-mapping that triggers a two-hop assertion. Surfaced by per-Fusion special vals where cloned pointers can be identical.
    csrc/statement_guard.cpp Trivial change: updates numVals() call to match simplified API (removes include_shortcuts parameter). Behavior is unchanged since special vals are now in vals_up_ anyway.
    tests/cpp/test_indexing.cpp Updates hardcoded val name indices (i114→i113, etc.) that shifted because special vals are no longer stored outside vals_up_, changing the name counter offset by 1. Expected consequence of the migration.
    tests/cpp/test_statement_guard.cpp Adds regression test for the dangling special val pointer bug: lazily creates special vals inside a StatementGuard, rolls back, then verifies vals are still valid (re-created on next access) and fusion remains executable.

    Class Diagram

    %%{init: {'theme': 'neutral'}}%%
    classDiagram
        class Fusion {
            -Val* zero_val_
            -Val* one_val_
            -Val* true_val_
            -Val* false_val_
            -NamedScalar* magic_zero_val_
            -unique_ptr~vector~Val*~~ axioms_
            -unordered_map~Val*, pair~ metadata_
            -unique_ptr~IrContainer~ ir_container_
            +zeroVal() Val*
            +oneVal() Val*
            +trueVal() Val*
            +falseVal() Val*
            +magicZeroVal() NamedScalar*
            +axioms() vector~Val*~
            +metadataOf(Val*) Val*
            +assumePositive(Val*)
            +assumeNonNegative(Val*)
            +removeStatementsCreatedAfter(int64_t, int64_t)
            +removeVal(Val*)
            +copy(Fusion*, Fusion*) IrCloner
            +swap(Fusion, Fusion)
            +clear()
        }
    
        class IrContainer {
            +deque~unique_ptr~Val~~ vals_up_
            +unordered_set~Val*~ vals_
            +deque~unique_ptr~Expr~~ exprs_up_
            +unordered_set~Expr*~ exprs_
            -Fusion* parent_
            +registerVal(Val*)
            +registerExpr(Expr*)
            +removeVal(Val*)
            +removeExpr(Expr*)
            +clear()
            +numVals() int64_t
        }
    
        class StatementGuard {
            -Fusion* fusion_
            -int64_t prev_num_exprs_
            -int64_t prev_num_vals_
            +~StatementGuard()
        }
    
        Fusion "1" *-- "1" IrContainer : owns via unique_ptr
        Fusion ..> IrContainer : "special vals memory\nowned by vals_up_"
        StatementGuard ..> Fusion : "calls removeStatementsCreatedAfter\non destruction"
    
    Loading

    Last reviewed commit: cf4ae4a

    Copy link
    Contributor

    @greptile-apps greptile-apps bot left a comment

    Choose a reason for hiding this comment

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

    8 files reviewed, no comments

    Edit Code Review Agent Settings | Greptile

    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