[Openmp-commits] [clang] [llvm] [openmp] [CLANG][OpenMP] Restore loop variable values after transformation directives (PR #214717)
Zahira Ammarguellat via Openmp-commits
openmp-commits at lists.llvm.org
Sun Oct 18 06:25:05 PDT 2026
zahiraam wrote:
> Check these tests:
>
> ```
> // Loop variables of transformation-affected loops must have the values
> // they would have without the directive (OpenMP 6.0 loop transformation
> // finalization rule). Each *_ref function is the untransformed reference;
> // the transformed twin must print the same values.
> //
> // Build with a compiler that has the PR applied, run, compare against the
> // CHECK lines. The tile/stripe rows currently produce j=5 on the PR:
> // the restore of an inner dimension fires even when the enclosing
> // dimension has zero iterations, but without the directive the inner loop
> // would not have run at all, so j must be left untouched. The interchange
> // row is left without a CHECK: the PR review ruled the shared case gets
> // the exit value (3); confirm that this also covers tile/stripe.
> #include <stdio.h>
>
> void tile_ref(int n) {
> int i, j;
> i = 77;
> j = 111;
> for (i = 0; i < n; i++)
> for (j = 0; j < 5; j++)
> ;
> printf("tile_ref i=%d j=%d\n", i, j);
> }
>
> void tile(int n) {
> int i, j;
> i = 77;
> j = 111;
> #pragma omp tile sizes(2, 3)
> for (i = 0; i < n; i++)
> for (j = 0; j < 5; j++)
> ;
> printf("tile i=%d j=%d\n", i, j);
> }
>
> void stripe_ref(int n) {
> int i, j;
> i = 77;
> j = 111;
> for (i = 0; i < n; i++)
> for (j = 0; j < 5; j++)
> ;
> printf("stripe_ref i=%d j=%d\n", i, j);
> }
>
> void stripe(int n) {
> int i, j;
> i = 77;
> j = 111;
> #pragma omp stripe sizes(2, 3)
> for (i = 0; i < n; i++)
> for (j = 0; j < 5; j++)
> ;
> printf("stripe i=%d j=%d\n", i, j);
> }
>
> void interchange_ref(int n) {
> int i, j;
> i = 77;
> j = 111;
> for (i = 0; i < n; i++)
> for (j = 0; j < 3; j++)
> ;
> printf("interchange_ref i=%d j=%d\n", i, j);
> }
>
> void interchange(int n) {
> int i, j;
> i = 77;
> j = 111;
> #pragma omp interchange
> for (i = 0; i < n; i++)
> for (j = 0; j < 3; j++)
> ;
> printf("interchange i=%d j=%d\n", i, j);
> }
>
> void tile_full_ref(void) {
> int i, j;
> i = 77;
> j = 111;
> for (i = 0; i < 6; i++)
> for (j = 0; j < 5; j++)
> ;
> printf("tile_full_ref i=%d j=%d\n", i, j);
> }
>
> void tile_full(void) {
> int i, j;
> i = 77;
> j = 111;
> #pragma omp tile sizes(2, 3)
> for (i = 0; i < 6; i++)
> for (j = 0; j < 5; j++)
> ;
> printf("tile_full i=%d j=%d\n", i, j);
> }
>
> int main(void) {
> tile_ref(0);
> tile(0);
> stripe_ref(0);
> stripe(0);
> interchange_ref(0);
> interchange(0);
> tile_full_ref();
> tile_full();
> return 0;
> }
>
> // Spec-correct output; the failing rows show the issue on the PR:
> // CHECK: tile_ref i=0 j=111
> // CHECK-NOT: tile i=0 j=5
> // CHECK: tile i=0 j=111
> // CHECK: stripe_ref i=0 j=111
> // CHECK-NOT: stripe i=0 j=5
> // CHECK: stripe i=0 j=111
> // CHECK: interchange_ref i=0 j=111
> // interchange row: PR review ruled the shared case gets the exit value;
> // verify the ruling covers tile/stripe as well.
> // CHECK: tile_full_ref i=6 j=5
> // CHECK: tile_full i=6 j=5
> ```
>
> ```
> // Restore statements after a fused loop must touch user variables only.
> // The internal fused-loop index is compiler generated, is never read after
> // the construct, and must not get a restoring store. The nested fuse here
> // makes the outer fuse collect the inner fused loop's helpers, whose
> // counter is that internal index.
> // RUN: %clang_cc1 -fopenmp -fopenmp-version=60 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s
>
> extern "C" void nested_fuse(void) {
> #pragma omp fuse looprange(2, 2)
> {
> #pragma omp fuse looprange(2, 2)
> {
> for (int i = 0; i < 8; ++i)
> ;
> for (int j = 0; j < 16; ++j)
> ;
> for (int k = 0; k < 32; ++k)
> ;
> }
> int l;
> for (l = 0; l < 4; ++l)
> ;
> int m;
> for (m = 0; m < 6; ++m)
> ;
> }
> }
>
> // After the outer fused loop: j and k are restored by the inner fuse, l by
> // this fuse. Nothing between those stores may write an internal fuse index.
> // On the PR there is a guarded block assigning .omp_fuse_index here, dead
> // code visible as a failed CHECK-NOT.
> // CHECK: store i32 16, ptr %j, align 4
> // CHECK: store i32 32, ptr %k, align 4
> // CHECK-NOT: store i32 {{.*}}, ptr %".omp_fuse_index{{[0-9]*}}"
> // CHECK: store i32 4, ptr %l, align 4
> ```
>
> ```
> // A range-based for is a canonical OpenMP loop; its control iterator must
> // be restored after a fuse like any other loop variable. The plain for
> // with a pointer control variable in the same sequence is restored, the
> // range-for iterator currently is not.
> // RUN: %clang_cc1 -fopenmp -fopenmp-version=60 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s
>
> extern "C" void fuse_range_for(void) {
> int arr[8];
> int t;
> int *q;
> #pragma omp fuse
> {
> for (t = 0; t < 4; ++t)
> ;
> for (q = arr; q != arr + 8; ++q)
> ;
> for (int &x : arr)
> ;
> }
> }
>
> // CHECK-LABEL: define {{.*}}@fuse_range_for
> // CHECK: store i32 4, ptr %t, align 4
> // // CHECK: store ptr {{.*}}, ptr %q, align 8
> // CHECK: getelementptr inbounds i32, ptr %arr, i64 8
> // CHECK: store ptr {{.*}}, ptr %q, align 8
> // The range-for iterator is restored to one past arr, as under tile
> // below; that store is missing on the PR and this CHECK exposes it.
> // CHECK: store ptr {{.*}}, ptr %__begin
>
> extern "C" void tile_range_for(void) {
> int arr[8];
> #pragma omp tile sizes(2)
> for (int &x : arr)
> ;
> }
>
> // CHECK-LABEL: define {{.*}}@tile_range_for
> // Control proving the helper expressions carry a valid restore for a
> // range-for; if this CHECK also fails, the skip is not specific to the
> // fuse filter.
> // CHECK: store ptr {{.*}}, ptr %__begin
> ```
>
> ```
> // Class-type loop variables are excluded from the restoration because the
> // builder only handles arithmetic and pointer counters. The finalization
> // rule covers them the same way: after the construct the control iterator
> // must equal end().
> // Build with a compiler that has the PR applied and run; compare against
> // the expected output below. The PR prints 0.
> #include <vector>
> #include <cstdio>
>
> int main() {
> std::vector<int> v(6);
> std::vector<int>::iterator it;
>
> it = v.begin();
> for (it = v.begin(); it != v.end(); ++it)
> ;
> std::printf("plain at_end=%d\n", it == v.end());
>
> it = v.begin();
> #pragma omp tile sizes(2)
> for (it = v.begin(); it != v.end(); ++it)
> ;
> std::printf("tile at_end=%d\n", it == v.end());
>
> return 0;
> }
>
> // Spec-correct output; the failing row shows the issue on the PR:
> // CHECK: plain at_end=1
> // CHECK-NOT: tile at_end=0
> // CHECK: tile at_end=1
> ```
Test 1-3 pass. Test 4 segfaults with clang with and without this PR, https://godbolt.org/z/PKaa4nTxv .
`Class-iterator` finalization stays as the FIXME; I'll fix it together with the crash in a follow-up.
https://github.com/llvm/llvm-project/pull/214717
More information about the Openmp-commits
mailing list