[MLIR][Affine] UNREACHABLE "unexpected op" in affine-loop-fusion getMemRef() with memref.copy inside affine.for
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
#### Version
git commit hash: b3df06651
#### Bug Description
`mlir-opt --affine-loop-fusion` crashes with **`llvm_unreachable("unexpected op")`** when building the `MemRefDependenceGraph` for fusion analysis. The crash occurs at `Utils.cpp:256` inside `getMemRef()`, which is called from `mayDependence()` during dependence graph initialization.
#### Crash Chain & Root Cause
**Location**: `mlir/lib/Dialect/Affine/Analysis/Utils.cpp:256`
Crash flow:
```
affine-loop-fusion pass
→ LoopFusion::runOnOperation()
→ mlir::detail::walk() over all functions with affine.for
→ MemRefDependenceGraph::init()
→ addNodeToMDG()
→ LoopNestStateCollector::collect()
→ memref.copy → hasEffect() → memrefLoads ←
→ memref.copy → hasEffect() → memrefStores ← (same op in both lists)
→ mayDependence()
→ hasNonAffineDep() lambda
→ getMemRef(memref.copy) // UNREACHABLE: not handled
```
#### Steps to Reproduce
Run the following command with the provided reproducer MLIR:
```bash
mlir-opt --affine-loop-fusion reproducer.mlir -o /dev/null
```
#### Reproducer (`reproducer.mlir`)
```mlir
// BUG-3: Minimal reproducer for affine-loop-fusion crash
// Crash: llvm_unreachable("unexpected op") at Utils.cpp:256 in getMemRef()
//
// Root cause: memref.copy has both Read and Write memory effects on memrefs.
// LoopNestStateCollector classifies it into BOTH memrefLoads and memrefStores.
// Later, mayDependence() calls getMemRef() on it, but getMemRef() only handles
// memref::LoadOp, AffineReadOpInterface, memref::StoreOp, AffineWriteOpInterface.
// memref.copy does not match any of these → llvm_unreachable("unexpected op").
//
// Pipeline: --affine-loop-fusion (single pass, no preprocessing needed)
module {
func.func @minimal_bug3() {
%mem = memref.alloca() : memref<10xi32>
%mem2 = memref.alloca() : memref<10xi32>
%c0_i32 = arith.constant 0 : i32
// affine.for #1: contains memref.copy (has read+write memory effects on memrefs)
// which gets classified into BOTH memrefLoads and memrefStores
affine.for %i = 0 to 10 {
memref.copy %mem, %mem2 : memref<10xi32> to memref<10xi32>
affine.store %c0_i32, %mem[%i] : memref<10xi32>
}
// affine.for #2: reads the same memref, creating a dependence pair
// between the two affine.for nodes that triggers mayDependence()
affine.for %j = 0 to 10 {
%v = affine.load %mem[%j] : memref<10xi32>
}
return
}
}
```
#### Stack Dump
```text
UNREACHABLE executed at .../mlir/lib/Dialect/Affine/Analysis/Utils.cpp:256!
Stack dump:
0. Program arguments: mlir-opt --affine-loop-fusion reproducer.mlir -o /dev/null
#0 0x00005e5967e97518 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (mlir-opt+0x1f8c518)
#1 0x00005e5967e943b5 llvm::sys::RunSignalHandlers() (mlir-opt+0x1f893b5)
#2 0x00005e5967e98641 SignalHandler(int, siginfo_t*, void*) Signals.cpp:0:0
#3 0x00007603ef242520 (/lib/x86_64-linux-gnu/libc.so.6+0x42520)
#4 0x00007603ef2969fc __pthread_kill_implementation ./nptl/pthread_kill.c:44:76
#5 0x00007603ef2969fc __pthread_kill_internal ./nptl/pthread_kill.c:78:10
#6 0x00007603ef2969fc pthread_kill ./nptl/pthread_kill.c:89:10
#7 0x00007603ef242476 gsignal ./signal/../sysdeps/posix/raise.c:27:6
#8 0x00007603ef2287f3 abort ./stdlib/abort.c:81:7
#9 0x00005e5967e7df5f (mlir-opt+0x1f72f5f)
#10 0x00005e596eb9a42c (mlir-opt+0x8c8f42c)
#11 0x00005e596eb82ec6 mlir::affine::MemRefDependenceGraph::init(bool) (mlir-opt+0x8c77ec6)
#12 0x00005e596e8954f6 void llvm::function_ref::callback_fn<(anonymous namespace)::LoopFusion::runOnOperation()::$_0>(long, mlir::Operation*) LoopFusion.cpp:0:0
#13 0x00005e5967f5e2ce void mlir::detail::walk(mlir::Operation*, llvm::function_ref, mlir::WalkOrder) (mlir-opt+0x20532ce)
#14 0x00005e596e892eba (anonymous namespace)::LoopFusion::runOnOperation() LoopFusion.cpp:0:0
#15 0x00005e59702f69c8 mlir::detail::OpToOpPassAdaptor::run(mlir::Pass*, mlir::Operation*, mlir::AnalysisManager, bool, unsigned int) (mlir-opt+0xa3eb9c8)
#16 0x00005e59702f77a4 mlir::detail::OpToOpPassAdaptor::runPipeline(mlir::OpPassManager&, mlir::Operation*, mlir::AnalysisManager, bool, unsigned int, mlir::PassInstrumentor*, mlir::PassInstrumentation::PipelineParentInfo const*) (mlir-opt+0xa3ec7a4)
#17 0x00005e59702feb32 mlir::PassManager::runPasses(mlir::Operation*, mlir::AnalysisManager) (mlir-opt+0xa3f3b32)
#18 0x00005e59702fe20a mlir::PassManager::run(mlir::Operation*) (mlir-opt+0xa3f320a)
#19 0x00005e5967f4af70 performActions(llvm::raw_ostream&, std::shared_ptr const&, mlir::MLIRContext*, mlir::MlirOptMainConfig const&) MlirOptMain.cpp:0:0
#20 0x00005e5967f4a2e8 llvm::LogicalResult llvm::function_ref>, llvm::MemoryBufferRef const&, llvm::raw_ostream&)>::callback_fn>, mlir::DialectRegistry&, mlir::MlirOptMainConfig const&)::$_0>(long, std::unique_ptr>, llvm::MemoryBufferRef const&, llvm::raw_ostream&) MlirOptMain.cpp:0:0
#21 0x00005e5970640145 mlir::splitAndProcessBuffer(std::unique_ptr>, llvm::function_ref>, llvm::MemoryBufferRef const&, llvm::raw_ostream&)>, llvm::raw_ostream&, llvm::StringRef, llvm::StringRef) (mlir-opt+0xa735145)
#22 0x00005e5967f3f8e5 mlir::MlirOptMain(llvm::raw_ostream&, std::unique_ptr>, mlir::DialectRegistry&, mlir::MlirOptMainConfig const&) (mlir-opt+0x20348e5)
#23 0x00005e5967f3fcb1 mlir::MlirOptMain(int, char**, llvm::StringRef, llvm::StringRef, mlir::DialectRegistry&) (mlir-opt+0x2034cb1)
#24 0x00005e5967f3ff1b mlir::MlirOptMain(int, char**, llvm::StringRef, mlir::DialectRegistry&) (mlir-opt+0x2034f1b)
#25 0x00005e5967e7d632 main (mlir-opt+0x1f72632)
#26 0x00007603ef229d90 __libc_start_call_main ./csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#27 0x00007603ef229e40 call_init ./csu/../csu/libc-start.c:128:20
#28 0x00007603ef229e40 __libc_start_main ./csu/../csu/libc-start.c:379:5
#29 0x00005e5967e7d165 _start (mlir-opt+0x1f72165)
/bin/bash: line 1: 1462843 Aborted (core dumped) ../../../llvm-project/build/bin/mlir-opt --affine-loop-fusion minimal_BUG-3.mlir -o /dev/null 2>&1
```
Contributor guide
Research direction
Reproduce the crash with mlir-opt --affine-loop-fusion and reproducer.mlir, then inspect mlir/lib/Dialect/Affine/Analysis/Utils.cpp around getMemRef() and the MemRefDependenceGraph initialization path. Trace how memref.copy enters the dependence analysis and add regression coverage for this reproducer. Done means the pass no longer aborts on the input and the regression test passes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 65/100