[MLIR][Affine] affine-loop-fusion miscompiles when an external func.call uses a memref.cast alias
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
`affine-loop-fusion` moves an external call that uses a cast alias from between the producer and consumer loops to before the producer, and then fuses the loops. This changes the program semantics.
Public reproducer: https://www.compiler-explorer.com/z/K3MErMWKc
Tested locally at `llvm/llvm-project` commit `450cf0aafeab9fed6666b112b018d3913a36527c` (`LLVM 24.0.0git`).
## Reproducer
```mlir
module {
func.func private @escape(memref)
func.func @control(%in: memref<32xf64>, %comm: memref<32xf64>, %out: memref<32xf64>) {
affine.for %i = 0 to 16 {
%a = affine.load %in[%i] : memref<32xf64>
%b = arith.addf %a, %a : f64
affine.store %b, %comm[%i] : memref<32xf64>
}
%view = memref.cast %comm : memref<32xf64> to memref
func.call @escape(%view) : (memref) -> ()
affine.for %j = 0 to 16 {
%c = affine.load %comm[%j] : memref<32xf64>
%d = arith.addf %c, %c : f64
affine.store %d, %out[%j] : memref<32xf64>
}
return
}
}
```
## Command
```sh
mlir-opt dc8.mlir \
--pass-pipeline='builtin.module(func.func(affine-loop-fusion{mode=producer maximal}))'
```
## Result
The pass emits:
```mlir
module {
func.func private @escape(memref)
func.func @control(%arg0: memref<32xf64>, %arg1: memref<32xf64>, %arg2: memref<32xf64>) {
%cast = memref.cast %arg1 : memref<32xf64> to memref
call @escape(%cast) : (memref) -> ()
affine.for %arg3 = 0 to 16 {
%0 = affine.load %arg0[%arg3] : memref<32xf64>
%1 = arith.addf %0, %0 : f64
affine.store %1, %arg1[%arg3] : memref<32xf64>
%2 = affine.load %arg1[%arg3] : memref<32xf64>
%3 = arith.addf %2, %2 : f64
affine.store %3, %arg2[%arg3] : memref<32xf64>
}
return
}
}
```
In the source, the producer fills `%comm`, then `@escape` modifies it, and only then does the consumer read it. In the transformed program, `@escape` runs before the producer, whose stores overwrite the call's results before the consumer reads them.
For a legal synchronous implementation that writes `comm[k] = 8192 + k` for `k = 0..15`, the source produces `out[0] = 16384`, while the transformed program produces `out[0] = 4`.
Both the source and transformed IR verify. The concrete execution uses initialized, distinct buffers, in-bounds accesses, and a synchronous non-retaining callee.
Contributor guide
Research direction
Start by running the supplied MLIR reproducer with mlir-opt and the affine-loop-fusion pass pipeline, then inspect how the producer loop, memref.cast alias, external func.call, and consumer loop are ordered. Done means the pass no longer produces a transformation that moves the call before the producer and changes the program's observable result.
Written by the indexing model from the issue text.
Assessment
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100