https://github.com/tommat01 updated https://github.com/llvm/llvm-project/pull/218453
>From 36e15af31ac7f58a5fc9d3b36dadeb595bb99176 Mon Sep 17 00:00:00 2001 From: Tomas Matheson <[email protected]> Date: Mon, 24 Aug 2026 11:07:18 +0100 Subject: [PATCH 1/2] [Attributor] Check callback broker argument aliases correctly AbstractCallSite uses separate callback argument and broker operand numbers. AANoAliasCallSiteArgument currently skips the callback argument number while iterating broker operands, which can skip an unrelated operand and incorrectly infer noalias for aliased callback arguments. Skip the mapped call-site operand instead. Add a duplicated-operand regression where the callback and broker argument numbers differ. --- .../Transforms/IPO/AttributorAttributes.cpp | 2 +- .../Transforms/Attributor/callback-noalias.ll | 57 +++++++++++++++++++ llvm/test/Transforms/Attributor/callbacks.ll | 54 +++++++++--------- .../Transforms/OpenMP/parallel_deletion.ll | 10 ++-- 4 files changed, 91 insertions(+), 32 deletions(-) create mode 100644 llvm/test/Transforms/Attributor/callback-noalias.ll diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp index 8d16f49525ca8b..f30395679a51dc 100644 --- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp +++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp @@ -3907,7 +3907,7 @@ struct AANoAliasCallSiteArgument final : AANoAliasImpl { const AAMemoryBehavior &MemBehaviorAA, const CallBase &CB, unsigned OtherArgNo) { // We do not need to worry about aliasing with the underlying IRP. - if (this->getCalleeArgNo() == (int)OtherArgNo) + if (this->getCallSiteArgNo() == (int)OtherArgNo) return false; // If it is not a pointer or pointer vector we do not alias. diff --git a/llvm/test/Transforms/Attributor/callback-noalias.ll b/llvm/test/Transforms/Attributor/callback-noalias.ll new file mode 100644 index 00000000000000..9a9ec05dc0e8f2 --- /dev/null +++ b/llvm/test/Transforms/Attributor/callback-noalias.ll @@ -0,0 +1,57 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6 +; RUN: opt -passes=attributor -S < %s | FileCheck %s --check-prefixes=CHECK,MODULE +; RUN: opt -passes=attributor-cgscc -S < %s | FileCheck %s --check-prefixes=CHECK,CGSCC + +; A callback argument number is not necessarily the corresponding broker +; operand number. Make sure the alias comparison skips the actual call-site +; operand, rather than an unrelated mapped argument. + + +define void @caller(i64 %value) { +; CHECK-LABEL: define void @caller( +; CHECK-SAME: i64 [[VALUE:%.*]]) { +; CHECK-NEXT: [[SLOT:%.*]] = alloca i64, align 8 +; CHECK-NEXT: store i64 [[VALUE]], ptr [[SLOT]], align 8 +; CHECK-NEXT: call void @broker(ptr noundef nonnull @callback, ptr nofree noundef nonnull writeonly align 8 captures(none) dereferenceable(8) [[SLOT]], ptr nofree noundef nonnull readonly align 8 captures(none) dereferenceable(8) [[SLOT]]) +; CHECK-NEXT: ret void +; + %slot = alloca i64, align 8 + store i64 %value, ptr %slot, align 8 + call void @broker(ptr @callback, ptr %slot, ptr %slot) + ret void +} + +define internal void @callback(ptr align 8 dereferenceable(8) %a, +; MODULE-LABEL: define internal void @callback( +; MODULE-SAME: ptr nofree noundef nonnull writeonly align 8 captures(none) dereferenceable(8) [[A:%.*]], ptr nofree noundef nonnull readonly align 8 captures(none) dereferenceable(8) [[B:%.*]]) { +; MODULE-NEXT: store i64 0, ptr [[A]], align 8 +; MODULE-NEXT: call void @sync() +; MODULE-NEXT: [[VALUE:%.*]] = load i64, ptr [[B]], align 8 +; MODULE-NEXT: call void @use(i64 [[VALUE]]) +; MODULE-NEXT: ret void +; +; CGSCC-LABEL: define internal void @callback( +; CGSCC-SAME: ptr nofree noundef nonnull writeonly align 8 captures(none) dereferenceable(8) [[A:%.*]], ptr nofree noundef nonnull readonly align 8 captures(none) dereferenceable(8) [[B:%.*]]) { +; CGSCC-NEXT: store i64 0, ptr [[A]], align 8 +; CGSCC-NEXT: call void @sync() +; CGSCC-NEXT: [[VALUE:%.*]] = load i64, ptr [[B]], align 8, !invariant.load [[META0:![0-9]+]] +; CGSCC-NEXT: call void @use(i64 [[VALUE]]) +; CGSCC-NEXT: ret void +; + ptr align 8 dereferenceable(8) %b) { + store i64 0, ptr %a, align 8 + call void @sync() + %value = load i64, ptr %b, align 8 + call void @use(i64 %value) + ret void +} + +declare !callback !0 void @broker(ptr, ptr, ptr) +declare void @sync() +declare void @use(i64) memory(none) + +!0 = !{!1} +!1 = !{i64 0, i64 1, i64 2, i1 false} +;. +; CGSCC: [[META0]] = !{} +;. diff --git a/llvm/test/Transforms/Attributor/callbacks.ll b/llvm/test/Transforms/Attributor/callbacks.ll index 80a0b2befbbeec..884496e1dab9ac 100644 --- a/llvm/test/Transforms/Attributor/callbacks.ll +++ b/llvm/test/Transforms/Attributor/callbacks.ll @@ -53,7 +53,7 @@ define internal void @t0_callback_callee(ptr %is_not_null, ptr %ptr, ptr %a, i64 ; TUNIT-LABEL: define {{[^@]+}}@t0_callback_callee ; TUNIT-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr align 256 [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) { ; TUNIT-NEXT: entry: -; TUNIT-NEXT: [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8 +; TUNIT-NEXT: [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8, !invariant.load [[META0:![0-9]+]] ; TUNIT-NEXT: store i32 [[PTR_VAL]], ptr [[IS_NOT_NULL]], align 4 ; TUNIT-NEXT: [[TMP0:%.*]] = load ptr, ptr [[C]], align 64 ; TUNIT-NEXT: tail call void @t0_check(ptr align 256 [[A]], i64 noundef 99, ptr align 32 [[TMP0]]) @@ -62,9 +62,9 @@ define internal void @t0_callback_callee(ptr %is_not_null, ptr %ptr, ptr %a, i64 ; CGSCC-LABEL: define {{[^@]+}}@t0_callback_callee ; CGSCC-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr align 256 [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) { ; CGSCC-NEXT: entry: -; CGSCC-NEXT: [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8 +; CGSCC-NEXT: [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8, !invariant.load [[META0:![0-9]+]] ; CGSCC-NEXT: store i32 [[PTR_VAL]], ptr [[IS_NOT_NULL]], align 4 -; CGSCC-NEXT: [[TMP0:%.*]] = load ptr, ptr [[C]], align 64 +; CGSCC-NEXT: [[TMP0:%.*]] = load ptr, ptr [[C]], align 64, !invariant.load [[META0]] ; CGSCC-NEXT: tail call void @t0_check(ptr align 256 [[A]], i64 noundef 99, ptr [[TMP0]]) ; CGSCC-NEXT: ret void ; @@ -95,7 +95,7 @@ define void @t1_caller(ptr noalias %a) { ; TUNIT-NEXT: [[PTR:%.*]] = alloca i32, align 128 ; TUNIT-NEXT: store i32 42, ptr [[B]], align 32 ; TUNIT-NEXT: store ptr [[B]], ptr [[C]], align 64 -; TUNIT-NEXT: call void (ptr, ptr, ptr, ...) @t1_callback_broker(ptr noundef null, ptr noalias noundef nonnull align 128 captures(none) dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) @t1_callback_callee, ptr align 256 captures(none) [[A]], i64 undef, ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C]]) +; TUNIT-NEXT: call void (ptr, ptr, ptr, ...) @t1_callback_broker(ptr noundef null, ptr noalias noundef nonnull align 128 captures(none) dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) @t1_callback_callee, ptr noalias align 256 captures(none) [[A]], i64 undef, ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C]]) ; TUNIT-NEXT: ret void ; ; CGSCC-LABEL: define {{[^@]+}}@t1_caller @@ -106,7 +106,7 @@ define void @t1_caller(ptr noalias %a) { ; CGSCC-NEXT: [[PTR:%.*]] = alloca i32, align 128 ; CGSCC-NEXT: store i32 42, ptr [[B]], align 32 ; CGSCC-NEXT: store ptr [[B]], ptr [[C]], align 64 -; CGSCC-NEXT: call void (ptr, ptr, ptr, ...) @t1_callback_broker(ptr noundef null, ptr noalias noundef nonnull align 128 captures(none) dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) @t1_callback_callee, ptr align 256 captures(none) [[A]], i64 noundef 99, ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C]]) +; CGSCC-NEXT: call void (ptr, ptr, ptr, ...) @t1_callback_broker(ptr noundef null, ptr noalias noundef nonnull align 128 captures(none) dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) @t1_callback_callee, ptr noalias align 256 captures(none) [[A]], i64 noundef 99, ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C]]) ; CGSCC-NEXT: ret void ; entry: @@ -125,9 +125,9 @@ define internal void @t1_callback_callee(ptr %is_not_null, ptr %ptr, ptr %a, i64 ; ; TUNIT: Function Attrs: nosync ; TUNIT-LABEL: define {{[^@]+}}@t1_callback_callee -; TUNIT-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr align 256 captures(none) [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) #[[ATTR0:[0-9]+]] { +; TUNIT-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr noalias align 256 captures(none) [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) #[[ATTR0:[0-9]+]] { ; TUNIT-NEXT: entry: -; TUNIT-NEXT: [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8 +; TUNIT-NEXT: [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8, !invariant.load [[META0]] ; TUNIT-NEXT: store i32 [[PTR_VAL]], ptr [[IS_NOT_NULL]], align 4 ; TUNIT-NEXT: [[TMP0:%.*]] = load ptr, ptr [[C]], align 64 ; TUNIT-NEXT: tail call void @t1_check(ptr align 256 captures(none) [[A]], i64 noundef 99, ptr align 32 captures(none) [[TMP0]]) @@ -135,11 +135,11 @@ define internal void @t1_callback_callee(ptr %is_not_null, ptr %ptr, ptr %a, i64 ; ; CGSCC: Function Attrs: nosync ; CGSCC-LABEL: define {{[^@]+}}@t1_callback_callee -; CGSCC-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr align 256 captures(none) [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) #[[ATTR0:[0-9]+]] { +; CGSCC-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr noalias align 256 captures(none) [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) #[[ATTR0:[0-9]+]] { ; CGSCC-NEXT: entry: -; CGSCC-NEXT: [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8 +; CGSCC-NEXT: [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8, !invariant.load [[META0]] ; CGSCC-NEXT: store i32 [[PTR_VAL]], ptr [[IS_NOT_NULL]], align 4 -; CGSCC-NEXT: [[TMP0:%.*]] = load ptr, ptr [[C]], align 64 +; CGSCC-NEXT: [[TMP0:%.*]] = load ptr, ptr [[C]], align 64, !invariant.load [[META0]] ; CGSCC-NEXT: tail call void @t1_check(ptr align 256 captures(none) [[A]], i64 noundef 99, ptr captures(none) [[TMP0]]) ; CGSCC-NEXT: ret void ; @@ -168,7 +168,7 @@ define void @t2_caller(ptr noalias %a) { ; TUNIT-NEXT: [[PTR:%.*]] = alloca i32, align 128 ; TUNIT-NEXT: store i32 42, ptr [[B]], align 32 ; TUNIT-NEXT: store ptr [[B]], ptr [[C]], align 64 -; TUNIT-NEXT: call void (ptr, ptr, ptr, ...) @t2_callback_broker(ptr noundef null, ptr noalias noundef nonnull align 128 captures(none) dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) @t2_callback_callee, ptr align 256 captures(none) [[A]], i64 undef, ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C]]) +; TUNIT-NEXT: call void (ptr, ptr, ptr, ...) @t2_callback_broker(ptr noundef null, ptr noalias noundef nonnull align 128 captures(none) dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) @t2_callback_callee, ptr noalias align 256 captures(none) [[A]], i64 undef, ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C]]) ; TUNIT-NEXT: ret void ; ; CGSCC-LABEL: define {{[^@]+}}@t2_caller @@ -179,7 +179,7 @@ define void @t2_caller(ptr noalias %a) { ; CGSCC-NEXT: [[PTR:%.*]] = alloca i32, align 128 ; CGSCC-NEXT: store i32 42, ptr [[B]], align 32 ; CGSCC-NEXT: store ptr [[B]], ptr [[C]], align 64 -; CGSCC-NEXT: call void (ptr, ptr, ptr, ...) @t2_callback_broker(ptr noundef null, ptr noalias noundef nonnull align 128 captures(none) dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) @t2_callback_callee, ptr align 256 captures(none) [[A]], i64 noundef 99, ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C]]) +; CGSCC-NEXT: call void (ptr, ptr, ptr, ...) @t2_callback_broker(ptr noundef null, ptr noalias noundef nonnull align 128 captures(none) dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) @t2_callback_callee, ptr noalias align 256 captures(none) [[A]], i64 noundef 99, ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C]]) ; CGSCC-NEXT: ret void ; entry: @@ -201,7 +201,7 @@ define internal void @t2_callback_callee(ptr %is_not_null, ptr %ptr, ptr %a, i64 ; TUNIT-LABEL: define {{[^@]+}}@t2_callback_callee ; TUNIT-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr align 256 captures(none) [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) { ; TUNIT-NEXT: entry: -; TUNIT-NEXT: [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8 +; TUNIT-NEXT: [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8, !invariant.load [[META0]] ; TUNIT-NEXT: store i32 [[PTR_VAL]], ptr [[IS_NOT_NULL]], align 4 ; TUNIT-NEXT: [[TMP0:%.*]] = load ptr, ptr [[C]], align 64 ; TUNIT-NEXT: tail call void @t2_check(ptr align 256 captures(none) [[A]], i64 noundef 99, ptr align 32 captures(none) [[TMP0]]) @@ -210,9 +210,9 @@ define internal void @t2_callback_callee(ptr %is_not_null, ptr %ptr, ptr %a, i64 ; CGSCC-LABEL: define {{[^@]+}}@t2_callback_callee ; CGSCC-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr align 256 captures(none) [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) { ; CGSCC-NEXT: entry: -; CGSCC-NEXT: [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8 +; CGSCC-NEXT: [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8, !invariant.load [[META0]] ; CGSCC-NEXT: store i32 [[PTR_VAL]], ptr [[IS_NOT_NULL]], align 4 -; CGSCC-NEXT: [[TMP0:%.*]] = load ptr, ptr [[C]], align 64 +; CGSCC-NEXT: [[TMP0:%.*]] = load ptr, ptr [[C]], align 64, !invariant.load [[META0]] ; CGSCC-NEXT: tail call void @t2_check(ptr align 256 captures(none) [[A]], i64 noundef 99, ptr captures(none) [[TMP0]]) ; CGSCC-NEXT: ret void ; @@ -241,8 +241,8 @@ define void @t3_caller(ptr noalias %a) { ; TUNIT-NEXT: [[PTR:%.*]] = alloca i32, align 128 ; TUNIT-NEXT: store i32 42, ptr [[B]], align 32 ; TUNIT-NEXT: store ptr [[B]], ptr [[C]], align 64 -; TUNIT-NEXT: call void (ptr, ptr, ptr, ...) @t3_callback_broker(ptr noundef null, ptr noalias noundef nonnull align 128 captures(none) dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) @t3_callback_callee, ptr align 256 captures(none) [[A]], i64 undef, ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C]]) -; TUNIT-NEXT: call void (ptr, ptr, ptr, ...) @t3_callback_broker(ptr noundef null, ptr noalias noundef nonnull align 128 captures(none) dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) @t3_callback_callee, ptr align 256 captures(none) [[A]], i64 undef, ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C]]) +; TUNIT-NEXT: call void (ptr, ptr, ptr, ...) @t3_callback_broker(ptr noundef null, ptr noalias noundef nonnull align 128 captures(none) dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) @t3_callback_callee, ptr noalias align 256 captures(none) [[A]], i64 undef, ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C]]) +; TUNIT-NEXT: call void (ptr, ptr, ptr, ...) @t3_callback_broker(ptr noundef null, ptr noalias noundef nonnull align 128 captures(none) dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) @t3_callback_callee, ptr noalias align 256 captures(none) [[A]], i64 undef, ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C]]) ; TUNIT-NEXT: ret void ; ; CGSCC-LABEL: define {{[^@]+}}@t3_caller @@ -253,8 +253,8 @@ define void @t3_caller(ptr noalias %a) { ; CGSCC-NEXT: [[PTR:%.*]] = alloca i32, align 128 ; CGSCC-NEXT: store i32 42, ptr [[B]], align 32 ; CGSCC-NEXT: store ptr [[B]], ptr [[C]], align 64 -; CGSCC-NEXT: call void (ptr, ptr, ptr, ...) @t3_callback_broker(ptr noundef null, ptr noalias noundef nonnull align 128 captures(none) dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) @t3_callback_callee, ptr align 256 captures(none) [[A]], i64 noundef 99, ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C]]) -; CGSCC-NEXT: call void (ptr, ptr, ptr, ...) @t3_callback_broker(ptr noundef null, ptr noalias noundef nonnull align 128 captures(none) dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) @t3_callback_callee, ptr align 256 captures(none) [[A]], i64 noundef 99, ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C]]) +; CGSCC-NEXT: call void (ptr, ptr, ptr, ...) @t3_callback_broker(ptr noundef null, ptr noalias noundef nonnull align 128 captures(none) dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) @t3_callback_callee, ptr noalias align 256 captures(none) [[A]], i64 noundef 99, ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C]]) +; CGSCC-NEXT: call void (ptr, ptr, ptr, ...) @t3_callback_broker(ptr noundef null, ptr noalias noundef nonnull align 128 captures(none) dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) @t3_callback_callee, ptr noalias align 256 captures(none) [[A]], i64 noundef 99, ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C]]) ; CGSCC-NEXT: ret void ; entry: @@ -277,7 +277,7 @@ define internal void @t3_callback_callee(ptr %is_not_null, ptr %ptr, ptr %a, i64 ; TUNIT-LABEL: define {{[^@]+}}@t3_callback_callee ; TUNIT-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr align 256 captures(none) [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) { ; TUNIT-NEXT: entry: -; TUNIT-NEXT: [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8 +; TUNIT-NEXT: [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8, !invariant.load [[META0]] ; TUNIT-NEXT: store i32 [[PTR_VAL]], ptr [[IS_NOT_NULL]], align 4 ; TUNIT-NEXT: [[TMP0:%.*]] = load ptr, ptr [[C]], align 64 ; TUNIT-NEXT: tail call void @t3_check(ptr align 256 captures(none) [[A]], i64 noundef 99, ptr align 32 captures(none) [[TMP0]]) @@ -286,9 +286,9 @@ define internal void @t3_callback_callee(ptr %is_not_null, ptr %ptr, ptr %a, i64 ; CGSCC-LABEL: define {{[^@]+}}@t3_callback_callee ; CGSCC-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr align 256 captures(none) [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) { ; CGSCC-NEXT: entry: -; CGSCC-NEXT: [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8 +; CGSCC-NEXT: [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8, !invariant.load [[META0]] ; CGSCC-NEXT: store i32 [[PTR_VAL]], ptr [[IS_NOT_NULL]], align 4 -; CGSCC-NEXT: [[TMP0:%.*]] = load ptr, ptr [[C]], align 64 +; CGSCC-NEXT: [[TMP0:%.*]] = load ptr, ptr [[C]], align 64, !invariant.load [[META0]] ; CGSCC-NEXT: tail call void @t3_check(ptr align 256 captures(none) [[A]], i64 noundef 99, ptr captures(none) [[TMP0]]) ; CGSCC-NEXT: ret void ; @@ -311,11 +311,13 @@ declare !callback !0 void @t3_callback_broker(ptr nocapture , ptr nocapture , pt ;. ; CGSCC: attributes #[[ATTR0]] = { nosync } ;. -; TUNIT: [[META0:![0-9]+]] = !{[[META1:![0-9]+]]} -; TUNIT: [[META1]] = !{i64 2, i64 -1, i64 -1, i1 true} +; TUNIT: [[META0]] = !{} +; TUNIT: [[META1:![0-9]+]] = !{[[META2:![0-9]+]]} +; TUNIT: [[META2]] = !{i64 2, i64 -1, i64 -1, i1 true} ;. -; CGSCC: [[META0:![0-9]+]] = !{[[META1:![0-9]+]]} -; CGSCC: [[META1]] = !{i64 2, i64 -1, i64 -1, i1 true} +; CGSCC: [[META0]] = !{} +; CGSCC: [[META1:![0-9]+]] = !{[[META2:![0-9]+]]} +; CGSCC: [[META2]] = !{i64 2, i64 -1, i64 -1, i1 true} ;. ;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line: ; CHECK: {{.*}} diff --git a/llvm/test/Transforms/OpenMP/parallel_deletion.ll b/llvm/test/Transforms/OpenMP/parallel_deletion.ll index 9d4eb7faa82561..c362cebeda9b04 100644 --- a/llvm/test/Transforms/OpenMP/parallel_deletion.ll +++ b/llvm/test/Transforms/OpenMP/parallel_deletion.ll @@ -284,10 +284,10 @@ define void @delete_parallel_2() { ; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 ; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr noundef nonnull align 4 dereferenceable(4) [[A]]) #[[ATTR18:[0-9]+]] ; CHECK-NEXT: store i32 0, ptr [[A]], align 4 -; CHECK-NEXT: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr noundef nonnull align 8 dereferenceable(24) @[[GLOB0]], i32 noundef 1, ptr noundef nonnull @.omp_outlined..3, ptr nofree noundef nonnull align 4 captures(none) dereferenceable(4) [[A]]) -; CHECK-NEXT: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr noundef nonnull align 8 dereferenceable(24) @[[GLOB0]], i32 noundef 1, ptr noundef nonnull @.omp_outlined..4, ptr nofree noundef nonnull align 4 captures(none) dereferenceable(4) [[A]]) -; CHECK-NEXT: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr noundef nonnull align 8 dereferenceable(24) @[[GLOB0]], i32 noundef 1, ptr noundef nonnull @.omp_outlined..5, ptr nofree noundef nonnull align 4 captures(none) dereferenceable(4) [[A]]) -; CHECK-NEXT: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr noundef nonnull align 8 dereferenceable(24) @[[GLOB0]], i32 noundef 1, ptr noundef nonnull @.omp_outlined..6, ptr nofree noundef nonnull align 4 captures(none) dereferenceable(4) [[A]]) +; CHECK-NEXT: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr noundef nonnull align 8 dereferenceable(24) @[[GLOB0]], i32 noundef 1, ptr noundef nonnull @.omp_outlined..3, ptr noalias nofree noundef nonnull align 4 captures(none) dereferenceable(4) [[A]]) +; CHECK-NEXT: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr noundef nonnull align 8 dereferenceable(24) @[[GLOB0]], i32 noundef 1, ptr noundef nonnull @.omp_outlined..4, ptr noalias nofree noundef nonnull align 4 captures(none) dereferenceable(4) [[A]]) +; CHECK-NEXT: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr noundef nonnull align 8 dereferenceable(24) @[[GLOB0]], i32 noundef 1, ptr noundef nonnull @.omp_outlined..5, ptr noalias nofree noundef nonnull align 4 captures(none) dereferenceable(4) [[A]]) +; CHECK-NEXT: call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr noundef nonnull align 8 dereferenceable(24) @[[GLOB0]], i32 noundef 1, ptr noundef nonnull @.omp_outlined..6, ptr noalias nofree noundef nonnull align 4 captures(none) dereferenceable(4) [[A]]) ; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr noundef nonnull [[A]]) ; CHECK-NEXT: ret void ; @@ -327,7 +327,7 @@ entry: define internal void @.omp_outlined..3(ptr noalias %.global_tid., ptr noalias %.bound_tid., ptr dereferenceable(4) %a) { ; CHECK-LABEL: define {{[^@]+}}@.omp_outlined..3 -; CHECK-SAME: (ptr noalias nofree readnone captures(none) [[DOTGLOBAL_TID_:%.*]], ptr noalias nofree readnone captures(none) [[DOTBOUND_TID_:%.*]], ptr nofree noundef nonnull align 4 captures(none) dereferenceable(4) [[A:%.*]]) #[[ATTR6:[0-9]+]] { +; CHECK-SAME: (ptr noalias nofree readnone captures(none) [[DOTGLOBAL_TID_:%.*]], ptr noalias nofree readnone captures(none) [[DOTBOUND_TID_:%.*]], ptr noalias nofree noundef nonnull align 4 captures(none) dereferenceable(4) [[A:%.*]]) #[[ATTR6:[0-9]+]] { ; CHECK-NEXT: entry: ; CHECK-NEXT: [[CALL:%.*]] = call i32 @omp_get_thread_num() #[[ATTR19:[0-9]+]] ; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[CALL]], 0 >From a365f99d3e823361686d66c3a26584053f7fb22c Mon Sep 17 00:00:00 2001 From: Tomas Matheson <[email protected]> Date: Mon, 24 Aug 2026 11:07:25 +0100 Subject: [PATCH 2/2] [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 | 29 ++++++++ .../OpenMP/callback-capture-lifetime.f90 | 40 +++++++++++ llvm/lib/Transforms/IPO/OpenMPOpt.cpp | 21 +++++- .../OpenMP/callback-capture-licm.ll | 67 +++++++++++++++++++ .../Transforms/OpenMP/callback-noalias.ll | 34 ++++++++++ .../Transforms/OpenMP/parallel_deletion.ll | 10 +-- .../OpenMP/parallel_region_merging.ll | 12 ++-- 8 files changed, 209 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-licm.ll create mode 100644 llvm/test/Transforms/OpenMP/callback-noalias.ll diff --git a/clang/test/OpenMP/bug54082.c b/clang/test/OpenMP/bug54082.c index 6b8c93e9ffc96c..b317a88d8aacbe 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 00000000000000..4e75c53197c930 --- /dev/null +++ b/clang/test/OpenMP/callback_capture_lifetime.cpp @@ -0,0 +1,29 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --functions ".*omp_outlined$" --filter "define internal void" --filter "load ptr, ptr" --filter "@opaque" --filter "getelementptr" --include-generated-funcs --version 6 +// Verify end-to-end that Clang's host OpenMP pipeline infers noalias for a +// captured pointer container and hoists its load out of a loop containing an +// unrelated call. +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -fopenmp -x c++ \ +// RUN: -emit-llvm -o - %s | FileCheck %s + +extern bool enabled[64]; +extern "C" void opaque(); +extern "C" void use(double); + +void use_enabled(double *values) { +#pragma omp parallel + for (int i = 0; i < 64; ++i) { + opaque(); + if (enabled[i]) + use(values[i]); + } +} +// CHECK-LABEL: define dso_local void @_Z11use_enabledPd( +// CHECK-SAME: ptr noundef [[VALUES:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] { +// +// CHECK-LABEL: define internal void @_Z11use_enabledPd.omp_outlined( +// CHECK-SAME: ptr noalias nofree readnone captures(none) [[DOTGLOBAL_TID_:%.*]], ptr noalias nofree readnone captures(none) [[DOTBOUND_TID_:%.*]], ptr noalias noundef nonnull readonly align 8 captures(none) dereferenceable(8) [[VALUES:%.*]]) #[[ATTR1:[0-9]+]] { +// CHECK: [[TMP0:%.*]] = load ptr, ptr [[VALUES]], align 8 +// CHECK: tail call void @opaque() #[[ATTR3:[0-9]+]] +// CHECK: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr @enabled, i64 [[INDVARS_IV:%.*]] +// CHECK: [[ARRAYIDX2:%.*]] = getelementptr inbounds nuw [8 x i8], ptr [[TMP0]], i64 [[INDVARS_IV]] +// 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 00000000000000..b824ddcba00c19 --- /dev/null +++ b/flang/test/Integration/OpenMP/callback-capture-lifetime.f90 @@ -0,0 +1,40 @@ +!===----------------------------------------------------------------------===! +! 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 OpenMPOpt infers noalias for Flang's aggregate capture +! container, allowing both captured addresses to be loaded before an opaque +! call. +! +! RUN: %flang_fc1 -O2 -fopenmp -emit-llvm %s -o - | FileCheck %s + +subroutine capture_lifetime(first, second) + integer(8), intent(in) :: first, second + + interface + subroutine opaque() + end subroutine + subroutine use_value(value) + integer(8), value :: value + end subroutine + end interface + + !$omp parallel shared(first, second) + call opaque() + call use_value(first) + call use_value(second) + !$omp end parallel +end subroutine + +! CHECK-LABEL: define internal void @capture_lifetime_..omp_par( +! CHECK-SAME: ptr noalias readonly captures(none) [[CAPTURES:%.*]]) +! CHECK: [[FIRST:%.*]] = load ptr, ptr [[CAPTURES]], align 8 +! CHECK-NEXT: [[SECOND_FIELD:%.*]] = getelementptr i8, ptr [[CAPTURES]], i64 8 +! CHECK-NEXT: [[SECOND:%.*]] = load ptr, ptr [[SECOND_FIELD]], align 8 +! CHECK-NEXT: tail call void @opaque_() +! CHECK: load i64, ptr [[FIRST]], align 8 +! CHECK: load i64, ptr [[SECOND]], align 8 diff --git a/llvm/lib/Transforms/IPO/OpenMPOpt.cpp b/llvm/lib/Transforms/IPO/OpenMPOpt.cpp index 22a5efb402504a..0fff56cf17ced4 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-licm.ll b/llvm/test/Transforms/OpenMP/callback-capture-licm.ll new file mode 100644 index 00000000000000..086ec1972393cc --- /dev/null +++ b/llvm/test/Transforms/OpenMP/callback-capture-licm.ll @@ -0,0 +1,67 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function outlined --version 6 +; Verify that noalias inferred for host callback capture storage lets LICM +; hoist its load out of a loop containing an unknown call. +; +; RUN: opt -passes='openmp-opt,function(loop-mssa(licm))' -S < %s | FileCheck %s + +target triple = "aarch64-unknown-linux-gnu" + +define void @run(ptr %value) { + %capture = alloca ptr, align 8 + store ptr %value, ptr %capture, align 8 + call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 1, ptr @outlined, ptr %capture) + ret void +} + +define internal void @outlined(ptr %global_tid, ptr %bound_tid, ptr align 8 dereferenceable(8) %capture) { +; CHECK-LABEL: define internal void @outlined( +; CHECK-SAME: ptr [[GLOBAL_TID:%.*]], ptr [[BOUND_TID:%.*]], ptr noalias readonly align 8 captures(none) dereferenceable(8) [[CAPTURE:%.*]]) { +; CHECK-NEXT: [[ENTRY:.*]]: +; CHECK-NEXT: [[VALUE:%.*]] = load ptr, ptr [[CAPTURE]], align 8 +; CHECK-NEXT: br label %[[LOOP:.*]] +; CHECK: [[LOOP]]: +; CHECK-NEXT: [[INDEX:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[NEXT:%.*]], %[[LATCH:.*]] ] +; CHECK-NEXT: call void @opaque() +; CHECK-NEXT: [[ENABLED:%.*]] = icmp eq i64 [[INDEX]], 7 +; CHECK-NEXT: br i1 [[ENABLED]], label %[[IF_THEN:.*]], label %[[LATCH]] +; CHECK: [[IF_THEN]]: +; CHECK-NEXT: call void @use(ptr [[VALUE]]) +; CHECK-NEXT: br label %[[LATCH]] +; CHECK: [[LATCH]]: +; CHECK-NEXT: [[NEXT]] = add nuw nsw i64 [[INDEX]], 1 +; CHECK-NEXT: [[DONE:%.*]] = icmp eq i64 [[NEXT]], 64 +; CHECK-NEXT: br i1 [[DONE]], label %[[EXIT:.*]], label %[[LOOP]] +; CHECK: [[EXIT]]: +; CHECK-NEXT: ret void +; +entry: + br label %loop + +loop: + %index = phi i64 [ 0, %entry ], [ %next, %latch ] + call void @opaque() + %enabled = icmp eq i64 %index, 7 + br i1 %enabled, label %if.then, label %latch + +if.then: + %value = load ptr, ptr %capture, align 8 + call void @use(ptr %value) + br label %latch + +latch: + %next = add nuw nsw i64 %index, 1 + %done = icmp eq i64 %next, 64 + br i1 %done, label %exit, label %loop + +exit: + ret void +} + +declare !callback !0 void @__kmpc_fork_call(ptr, i32, ptr, ...) +declare void @opaque() +declare void @use(ptr) memory(none) + +!0 = !{!1} +!1 = !{i64 2, i64 -1, i64 -1, i1 true} +!llvm.module.flags = !{!2} +!2 = !{i32 7, !"openmp", i32 51} diff --git a/llvm/test/Transforms/OpenMP/callback-noalias.ll b/llvm/test/Transforms/OpenMP/callback-noalias.ll new file mode 100644 index 00000000000000..aee80f31a7235c --- /dev/null +++ b/llvm/test/Transforms/OpenMP/callback-noalias.ll @@ -0,0 +1,34 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function outlined --version 6 +; Verify that host OpenMPOpt seeds noalias reasoning for a callback argument +; mapped to capture storage by callback metadata. +; +; RUN: opt -passes=openmp-opt -S < %s | FileCheck %s + +target triple = "aarch64-unknown-linux-gnu" + +define void @run(i64 %value) { + %capture = alloca i64, align 8 + store i64 %value, ptr %capture, align 8 + call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 1, ptr @outlined, ptr %capture) + ret void +} + +define internal void @outlined(ptr %global_tid, ptr %bound_tid, ptr align 8 dereferenceable(8) %capture) { +; CHECK-LABEL: define internal void @outlined( +; CHECK-SAME: ptr [[GLOBAL_TID:%.*]], ptr [[BOUND_TID:%.*]], ptr noalias readonly align 8 captures(none) dereferenceable(8) [[CAPTURE:%.*]]) #[[ATTR1:[0-9]+]] { +; CHECK-NEXT: [[VALUE:%.*]] = load i64, ptr [[CAPTURE]], align 8 +; CHECK-NEXT: call void @use(i64 [[VALUE]]) #[[ATTR3:[0-9]+]] +; CHECK-NEXT: ret void +; + %value = load i64, ptr %capture, align 8 + call void @use(i64 %value) + ret void +} + +declare !callback !0 void @__kmpc_fork_call(ptr, i32, ptr, ...) +declare void @use(i64) memory(none) + +!0 = !{!1} +!1 = !{i64 2, i64 -1, i64 -1, i1 true} +!llvm.module.flags = !{!2} +!2 = !{i32 7, !"openmp", i32 51} diff --git a/llvm/test/Transforms/OpenMP/parallel_deletion.ll b/llvm/test/Transforms/OpenMP/parallel_deletion.ll index c362cebeda9b04..7003f81756ac9f 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 6d3e5935d2e110..312a4ec8be74b1 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
