[llvm] [LoopInterchange] Reject interchange when a freeze would move or be cloned (PR #213309)

via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 4 02:20:33 PDT 2026


================
@@ -0,0 +1,135 @@
+; A freeze whose execution count changes under interchange can resample poison
+; and break the correlation shared by its uses. Reject that nest, but retain
+; interchange for an otherwise-identical profitable control.
+;
+; RUN: opt < %s -passes='loop(loop-interchange),print<loops>' \
+; RUN:     -cache-line-size=64 -disable-output 2>&1 \
+; RUN:     | FileCheck %s --check-prefix=LOOPS
+; RUN: opt < %s -passes=loop-interchange -cache-line-size=64 \
+; RUN:     -pass-remarks-output=%t.yaml -disable-output
+; RUN: FileCheck %s --check-prefix=REMARK --input-file=%t.yaml
+; RUN: llvm-extract -S -func=outer_header_freeze %s -o %t.freeze
+; RUN: opt -S -passes=no-op-loopnest %t.freeze -o %t.noop
+; RUN: opt -S -passes=loop-interchange -cache-line-size=64 \
+; RUN:     %t.freeze -o %t.out
+; RUN: diff -u %t.noop %t.out
+
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+
+define void @profitable_control(ptr noalias %A, ptr noalias %R) {
+entry:
+  br label %outer.header
+
+outer.header:
+  %i = phi i64 [ 0, %entry ], [ %i.next, %outer.latch ]
+  %sum.i = phi double [ 0.000000e+00, %entry ], [ %sum.next, %outer.latch ]
+  br label %inner.header
+
+inner.header:
+  %j = phi i64 [ 0, %outer.header ], [ %j.next, %inner.header ]
+  %sum.j = phi double [ %sum.i, %outer.header ], [ %sum.next.j, %inner.header ]
+  %idx = getelementptr inbounds [4 x double], ptr %A, i64 %j, i64 %i
+  %value = load double, ptr %idx, align 8
+  %sum.next.j = fadd reassoc double %sum.j, %value
+  %j.next = add i64 %j, 1
+  %j.ec = icmp eq i64 %j.next, 4
+  br i1 %j.ec, label %outer.latch, label %inner.header
+
+outer.latch:
+  %sum.next = phi double [ %sum.next.j, %inner.header ]
+  %i.next = add i64 %i, 1
+  %i.ec = icmp eq i64 %i.next, 4
+  br i1 %i.ec, label %exit, label %outer.header
+
+exit:
+  %sum.result = phi double [ %sum.next, %outer.latch ]
+  store double %sum.result, ptr %R, align 8
+  ret void
+}
+
+; LOOPS-LABEL: Loop info for function 'profitable_control':
+; LOOPS:         Loop at depth 1 containing: %inner.header<header>
+; LOOPS-NEXT:      Loop at depth 2 containing: %outer.header<header>
+; REMARK:      --- !Passed
+; REMARK:      Name:            Interchanged
+; REMARK-NEXT: Function:        profitable_control
+
+define void @outer_preheader_freeze(ptr noalias %A, ptr noalias %R) {
+entry:
+  %choice = freeze i1 poison
+  br label %outer.header
+
+outer.header:
+  %i = phi i64 [ 0, %entry ], [ %i.next, %outer.latch ]
+  %sum.i = phi double [ 0.000000e+00, %entry ], [ %sum.next, %outer.latch ]
+  br label %inner.header
+
+inner.header:
+  %j = phi i64 [ 0, %outer.header ], [ %j.next, %inner.header ]
+  %sum.j = phi double [ %sum.i, %outer.header ], [ %sum.next.j, %inner.header ]
+  %idx = getelementptr inbounds [4 x double], ptr %A, i64 %j, i64 %i
+  %value = load double, ptr %idx, align 8
+  %selected = select i1 %choice, double %value, double 0.000000e+00
+  %sum.next.j = fadd reassoc double %sum.j, %selected
+  %j.next = add i64 %j, 1
+  %j.ec = icmp eq i64 %j.next, 4
+  br i1 %j.ec, label %outer.latch, label %inner.header
+
+outer.latch:
+  %sum.next = phi double [ %sum.next.j, %inner.header ]
+  %i.next = add i64 %i, 1
+  %i.ec = icmp eq i64 %i.next, 4
+  br i1 %i.ec, label %exit, label %outer.header
+
+exit:
+  %sum.result = phi double [ %sum.next, %outer.latch ]
+  store double %sum.result, ptr %R, align 8
+  ret void
+}
+
+; LOOPS-LABEL: Loop info for function 'outer_preheader_freeze':
+; LOOPS:         Loop at depth 1 containing: %inner.header<header>
+; LOOPS-NEXT:      Loop at depth 2 containing: %outer.header<header>
+; REMARK:      --- !Passed
+; REMARK:      Name:            Interchanged
+; REMARK-NEXT: Function:        outer_preheader_freeze
+
+define void @outer_header_freeze(ptr noalias %A, ptr noalias %R) {
+entry:
+  br label %outer.header
+
+outer.header:
+  %i = phi i64 [ 0, %entry ], [ %i.next, %outer.latch ]
+  %sum.i = phi double [ 0.000000e+00, %entry ], [ %sum.next, %outer.latch ]
+  %choice = freeze i1 poison
+  br label %inner.header
+
+inner.header:
+  %j = phi i64 [ 0, %outer.header ], [ %j.next, %inner.header ]
+  %sum.j = phi double [ %sum.i, %outer.header ], [ %sum.next.j, %inner.header ]
+  %idx = getelementptr inbounds [4 x double], ptr %A, i64 %j, i64 %i
+  %value = load double, ptr %idx, align 8
+  %selected = select i1 %choice, double %value, double 0.000000e+00
+  %sum.next.j = fadd reassoc double %sum.j, %selected
+  %j.next = add i64 %j, 1
+  %j.ec = icmp eq i64 %j.next, 4
+  br i1 %j.ec, label %outer.latch, label %inner.header
+
+outer.latch:
+  %sum.next = phi double [ %sum.next.j, %inner.header ]
+  %i.next = add i64 %i, 1
+  %i.ec = icmp eq i64 %i.next, 4
+  br i1 %i.ec, label %exit, label %outer.header
+
+exit:
+  %sum.result = phi double [ %sum.next, %outer.latch ]
+  store double %sum.result, ptr %R, align 8
+  ret void
+}
+
----------------
MattPD wrote:

I added `@inner_preheader_freeze`, `@inner_exit_freeze`, and `@outer_latch_freeze`. Together with `@outer_header_freeze`, these tests cover all four blocks scanned by `findFreezeInReNestedBlocks`.

`@inner_latch_condition_freeze` and `@inner_latch_induction_freeze` are cases rejected only by `findFreezeInInnerLatchCloneSet`. SCEV simplifies the freeze-derived term to zero, so the trip count and the induction remain analyzable. One test computes the latch condition from the `freeze`, and the other computes the induction update from it. Without the clone-set check, the transform would clone the `freeze` in both.

The test file also has three freeze-free controls. `@header_preheader_control` pairs with `@outer_header_freeze` and `@inner_preheader_freeze`. `@exit_latch_control` pairs with `@inner_exit_freeze` and `@outer_latch_freeze`. `@inner_latch_clone_control` pairs with `@inner_latch_condition_freeze` and `@inner_latch_induction_freeze`. Each control repeats the loop shape of its two negative tests and uses an `xor` instead of `freeze`. Each control is still interchanged. Replacing the `xor` with a `freeze` causes the rejection. No other legality check rejects the matching negative tests.

The clone-set question also led me to rewrite the title and description (the rule itself is unchanged). The old title mentioned only moving a `freeze` to a different loop depth. The new title states that interchange is rejected when a `freeze` would move or be cloned. The description explains how a move and a clone can each cause uses to observe different frozen values.

https://github.com/llvm/llvm-project/pull/213309


More information about the llvm-commits mailing list