Skip to content

Commit 914c502

Browse files
committed
[IR] Introduce !captures metadata
This introduces `!captures` metadata on stores, which looks like this: ``` store ptr %x, ptr %y, !captures !{!"address", !"read_provenance"} ``` The semantics are equivalent to replacing the store with a call like this: ``` call void @llvm.store(ptr captures(address, read_provenance) %x, ptr %y) ``` This metadata is intended for annotation by frontends -- it's not something we can feasibly infer at this pointer, as it would require analyzing uses of the pointer stored in memory. The motivating use case for this is Rust's `println!()` machinery, which involves storing a reference to the value inside a structure. This means that printing code (including conditional debugging code), can inhibit optimizations because the pointer escapes. With the new metadata we could annotate this as a read-only capture, which would have less impact on optimizations.
1 parent ea452c0 commit 914c502

File tree

10 files changed

+320
-1
lines changed

10 files changed

+320
-1
lines changed

llvm/docs/LangRef.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,6 +1489,8 @@ Currently, only the following parameter attributes are defined:
14891489
function, returning a pointer to allocated storage disjoint from the
14901490
storage for any other object accessible to the caller.
14911491

1492+
.. _captures_attr:
1493+
14921494
``captures(...)``
14931495
This attribute restricts the ways in which the callee may capture the
14941496
pointer. This is not a valid attribute for return values. This attribute
@@ -7543,6 +7545,33 @@ The number of bytes known to be dereferenceable is specified by the integer
75437545
value in the metadata node. This is analogous to the ''dereferenceable_or_null''
75447546
attribute on parameters and return values.
75457547

7548+
'``captures``' Metadata
7549+
^^^^^^^^^^^^^^^^^^^^^^^
7550+
7551+
The ``!captures`` metadata can only be applied to ``store`` instructions with
7552+
a pointer-typed value operand. It restricts the capturing behavior of the store
7553+
value operand in the same way the ``captures(...)`` attribute would do on a
7554+
call. See the :ref:`pointer capture section <pointercapture>` for a detailed
7555+
discussion of capture semantics.
7556+
7557+
The ``!captures`` metadata accepts a non-empty list of strings from the same
7558+
set as the :ref:`captures attribute <captures_attr>`:
7559+
``!"address"``, ``!"address_is_null"``, ``!"provenance"`` and
7560+
``!"read_provenance"``. ``!"none"`` is not supported.
7561+
7562+
For example ``store ptr %x, ptr %y, !captures !{!"address"}`` indicates that
7563+
the copy of pointer ``%x`` stored to location ``%y`` will only be used to
7564+
inspect its integral address value, and not dereferenced. Dereferencing the
7565+
pointer would result in undefined behavior.
7566+
7567+
Similarly ``store ptr %x, ptr %y, !captures !{!"address", !"read_provenance"}``
7568+
indicates that while reads through the stored pointer are allowed, writes would
7569+
result in undefined behavior.
7570+
7571+
The ``!captures`` attribute makes no statement about other uses of ``%x``, or
7572+
uses of the stored-to memory location after it has been overwritten with a
7573+
different value.
7574+
75467575
.. _llvm.loop:
75477576

75487577
'``llvm.loop``'

llvm/include/llvm/IR/FixedMetadataKinds.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,4 @@ LLVM_FIXED_MD_KIND(MD_mmra, "mmra", 40)
5555
LLVM_FIXED_MD_KIND(MD_noalias_addrspace, "noalias.addrspace", 41)
5656
LLVM_FIXED_MD_KIND(MD_callee_type, "callee_type", 42)
5757
LLVM_FIXED_MD_KIND(MD_nofree, "nofree", 43)
58+
LLVM_FIXED_MD_KIND(MD_captures, "captures", 44)

llvm/include/llvm/IR/Instructions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,9 @@ class StoreInst : public Instruction {
393393
return getPointerOperandType()->getPointerAddressSpace();
394394
}
395395

396+
/// Get capturing behavior of the value operand, based on !captures metadata.
397+
CaptureComponents getCaptureComponents() const;
398+
396399
// Methods for support type inquiry through isa, cast, and dyn_cast:
397400
static bool classof(const Instruction *I) {
398401
return I->getOpcode() == Instruction::Store;

llvm/lib/Analysis/CaptureTracking.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,8 +320,11 @@ UseCaptureInfo llvm::DetermineUseCaptureKind(const Use &U, const Value *Base) {
320320
return CaptureComponents::None;
321321
case Instruction::Store:
322322
// Stored the pointer - conservatively assume it may be captured.
323+
if (U.getOperandNo() == 0)
324+
return cast<StoreInst>(I)->getCaptureComponents();
325+
323326
// Volatile stores make the address observable.
324-
if (U.getOperandNo() == 0 || cast<StoreInst>(I)->isVolatile())
327+
if (cast<StoreInst>(I)->isVolatile())
325328
return CaptureComponents::All;
326329
return CaptureComponents::None;
327330
case Instruction::AtomicRMW: {

llvm/lib/IR/Instructions.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,6 +1390,24 @@ StoreInst::StoreInst(Value *val, Value *addr, bool isVolatile, Align Align,
13901390
AssertOK();
13911391
}
13921392

1393+
CaptureComponents StoreInst::getCaptureComponents() const {
1394+
const MDNode *MD = getMetadata(LLVMContext::MD_captures);
1395+
if (!MD)
1396+
return CaptureComponents::All;
1397+
1398+
CaptureComponents CC = CaptureComponents::None;
1399+
for (Metadata *Op : MD->operands()) {
1400+
CaptureComponents Component =
1401+
StringSwitch<CaptureComponents>(cast<MDString>(Op)->getString())
1402+
.Case("address", CaptureComponents::Address)
1403+
.Case("address_is_null", CaptureComponents::AddressIsNull)
1404+
.Case("provenance", CaptureComponents::Provenance)
1405+
.Case("read_provenance", CaptureComponents::ReadProvenance);
1406+
CC |= Component;
1407+
}
1408+
return CC;
1409+
}
1410+
13931411
//===----------------------------------------------------------------------===//
13941412
// AtomicCmpXchgInst Implementation
13951413
//===----------------------------------------------------------------------===//

llvm/lib/IR/Verifier.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,7 @@ class Verifier : public InstVisitor<Verifier>, VerifierSupport {
542542
void visitAliasScopeMetadata(const MDNode *MD);
543543
void visitAliasScopeListMetadata(const MDNode *MD);
544544
void visitAccessGroupMetadata(const MDNode *MD);
545+
void visitCapturesMetadata(Instruction &I, const MDNode *Captures);
545546

546547
template <class Ty> bool isValidMetadataArray(const MDTuple &N);
547548
#define HANDLE_SPECIALIZED_MDNODE_LEAF(CLASS) void visit##CLASS(const CLASS &N);
@@ -5373,6 +5374,27 @@ void Verifier::visitAccessGroupMetadata(const MDNode *MD) {
53735374
}
53745375
}
53755376

5377+
void Verifier::visitCapturesMetadata(Instruction &I, const MDNode *Captures) {
5378+
static const char *ValidArgs[] = {"address_is_null", "address",
5379+
"read_provenance", "provenance"};
5380+
5381+
auto *SI = dyn_cast<StoreInst>(&I);
5382+
Check(SI, "!captures metadata can only be applied to store instructions", &I);
5383+
Check(SI->getValueOperand()->getType()->isPointerTy(),
5384+
"!captures metadata can only be applied to store with value operand of "
5385+
"pointer type",
5386+
&I);
5387+
Check(Captures->getNumOperands() != 0, "!captures metadata cannot be empty",
5388+
&I);
5389+
5390+
for (Metadata *Op : Captures->operands()) {
5391+
auto *Str = dyn_cast<MDString>(Op);
5392+
Check(Str, "!captures metadata must be a list of strings", &I);
5393+
Check(is_contained(ValidArgs, Str->getString()),
5394+
"invalid entry in !captures metadata", &I, Str);
5395+
}
5396+
}
5397+
53765398
/// verifyInstruction - Verify that an instruction is well formed.
53775399
///
53785400
void Verifier::visitInstruction(Instruction &I) {
@@ -5600,6 +5622,9 @@ void Verifier::visitInstruction(Instruction &I) {
56005622
if (MDNode *Annotation = I.getMetadata(LLVMContext::MD_annotation))
56015623
visitAnnotationMetadata(Annotation);
56025624

5625+
if (MDNode *Captures = I.getMetadata(LLVMContext::MD_captures))
5626+
visitCapturesMetadata(I, Captures);
5627+
56035628
if (MDNode *N = I.getDebugLoc().getAsMDNode()) {
56045629
CheckDI(isa<DILocation>(N), "invalid !dbg metadata attachment", &I, N);
56055630
visitMDNode(*N, AreDebugLocsAllowed::Yes);

llvm/lib/Transforms/Utils/Local.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3025,6 +3025,9 @@ static void combineMetadata(Instruction *K, const Instruction *J,
30253025
// Preserve !nosanitize if both K and J have it.
30263026
K->setMetadata(Kind, JMD);
30273027
break;
3028+
case LLVMContext::MD_captures:
3029+
K->setMetadata(Kind, JMD ? MDNode::concatenate(JMD, KMD) : nullptr);
3030+
break;
30283031
}
30293032
}
30303033
// Set !invariant.group from J if J has it. If both instructions have it

llvm/test/Transforms/FunctionAttrs/nocapture.ll

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,5 +1398,73 @@ define void @assume_nonnull(ptr %p) {
13981398
ret void
13991399
}
14001400

1401+
define void @captures_metadata_address_is_null(ptr %x, ptr %y) {
1402+
; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write)
1403+
; FNATTRS-LABEL: define void @captures_metadata_address_is_null
1404+
; FNATTRS-SAME: (ptr captures(address_is_null) [[X:%.*]], ptr writeonly captures(none) initializes((0, 8)) [[Y:%.*]]) #[[ATTR17]] {
1405+
; FNATTRS-NEXT: store ptr [[X]], ptr [[Y]], align 8, !captures [[META0:![0-9]+]]
1406+
; FNATTRS-NEXT: ret void
1407+
;
1408+
; ATTRIBUTOR: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write)
1409+
; ATTRIBUTOR-LABEL: define void @captures_metadata_address_is_null
1410+
; ATTRIBUTOR-SAME: (ptr nofree writeonly [[X:%.*]], ptr nofree nonnull writeonly captures(none) [[Y:%.*]]) #[[ATTR13]] {
1411+
; ATTRIBUTOR-NEXT: store ptr [[X]], ptr [[Y]], align 8, !captures [[META0:![0-9]+]]
1412+
; ATTRIBUTOR-NEXT: ret void
1413+
;
1414+
store ptr %x, ptr %y, !captures !{!"address_is_null"}
1415+
ret void
1416+
}
1417+
1418+
define void @captures_metadata_address(ptr %x, ptr %y) {
1419+
; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write)
1420+
; FNATTRS-LABEL: define void @captures_metadata_address
1421+
; FNATTRS-SAME: (ptr captures(address) [[X:%.*]], ptr writeonly captures(none) initializes((0, 8)) [[Y:%.*]]) #[[ATTR17]] {
1422+
; FNATTRS-NEXT: store ptr [[X]], ptr [[Y]], align 8, !captures [[META1:![0-9]+]]
1423+
; FNATTRS-NEXT: ret void
1424+
;
1425+
; ATTRIBUTOR: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write)
1426+
; ATTRIBUTOR-LABEL: define void @captures_metadata_address
1427+
; ATTRIBUTOR-SAME: (ptr nofree writeonly [[X:%.*]], ptr nofree nonnull writeonly captures(none) [[Y:%.*]]) #[[ATTR13]] {
1428+
; ATTRIBUTOR-NEXT: store ptr [[X]], ptr [[Y]], align 8, !captures [[META1:![0-9]+]]
1429+
; ATTRIBUTOR-NEXT: ret void
1430+
;
1431+
store ptr %x, ptr %y, !captures !{!"address"}
1432+
ret void
1433+
}
1434+
1435+
define void @captures_metadata_address_read_provenance(ptr %x, ptr %y) {
1436+
; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write)
1437+
; FNATTRS-LABEL: define void @captures_metadata_address_read_provenance
1438+
; FNATTRS-SAME: (ptr captures(address, read_provenance) [[X:%.*]], ptr writeonly captures(none) initializes((0, 8)) [[Y:%.*]]) #[[ATTR17]] {
1439+
; FNATTRS-NEXT: store ptr [[X]], ptr [[Y]], align 8, !captures [[META2:![0-9]+]]
1440+
; FNATTRS-NEXT: ret void
1441+
;
1442+
; ATTRIBUTOR: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write)
1443+
; ATTRIBUTOR-LABEL: define void @captures_metadata_address_read_provenance
1444+
; ATTRIBUTOR-SAME: (ptr nofree writeonly [[X:%.*]], ptr nofree nonnull writeonly captures(none) [[Y:%.*]]) #[[ATTR13]] {
1445+
; ATTRIBUTOR-NEXT: store ptr [[X]], ptr [[Y]], align 8, !captures [[META2:![0-9]+]]
1446+
; ATTRIBUTOR-NEXT: ret void
1447+
;
1448+
store ptr %x, ptr %y, !captures !{!"address", !"read_provenance"}
1449+
ret void
1450+
}
1451+
1452+
define void @captures_metadata_provenance(ptr %x, ptr %y) {
1453+
; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write)
1454+
; FNATTRS-LABEL: define void @captures_metadata_provenance
1455+
; FNATTRS-SAME: (ptr captures(provenance) [[X:%.*]], ptr writeonly captures(none) initializes((0, 8)) [[Y:%.*]]) #[[ATTR17]] {
1456+
; FNATTRS-NEXT: store ptr [[X]], ptr [[Y]], align 8, !captures [[META3:![0-9]+]]
1457+
; FNATTRS-NEXT: ret void
1458+
;
1459+
; ATTRIBUTOR: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write)
1460+
; ATTRIBUTOR-LABEL: define void @captures_metadata_provenance
1461+
; ATTRIBUTOR-SAME: (ptr nofree writeonly [[X:%.*]], ptr nofree nonnull writeonly captures(none) [[Y:%.*]]) #[[ATTR13]] {
1462+
; ATTRIBUTOR-NEXT: store ptr [[X]], ptr [[Y]], align 8, !captures [[META3:![0-9]+]]
1463+
; ATTRIBUTOR-NEXT: ret void
1464+
;
1465+
store ptr %x, ptr %y, !captures !{!"provenance"}
1466+
ret void
1467+
}
1468+
14011469
declare ptr @llvm.launder.invariant.group.p0(ptr)
14021470
declare ptr @llvm.strip.invariant.group.p0(ptr)

llvm/test/Transforms/SimplifyCFG/hoist-with-metadata.ll

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,134 @@ join:
424424
ret ptr %phi
425425
}
426426

427+
define void @hoist_captures_same(i1 %c, ptr %x, ptr %y) {
428+
; CHECK-LABEL: @hoist_captures_same(
429+
; CHECK-NEXT: if:
430+
; CHECK-NEXT: store ptr [[X:%.*]], ptr [[Y:%.*]], align 8, !captures [[META9:![0-9]+]]
431+
; CHECK-NEXT: ret void
432+
;
433+
if:
434+
br i1 %c, label %then, label %else
435+
436+
then:
437+
store ptr %x, ptr %y, !captures !{!"address"}
438+
br label %out
439+
440+
else:
441+
store ptr %x, ptr %y, !captures !{!"address"}
442+
br label %out
443+
444+
out:
445+
ret void
446+
}
447+
448+
define void @hoist_captures_different(i1 %c, ptr %x, ptr %y) {
449+
; CHECK-LABEL: @hoist_captures_different(
450+
; CHECK-NEXT: if:
451+
; CHECK-NEXT: store ptr [[X:%.*]], ptr [[Y:%.*]], align 8, !captures [[META10:![0-9]+]]
452+
; CHECK-NEXT: ret void
453+
;
454+
if:
455+
br i1 %c, label %then, label %else
456+
457+
then:
458+
store ptr %x, ptr %y, !captures !{!"address"}
459+
br label %out
460+
461+
else:
462+
store ptr %x, ptr %y, !captures !{!"read_provenance"}
463+
br label %out
464+
465+
out:
466+
ret void
467+
}
468+
469+
define void @hoist_captures_overlap(i1 %c, ptr %x, ptr %y) {
470+
; CHECK-LABEL: @hoist_captures_overlap(
471+
; CHECK-NEXT: if:
472+
; CHECK-NEXT: store ptr [[X:%.*]], ptr [[Y:%.*]], align 8, !captures [[META11:![0-9]+]]
473+
; CHECK-NEXT: ret void
474+
;
475+
if:
476+
br i1 %c, label %then, label %else
477+
478+
then:
479+
store ptr %x, ptr %y, !captures !{!"address"}
480+
br label %out
481+
482+
else:
483+
store ptr %x, ptr %y, !captures !{!"address", !"read_provenance"}
484+
br label %out
485+
486+
out:
487+
ret void
488+
}
489+
490+
; We could also omit the attribute in this case, as it provides no additional
491+
; information.
492+
define void @hoist_captures_full_set(i1 %c, ptr %x, ptr %y) {
493+
; CHECK-LABEL: @hoist_captures_full_set(
494+
; CHECK-NEXT: if:
495+
; CHECK-NEXT: store ptr [[X:%.*]], ptr [[Y:%.*]], align 8, !captures [[META12:![0-9]+]]
496+
; CHECK-NEXT: ret void
497+
;
498+
if:
499+
br i1 %c, label %then, label %else
500+
501+
then:
502+
store ptr %x, ptr %y, !captures !{!"address"}
503+
br label %out
504+
505+
else:
506+
store ptr %x, ptr %y, !captures !{!"provenance"}
507+
br label %out
508+
509+
out:
510+
ret void
511+
}
512+
513+
define void @hoist_captures_only_one1(i1 %c, ptr %x, ptr %y) {
514+
; CHECK-LABEL: @hoist_captures_only_one1(
515+
; CHECK-NEXT: if:
516+
; CHECK-NEXT: store ptr [[X:%.*]], ptr [[Y:%.*]], align 8
517+
; CHECK-NEXT: ret void
518+
;
519+
if:
520+
br i1 %c, label %then, label %else
521+
522+
then:
523+
store ptr %x, ptr %y, !captures !{!"address"}
524+
br label %out
525+
526+
else:
527+
store ptr %x, ptr %y
528+
br label %out
529+
530+
out:
531+
ret void
532+
}
533+
534+
define void @hoist_captures_only_one2(i1 %c, ptr %x, ptr %y) {
535+
; CHECK-LABEL: @hoist_captures_only_one2(
536+
; CHECK-NEXT: if:
537+
; CHECK-NEXT: store ptr [[X:%.*]], ptr [[Y:%.*]], align 8
538+
; CHECK-NEXT: ret void
539+
;
540+
if:
541+
br i1 %c, label %then, label %else
542+
543+
then:
544+
store ptr %x, ptr %y
545+
br label %out
546+
547+
else:
548+
store ptr %x, ptr %y, !captures !{!"address"}
549+
br label %out
550+
551+
out:
552+
ret void
553+
}
554+
427555
!0 = !{ i8 0, i8 1 }
428556
!1 = !{ i8 3, i8 5 }
429557
!2 = !{}
@@ -445,4 +573,8 @@ join:
445573
; CHECK: [[META6]] = !{float 2.500000e+00}
446574
; CHECK: [[META7]] = !{i32 5, i32 6}
447575
; CHECK: [[META8]] = !{i32 4, i32 5}
576+
; CHECK: [[META9]] = !{!"address"}
577+
; CHECK: [[META10]] = !{!"read_provenance", !"address"}
578+
; CHECK: [[META11]] = !{!"address", !"read_provenance"}
579+
; CHECK: [[META12]] = !{!"provenance", !"address"}
448580
;.

0 commit comments

Comments
 (0)