[llvm-branch-commits] [clang] [flang] [llvm] [OpenMP][Attributor] Infer noalias for host callback captures (PR #218453)
Tomas Matheson via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Tue Aug 25 07:07:34 PDT 2026
https://github.com/tommat01 updated https://github.com/llvm/llvm-project/pull/218453
>From c94f75baffbc4d76a8f15f2976f61f0e246a612d Mon Sep 17 00:00:00 2001
From: Tomas Matheson <tomas.matheson at arm.com>
Date: Mon, 24 Aug 2026 11:07:25 +0100
Subject: [PATCH] [OpenMP] Seed noalias for host callback captures
The generic Attributor can derive noalias and the related lifetime facts for callback-mapped capture arguments, but OpenMPOpt does not seed AANoAlias in its restricted host run. Consequently capture-container loads remain in loops under dereferenceable-at-point semantics.
Seed AANoAlias for pointer arguments mapped to broker operands by callback metadata. Existing call-site reasoning checks every callback and direct call site and rejects escaped or aliased slots. LICM can then hoist immutable capture-container loads without changing the callback ABI or adding an OpenMP-specific hoisting transform.
Add LLVM, Clang, and Flang coverage for pointer, scalar, and aggregate captures together with escaped, duplicated, unmapped, and unknown-use negatives.
---
clang/test/OpenMP/bug54082.c | 20 +-
.../test/OpenMP/callback_capture_lifetime.cpp | 41 +++
.../OpenMP/callback-capture-lifetime.f90 | 48 ++++
llvm/lib/Transforms/IPO/OpenMPOpt.cpp | 21 +-
.../OpenMP/callback-capture-lifetime.ll | 262 ++++++++++++++++++
.../Transforms/OpenMP/parallel_deletion.ll | 10 +-
.../OpenMP/parallel_region_merging.ll | 12 +-
7 files changed, 390 insertions(+), 24 deletions(-)
create mode 100644 clang/test/OpenMP/callback_capture_lifetime.cpp
create mode 100644 flang/test/Integration/OpenMP/callback-capture-lifetime.f90
create mode 100644 llvm/test/Transforms/OpenMP/callback-capture-lifetime.ll
diff --git a/clang/test/OpenMP/bug54082.c b/clang/test/OpenMP/bug54082.c
index 6b8c93e9ffc96..b317a88d8aacb 100644
--- a/clang/test/OpenMP/bug54082.c
+++ b/clang/test/OpenMP/bug54082.c
@@ -69,43 +69,41 @@ void foo() {
// CHECK-NEXT: [[X_TRAITS:%.*]] = alloca [1 x [[STRUCT_OMP_ALLOCTRAIT_T:%.*]]], align 16
// CHECK-NEXT: [[X_ALLOC:%.*]] = alloca i64, align 8
// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[X_TRAITS]]) #[[ATTR4:[0-9]+]]
-// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull align 16 dereferenceable(16) [[X_TRAITS]], ptr noundef nonnull align 16 dereferenceable(16) @__const.foo.x_traits, i64 16, i1 false)
+// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull writeonly align 16 dereferenceable(16) [[X_TRAITS]], ptr noundef nonnull readonly align 16 dereferenceable(16) @__const.foo.x_traits, i64 16, i1 false)
// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[X_ALLOC]]) #[[ATTR4]]
// CHECK-NEXT: [[CALL:%.*]] = call i64 @omp_init_allocator(i64 noundef 0, i32 noundef 1, ptr noundef nonnull [[X_TRAITS]]) #[[ATTR4]]
// CHECK-NEXT: store i64 [[CALL]], ptr [[X_ALLOC]], align 8, !tbaa [[LONG_TBAA7:![0-9]+]]
-// CHECK-NEXT: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr nonnull @[[GLOB2:[0-9]+]], i32 1, ptr nonnull @foo.omp_outlined, ptr nonnull [[X_ALLOC]])
+// CHECK-NEXT: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr nonnull @[[GLOB2:[0-9]+]], i32 1, ptr nonnull @foo.omp_outlined, ptr noalias nonnull readonly captures(none) [[X_ALLOC]])
// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[X_ALLOC]]) #[[ATTR4]]
// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[X_TRAITS]]) #[[ATTR4]]
// CHECK-NEXT: ret void
//
//
// CHECK-LABEL: define internal void @foo.omp_outlined(
-// CHECK-SAME: ptr noalias nofree noundef readonly captures(none) [[DOTGLOBAL_TID_:%.*]], ptr noalias nofree readnone captures(none) [[DOTBOUND_TID_:%.*]], ptr nofree noundef nonnull readonly align 8 captures(none) dereferenceable(8) [[X_ALLOC:%.*]]) #[[ATTR3:[0-9]+]] {
+// CHECK-SAME: ptr noalias nofree noundef readonly captures(none) [[DOTGLOBAL_TID_:%.*]], ptr noalias nofree readnone captures(none) [[DOTBOUND_TID_:%.*]], ptr noalias noundef nonnull readonly align 8 captures(none) dereferenceable(8) [[X_ALLOC:%.*]]) #[[ATTR3:[0-9]+]] {
// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[DOTOMP_LB:%.*]] = alloca i32, align 4
// CHECK-NEXT: [[DOTOMP_UB:%.*]] = alloca i32, align 4
// CHECK-NEXT: [[DOTOMP_STRIDE:%.*]] = alloca i32, align 4
// CHECK-NEXT: [[DOTOMP_IS_LAST:%.*]] = alloca i32, align 4
-// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[DOTOMP_LB]]) #[[ATTR4]]
+// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[DOTOMP_LB]]) #[[ATTR6:[0-9]+]]
// CHECK-NEXT: store i32 0, ptr [[DOTOMP_LB]], align 4, !tbaa [[INT_TBAA9:![0-9]+]]
-// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[DOTOMP_UB]]) #[[ATTR4]]
+// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[DOTOMP_UB]]) #[[ATTR6]]
// CHECK-NEXT: store i32 1023, ptr [[DOTOMP_UB]], align 4, !tbaa [[INT_TBAA9]]
-// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[DOTOMP_STRIDE]]) #[[ATTR4]]
+// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[DOTOMP_STRIDE]]) #[[ATTR6]]
// CHECK-NEXT: store i32 1, ptr [[DOTOMP_STRIDE]], align 4, !tbaa [[INT_TBAA9]]
-// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[DOTOMP_IS_LAST]]) #[[ATTR4]]
+// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[DOTOMP_IS_LAST]]) #[[ATTR6]]
// CHECK-NEXT: store i32 0, ptr [[DOTOMP_IS_LAST]], align 4, !tbaa [[INT_TBAA9]]
// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[DOTGLOBAL_TID_]], align 4, !tbaa [[INT_TBAA9]]
// CHECK-NEXT: [[TMP1:%.*]] = load i64, ptr [[X_ALLOC]], align 8, !tbaa [[LONG_TBAA7]]
// CHECK-NEXT: [[CONV:%.*]] = inttoptr i64 [[TMP1]] to ptr
-// CHECK-NEXT: [[DOTX__VOID_ADDR:%.*]] = tail call ptr @__kmpc_alloc(i32 [[TMP0]], i64 8, ptr [[CONV]])
+// CHECK-NEXT: [[DOTX__VOID_ADDR:%.*]] = tail call ptr @__kmpc_alloc(i32 [[TMP0]], i64 8, ptr [[CONV]]) #[[ATTR4]]
// CHECK-NEXT: call void @__kmpc_for_static_init_4(ptr nonnull @[[GLOB1:[0-9]+]], i32 [[TMP0]], i32 34, ptr nonnull [[DOTOMP_IS_LAST]], ptr nonnull [[DOTOMP_LB]], ptr nonnull [[DOTOMP_UB]], ptr nonnull [[DOTOMP_STRIDE]], i32 1, i32 1)
// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4, !tbaa [[INT_TBAA9]]
// CHECK-NEXT: [[COND:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP2]], i32 1023)
// CHECK-NEXT: store i32 [[COND]], ptr [[DOTOMP_UB]], align 4, !tbaa [[INT_TBAA9]]
// CHECK-NEXT: call void @__kmpc_for_static_fini(ptr nonnull @[[GLOB1]], i32 [[TMP0]])
-// CHECK-NEXT: [[TMP3:%.*]] = load i64, ptr [[X_ALLOC]], align 8, !tbaa [[LONG_TBAA7]]
-// CHECK-NEXT: [[CONV5:%.*]] = inttoptr i64 [[TMP3]] to ptr
-// CHECK-NEXT: call void @__kmpc_free(i32 [[TMP0]], ptr [[DOTX__VOID_ADDR]], ptr [[CONV5]])
+// CHECK-NEXT: call void @__kmpc_free(i32 [[TMP0]], ptr [[DOTX__VOID_ADDR]], ptr [[CONV]])
// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[DOTOMP_IS_LAST]]) #[[ATTR4]]
// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[DOTOMP_STRIDE]]) #[[ATTR4]]
// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[DOTOMP_UB]]) #[[ATTR4]]
diff --git a/clang/test/OpenMP/callback_capture_lifetime.cpp b/clang/test/OpenMP/callback_capture_lifetime.cpp
new file mode 100644
index 0000000000000..c4e1195c550c7
--- /dev/null
+++ b/clang/test/OpenMP/callback_capture_lifetime.cpp
@@ -0,0 +1,41 @@
+// Verify that OpenMP callback analysis proves the captured pointer containers
+// noalias, allowing their loads to be hoisted out of the loop across an
+// unrelated opaque call.
+//
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -ffast-math -fopenmp \
+// RUN: -x c++ -emit-llvm -o - %s \
+// RUN: | FileCheck %s
+
+extern "C" int omp_get_num_threads();
+extern "C" int omp_get_thread_num();
+extern "C" void opaque();
+
+double sum_if(double *values, const bool *enabled, int count) {
+ double sum = 0.0;
+#pragma omp parallel reduction(+ : sum)
+ {
+ int threads = omp_get_num_threads();
+ int thread = omp_get_thread_num();
+
+ for (int i = thread * count / threads;
+ i < (thread + 1) * count / threads; ++i) {
+ opaque();
+ if (enabled[i])
+ sum += values[i];
+ }
+ }
+ return sum;
+}
+
+// CHECK-LABEL: define internal void @_Z6sum_ifPdPKbi.omp_outlined(
+// CHECK-SAME: ptr noalias noundef nonnull readonly align 8 captures(none) dereferenceable(8) %enabled,
+// CHECK-SAME: {{.*}}ptr noalias noundef nonnull readonly align 8 captures(none) dereferenceable(8) %values)
+// CHECK: call i32 @omp_get_num_threads()
+// CHECK: call i32 @omp_get_thread_num()
+// CHECK: for.body.lr.ph:
+// CHECK: [[VALUES:%.*]] = load ptr, ptr %values
+// CHECK: for.body:
+// CHECK-NOT: load ptr, ptr %values
+// CHECK: call void @opaque()
+// CHECK: if.then:
+// CHECK: getelementptr {{.*}}, ptr [[VALUES]],
diff --git a/flang/test/Integration/OpenMP/callback-capture-lifetime.f90 b/flang/test/Integration/OpenMP/callback-capture-lifetime.f90
new file mode 100644
index 0000000000000..742cc8ca7f765
--- /dev/null
+++ b/flang/test/Integration/OpenMP/callback-capture-lifetime.f90
@@ -0,0 +1,48 @@
+!===----------------------------------------------------------------------===!
+! This directory can be used to add Integration tests involving multiple
+! stages of the compiler (for eg. from Fortran to LLVM IR). It should not
+! contain executable tests. We should only add tests here sparingly and only
+! if there is no other way to test. Repeat this message in each test that is
+! added to this directory and sub-directories.
+!===----------------------------------------------------------------------===!
+
+! Verify that host OpenMP callback analysis makes Flang's aggregate capture
+! container noalias, allowing its fields to be loaded before an opaque call.
+!
+! RUN: %flang_fc1 -O2 -fopenmp -emit-llvm %s -o - | FileCheck %s
+
+subroutine capture_lifetime(input, enabled)
+ integer(8), intent(in) :: input
+ logical(1), intent(in) :: enabled(64)
+ integer(8) :: captured
+ integer :: i
+
+ interface
+ subroutine opaque()
+ end subroutine
+ subroutine use_value(value)
+ integer(8), value :: value
+ end subroutine
+ end interface
+
+ captured = input
+ !$omp parallel shared(captured, enabled) private(i)
+ call opaque()
+ do i = 1, 64
+ if (enabled(i)) call use_value(captured)
+ end do
+ !$omp end parallel
+end subroutine
+
+! CHECK-LABEL: define internal void @capture_lifetime_..omp_par(
+! CHECK-SAME: ptr noalias readonly captures(none) [[CAPTURES:%.*]])
+! CHECK: omp.par.entry:
+! CHECK-NEXT: [[ENABLED:%.*]] = load ptr, ptr [[CAPTURES]], align 8
+! CHECK-NEXT: [[CAPTURE_FIELD:%.*]] = getelementptr i8, ptr [[CAPTURES]], i64 8
+! CHECK-NEXT: [[CAPTURED:%.*]] = load ptr, ptr [[CAPTURE_FIELD]], align 8
+! CHECK-NEXT: tail call void @opaque_()
+! CHECK: omp.par.region3:
+! CHECK-NOT: load ptr, ptr [[CAPTURE_FIELD]]
+! CHECK: omp.par.region4:
+! CHECK-NOT: load ptr, ptr [[CAPTURE_FIELD]]
+! CHECK: load i64, ptr [[CAPTURED]], align 8
diff --git a/llvm/lib/Transforms/IPO/OpenMPOpt.cpp b/llvm/lib/Transforms/IPO/OpenMPOpt.cpp
index 22a5efb402504..0fff56cf17ced 100644
--- a/llvm/lib/Transforms/IPO/OpenMPOpt.cpp
+++ b/llvm/lib/Transforms/IPO/OpenMPOpt.cpp
@@ -5627,10 +5627,27 @@ void OpenMPOpt::registerAAs(bool IsModulePass) {
}
}
+ // Seed the generic noalias deduction for callback-mapped pointer arguments
+ // in host regions. It will inspect every callback and direct call site before
+ // manifesting the attribute.
+ if (!isOpenMPDevice(M)) {
+ for (Function *F : SCC) {
+ if (F->isDeclaration())
+ continue;
+ for (Use &U : F->uses()) {
+ AbstractCallSite ACS(&U);
+ if (!ACS || !ACS.isCallbackCall())
+ continue;
+ for (Argument &Arg : F->args())
+ if (Arg.getType()->isPointerTy() && ACS.getCallArgOperandNo(Arg) >= 0)
+ A.getOrCreateAAFor<AANoAlias>(IRPosition::argument(Arg));
+ }
+ }
+ return;
+ }
+
// Create an ExecutionDomain AA for every function and a HeapToStack AA for
// every function if there is a device kernel.
- if (!isOpenMPDevice(M))
- return;
for (auto *F : SCC) {
if (F->isDeclaration())
diff --git a/llvm/test/Transforms/OpenMP/callback-capture-lifetime.ll b/llvm/test/Transforms/OpenMP/callback-capture-lifetime.ll
new file mode 100644
index 0000000000000..a646e58eb0918
--- /dev/null
+++ b/llvm/test/Transforms/OpenMP/callback-capture-lifetime.ll
@@ -0,0 +1,262 @@
+; RUN: opt -passes=attributor -S < %s | FileCheck %s --check-prefix=ATTRIBUTOR
+; RUN: opt -passes=openmp-opt -S < %s | FileCheck %s --check-prefix=OPENMP
+; RUN: opt -passes='openmp-opt,function(loop-mssa(licm))' -S < %s | FileCheck %s --check-prefix=LICM
+; RUN: opt -use-dereferenceable-at-point-semantics=false -passes='openmp-opt,function(loop-mssa(licm))' -S < %s | FileCheck %s --check-prefix=LICM
+
+target triple = "aarch64-unknown-linux-gnu"
+
+define void @run(ptr %values) {
+entry:
+ %values.addr = alloca ptr, align 8
+ store ptr %values, ptr %values.addr, align 8
+ call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 1,
+ ptr @outlined, ptr %values.addr)
+ ret void
+}
+
+; Full Attributor already derives the lifetime attributes for mapped callback
+; arguments. OpenMPOpt should seed the same generic reasoning.
+; ATTRIBUTOR-LABEL: define internal void @outlined(
+; ATTRIBUTOR-SAME: {{.*}}ptr noalias nofree noundef nonnull readonly align 8 captures(none) dereferenceable(8) %values.capture) {
+; OPENMP-LABEL: define internal void @outlined(
+; OPENMP-SAME: {{.*}}ptr noalias readonly align 8 captures(none) dereferenceable(8) %values.capture) {
+
+; Once noalias and readonly establish that the capture storage remains live,
+; LICM can speculate the load out of the conditional loop even though an
+; unknown call precedes it.
+; LICM-LABEL: define internal void @outlined(
+; LICM-SAME: {{.*}}ptr noalias readonly align 8 captures(none) dereferenceable(8) %values.capture) {
+; LICM: call void @opaque()
+; LICM-NEXT: [[VALUES:%.*]] = load ptr, ptr %values.capture, align 8
+; LICM: loop:
+; LICM-NOT: load ptr, ptr %values.capture
+; LICM: if.then:
+; LICM-NEXT: [[ELEMENT:%.*]] = getelementptr double, ptr [[VALUES]], i64 [[I:%.*]]
+
+define internal void @outlined(ptr noalias %global_tid, ptr noalias %bound_tid,
+ ptr align 8 dereferenceable(8) %values.capture) {
+entry:
+ %threads = call i32 @omp_get_num_threads()
+ %thread = call i32 @omp_get_thread_num()
+ call void @opaque()
+ br label %loop
+
+loop:
+ %i = phi i64 [ 0, %entry ], [ %next, %latch ]
+ %enabled = icmp eq i64 %i, 7
+ br i1 %enabled, label %if.then, label %latch
+
+if.then:
+ %values = load ptr, ptr %values.capture, align 8
+ %element = getelementptr double, ptr %values, i64 %i
+ %value = load double, ptr %element, align 8
+ call void @use(double %value, i32 %threads, i32 %thread)
+ br label %latch
+
+latch:
+ %next = add nuw nsw i64 %i, 1
+ %done = icmp eq i64 %next, 64
+ br i1 %done, label %exit, label %loop
+
+exit:
+ ret void
+}
+
+; Scalar and aggregate capture storage use the same pointer-argument proof.
+; OPENMP-LABEL: define internal void @outlined.scalar(
+; OPENMP-SAME: {{.*}}ptr noalias readonly align 8 captures(none) dereferenceable(8) %capture)
+define void @run_scalar(i64 %value) {
+ %slot = alloca i64, align 8
+ store i64 %value, ptr %slot, align 8
+ call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 1,
+ ptr @outlined.scalar, ptr %slot)
+ ret void
+}
+
+define internal void @outlined.scalar(ptr %gtid, ptr %btid,
+ ptr align 8 dereferenceable(8) %capture) {
+ %value = load i64, ptr %capture, align 8
+ call void @use.i64(i64 %value)
+ ret void
+}
+
+; OPENMP-LABEL: define internal void @outlined.aggregate(
+; OPENMP-SAME: {{.*}}ptr noalias readonly align 8 captures(none) dereferenceable(16) %capture)
+define void @run_aggregate(ptr %a, ptr %b) {
+ %slot = alloca { ptr, ptr }, align 8
+ store ptr %a, ptr %slot, align 8
+ %second = getelementptr { ptr, ptr }, ptr %slot, i32 0, i32 1
+ store ptr %b, ptr %second, align 8
+ call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 1,
+ ptr @outlined.aggregate, ptr %slot)
+ ret void
+}
+
+define internal void @outlined.aggregate(
+ ptr %gtid, ptr %btid, ptr align 8 dereferenceable(16) %capture) {
+ %value = load { ptr, ptr }, ptr %capture, align 8
+ %a = extractvalue { ptr, ptr } %value, 0
+ call void @use.ptr(ptr %a)
+ ret void
+}
+
+; Every callback and direct call site must support the inference.
+; OPENMP-LABEL: define internal void @outlined.serialized(
+; OPENMP-SAME: {{.*}}ptr noalias readonly align 8 captures(none) dereferenceable(8) %capture)
+define void @run_serialized(ptr %values, i32 %condition) {
+ %slot = alloca ptr, align 8
+ store ptr %values, ptr %slot, align 8
+ call void @__kmpc_fork_call_if(ptr null, i32 1, ptr @outlined.serialized,
+ i32 %condition, ptr %slot)
+ call void @outlined.serialized(ptr null, ptr null, ptr %slot)
+ ret void
+}
+
+define internal void @outlined.serialized(
+ ptr %gtid, ptr %btid, ptr align 8 dereferenceable(8) %capture) {
+ %value = load ptr, ptr %capture, align 8
+ call void @use.ptr(ptr %value)
+ ret void
+}
+
+; OPENMP-LABEL: define internal void @outlined.multiple(
+; OPENMP-SAME: {{.*}}ptr noalias readonly align 8 captures(none) dereferenceable(8) %a.capture, ptr noalias readonly align 8 captures(none) dereferenceable(8) %b.capture)
+define void @run_multiple(ptr %a, ptr %b) {
+ %a.slot = alloca ptr, align 8
+ %b.slot = alloca ptr, align 8
+ store ptr %a, ptr %a.slot, align 8
+ store ptr %b, ptr %b.slot, align 8
+ call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 2,
+ ptr @outlined.multiple, ptr %a.slot, ptr %b.slot)
+ call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 2,
+ ptr @outlined.multiple, ptr %a.slot, ptr %b.slot)
+ ret void
+}
+
+define internal void @outlined.multiple(
+ ptr %gtid, ptr %btid,
+ ptr align 8 dereferenceable(8) %a.capture,
+ ptr align 8 dereferenceable(8) %b.capture) {
+ %a = load ptr, ptr %a.capture, align 8
+ %b = load ptr, ptr %b.capture, align 8
+ call void @use.ptr(ptr %a)
+ call void @use.ptr(ptr %b)
+ ret void
+}
+
+; Passing the same storage for two arguments does not satisfy noalias.
+; OPENMP-LABEL: define internal void @outlined.aliased(
+; OPENMP-SAME: {{.*}}ptr writeonly align 8 captures(none) dereferenceable(8) %a,
+; OPENMP-SAME: ptr readonly align 8 captures(none) dereferenceable(8) %b)
+define void @run_aliased(i64 %value) {
+ %slot = alloca i64, align 8
+ store i64 %value, ptr %slot, align 8
+ call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 2,
+ ptr @outlined.aliased, ptr %slot, ptr %slot)
+ ret void
+}
+
+define internal void @outlined.aliased(
+ ptr %gtid, ptr %btid, ptr align 8 dereferenceable(8) %a,
+ ptr align 8 dereferenceable(8) %b) {
+ store i64 0, ptr %a, align 8
+ call void @sync()
+ %bv = load i64, ptr %b, align 8
+ call void @use.i64(i64 %bv)
+ ret void
+}
+
+; Capture storage that escaped before the broker call is not noalias.
+; OPENMP-LABEL: define internal void @outlined.escaped(
+; OPENMP-SAME: {{.*}}ptr readonly align 8 captures(none) dereferenceable(8) %capture)
+define void @run_escaped(i64 %value) {
+ %slot = alloca i64, align 8
+ store i64 %value, ptr %slot, align 8
+ call void @escape(ptr %slot)
+ call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 1,
+ ptr @outlined.escaped, ptr %slot)
+ ret void
+}
+
+define internal void @outlined.escaped(
+ ptr %gtid, ptr %btid, ptr align 8 dereferenceable(8) %capture) {
+ %value = load i64, ptr %capture, align 8
+ call void @use.i64(i64 %value)
+ ret void
+}
+
+; Missing callback metadata means the argument is not seeded.
+; OPENMP-LABEL: define internal void @outlined.unmapped(
+; OPENMP-SAME: {{.*}}ptr align 8 dereferenceable(8) %capture)
+define void @run_unmapped(i64 %value) {
+ %slot = alloca i64, align 8
+ store i64 %value, ptr %slot, align 8
+ call void (ptr, i32, ptr, ...) @fork_without_metadata(ptr null, i32 1,
+ ptr @outlined.unmapped, ptr %slot)
+ ret void
+}
+
+define internal void @outlined.unmapped(
+ ptr %gtid, ptr %btid, ptr align 8 dereferenceable(8) %capture) {
+ %value = load i64, ptr %capture, align 8
+ call void @use.i64(i64 %value)
+ ret void
+}
+
+; An unknown use of the callback prevents all-call-site reasoning.
+ at callback_slot = global ptr null
+
+; OPENMP-LABEL: define internal void @outlined.unknown(
+; OPENMP-SAME: {{.*}}ptr align 8 dereferenceable(8) %capture)
+define void @run_unknown(i64 %value) {
+ %slot = alloca i64, align 8
+ store i64 %value, ptr %slot, align 8
+ store ptr @outlined.unknown, ptr @callback_slot
+ call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 1,
+ ptr @outlined.unknown, ptr %slot)
+ ret void
+}
+
+define internal void @outlined.unknown(
+ ptr %gtid, ptr %btid, ptr align 8 dereferenceable(8) %capture) {
+ %value = load i64, ptr %capture, align 8
+ call void @use.i64(i64 %value)
+ ret void
+}
+
+; Externally visible callbacks do not have a complete set of call sites.
+; OPENMP-LABEL: define void @outlined.external(
+; OPENMP-SAME: {{.*}}ptr align 8 dereferenceable(8) %capture)
+define void @outlined.external(
+ ptr %gtid, ptr %btid, ptr align 8 dereferenceable(8) %capture) {
+ %value = load i64, ptr %capture, align 8
+ call void @use.i64(i64 %value)
+ ret void
+}
+
+define void @run_external(i64 %value) {
+ %slot = alloca i64, align 8
+ store i64 %value, ptr %slot, align 8
+ call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 1,
+ ptr @outlined.external, ptr %slot)
+ ret void
+}
+
+declare !callback !0 void @__kmpc_fork_call(ptr, i32, ptr, ...)
+declare !callback !3 void @__kmpc_fork_call_if(ptr, i32, ptr, i32, ptr)
+declare void @fork_without_metadata(ptr, i32, ptr, ...)
+declare i32 @omp_get_num_threads()
+declare i32 @omp_get_thread_num()
+declare void @opaque()
+declare void @sync()
+declare void @use(double, i32, i32) memory(none)
+declare void @use.ptr(ptr) memory(none)
+declare void @use.i64(i64) memory(none)
+declare void @escape(ptr)
+
+!0 = !{!1}
+!1 = !{i64 2, i64 -1, i64 -1, i1 true}
+!llvm.module.flags = !{!2}
+!2 = !{i32 7, !"openmp", i32 51}
+!3 = !{!4}
+!4 = !{i64 2, i64 -1, i64 -1, i64 4, i1 false}
diff --git a/llvm/test/Transforms/OpenMP/parallel_deletion.ll b/llvm/test/Transforms/OpenMP/parallel_deletion.ll
index c362cebeda9b0..7003f81756ac9 100644
--- a/llvm/test/Transforms/OpenMP/parallel_deletion.ll
+++ b/llvm/test/Transforms/OpenMP/parallel_deletion.ll
@@ -386,7 +386,7 @@ define internal void @.omp_outlined..4(ptr noalias %.global_tid., ptr noalias %.
; CHECK-SAME: (ptr noalias nofree noundef nonnull readonly align 4 captures(none) dereferenceable(4) [[DOTGLOBAL_TID_:%.*]], ptr noalias nofree readnone captures(none) [[DOTBOUND_TID_:%.*]], ptr nofree noundef nonnull align 4 captures(none) dereferenceable(4) [[A:%.*]]) {
; CHECK-NEXT: entry:
; CHECK-NEXT: [[TMP:%.*]] = load i32, ptr [[DOTGLOBAL_TID_]], align 4, !invariant.load [[META1:![0-9]+]]
-; CHECK-NEXT: [[TMP1:%.*]] = call i32 @__kmpc_master(ptr noundef nonnull @[[GLOB0]], i32 [[TMP]])
+; CHECK-NEXT: [[TMP1:%.*]] = call i32 @__kmpc_master(ptr noundef nonnull @[[GLOB0]], i32 [[TMP]]) #[[ATTR9:[0-9]+]]
; CHECK-NEXT: [[TMP2:%.*]] = icmp eq i32 [[TMP1]], 0
; CHECK-NEXT: br i1 [[TMP2]], label [[OMP_IF_END:%.*]], label [[OMP_IF_THEN:%.*]]
; CHECK: omp_if.then:
@@ -459,7 +459,7 @@ define internal void @.omp_outlined..5(ptr noalias %.global_tid., ptr noalias %.
; CHECK-NEXT: entry:
; CHECK-NEXT: [[OMP_GLOBAL_THREAD_NUM:%.*]] = call i32 @__kmpc_global_thread_num(ptr noundef nonnull @[[GLOB0]]) #[[ATTR19]]
; CHECK-NEXT: [[TMP:%.*]] = load i32, ptr [[DOTGLOBAL_TID_]], align 4, !invariant.load [[META1]]
-; CHECK-NEXT: [[TMP1:%.*]] = call i32 @__kmpc_single(ptr noundef nonnull @[[GLOB0]], i32 [[TMP]])
+; CHECK-NEXT: [[TMP1:%.*]] = call i32 @__kmpc_single(ptr noundef nonnull @[[GLOB0]], i32 [[TMP]]) #[[ATTR9]]
; CHECK-NEXT: [[TMP2:%.*]] = icmp eq i32 [[TMP1]], 0
; CHECK-NEXT: br i1 [[TMP2]], label [[OMP_IF_END:%.*]], label [[OMP_IF_THEN:%.*]]
; CHECK: omp_if.then:
@@ -527,7 +527,7 @@ omp_if.end: ; preds = %entry, %omp_if.then
define internal void @.omp_outlined..6(ptr noalias %.global_tid., ptr noalias %.bound_tid., ptr dereferenceable(4) %a) {
; CHECK-LABEL: define {{[^@]+}}@.omp_outlined..6
-; CHECK-SAME: (ptr noalias nofree noundef nonnull readonly align 4 captures(none) dereferenceable(4) [[DOTGLOBAL_TID_:%.*]], ptr noalias nofree readnone captures(none) [[DOTBOUND_TID_:%.*]], ptr nofree noundef nonnull align 4 captures(none) dereferenceable(4) [[A:%.*]]) {
+; CHECK-SAME: (ptr noalias nofree noundef nonnull readonly align 4 captures(none) dereferenceable(4) [[DOTGLOBAL_TID_:%.*]], ptr noalias nofree readnone captures(none) [[DOTBOUND_TID_:%.*]], ptr nofree noundef nonnull align 4 captures(none) dereferenceable(4) [[A:%.*]]) #[[ATTR9]] {
; CHECK-NEXT: entry:
; CHECK-NEXT: [[A1:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[DOTOMP_REDUCTION_RED_LIST:%.*]] = alloca [1 x ptr], align 8
@@ -535,7 +535,7 @@ define internal void @.omp_outlined..6(ptr noalias %.global_tid., ptr noalias %.
; CHECK-NEXT: store i32 1, ptr [[A1]], align 4
; CHECK-NEXT: store ptr [[A1]], ptr [[DOTOMP_REDUCTION_RED_LIST]], align 8
; CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[DOTGLOBAL_TID_]], align 4, !invariant.load [[META1]]
-; CHECK-NEXT: [[TMP4:%.*]] = call i32 @__kmpc_reduce_nowait(ptr noundef nonnull @[[GLOB2:[0-9]+]], i32 [[TMP2]], i32 noundef 1, i64 noundef 8, ptr noundef nonnull align 8 [[DOTOMP_REDUCTION_RED_LIST]], ptr noundef nonnull @.omp.reduction.reduction_func, ptr noundef nonnull @.gomp_critical_user_.reduction.var)
+; CHECK-NEXT: [[TMP4:%.*]] = call i32 @__kmpc_reduce_nowait(ptr noundef nonnull @[[GLOB2:[0-9]+]], i32 [[TMP2]], i32 noundef 1, i64 noundef 8, ptr noundef nonnull align 8 [[DOTOMP_REDUCTION_RED_LIST]], ptr noundef nonnull @.omp.reduction.reduction_func, ptr noundef nonnull @.gomp_critical_user_.reduction.var) #[[ATTR9]]
; CHECK-NEXT: switch i32 [[TMP4]], label [[DOTOMP_REDUCTION_DEFAULT:%.*]] [
; CHECK-NEXT: i32 1, label [[DOTOMP_REDUCTION_CASE1:%.*]]
; CHECK-NEXT: i32 2, label [[DOTOMP_REDUCTION_CASE2:%.*]]
@@ -545,7 +545,7 @@ define internal void @.omp_outlined..6(ptr noalias %.global_tid., ptr noalias %.
; CHECK-NEXT: [[TMP6:%.*]] = load i32, ptr [[A1]], align 4
; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP5]], [[TMP6]]
; CHECK-NEXT: store i32 [[ADD]], ptr [[A]], align 4
-; CHECK-NEXT: call void @__kmpc_end_reduce_nowait(ptr noundef nonnull @[[GLOB2]], i32 [[TMP2]], ptr noundef nonnull @.gomp_critical_user_.reduction.var)
+; CHECK-NEXT: call void @__kmpc_end_reduce_nowait(ptr noundef nonnull @[[GLOB2]], i32 [[TMP2]], ptr noundef nonnull @.gomp_critical_user_.reduction.var) #[[ATTR9]]
; CHECK-NEXT: br label [[DOTOMP_REDUCTION_DEFAULT]]
; CHECK: .omp.reduction.case2:
; CHECK-NEXT: [[TMP7:%.*]] = load i32, ptr [[A1]], align 4
diff --git a/llvm/test/Transforms/OpenMP/parallel_region_merging.ll b/llvm/test/Transforms/OpenMP/parallel_region_merging.ll
index 6d3e5935d2e11..312a4ec8be74b 100644
--- a/llvm/test/Transforms/OpenMP/parallel_region_merging.ll
+++ b/llvm/test/Transforms/OpenMP/parallel_region_merging.ll
@@ -5252,12 +5252,12 @@ entry:
; CHECK2: omp.par.pre_finalize:
; CHECK2-NEXT: br label [[DOTFINI:%.*]]
; CHECK2: .fini:
-; CHECK2-NEXT: call void @llvm.lifetime.end.p0(ptr noundef nonnull [[B]])
+; CHECK2-NEXT: call void @llvm.lifetime.end.p0(ptr noundef nonnull captures(none) [[B]])
; CHECK2-NEXT: br label [[OMP_PAR_EXIT_EXITSTUB:%.*]]
; CHECK2: omp_region.body:
; CHECK2-NEXT: br label [[SEQ_PAR_MERGED:%.*]]
; CHECK2: seq.par.merged:
-; CHECK2-NEXT: call void @llvm.lifetime.start.p0(ptr noundef nonnull align 4 dereferenceable(4) [[B]])
+; CHECK2-NEXT: call void @llvm.lifetime.start.p0(ptr noundef nonnull align 4 captures(none) dereferenceable(4) [[B]])
; CHECK2-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP2]], 1
; CHECK2-NEXT: store i32 [[ADD]], ptr [[B]], align 4
; CHECK2-NEXT: br label [[OMP_PAR_MERGED_SPLIT:%.*]]
@@ -5352,7 +5352,7 @@ entry:
; CHECK2-NEXT: br i1 [[TOBOOL_NOT]], label [[IF_END:%.*]], label [[IF_THEN:%.*]]
; CHECK2: if.then:
; CHECK2-NEXT: [[TMP1:%.*]] = load i32, ptr [[DOTGLOBAL_TID_]], align 4, !invariant.load [[META7:![0-9]+]]
-; CHECK2-NEXT: [[TMP2:%.*]] = call i32 @__kmpc_cancel(ptr noundef nonnull @[[GLOB1]], i32 [[TMP1]], i32 noundef 1)
+; CHECK2-NEXT: [[TMP2:%.*]] = call i32 @__kmpc_cancel(ptr noundef nonnull @[[GLOB1]], i32 [[TMP1]], i32 noundef 1) #[[ATTR0]]
; CHECK2-NEXT: ret void
; CHECK2: if.end:
; CHECK2-NEXT: ret void
@@ -5366,7 +5366,7 @@ entry:
; CHECK2-NEXT: br i1 [[TOBOOL_NOT]], label [[IF_END:%.*]], label [[IF_THEN:%.*]]
; CHECK2: if.then:
; CHECK2-NEXT: [[TMP1:%.*]] = load i32, ptr [[DOTGLOBAL_TID_]], align 4, !invariant.load [[META7]]
-; CHECK2-NEXT: [[TMP2:%.*]] = call i32 @__kmpc_cancel(ptr noundef nonnull @[[GLOB1]], i32 [[TMP1]], i32 noundef 1)
+; CHECK2-NEXT: [[TMP2:%.*]] = call i32 @__kmpc_cancel(ptr noundef nonnull @[[GLOB1]], i32 [[TMP1]], i32 noundef 1) #[[ATTR0]]
; CHECK2-NEXT: ret void
; CHECK2: if.end:
; CHECK2-NEXT: ret void
@@ -5464,7 +5464,7 @@ entry:
; CHECK2-NEXT: br i1 [[TOBOOL_NOT]], label [[IF_END:%.*]], label [[IF_THEN:%.*]]
; CHECK2: if.then:
; CHECK2-NEXT: [[TMP1:%.*]] = load i32, ptr [[DOTGLOBAL_TID_]], align 4, !invariant.load [[META7]]
-; CHECK2-NEXT: [[TMP2:%.*]] = call i32 @__kmpc_cancel(ptr noundef nonnull @[[GLOB1]], i32 [[TMP1]], i32 noundef 1)
+; CHECK2-NEXT: [[TMP2:%.*]] = call i32 @__kmpc_cancel(ptr noundef nonnull @[[GLOB1]], i32 [[TMP1]], i32 noundef 1) #[[ATTR0]]
; CHECK2-NEXT: ret void
; CHECK2: if.end:
; CHECK2-NEXT: ret void
@@ -5478,7 +5478,7 @@ entry:
; CHECK2-NEXT: br i1 [[TOBOOL_NOT]], label [[IF_END:%.*]], label [[IF_THEN:%.*]]
; CHECK2: if.then:
; CHECK2-NEXT: [[TMP1:%.*]] = load i32, ptr [[DOTGLOBAL_TID_]], align 4, !invariant.load [[META7]]
-; CHECK2-NEXT: [[TMP2:%.*]] = call i32 @__kmpc_cancel(ptr noundef nonnull @[[GLOB1]], i32 [[TMP1]], i32 noundef 1)
+; CHECK2-NEXT: [[TMP2:%.*]] = call i32 @__kmpc_cancel(ptr noundef nonnull @[[GLOB1]], i32 [[TMP1]], i32 noundef 1) #[[ATTR0]]
; CHECK2-NEXT: ret void
; CHECK2: if.end:
; CHECK2-NEXT: ret void
More information about the llvm-branch-commits
mailing list