Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AssumeBundles] Dereferenceable used in bundle only applies at assume. #126117

Merged
merged 3 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion llvm/docs/LangRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1474,7 +1474,9 @@ Currently, only the following parameter attributes are defined:
``null_pointer_is_valid`` function attribute is present.
``n`` should be a positive number. The pointer should be well defined,
otherwise it is undefined behavior. This means ``dereferenceable(<n>)``
implies ``noundef``.
implies ``noundef``. When used in an assume operand bundle, more restricted
semantics apply. See :ref:`assume operand bundles <assume_opbundles>` for
more details.

``dereferenceable_or_null(<n>)``
This indicates that the parameter or return value isn't both
Expand Down Expand Up @@ -2926,6 +2928,11 @@ the behavior is undefined, unless one of the following exceptions applies:
(including a zero alignment). If this is the case, then the pointer value
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"and may not be" seems like you're referring to the assumption at first. Maybe: ". The pointer may not be " (the rest of the sentence is fine)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, thanks!

must be a null pointer, otherwise the behavior is undefined.

* ``dereferenceable(<n>)`` operand bundles only guarantee the pointer is
dereferenceable at the point of the assumption. The pointer may not be
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is indented too far and ends up rendering weirdly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should hopefully be fixed by 2d6330f

dereferenceable at later pointers, e.g. because it could have been
freed.

In addition to allowing operand bundles encoding function and parameter
attributes, an assume operand bundle my also encode a ``separate_storage``
operand bundle. This has the form:
Expand Down
9 changes: 6 additions & 3 deletions llvm/lib/Analysis/Loads.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@

using namespace llvm;

extern cl::opt<bool> UseDerefAtPointSemantics;

static bool isAligned(const Value *Base, Align Alignment,
const DataLayout &DL) {
return Base->getPointerAlignment(DL) >= Alignment;
Expand Down Expand Up @@ -171,7 +169,12 @@ static bool isDereferenceableAndAlignedPointer(
Size, DL, CtxI, AC, DT, TLI,
Visited, MaxDepth);

if (CtxI && (!UseDerefAtPointSemantics || !V->canBeFreed())) {
// Dereferenceable information from assumptions is only valid if the value
// cannot be freed between the assumption and use. For now just use the
// information for values that cannot be freed in the function.
// TODO: More precisely check if the pointer can be freed between assumption
// and use.
if (CtxI && !V->canBeFreed()) {
/// Look through assumes to see if both dereferencability and alignment can
/// be proven by an assume if needed.
RetainedKnowledge AlignRK;
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Analysis/ValueTracking/assume-queries-counter.ll
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ define dso_local i1 @test2(ptr readonly %0) {
ret i1 %2
}

define dso_local i32 @test4(ptr readonly %0, i1 %cond) {
define dso_local i32 @test4(ptr readonly %0, i1 %cond) nofree nosync {
; COUNTER1-LABEL: @test4(
; COUNTER1-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP0:%.*]], i32 4) ]
; COUNTER1-NEXT: br i1 [[COND:%.*]], label [[A:%.*]], label [[B:%.*]]
Expand Down
4 changes: 2 additions & 2 deletions llvm/test/Analysis/ValueTracking/assume.ll
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ define dso_local i1 @test2(ptr readonly %0) {
ret i1 %2
}

define dso_local i32 @test4(ptr readonly %0, i1 %cond) {
define dso_local i32 @test4(ptr readonly %0, i1 %cond) nofree nosync {
; CHECK-LABEL: @test4(
; CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP0:%.*]], i32 4) ]
; CHECK-NEXT: br i1 [[COND:%.*]], label [[A:%.*]], label [[B:%.*]]
Expand Down Expand Up @@ -91,7 +91,7 @@ A:
ret i32 %6
}

define dso_local i32 @test4a(ptr readonly %0, i1 %cond) {
define dso_local i32 @test4a(ptr readonly %0, i1 %cond) nofree nosync {
; CHECK-LABEL: @test4a(
; CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP0:%.*]], i32 4), "align"(ptr [[TMP0]], i32 8) ]
; CHECK-NEXT: br i1 [[COND:%.*]], label [[A:%.*]], label [[B:%.*]]
Expand Down
110 changes: 109 additions & 1 deletion llvm/test/Transforms/LICM/hoist-speculatable-load.ll
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ define void @f(i32 %ptr_i, ptr %ptr2, i1 %cond) {
; CHECK-NEXT: store i32 0, ptr [[PTR2:%.*]], align 4
; CHECK-NEXT: br label [[FOR_BODY_LR_PH]]
; CHECK: for.body.lr.ph:
; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[PTR]], align 4
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'd preserve test intent by adding nofree nosyc to this one (maybe with a name change), and then adding a separate one without it.

It isn't clear to my why with the additional attributes we still can't hoist the load. That's concerning?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added f_nofree_nosync below; the problem seems to be that %ptr is formed using inttoptr and Value::canBeFreed only applies to function arguments I think (it would be valid for the function to allocate and free locally).

So eventually we will probably need more powerful analysis.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a variant which doesn't involve the inttoptr then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a variant at the bottom, thanks

; CHECK-NEXT: br label [[FOR_BODY:%.*]]
; CHECK: for.body:
; CHECK-NEXT: [[I_08:%.*]] = phi i32 [ 0, [[FOR_BODY_LR_PH]] ], [ [[INC:%.*]], [[IF_END:%.*]] ]
; CHECK-NEXT: br i1 [[COND]], label [[IF_END]], label [[IF:%.*]]
; CHECK: if:
; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[PTR]], align 4, !invariant.load [[META0:![0-9]+]]
; CHECK-NEXT: store i32 [[TMP0]], ptr [[PTR2]], align 4
; CHECK-NEXT: br label [[IF_END]]
; CHECK: if.end:
Expand Down Expand Up @@ -56,4 +56,112 @@ exit: ; preds = %if.end, %entry
ret void
}

define void @f_nofree_nosync(i32 %ptr_i, ptr %ptr2, i1 %cond) nofree nosync {
; CHECK-LABEL: @f_nofree_nosync(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[PTR:%.*]] = inttoptr i32 [[PTR_I:%.*]] to ptr
; CHECK-NEXT: call void @llvm.assume(i1 true) [ "align"(ptr [[PTR]], i32 16), "dereferenceable"(ptr [[PTR]], i32 16) ]
; CHECK-NEXT: br i1 [[COND:%.*]], label [[FOR_BODY_LR_PH:%.*]], label [[IF0:%.*]]
; CHECK: if0:
; CHECK-NEXT: store i32 0, ptr [[PTR2:%.*]], align 4
; CHECK-NEXT: br label [[FOR_BODY_LR_PH]]
; CHECK: for.body.lr.ph:
; CHECK-NEXT: br label [[FOR_BODY:%.*]]
; CHECK: for.body:
; CHECK-NEXT: [[I_08:%.*]] = phi i32 [ 0, [[FOR_BODY_LR_PH]] ], [ [[INC:%.*]], [[IF_END:%.*]] ]
; CHECK-NEXT: br i1 [[COND]], label [[IF_END]], label [[IF:%.*]]
; CHECK: if:
; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[PTR]], align 4, !invariant.load [[META0]]
; CHECK-NEXT: store i32 [[TMP0]], ptr [[PTR2]], align 4
; CHECK-NEXT: br label [[IF_END]]
; CHECK: if.end:
; CHECK-NEXT: [[INC]] = add nuw nsw i32 [[I_08]], 1
; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[INC]], 2
; CHECK-NEXT: br i1 [[CMP]], label [[FOR_BODY]], label [[EXIT:%.*]]
; CHECK: exit:
; CHECK-NEXT: ret void
;
entry:
%ptr = inttoptr i32 %ptr_i to ptr
call void @llvm.assume(i1 true) [ "align"(ptr %ptr, i32 16), "dereferenceable"(ptr %ptr, i32 16) ]
br i1 %cond, label %for.body.lr.ph, label %if0

if0:
store i32 0, ptr %ptr2, align 4
br label %for.body.lr.ph

for.body.lr.ph: ; preds = %entry
br label %for.body

for.body: ; preds = %for.body.lr.ph, %if.end
%i.08 = phi i32 [ 0, %for.body.lr.ph ], [ %inc, %if.end ]
br i1 %cond, label %if.end, label %if

if:
%0 = load i32, ptr %ptr, align 4, !invariant.load !{}
store i32 %0, ptr %ptr2, align 4
br label %if.end

if.end: ; preds = %for.body
%inc = add nuw nsw i32 %i.08, 1
%cmp = icmp slt i32 %inc, 2
br i1 %cmp, label %for.body, label %exit

exit: ; preds = %if.end, %entry
ret void
}

define void @f_without_ptrtoint_and_with_nofree_nosync(ptr %ptr, ptr %ptr2, i1 %cond) nofree nosync {
; CHECK-LABEL: @f_without_ptrtoint_and_with_nofree_nosync(
; CHECK-NEXT: entry:
; CHECK-NEXT: call void @llvm.assume(i1 true) [ "align"(ptr [[PTR:%.*]], i32 16), "dereferenceable"(ptr [[PTR]], i32 16) ]
; CHECK-NEXT: br i1 [[COND:%.*]], label [[FOR_BODY_LR_PH:%.*]], label [[IF0:%.*]]
; CHECK: if0:
; CHECK-NEXT: store i32 0, ptr [[PTR2:%.*]], align 4
; CHECK-NEXT: br label [[FOR_BODY_LR_PH]]
; CHECK: for.body.lr.ph:
; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[PTR]], align 4
; CHECK-NEXT: br label [[FOR_BODY:%.*]]
; CHECK: for.body:
; CHECK-NEXT: [[I_08:%.*]] = phi i32 [ 0, [[FOR_BODY_LR_PH]] ], [ [[INC:%.*]], [[IF_END:%.*]] ]
; CHECK-NEXT: br i1 [[COND]], label [[IF_END]], label [[IF:%.*]]
; CHECK: if:
; CHECK-NEXT: store i32 [[TMP0]], ptr [[PTR2]], align 4
; CHECK-NEXT: br label [[IF_END]]
; CHECK: if.end:
; CHECK-NEXT: [[INC]] = add nuw nsw i32 [[I_08]], 1
; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[INC]], 2
; CHECK-NEXT: br i1 [[CMP]], label [[FOR_BODY]], label [[EXIT:%.*]]
; CHECK: exit:
; CHECK-NEXT: ret void
;
entry:
call void @llvm.assume(i1 true) [ "align"(ptr %ptr, i32 16), "dereferenceable"(ptr %ptr, i32 16) ]
br i1 %cond, label %for.body.lr.ph, label %if0

if0:
store i32 0, ptr %ptr2, align 4
br label %for.body.lr.ph

for.body.lr.ph: ; preds = %entry
br label %for.body

for.body: ; preds = %for.body.lr.ph, %if.end
%i.08 = phi i32 [ 0, %for.body.lr.ph ], [ %inc, %if.end ]
br i1 %cond, label %if.end, label %if

if:
%0 = load i32, ptr %ptr, align 4, !invariant.load !{}
store i32 %0, ptr %ptr2, align 4
br label %if.end

if.end: ; preds = %for.body
%inc = add nuw nsw i32 %i.08, 1
%cmp = icmp slt i32 %inc, 2
br i1 %cmp, label %for.body, label %exit

exit: ; preds = %if.end, %entry
ret void
}

declare void @llvm.assume(i1 noundef)
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
; RUN: opt -p loop-vectorize -force-vector-width=2 -use-dereferenceable-at-point-semantics -S %s | FileCheck %s
; RUN: opt -p loop-vectorize -force-vector-width=2 -S %s | FileCheck %s

declare void @llvm.assume(i1)

Expand Down Expand Up @@ -1411,7 +1411,6 @@ exit:
}

; %a may be freed between the dereferenceable assumption and accesses.
; It is not safe to use with -use-dereferenceable-at-point-semantics.
define void @may_free_align_deref_assumption_in_header_constant_trip_count_loop_invariant_ptr(ptr noalias %a, ptr noalias %b, ptr noalias %c) {
; CHECK-LABEL: define void @may_free_align_deref_assumption_in_header_constant_trip_count_loop_invariant_ptr(
; CHECK-SAME: ptr noalias [[A:%.*]], ptr noalias [[B:%.*]], ptr noalias [[C:%.*]]) {
Expand Down Expand Up @@ -1505,7 +1504,6 @@ exit:
}

; %a may be freed between the dereferenceable assumption and accesses.
; It is not safe to use with -use-dereferenceable-at-point-semantics.
define void @may_free_local_ptr_align_deref_assumption_in_header_constant_trip_count_loop_invariant_ptr(ptr noalias %b, ptr noalias %c) nofree nosync {
; CHECK-LABEL: define void @may_free_local_ptr_align_deref_assumption_in_header_constant_trip_count_loop_invariant_ptr(
; CHECK-SAME: ptr noalias [[B:%.*]], ptr noalias [[C:%.*]]) #[[ATTR1]] {
Expand Down
68 changes: 64 additions & 4 deletions llvm/test/Transforms/SimplifyCFG/speculate-derefable-load.ll
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
define i64 @align_deref_align(i1 %c, ptr %p) {
; CHECK-LABEL: define i64 @align_deref_align(
; CHECK-SAME: i1 [[C:%.*]], ptr [[P:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: [[ENTRY:.*]]:
; CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[P]], i64 8), "align"(ptr [[P]], i64 8) ]
; CHECK-NEXT: br i1 [[C]], label %[[IF:.*]], label %[[EXIT:.*]]
; CHECK: [[IF]]:
; CHECK-NEXT: [[V:%.*]] = load i64, ptr [[P]], align 8
; CHECK-NEXT: [[RES:%.*]] = select i1 [[C]], i64 [[V]], i64 0
; CHECK-NEXT: br label %[[EXIT]]
; CHECK: [[EXIT]]:
; CHECK-NEXT: [[RES:%.*]] = phi i64 [ [[V]], %[[IF]] ], [ 0, %[[ENTRY]] ]
; CHECK-NEXT: ret i64 [[RES]]
;
entry:
Expand All @@ -23,9 +27,64 @@ exit:
ret i64 %res
}

define i64 @align_deref_align_nofree_nosync(i1 %c, ptr %p) nofree nosync {
; CHECK-LABEL: define i64 @align_deref_align_nofree_nosync(
; CHECK-SAME: i1 [[C:%.*]], ptr [[P:%.*]]) #[[ATTR0:[0-9]+]] {
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[P]], i64 8), "align"(ptr [[P]], i64 8) ]
; CHECK-NEXT: [[V:%.*]] = load i64, ptr [[P]], align 8
; CHECK-NEXT: [[SPEC_SELECT:%.*]] = select i1 [[C]], i64 [[V]], i64 0
; CHECK-NEXT: ret i64 [[SPEC_SELECT]]
;
entry:
call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %p, i64 8), "align"(ptr %p, i64 8) ]
br i1 %c, label %if, label %exit

if:
%v = load i64, ptr %p, align 8
br label %exit

exit:
%res = phi i64 [ %v, %if ], [ 0, %entry ]
ret i64 %res
}

define i64 @assume_deref_align2(i1 %c1, i32 %x, ptr %p) {
; CHECK-LABEL: define i64 @assume_deref_align2(
; CHECK-SAME: i1 [[C1:%.*]], i32 [[X:%.*]], ptr [[P:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
; CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[P]], i64 8), "align"(ptr [[P]], i64 8) ]
; CHECK-NEXT: br i1 [[C1]], label %[[IF1:.*]], label %[[EXIT:.*]]
; CHECK: [[IF1]]:
; CHECK-NEXT: [[C2:%.*]] = icmp ugt i32 [[X]], 10
; CHECK-NEXT: br i1 [[C2]], label %[[IF2:.*]], label %[[EXIT]]
; CHECK: [[IF2]]:
; CHECK-NEXT: [[V:%.*]] = load i64, ptr [[P]], align 8
; CHECK-NEXT: br label %[[EXIT]]
; CHECK: [[EXIT]]:
; CHECK-NEXT: [[RES:%.*]] = phi i64 [ [[V]], %[[IF2]] ], [ 1, %[[IF1]] ], [ 0, %[[ENTRY]] ]
; CHECK-NEXT: ret i64 [[RES]]
;
entry:
call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %p, i64 8), "align"(ptr %p, i64 8) ]
br i1 %c1, label %if1, label %exit

if1:
%c2 = icmp ugt i32 %x, 10
br i1 %c2, label %if2, label %exit

if2:
%v = load i64, ptr %p, align 8
br label %exit

exit:
%res = phi i64 [ %v, %if2 ], [ 1, %if1 ], [ 0, %entry ]
ret i64 %res
}

define i64 @assume_deref_align2_nofree_nosync(i1 %c1, i32 %x, ptr %p) nofree nosync {
; CHECK-LABEL: define i64 @assume_deref_align2_nofree_nosync(
; CHECK-SAME: i1 [[C1:%.*]], i32 [[X:%.*]], ptr [[P:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[P]], i64 8), "align"(ptr [[P]], i64 8) ]
; CHECK-NEXT: [[C2:%.*]] = icmp ugt i32 [[X]], 10
Expand All @@ -51,9 +110,10 @@ exit:
ret i64 %res
}

define i64 @assume_deref_align_not_dominating(i1 %c, ptr %p) {

define i64 @assume_deref_align_not_dominating(i1 %c, ptr %p) nofree nosync {
; CHECK-LABEL: define i64 @assume_deref_align_not_dominating(
; CHECK-SAME: i1 [[C:%.*]], ptr [[P:%.*]]) {
; CHECK-SAME: i1 [[C:%.*]], ptr [[P:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[ENTRY:.*]]:
; CHECK-NEXT: br i1 [[C]], label %[[IF:.*]], label %[[EXIT:.*]]
; CHECK: [[IF]]:
Expand Down