blob: 477c86b4ead4425b695c8b9d0b2b3d0a9ecfb057 [file] [edit]
// RUN: %clang_cc1 -verify -triple x86_64-pc-linux-gnu -fclang-abi-compat=latest -std=c++20 -fopenmp -ast-print %s | FileCheck %s --check-prefix=PRINT
// RUN: %clang_cc1 -verify -triple x86_64-pc-linux-gnu -fclang-abi-compat=latest -std=c++20 -fopenmp -emit-llvm %s -o - | FileCheck %s --check-prefix=IR
// Check same results after serialization round-trip, which exercises reading
// and writing OMPInvariantPredicateBoundAttr including its optional predicate.
// RUN: %clang_cc1 -verify -triple x86_64-pc-linux-gnu -fclang-abi-compat=latest -std=c++20 -fopenmp -emit-pch -o %t %s
// RUN: %clang_cc1 -verify -triple x86_64-pc-linux-gnu -fclang-abi-compat=latest -std=c++20 -fopenmp -include-pch %t -ast-print %s | FileCheck %s --check-prefix=PRINT
// expected-no-diagnostics
// How a loop-associated directive consumes a '#pragma omp tile': the stored
// min-bounded intra-tile loop is reinterpreted as a rectangular one with a
// constant trip count (the tile size), and a partial last tile is handled by a
// body guard. Worksharing, 'distribute' and 'ordered(n)' consumers each read
// the iteration space differently, so all three are covered. The hint is
// internal and never dumped, so it is observed through the guard it produces
// ('omp.body.next') and through the PCH round-trip above.
#ifndef HEADER
#define HEADER
extern "C" void body(...) {}
// PRINT-LABEL: void collapse2_tile1d(
// PRINT: #pragma omp parallel for collapse(2)
// PRINT-NEXT: #pragma omp tile sizes(2)
// IR-LABEL: define internal void @collapse2_tile1d.omp_outlined
// IR: %.floor_0.iv{{[0-9a-z.]*}} = alloca i32
// IR: %.tile_0.iv{{[0-9a-z.]*}} = alloca i32
// IR: omp.inner.for.body:
// IR: store i32 %{{.*}}, ptr %.tile_0.iv
// IR: %[[PRED:.*]] = icmp slt i32 %{{.*}}, %{{.*}}
// IR-NEXT: br i1 %[[PRED]], label %omp.body.next, label %omp.body.continue
// IR: omp.body.next:
// IR: call void (...) @body
// IR-NEXT: br label %omp.body.continue
// IR: omp.body.continue:
extern "C" void collapse2_tile1d(int n) {
#pragma omp parallel for collapse(2)
#pragma omp tile sizes(2)
for (int i = 0; i < n; ++i)
body(i);
}
// A non-tiled inner loop joins the collapsed nest as a third counter, and the
// guard still applies to the tile counter alone.
// PRINT-LABEL: void collapse3_tile1d_nested(
// PRINT: #pragma omp parallel for collapse(3)
// PRINT-NEXT: #pragma omp tile sizes(5)
// IR-LABEL: define internal void @collapse3_tile1d_nested.omp_outlined
// IR: omp.inner.for.body:
// IR: store i32 %{{.*}}, ptr %.tile_0.iv
// IR: store i32 %{{.*}}, ptr %j
// IR: %[[TILEIV:.*]] = load i32, ptr %.tile_0.iv
// IR: %[[PRED:.*]] = icmp slt i32 %[[TILEIV]], %{{.*}}
// IR-NEXT: br i1 %[[PRED]], label %omp.body.next, label %omp.body.continue
extern "C" void collapse3_tile1d_nested(int n) {
#pragma omp parallel for collapse(3)
#pragma omp tile sizes(5)
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
body(i, j);
}
// An enclosing 'tile' is another loop transformation rather than a consumer, so
// the inner intra-tile loop stays min-bounded and gets no guard.
// PRINT-LABEL: void tile2d_inside_tile1d(
// PRINT: #pragma omp tile sizes(3)
// PRINT-NEXT: #pragma omp tile sizes(2)
// IR-LABEL: define {{.*}} @tile2d_inside_tile1d(
// IR: %.tile_0.iv{{[0-9a-z.]*}} = alloca i32
// IR: cond.true:
// IR: cond.false:
// IR: cond.end:
// IR: %{{.*}} = phi i32
// IR-NOT: omp.body.next
extern "C" void tile2d_inside_tile1d(int n) {
#pragma omp tile sizes(3)
#pragma omp tile sizes(2)
for (int i = 0; i < n; ++i)
body(i);
}
// PRINT-LABEL: void collapse3_tile2d(
// PRINT: #pragma omp parallel for collapse(3)
// PRINT-NEXT: #pragma omp tile sizes(2, 3)
// IR-LABEL: define internal void @collapse3_tile2d.omp_outlined
// IR: omp.inner.for.body:
// IR: store i32 %{{.*}}, ptr %.tile_0.iv
// IR: br i1 %{{.*}}, label %omp.body.next, label %omp.body.continue
// IR: omp.body.next:
// IR: call void (...) @body
extern "C" void collapse3_tile2d(int n) {
#pragma omp parallel for collapse(3)
#pragma omp tile sizes(2, 3)
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
body(i, j);
}
// Trip count 10 is a multiple of the tile size 5, so no tile is partial, the
// hint carries no predicate, and the body is emitted with no guard.
// PRINT-LABEL: void collapse2_tile1d_no_partial_tile(
// PRINT: #pragma omp parallel for collapse(2)
// PRINT-NEXT: #pragma omp tile sizes(5)
// IR-LABEL: define internal void @collapse2_tile1d_no_partial_tile.omp_outlined
// IR: omp.inner.for.body:
// IR-NOT: omp.body.next
// IR: call void (...) @body
// IR: omp.body.continue:
extern "C" void collapse2_tile1d_no_partial_tile() {
#pragma omp parallel for collapse(2)
#pragma omp tile sizes(5)
for (int i = 0; i < 10; ++i)
body(i);
}
// Trip count 11 is not a multiple of the tile size 5, so the predicate is kept
// even though both values are compile-time constants.
// PRINT-LABEL: void collapse2_tile1d_constant_partial_tile(
// PRINT: #pragma omp parallel for collapse(2)
// PRINT-NEXT: #pragma omp tile sizes(5)
// IR-LABEL: define internal void @collapse2_tile1d_constant_partial_tile.omp_outlined
// IR: omp.inner.for.body:
// IR: br i1 %{{.*}}, label %omp.body.next, label %omp.body.continue
// IR: omp.body.next:
// IR: call void (...) @body
extern "C" void collapse2_tile1d_constant_partial_tile() {
#pragma omp parallel for collapse(2)
#pragma omp tile sizes(5)
for (int i = 0; i < 11; ++i)
body(i);
}
// 'distribute' computes bounds through its own LB/UB/EUB/DistCond fields, so
// check that the reinterpretation and its guard survive that path too.
// PRINT-LABEL: void dist_collapse2_tile1d(
// PRINT: #pragma omp teams distribute collapse(2)
// PRINT-NEXT: #pragma omp tile sizes(5)
// IR-LABEL: define internal void @dist_collapse2_tile1d.omp_outlined
// IR: omp.inner.for.body:
// IR: store i32 %{{.*}}, ptr %.tile_0.iv
// IR: br i1 %{{.*}}, label %omp.body.next, label %omp.body.continue
// IR: omp.body.next:
// IR: call void (...) @body
extern "C" void dist_collapse2_tile1d(int n) {
#pragma omp teams distribute collapse(2)
#pragma omp tile sizes(5)
for (int i = 0; i < n; ++i)
body(i);
}
// 'ordered(n)' feeds each collapsed loop's trip count to __kmpc_doacross_init,
// so the intra-tile dimension must report the constant tile size, matching the
// space 'collapse' linearizes.
// PRINT-LABEL: void ordered_collapse2_tile1d(
// PRINT: #pragma omp for collapse(2) ordered(2)
// PRINT-NEXT: #pragma omp tile sizes(5)
// IR-LABEL: define {{.*}} @ordered_collapse2_tile1d(
// IR: %[[DIM1:.*]] = getelementptr inbounds [2 x %struct.kmp_dim], ptr %dims, i64 0, i64 1
// IR: %[[UP1:.*]] = getelementptr inbounds nuw %struct.kmp_dim, ptr %[[DIM1]], i32 0, i32 1
// IR-NEXT: store i64 5, ptr %[[UP1]]
// IR: call void @__kmpc_doacross_init(ptr @{{[0-9]+}}, i32 %{{.*}}, i32 2, ptr %{{.*}})
// IR: omp.inner.for.body:
// IR: br i1 %{{.*}}, label %omp.body.next, label %omp.body.continue
// IR: call void @__kmpc_doacross_fini(
extern "C" void ordered_collapse2_tile1d(int n) {
#pragma omp for collapse(2) ordered(2)
#pragma omp tile sizes(5)
for (int i = 0; i < n; ++i)
body(i);
}
#endif /* HEADER */