Skip to content

Commit bf5d96c

Browse files
authored
[IR] Add dead_on_unwind attribute (#74289)
Add the `dead_on_unwind` attribute, which states that the caller will not read from this argument if the call unwinds. This allows eliding stores that could otherwise be visible on the unwind path, for example: ``` declare void @may_unwind() define void @src(ptr noalias dead_on_unwind %out) { store i32 0, ptr %out call void @may_unwind() store i32 1, ptr %out ret void } define void @tgt(ptr noalias dead_on_unwind %out) { call void @may_unwind() store i32 1, ptr %out ret void } ``` The optimization is not valid without `dead_on_unwind`, because the `i32 0` value might be read if `@may_unwind` unwinds. This attribute is primarily intended to be used on sret arguments. In fact, I previously wanted to change the semantics of sret to include this "no read after unwind" property (see D116998), but based on the feedback there it is better to keep these attributes orthogonal (sret is an ABI attribute, dead_on_unwind is an optimization attribute). This is a reboot of that change with a separate attribute.
1 parent a2691e3 commit bf5d96c

File tree

13 files changed

+80
-15
lines changed

13 files changed

+80
-15
lines changed

llvm/docs/LangRef.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1576,6 +1576,20 @@ Currently, only the following parameter attributes are defined:
15761576
``readonly`` or a ``memory`` attribute that does not contain
15771577
``argmem: write``.
15781578

1579+
``dead_on_unwind``
1580+
At a high level, this attribute indicates that the pointer argument is dead
1581+
if the call unwinds, in the sense that the caller will not depend on the
1582+
contents of the memory. Stores that would only be visible on the unwind
1583+
path can be elided.
1584+
1585+
More precisely, the behavior is as-if any memory written through the
1586+
pointer during the execution of the function is overwritten with a poison
1587+
value on unwind. This includes memory written by the implicit write implied
1588+
by the ``writable`` attribute. The caller is allowed to access the affected
1589+
memory, but all loads that are not preceded by a store will return poison.
1590+
1591+
This attribute cannot be applied to return values.
1592+
15791593
.. _gc:
15801594

15811595
Garbage Collector Strategy Names

llvm/include/llvm/Bitcode/LLVMBitCodes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,7 @@ enum AttributeKindCodes {
723723
ATTR_KIND_OPTIMIZE_FOR_DEBUGGING = 88,
724724
ATTR_KIND_WRITABLE = 89,
725725
ATTR_KIND_CORO_ONLY_DESTROY_WHEN_COMPLETE = 90,
726+
ATTR_KIND_DEAD_ON_UNWIND = 91,
726727
};
727728

728729
enum ComdatSelectionKindCodes {

llvm/include/llvm/IR/Attributes.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,9 @@ def NoDuplicate : EnumAttr<"noduplicate", [FnAttr]>;
151151
/// Function does not deallocate memory.
152152
def NoFree : EnumAttr<"nofree", [FnAttr, ParamAttr]>;
153153

154+
/// Argument is dead if the call unwinds.
155+
def DeadOnUnwind : EnumAttr<"dead_on_unwind", [ParamAttr]>;
156+
154157
/// Disable implicit floating point insts.
155158
def NoImplicitFloat : EnumAttr<"noimplicitfloat", [FnAttr]>;
156159

llvm/lib/Analysis/AliasAnalysis.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -896,7 +896,7 @@ bool llvm::isNotVisibleOnUnwind(const Value *Object,
896896

897897
// Byval goes out of scope on unwind.
898898
if (auto *A = dyn_cast<Argument>(Object))
899-
return A->hasByValAttr();
899+
return A->hasByValAttr() || A->hasAttribute(Attribute::DeadOnUnwind);
900900

901901
// A noalias return is not accessible from any other code. If the pointer
902902
// does not escape prior to the unwind, then the caller cannot access the

llvm/lib/Bitcode/Reader/BitcodeReader.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2098,6 +2098,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
20982098
return Attribute::Writable;
20992099
case bitc::ATTR_KIND_CORO_ONLY_DESTROY_WHEN_COMPLETE:
21002100
return Attribute::CoroDestroyOnlyWhenComplete;
2101+
case bitc::ATTR_KIND_DEAD_ON_UNWIND:
2102+
return Attribute::DeadOnUnwind;
21012103
}
21022104
}
21032105

llvm/lib/Bitcode/Writer/BitcodeWriter.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
827827
return bitc::ATTR_KIND_WRITABLE;
828828
case Attribute::CoroDestroyOnlyWhenComplete:
829829
return bitc::ATTR_KIND_CORO_ONLY_DESTROY_WHEN_COMPLETE;
830+
case Attribute::DeadOnUnwind:
831+
return bitc::ATTR_KIND_DEAD_ON_UNWIND;
830832
case Attribute::EndAttrKinds:
831833
llvm_unreachable("Can not encode end-attribute kinds marker.");
832834
case Attribute::None:

llvm/lib/IR/Attributes.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1962,7 +1962,8 @@ AttributeMask AttributeFuncs::typeIncompatible(Type *Ty,
19621962
.addAttribute(Attribute::ReadOnly)
19631963
.addAttribute(Attribute::Dereferenceable)
19641964
.addAttribute(Attribute::DereferenceableOrNull)
1965-
.addAttribute(Attribute::Writable);
1965+
.addAttribute(Attribute::Writable)
1966+
.addAttribute(Attribute::DeadOnUnwind);
19661967
if (ASK & ASK_UNSAFE_TO_DROP)
19671968
Incompatible.addAttribute(Attribute::Nest)
19681969
.addAttribute(Attribute::SwiftError)

llvm/lib/Transforms/Utils/CodeExtractor.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,7 @@ Function *CodeExtractor::constructFunction(const ValueSet &inputs,
998998
case Attribute::ByRef:
999999
case Attribute::WriteOnly:
10001000
case Attribute::Writable:
1001+
case Attribute::DeadOnUnwind:
10011002
// These are not really attributes.
10021003
case Attribute::None:
10031004
case Attribute::EndAttrKinds:

llvm/test/Bitcode/attributes.ll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,11 @@ define void @f90(ptr writable %p) {
521521
ret void
522522
}
523523

524+
; CHECK: define void @f91(ptr dead_on_unwind %p)
525+
define void @f91(ptr dead_on_unwind %p) {
526+
ret void
527+
}
528+
524529
; CHECK: attributes #0 = { noreturn }
525530
; CHECK: attributes #1 = { nounwind }
526531
; CHECK: attributes #2 = { memory(none) }

llvm/test/Transforms/DeadStoreElimination/simple.ll

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -514,13 +514,12 @@ define void @test34(ptr noalias %p) {
514514
store i32 0, ptr %p
515515
ret void
516516
}
517-
; Same as previous case, but with an sret argument.
518-
; TODO: The first store could be eliminated if sret is not visible on unwind.
519-
define void @test34_sret(ptr noalias sret(i32) %p) {
520-
; CHECK-LABEL: @test34_sret(
521-
; CHECK-NEXT: store i32 1, ptr [[P:%.*]], align 4
517+
518+
; Same as previous case, but with a dead_on_unwind argument.
519+
define void @test34_dead_on_unwind(ptr noalias dead_on_unwind %p) {
520+
; CHECK-LABEL: @test34_dead_on_unwind(
522521
; CHECK-NEXT: call void @unknown_func()
523-
; CHECK-NEXT: store i32 0, ptr [[P]], align 4
522+
; CHECK-NEXT: store i32 0, ptr [[P:%.*]], align 4
524523
; CHECK-NEXT: ret void
525524
;
526525
store i32 1, ptr %p

llvm/test/Transforms/LICM/scalar-promote-unwind.ll

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,17 +147,15 @@ for.cond.cleanup:
147147
ret void
148148
}
149149

150-
; TODO: sret could be specified to not be accessed on unwind either.
151-
define void @test_sret(ptr noalias sret(i32) %a, i1 zeroext %y) uwtable {
152-
; CHECK-LABEL: @test_sret(
150+
define void @test_dead_on_unwind(ptr noalias dead_on_unwind %a, i1 zeroext %y) uwtable {
151+
; CHECK-LABEL: @test_dead_on_unwind(
153152
; CHECK-NEXT: entry:
154153
; CHECK-NEXT: [[A_PROMOTED:%.*]] = load i32, ptr [[A:%.*]], align 4
155154
; CHECK-NEXT: br label [[FOR_BODY:%.*]]
156155
; CHECK: for.body:
157156
; CHECK-NEXT: [[ADD1:%.*]] = phi i32 [ [[A_PROMOTED]], [[ENTRY:%.*]] ], [ [[ADD:%.*]], [[FOR_INC:%.*]] ]
158157
; CHECK-NEXT: [[I_03:%.*]] = phi i32 [ 0, [[ENTRY]] ], [ [[INC:%.*]], [[FOR_INC]] ]
159158
; CHECK-NEXT: [[ADD]] = add nsw i32 [[ADD1]], 1
160-
; CHECK-NEXT: store i32 [[ADD]], ptr [[A]], align 4
161159
; CHECK-NEXT: br i1 [[Y:%.*]], label [[IF_THEN:%.*]], label [[FOR_INC]]
162160
; CHECK: if.then:
163161
; CHECK-NEXT: tail call void @f()
@@ -167,6 +165,8 @@ define void @test_sret(ptr noalias sret(i32) %a, i1 zeroext %y) uwtable {
167165
; CHECK-NEXT: [[EXITCOND:%.*]] = icmp eq i32 [[INC]], 10000
168166
; CHECK-NEXT: br i1 [[EXITCOND]], label [[FOR_COND_CLEANUP:%.*]], label [[FOR_BODY]]
169167
; CHECK: for.cond.cleanup:
168+
; CHECK-NEXT: [[ADD_LCSSA:%.*]] = phi i32 [ [[ADD]], [[FOR_INC]] ]
169+
; CHECK-NEXT: store i32 [[ADD_LCSSA]], ptr [[A]], align 4
170170
; CHECK-NEXT: ret void
171171
;
172172
entry:

llvm/test/Transforms/MemCpyOpt/callslot_throw.ll

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,39 @@ entry:
5656
ret void
5757
}
5858

59-
; TODO: With updated semantics, sret could also be invisible on unwind.
60-
define void @test_sret(ptr nocapture noalias dereferenceable(4) sret(i32) %x) {
61-
; CHECK-LABEL: @test_sret(
59+
define void @test_dead_on_unwind(ptr nocapture noalias writable dead_on_unwind dereferenceable(4) %x) {
60+
; CHECK-LABEL: @test_dead_on_unwind(
61+
; CHECK-NEXT: entry:
62+
; CHECK-NEXT: [[T:%.*]] = alloca i32, align 4
63+
; CHECK-NEXT: call void @may_throw(ptr nonnull [[X:%.*]])
64+
; CHECK-NEXT: ret void
65+
;
66+
entry:
67+
%t = alloca i32, align 4
68+
call void @may_throw(ptr nonnull %t)
69+
%load = load i32, ptr %t, align 4
70+
store i32 %load, ptr %x, align 4
71+
ret void
72+
}
73+
74+
; Same as previous test, but dereferenceability information is provided by sret.
75+
define void @test_dead_on_unwind_sret(ptr nocapture noalias writable dead_on_unwind sret(i32) %x) {
76+
; CHECK-LABEL: @test_dead_on_unwind_sret(
77+
; CHECK-NEXT: entry:
78+
; CHECK-NEXT: [[T:%.*]] = alloca i32, align 4
79+
; CHECK-NEXT: call void @may_throw(ptr nonnull [[X:%.*]])
80+
; CHECK-NEXT: ret void
81+
;
82+
entry:
83+
%t = alloca i32, align 4
84+
call void @may_throw(ptr nonnull %t)
85+
%load = load i32, ptr %t, align 4
86+
store i32 %load, ptr %x, align 4
87+
ret void
88+
}
89+
90+
define void @test_dead_on_unwind_missing_writable(ptr nocapture noalias dead_on_unwind dereferenceable(4) %x) {
91+
; CHECK-LABEL: @test_dead_on_unwind_missing_writable(
6292
; CHECK-NEXT: entry:
6393
; CHECK-NEXT: [[T:%.*]] = alloca i32, align 4
6494
; CHECK-NEXT: call void @may_throw(ptr nonnull [[T]])

llvm/test/Verifier/dead-on-unwind.ll

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
; RUN: not llvm-as -disable-output %s 2>&1 | FileCheck %s
2+
3+
; CHECK: Attribute 'dead_on_unwind' applied to incompatible type!
4+
; CHECK-NEXT: ptr @not_pointer
5+
define void @not_pointer(i32 dead_on_unwind %arg) {
6+
ret void
7+
}

0 commit comments

Comments
 (0)