Skip to content

[MLIR] Add StoreLikeInterface to decouple activity analysis from stores#2978

Open
vchuravy wants to merge 11 commits into
mainfrom
worktree-active-store-interface
Open

[MLIR] Add StoreLikeInterface to decouple activity analysis from stores#2978
vchuravy wants to merge 11 commits into
mainfrom
worktree-active-store-interface

Conversation

@vchuravy

Copy link
Copy Markdown
Member

Activity analysis currently hard-codes which ops are stores (dyn_cast<LLVM::StoreOp> / memref::StoreOp) when reasoning about stored-value / pointer activity. That prevents out-of-tree dialects from participating and duplicates the same logic per dialect.

Change

  • Add enzyme::ActiveStoreOpInterface with getStoredValue() / getStoredPointer().
  • Attach it to memref.store and llvm.store.
  • Use it at the potential-active-store site in ActivityAnalysis.cpp that already handled both dialects — now a single dialect-agnostic branch.

Any dialect can now opt into store-activity handling by attaching the interface (e.g. an out-of-tree fir.store / hlfir.assign), without touching the core analysis.

Behavior

Behavior-preserving: llvm.store/memref.store return the same value/pointer through the interface as the hard-coded accessors did. The remaining LLVM-specific store sites (which recurse through llvm.alloca / llvm.load origins) are intentionally left as-is.

The MLIR test suite is unchanged: 135/137 pass, and the 2 ReverseMode failures are pre-existing on this LLVM build (they fail identically without this change).

Motivation

This is a standalone extract from the Enzyme⇄Flang HLFIR work (#2969): it lets fir.store/hlfir.assign participate in activity analysis so by-reference Fortran differentiates, while keeping Analysis/ free of any FIR/HLFIR dependency.

🤖 Generated with Claude Code

Comment thread enzyme/Enzyme/MLIR/Interfaces/AutoDiffOpInterface.td Outdated
@vchuravy vchuravy changed the title [MLIR] Add ActiveStoreOpInterface to decouple activity analysis from stores [MLIR] Add StoreLikeInterface to decouple activity analysis from stores Jul 23, 2026
Comment thread enzyme/Enzyme/MLIR/Analysis/ActivityAnalysis.cpp Outdated
Comment thread enzyme/Enzyme/MLIR/Analysis/ActivityAnalysis.cpp Outdated
@vchuravy
vchuravy changed the base branch from main to flang-fir-plugin July 23, 2026 07:43
@vchuravy
vchuravy changed the base branch from flang-fir-plugin to main July 23, 2026 07:43
@vchuravy
vchuravy requested a review from Pangoraw July 23, 2026 07:45
@vchuravy
vchuravy force-pushed the worktree-active-store-interface branch from ef1e602 to 139bc9e Compare July 23, 2026 07:48
Comment thread enzyme/Enzyme/MLIR/Interfaces/AutoDiffOpInterface.td Outdated

@Pangoraw Pangoraw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

maybe could be implemented for affine.store as well?

@wsmoses

wsmoses commented Jul 23, 2026

Copy link
Copy Markdown
Member

ci err


  shell: /home/runner/externals/node20/bin/node {0}
using script executor
[0/2] Re-checking globbed directories...
[0/1] Running MLIR regression tests
-- Testing: 141 tests, 32 workers --
Testing:  0.. 10.. 20.. 
FAIL: Enzyme :: MLIR/ForwardMode/affine.mlir (44 of 141)
******************** TEST 'Enzyme :: MLIR/ForwardMode/affine.mlir' FAILED ********************
Exit Code: 1

Command Output (stderr):
--
/__w/Enzyme/Enzyme/Enzyme/enzyme-build/Enzyme/MLIR/enzymemlir-opt --enzyme /__w/Enzyme/Enzyme/Enzyme/enzyme/test/MLIR/ForwardMode/affine.mlir | FileCheck /__w/Enzyme/Enzyme/Enzyme/enzyme/test/MLIR/ForwardMode/affine.mlir # RUN: at line 1
+ /__w/Enzyme/Enzyme/Enzyme/enzyme-build/Enzyme/MLIR/enzymemlir-opt --enzyme /__w/Enzyme/Enzyme/Enzyme/enzyme/test/MLIR/ForwardMode/affine.mlir
+ FileCheck /__w/Enzyme/Enzyme/Enzyme/enzyme/test/MLIR/ForwardMode/affine.mlir
/__w/Enzyme/Enzyme/Enzyme/enzyme/test/MLIR/ForwardMode/affine.mlir:86:16: error: CHECK-DAG: expected string not found in input
 // CHECK-DAG: %[[cst0:.+]] = arith.constant 0.000000e+00 : f64
               ^
<stdin>:80:43: note: scanning from here
 %alloc_1 = memref.alloc() : memref<1xf64>
                                          ^
<stdin>:80:43: note: pattern attempts to capture variables: "cst0"
 %alloc_1 = memref.alloc() : memref<1xf64>
                                          ^
<stdin>:86:2: note: possible intended match here
 %7 = arith.mulf %arg0, %arg0 : f64
 ^

Input file: <stdin>
Check file: /__w/Enzyme/Enzyme/Enzyme/enzyme/test/MLIR/ForwardMode/affine.mlir

-dump-input=help explains the following input dump.

Input was:
<<<<<<
          .
          .
          .
         75:  } 
         76:  func.func private @fwddiffeif_then(%arg0: f64, %arg1: f64, %arg2: i1) -> f64 { 
         77:  %cst = arith.constant 2.000000e+00 : f64 
         78:  %cst_0 = arith.constant 1.000000e+01 : f64 
         79:  %alloc = memref.alloc() : memref<1xf64> 
         80:  %alloc_1 = memref.alloc() : memref<1xf64> 
dag:86'0                                              {   search range start (exclusive)
dag:86'1                                                  error: no match found in search range
dag:86'2                                                  pattern attempts to capture variables: "cst0"
         81:  affine.store %cst, %alloc_1[0] : memref<1xf64> 
         82:  scf.if %arg2 { 
         83:  %4 = arith.mulf %arg1, %arg0 : f64 
         84:  %5 = arith.mulf %arg1, %arg0 : f64 
         85:  %6 = arith.addf %4, %5 : f64 
         86:  %7 = arith.mulf %arg0, %arg0 : f64 
dag:86'3      ?                                    possible intended match
         87:  affine.store %6, %alloc[0] : memref<1xf64> 
         88:  affine.store %7, %alloc_1[0] : memref<1xf64> 
         89:  } 
         90:  %0 = affine.load %alloc[0] : memref<1xf64> 
         91:  %1 = affine.load %alloc_1[0] : memref<1xf64> 
         92:  %2 = arith.mulf %0, %cst : f64 
         93:  %3 = arith.mulf %cst, %1 : f64 
         94:  return %2 : f64 
         95:  } 
         96: } 
         97:  
dag:86'4      } search range end (exclusive)
>>>>>>

--

********************
Testing:  0.. 10.. 20.. 30.. 40.. 50.. 60.. 70.. 80.. 90.. 
********************
Failed Tests (1):
  Enzyme :: MLIR/ForwardMode/affine.mlir


Testing Time: 0.60s

Total Discovered Tests: 141
  Passed: 140 (99.29%)
  Failed:   1 (0.71%)
FAILED: test/MLIR/CMakeFiles/check-enzymemlir /__w/Enzyme/Enzyme/Enzyme/enzyme-build/test/MLIR/CMakeFiles/check-enzymemlir 
cd /__w/Enzyme/Enzyme/Enzyme/enzyme-build/test/MLIR && /__w/Enzyme/Enzyme/llvm-project/mlir-build/bin/llvm-lit -v -sv /__w/Enzyme/Enzyme/Enzyme/enzyme-build/test/MLIR

//
// This is the memref analogue of the affine.store `@if_then` test.
//
// XFAIL: *

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can you fix this xfail?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, planning on it, I just wanted to have a test first.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

👍

// This is the llvm.store analogue of the affine.store `@if_then` test.
//
// XFAIL: *
// FIXME: forward-mode differentiation of llvm.store does not currently emit the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same here

vchuravy and others added 11 commits July 23, 2026 17:47
…s deps

Two dialect-agnostic, non-Fortran registration changes, factored out so they can
land independently of the FIR/HLFIR autodiff work that motivated them.

  * Move registerCoreDialectAutodiffInterfaces (the aggregate that force-links
    every core-dialect autodiff model, Linalg/NVVM included) out of
    CoreDialectsAutoDiffImplementations.cpp into a dedicated translation unit,
    CoreDialectsAutoDiffRegistration.cpp. That old TU also defines the
    memory-identity handler the FIR/HLFIR models depend on, so anything linking
    those models used to drag in the whole aggregate. Isolating it lets a
    consumer that only wants a subset of dialects (e.g. a lean `flang -fc1`
    plugin that must not pull in Linalg symbols flang does not export) link the
    individual register*DialectAutoDiffInterface entry points and never reference
    the aggregate TU.

  * Drop the stale linalg and tensor entries from the `enzyme` / `enzyme-wrap`
    pass dependentDialects. Neither pass creates linalg ops; tensor is only
    needed for vector-mode concat and every in-tree tool registers it itself.
    These were the last undefined dialect symbols that blocked dlopen'ing the
    plugin against a host that does not export Linalg/Tensor TypeIDs.

No behavior change for the existing tools (enzymemlir-opt still calls the
aggregate). Full MLIR suite green except the 2 pre-existing LLVM-24 failures
(complex_create_im, affine_parallel_mincut).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkvJByk8nTti2GiF4RudNG
- Restore tensor::TensorDialect in the `enzyme` pass dependentDialects
  (Passes.td) and the DifferentiatePass registry.insert override
  (EnzymeMLIRPass.cpp). The pass batches scalar ops into tensors by
  default (ArithConstantOpBatchInterface), so tensor must be loaded.
  Only the stale linalg entry is dropped.
- Remove the explanatory NOTE comment left where
  registerCoreDialectAutodiffInterfaces used to live.
- Add trailing newline to CoreDialectsAutoDiffRegistration.cpp.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…stores

Activity analysis hard-codes which ops are stores (dyn_cast<LLVM::StoreOp> /
memref::StoreOp) when reasoning about stored-value / pointer activity. That
prevents out-of-tree dialects from participating and duplicates logic per
dialect.

Add enzyme::ActiveStoreOpInterface exposing (getStoredValue, getStoredPointer),
attach it to memref.store and llvm.store, and use it at the potential-active-
store site that already handled both dialects -- now a single dialect-agnostic
branch. Any dialect (e.g. an out-of-tree fir.store / hlfir.assign) can opt in by
attaching the interface.

Behavior-preserving: llvm.store/memref.store return the same value/pointer
through the interface. The MLIR test suite is unchanged (the two
ReverseMode failures are pre-existing on this LLVM build, independent of this
change). The remaining LLVM-specific store sites (which walk llvm.alloca /
llvm.load origins) are intentionally left as-is.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address review: rename the interface and clarify getStoredPointer's
description to note it returns the base pointer before any in-op offsets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends the StoreLikeInterface generalization to the remaining store sites
in ActivityAnalysis and gates the pointer-like type check on the type
interface, so out-of-tree dialects (e.g. FIR/HLFIR) participate fully in
activity analysis without hard-coded dyn_casts. These are the
Fortran-independent pieces of #2969.

- isConstantValue: besides LLVM ptr / memref, treat any type whose
  AutoDiffTypeInterface reports isMutable() (e.g. !fir.ref) as a reference
  that carries active memory. Behavior-preserving in-tree: among core
  dialects only memref and LLVM pointer report isMutable(), and both are
  already matched by the existing isa<> check.
- isOperationInactiveFromOrigin: dialect-agnostic store branch mirroring
  the LLVM::StoreOp case (inactive iff stored value or pointer is constant).
- isValueActivelyStoredOrReturned: dialect-agnostic store branch mirroring
  the LLVM::StoreOp case.

Deliberately excludes the Fortran-coupled / behavior-changing parts of
#2969 (FIR/HLFIR models, flang plugin, the CoreDialects registration TU
split, and dropping tensor/linalg from the pass dependent dialects).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Paul Berg <naydex.mc+github@gmail.com>
Lets activity analysis reason about affine.store's stored-value/pointer
activity generically, the same way memref.store and llvm.store now do,
instead of requiring a hard-coded dyn_cast. getStoredPointer returns the
base memref (the affine map's indices are applied within the op).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The affine.store `@if_then` test in ForwardMode/affine.mlir covers a memref
that is unconditionally initialized with a constant and then conditionally
overwritten with an active value: forward mode must zero-initialize the shadow
before the conditional store, otherwise the not-taken path reads uninitialized
shadow memory and returns a garbage tangent instead of 0.

No equivalent coverage existed for memref.store or llvm.store. Add memref_if.mlir
and llvm_if.mlir as the direct analogues. Both are marked XFAIL: forward-mode
differentiation of memref.store / llvm.store does not currently emit the shadow
zero-initialization, so the tests document the known bug and will XPASS once it
is fixed.

Verified with a local enzymemlir-opt build: FileCheck fails on exactly the
zero-init CHECK-DAG line and matches everywhere else.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vchuravy
vchuravy force-pushed the worktree-active-store-interface branch from c27d97a to 4858baa Compare July 23, 2026 15:48
@vchuravy
vchuravy changed the base branch from main to enzyme-core-registration-split July 23, 2026 15:48
Base automatically changed from enzyme-core-registration-split to main July 23, 2026 18:16
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