llvm / llvm/llvm-project

[MLIR][Affine] affine-loop-fusion miscompiles a producer loop with an external func.call

Open
#211,591 3 comments 0 reactions 0 assignees View on GitHub
miscompilation mlir
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

`affine-loop-fusion` incorrectly fuses a consumer into a producer loop that passes the produced memref to an external `func.call`.

The silent miscompilation is the primary issue. The same reduced input also crashes in assertions-enabled/current public trunk builds.

Public reproducer: https://www.compiler-explorer.com/z/a8ba738Pb

Tested locally at `llvm/llvm-project` commit `450cf0aafeab9fed6666b112b018d3913a36527c` (`LLVM 24.0.0git`).

## Reproducer

```mlir
module {
func.func private @opaque(memref<32xf64>)
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>
func.call @opaque(%comm) : (memref<32xf64>) -> ()
}
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 dc6.mlir \
--pass-pipeline='builtin.module(func.func(affine-loop-fusion{mode=producer maximal}))'
```

## Result

With assertions disabled, the consumer is fused into the producer:

```mlir
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>
func.call @opaque(%comm) : (memref<32xf64>) -> ()
%c = affine.load %comm[%i] : memref<32xf64>
%d = arith.addf %c, %c : f64
affine.store %d, %out[%i] : memref<32xf64>
}
```

In the source, all calls complete before the consumer reads `%comm`. After fusion, each consumer iteration runs before the remaining calls.

Using a legal synchronous callee that updates `%comm[0]` on every call, the source produces `out[0] = 8222`, while the transformed program produces `out[0] = 8192`.

Both IR modules verify, and the concrete execution uses initialized buffers, in-bounds accesses, and a synchronous non-retaining callee.

With assertions enabled on the local commit, the same command exits with status 134:

```text
unexpected op
UNREACHABLE executed at mlir/lib/Dialect/Affine/Analysis/Utils.cpp:256!
```

At filing time, Compiler Explorer's `MLIR opt (trunk)` terminates on the same input with `SIGSEGV` (exit code 139).

Related: #205745 reports a crash in the same pass area. PR #205865 prevents the local assertion failure for this reproducer, but the assertions-disabled build still emits the incorrect fusion.

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the issue with mlir-opt using the inline MLIR and affine-loop-fusion pipeline, then trace the fusion decision around the external func.call; the assertion location is mlir/lib/Dialect/Affine/Analysis/Utils.cpp:256. Done means the pass does not fuse across the call or miscompile the program, and the reproducer no longer fails in assertion-enabled builds.

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
Clearly specified
Newbie friendliness
50/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.