diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index 33acb5e73d5ff..8cc9036d1b67f 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -1397,6 +1397,42 @@ Currently, only the following parameter attributes are defined: function, returning a pointer to allocated storage disjoint from the storage for any other object accessible to the caller. +``captures(...)`` + This attributes restrict the ways in which the callee may capture the + pointer. This is not a valid attribute for return values. This attribute + applies only to the particular copy of the pointer passed in this argument. + + The arguments of ``captures`` is a list of captured pointer components, + which may be ``none``, or a combination of: + + - ``address``: The integral address of the pointer. + - ``address_is_null`` (subset of ``address``): Whether the address is null. + - ``provenance``: The ability to access the pointer for both read and write + after the function returns. + - ``read_provenance`` (subset of ``provenance``): The ability to access the + pointer only for reads after the function returns. + + Additionally, it is possible to specify that some components are only + captured in certain locations. Currently only the return value (``ret``) + and other (default) locations are supported. + + The `pointer capture section <pointercapture>` discusses these semantics + in more detail. + + Some examples of how to use the attribute: + + - ``captures(none)``: Pointer not captured. + - ``captures(address, provenance)``: Equivalent to omitting the attribute. + - ``captures(address)``: Address may be captured, but not provenance. + - ``captures(address_is_null)``: Only captures whether the address is null. + - ``captures(address, read_provenance)``: Both address and provenance + captured, but only for read-only access. + - ``captures(ret: address, provenance)``: Pointer captured through return + value only. + - ``captures(address_is_null, ret: address, provenance)``: The whole pointer + is captured through the return value, and additionally whether the pointer + is null is captured in some other way. + .. _nocapture: ``nocapture`` @@ -3339,10 +3375,92 @@ Pointer Capture --------------- Given a function call and a pointer that is passed as an argument or stored in -the memory before the call, a pointer is *captured* by the call if it makes a -copy of any part of the pointer that outlives the call. -To be precise, a pointer is captured if one or more of the following conditions -hold: +memory before the call, the call may capture two components of the pointer: + + * The address of the pointer, which is its integral value. This also includes + parts of the address or any information about the address, including the + fact that it does not equal one specific value. We further distinguish + whether only the fact that the address is/isn't null is captured. + * The provenance of the pointer, which is the ability to perform memory + accesses through the pointer, in the sense of the :ref:`pointer aliasing + rules <pointeraliasing>`. We further distinguish whether only read acceses + are allowed, or both reads and writes. + +For example, the following function captures the address of ``%a``, because +it is compared to a pointer, leaking information about the identitiy of the +pointer: + +.. code-block:: llvm + + @glb = global i8 0 + + define i1 @f(ptr %a) { + %c = icmp eq ptr %a, @glb + ret i1 %c + } + +The function does not capture the provenance of the pointer, because the +``icmp`` instruction only operates on the pointer address. The following +function captures both the address and provenance of the pointer, as both +may be read from ``@glb`` after the function returns: + +.. code-block:: llvm + + @glb = global ptr null + + define void @f(ptr %a) { + store ptr %a, ptr @glb + ret void + } + +The following function captures *neither* the address nor the provenance of +the pointer: + +.. code-block:: llvm + + define i32 @f(ptr %a) { + %v = load i32, ptr %a + ret i32 + } + +While address capture includes uses of the address within the body of the +function, provenance capture refers exclusively to the ability to perform +accesses *after* the function returns. Memory accesses within the function +itself are not considered pointer captures. + +We can further say that the capture only occurs through a specific location. +In the following example, the pointer (both address and provenance) is captured +through the return value only: + +.. code-block:: llvm + + define ptr @f(ptr %a) { + %gep = getelementptr i8, ptr %a, i64 4 + ret ptr %gep + } + +However, we always consider direct inspection of the pointer address +(e.g. using ``ptrtoint``) to be location-independent. The following example +is *not* considered a return-only capture, even though the ``ptrtoint`` +ultimately only contribues to the return value: + +.. code-block:: llvm + + @lookup = constant [4 x i8] [i8 0, i8 1, i8 2, i8 3] + + define ptr @f(ptr %a) { + %a.addr = ptrtoint ptr %a to i64 + %mask = and i64 %a.addr, 3 + %gep = getelementptr i8, ptr @lookup, i64 %mask + ret ptr %gep + } + +This definition is chosen to allow capture analysis to continue with the return +value in the usual fashion. + +The following describes possible ways to capture a pointer in more detail, +where unqualified uses of the word "capture" refer to capturing both address +and provenance. 1. The call stores any bit of the pointer carrying information into a place, and the stored bits can be read from the place by the caller after this call @@ -3381,13 +3499,14 @@ hold: @lock = global i1 true define void @f(ptr %a) { - store ptr %a, ptr* @glb + store ptr %a, ptr @glb store atomic i1 false, ptr @lock release ; %a is captured because another thread can safely read @glb store ptr null, ptr @glb ret void } -3. The call's behavior depends on any bit of the pointer carrying information. +3. The call's behavior depends on any bit of the pointer carrying information + (address capture only). .. code-block:: llvm @@ -3395,7 +3514,7 @@ hold: define void @f(ptr %a) { %c = icmp eq ptr %a, @glb - br i1 %c, label %BB_EXIT, label %BB_CONTINUE ; escapes %a + br i1 %c, label %BB_EXIT, label %BB_CONTINUE ; captures address of %a only BB_EXIT: call void @exit() unreachable @@ -3403,8 +3522,7 @@ hold: ret void } -4. The pointer is used in a volatile access as its address. - +4. The pointer is used as the pointer operand of a volatile access. .. _volatile: diff --git a/llvm/include/llvm/AsmParser/LLParser.h b/llvm/include/llvm/AsmParser/LLParser.h index 8b195b028783f..c01de4a289a69 100644 --- a/llvm/include/llvm/AsmParser/LLParser.h +++ b/llvm/include/llvm/AsmParser/LLParser.h @@ -379,6 +379,7 @@ namespace llvm { bool inAttrGrp, LocTy &BuiltinLoc); bool parseRangeAttr(AttrBuilder &B); bool parseInitializesAttr(AttrBuilder &B); + bool parseCapturesAttr(AttrBuilder &B); bool parseRequiredTypeAttr(AttrBuilder &B, lltok::Kind AttrToken, Attribute::AttrKind AttrKind); diff --git a/llvm/include/llvm/AsmParser/LLToken.h b/llvm/include/llvm/AsmParser/LLToken.h index 178c911120b4c..7b47bc88ddb25 100644 --- a/llvm/include/llvm/AsmParser/LLToken.h +++ b/llvm/include/llvm/AsmParser/LLToken.h @@ -207,6 +207,12 @@ enum Kind { kw_inaccessiblememonly, kw_inaccessiblemem_or_argmemonly, + // Captures attribute: + kw_address, + kw_address_is_null, + kw_provenance, + kw_read_provenance, + // nofpclass attribute: kw_all, kw_nan, diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h index 21fd27d9838db..9eb38c3e44829 100644 --- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -788,6 +788,7 @@ enum AttributeKindCodes { ATTR_KIND_NO_EXT = 99, ATTR_KIND_NO_DIVERGENCE_SOURCE = 100, ATTR_KIND_SANITIZE_TYPE = 101, + ATTR_KIND_CAPTURES = 102, }; enum ComdatSelectionKindCodes { diff --git a/llvm/include/llvm/IR/Attributes.h b/llvm/include/llvm/IR/Attributes.h index 2755ced404ddd..7612e553fe32e 100644 --- a/llvm/include/llvm/IR/Attributes.h +++ b/llvm/include/llvm/IR/Attributes.h @@ -284,6 +284,9 @@ class Attribute { /// Returns memory effects. MemoryEffects getMemoryEffects() const; + /// Returns information from captures attribute. + CaptureInfo getCaptureInfo() const; + /// Return the FPClassTest for nofpclass FPClassTest getNoFPClass() const; @@ -436,6 +439,7 @@ class AttributeSet { UWTableKind getUWTableKind() const; AllocFnKind getAllocKind() const; MemoryEffects getMemoryEffects() const; + CaptureInfo getCaptureInfo() const; FPClassTest getNoFPClass() const; std::string getAsString(bool InAttrGrp = false) const; @@ -1260,6 +1264,9 @@ class AttrBuilder { /// Add memory effect attribute. AttrBuilder &addMemoryAttr(MemoryEffects ME); + /// Add captures attribute. + AttrBuilder &addCapturesAttr(CaptureInfo CI); + // Add nofpclass attribute AttrBuilder &addNoFPClassAttr(FPClassTest NoFPClassMask); diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td index 61955cf883c3f..4396ec4d04c41 100644 --- a/llvm/include/llvm/IR/Attributes.td +++ b/llvm/include/llvm/IR/Attributes.td @@ -183,6 +183,9 @@ def NoCallback : EnumAttr<"nocallback", IntersectAnd, [FnAttr]>; /// Function creates no aliases of pointer. def NoCapture : EnumAttr<"nocapture", IntersectAnd, [ParamAttr]>; +/// Specify how the pointer may be captured. +def Captures : IntAttr<"captures", IntersectCustom, [ParamAttr]>; + /// Function is not a source of divergence. def NoDivergenceSource : EnumAttr<"nodivergencesource", IntersectAnd, [FnAttr]>; diff --git a/llvm/include/llvm/Support/ModRef.h b/llvm/include/llvm/Support/ModRef.h index 5a9d80c87ae27..9ecdab71ec8ca 100644 --- a/llvm/include/llvm/Support/ModRef.h +++ b/llvm/include/llvm/Support/ModRef.h @@ -273,6 +273,107 @@ raw_ostream &operator<<(raw_ostream &OS, MemoryEffects RMRB); // Legacy alias. using FunctionModRefBehavior = MemoryEffects; +/// Components of the pointer that may be captured. +enum class CaptureComponents : uint8_t { + None = 0, + AddressIsNull = (1 << 0), + Address = (1 << 1) | AddressIsNull, + ReadProvenance = (1 << 2), + Provenance = (1 << 3) | ReadProvenance, + All = Address | Provenance, + LLVM_MARK_AS_BITMASK_ENUM(Provenance), +}; + +inline bool capturesNothing(CaptureComponents CC) { + return CC == CaptureComponents::None; +} + +inline bool capturesAnything(CaptureComponents CC) { + return CC != CaptureComponents::None; +} + +inline bool capturesAddressIsNullOnly(CaptureComponents CC) { + return (CC & CaptureComponents::Address) == CaptureComponents::AddressIsNull; +} + +inline bool capturesAddress(CaptureComponents CC) { + return (CC & CaptureComponents::Address) != CaptureComponents::None; +} + +inline bool capturesReadProvenanceOnly(CaptureComponents CC) { + return (CC & CaptureComponents::Provenance) == + CaptureComponents::ReadProvenance; +} + +inline bool capturesFullProvenance(CaptureComponents CC) { + return (CC & CaptureComponents::Provenance) == CaptureComponents::Provenance; +} + +raw_ostream &operator<<(raw_ostream &OS, CaptureComponents CC); + +/// Represents which components of the pointer may be captured in which +/// location. This represents the captures(...) attribute in IR. +/// +/// For more information on the precise semantics see LangRef. +class CaptureInfo { + CaptureComponents OtherComponents; + CaptureComponents RetComponents; + +public: + CaptureInfo(CaptureComponents OtherComponents, + CaptureComponents RetComponents) + : OtherComponents(OtherComponents), RetComponents(RetComponents) {} + + CaptureInfo(CaptureComponents Components) + : OtherComponents(Components), RetComponents(Components) {} + + /// Create CaptureInfo that may capture all components of the pointer. + static CaptureInfo all() { return CaptureInfo(CaptureComponents::All); } + + /// Get components potentially captured by the return value. + CaptureComponents getRetComponents() const { return RetComponents; } + + /// Get components potentially captured through locations other than the + /// return value. + CaptureComponents getOtherComponents() const { return OtherComponents; } + + /// Get the potentially captured components of the pointer (regardless of + /// location). + operator CaptureComponents() const { return OtherComponents | RetComponents; } + + bool operator==(CaptureInfo Other) const { + return OtherComponents == Other.OtherComponents && + RetComponents == Other.RetComponents; + } + + bool operator!=(CaptureInfo Other) const { return !(*this == Other); } + + /// Compute union of CaptureInfos. + CaptureInfo operator|(CaptureInfo Other) const { + return CaptureInfo(OtherComponents | Other.OtherComponents, + RetComponents | Other.RetComponents); + } + + /// Compute intersection of CaptureInfos. + CaptureInfo operator&(CaptureInfo Other) const { + return CaptureInfo(OtherComponents & Other.OtherComponents, + RetComponents & Other.RetComponents); + } + + static CaptureInfo createFromIntValue(uint32_t Data) { + return CaptureInfo(CaptureComponents(Data >> 4), + CaptureComponents(Data & 0xf)); + } + + /// Convert CaptureInfo into an encoded integer value (used by captures + /// attribute). + uint32_t toIntValue() const { + return (uint32_t(OtherComponents) << 4) | uint32_t(RetComponents); + } +}; + +raw_ostream &operator<<(raw_ostream &OS, CaptureInfo Info); + } // namespace llvm #endif diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp index 1b8e033134f51..5ea507c009bdc 100644 --- a/llvm/lib/AsmParser/LLLexer.cpp +++ b/llvm/lib/AsmParser/LLLexer.cpp @@ -704,6 +704,10 @@ lltok::Kind LLLexer::LexIdentifier() { KEYWORD(argmemonly); KEYWORD(inaccessiblememonly); KEYWORD(inaccessiblemem_or_argmemonly); + KEYWORD(address_is_null); + KEYWORD(address); + KEYWORD(provenance); + KEYWORD(read_provenance); // nofpclass attribute KEYWORD(all); diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp index 52d48a69f0eb5..81d048b32e139 100644 --- a/llvm/lib/AsmParser/LLParser.cpp +++ b/llvm/lib/AsmParser/LLParser.cpp @@ -1644,6 +1644,8 @@ bool LLParser::parseEnumAttribute(Attribute::AttrKind Attr, AttrBuilder &B, return parseRangeAttr(B); case Attribute::Initializes: return parseInitializesAttr(B); + case Attribute::Captures: + return parseCapturesAttr(B); default: B.addAttribute(Attr); Lex.Lex(); @@ -3165,6 +3167,65 @@ bool LLParser::parseInitializesAttr(AttrBuilder &B) { return false; } +bool LLParser::parseCapturesAttr(AttrBuilder &B) { + CaptureComponents Other = CaptureComponents::None; + std::optional<CaptureComponents> Ret; + + // We use syntax like captures(ret: address, provenance), so the colon + // should not be interpreted as a label terminator. + Lex.setIgnoreColonInIdentifiers(true); + auto _ = make_scope_exit([&] { Lex.setIgnoreColonInIdentifiers(false); }); + + Lex.Lex(); + if (parseToken(lltok::lparen, "expected '('")) + return true; + + CaptureComponents *Current = &Other; + bool SeenComponent = false; + while (true) { + if (EatIfPresent(lltok::kw_ret)) { + if (parseToken(lltok::colon, "expected ':'")) + return true; + if (Ret) + return tokError("duplicate 'ret' location"); + Ret = CaptureComponents::None; + Current = &*Ret; + SeenComponent = false; + } + + if (EatIfPresent(lltok::kw_none)) { + if (SeenComponent) + return tokError("cannot use 'none' with other component"); + *Current = CaptureComponents::None; + } else { + if (SeenComponent && capturesNothing(*Current)) + return tokError("cannot use 'none' with other component"); + + if (EatIfPresent(lltok::kw_address_is_null)) + *Current |= CaptureComponents::AddressIsNull; + else if (EatIfPresent(lltok::kw_address)) + *Current |= CaptureComponents::Address; + else if (EatIfPresent(lltok::kw_provenance)) + *Current |= CaptureComponents::Provenance; + else if (EatIfPresent(lltok::kw_read_provenance)) + *Current |= CaptureComponents::ReadProvenance; + else + return tokError("expected one of 'none', 'address', 'address_is_null', " + "'provenance' or 'read_provenance'"); + } + + SeenComponent = true; + if (EatIfPresent(lltok::rparen)) + break; + + if (parseToken(lltok::comma, "expected ',' or ')'")) + return true; + } + + B.addCapturesAttr(CaptureInfo(Other, Ret.value_or(Other))); + return false; +} + /// parseOptionalOperandBundles /// ::= /*empty*/ /// ::= '[' OperandBundle [, OperandBundle ]* ']' diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp index a01ecf0d56642..56f5ff4b20e5d 100644 --- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -2250,6 +2250,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) { return Attribute::CoroElideSafe; case bitc::ATTR_KIND_NO_EXT: return Attribute::NoExt; + case bitc::ATTR_KIND_CAPTURES: + return Attribute::Captures; } } @@ -2389,6 +2391,8 @@ Error BitcodeReader::parseAttributeGroupBlock() { B.addAllocKindAttr(static_cast<AllocFnKind>(Record[++i])); else if (Kind == Attribute::Memory) B.addMemoryAttr(MemoryEffects::createFromIntValue(Record[++i])); + else if (Kind == Attribute::Captures) + B.addCapturesAttr(CaptureInfo::createFromIntValue(Record[++i])); else if (Kind == Attribute::NoFPClass) B.addNoFPClassAttr( static_cast<FPClassTest>(Record[++i] & fcAllFlags)); diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp index b4efd3928a2e6..94d3afa6c1e33 100644 --- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -907,6 +907,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) { return bitc::ATTR_KIND_INITIALIZES; case Attribute::NoExt: return bitc::ATTR_KIND_NO_EXT; + case Attribute::Captures: + return bitc::ATTR_KIND_CAPTURES; case Attribute::EndAttrKinds: llvm_unreachable("Can not encode end-attribute kinds marker."); case Attribute::None: diff --git a/llvm/lib/IR/AttributeImpl.h b/llvm/lib/IR/AttributeImpl.h index 82c501dcafcb7..59cc489ade40d 100644 --- a/llvm/lib/IR/AttributeImpl.h +++ b/llvm/lib/IR/AttributeImpl.h @@ -346,6 +346,7 @@ class AttributeSetNode final UWTableKind getUWTableKind() const; AllocFnKind getAllocKind() const; MemoryEffects getMemoryEffects() const; + CaptureInfo getCaptureInfo() const; FPClassTest getNoFPClass() const; std::string getAsString(bool InAttrGrp) const; Type *getAttributeType(Attribute::AttrKind Kind) const; diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp index e9daa01b899e8..ceb31856283c9 100644 --- a/llvm/lib/IR/Attributes.cpp +++ b/llvm/lib/IR/Attributes.cpp @@ -487,6 +487,12 @@ MemoryEffects Attribute::getMemoryEffects() const { return MemoryEffects::createFromIntValue(pImpl->getValueAsInt()); } +CaptureInfo Attribute::getCaptureInfo() const { + assert(hasAttribute(Attribute::Captures) && + "Can only call getCaptureInfo() on captures attribute"); + return CaptureInfo::createFromIntValue(pImpl->getValueAsInt()); +} + FPClassTest Attribute::getNoFPClass() const { assert(hasAttribute(Attribute::NoFPClass) && "Can only call getNoFPClass() on nofpclass attribute"); @@ -647,6 +653,13 @@ std::string Attribute::getAsString(bool InAttrGrp) const { return Result; } + if (hasAttribute(Attribute::Captures)) { + std::string Result; + raw_string_ostream OS(Result); + OS << getCaptureInfo(); + return Result; + } + if (hasAttribute(Attribute::NoFPClass)) { std::string Result = "nofpclass"; raw_string_ostream OS(Result); @@ -1050,6 +1063,10 @@ AttributeSet::intersectWith(LLVMContext &C, AttributeSet Other) const { Intersected.addMemoryAttr(Attr0.getMemoryEffects() | Attr1.getMemoryEffects()); break; + case Attribute::Captures: + Intersected.addCapturesAttr(Attr0.getCaptureInfo() | + Attr1.getCaptureInfo()); + break; case Attribute::NoFPClass: Intersected.addNoFPClassAttr(Attr0.getNoFPClass() & Attr1.getNoFPClass()); @@ -1170,6 +1187,10 @@ MemoryEffects AttributeSet::getMemoryEffects() const { return SetNode ? SetNode->getMemoryEffects() : MemoryEffects::unknown(); } +CaptureInfo AttributeSet::getCaptureInfo() const { + return SetNode ? SetNode->getCaptureInfo() : CaptureInfo::all(); +} + FPClassTest AttributeSet::getNoFPClass() const { return SetNode ? SetNode->getNoFPClass() : fcNone; } @@ -1358,6 +1379,12 @@ MemoryEffects AttributeSetNode::getMemoryEffects() const { return MemoryEffects::unknown(); } +CaptureInfo AttributeSetNode::getCaptureInfo() const { + if (auto A = findEnumAttribute(Attribute::Captures)) + return A->getCaptureInfo(); + return CaptureInfo::all(); +} + FPClassTest AttributeSetNode::getNoFPClass() const { if (auto A = findEnumAttribute(Attribute::NoFPClass)) return A->getNoFPClass(); @@ -2190,6 +2217,10 @@ AttrBuilder &AttrBuilder::addMemoryAttr(MemoryEffects ME) { return addRawIntAttr(Attribute::Memory, ME.toIntValue()); } +AttrBuilder &AttrBuilder::addCapturesAttr(CaptureInfo CI) { + return addRawIntAttr(Attribute::Captures, CI.toIntValue()); +} + AttrBuilder &AttrBuilder::addNoFPClassAttr(FPClassTest Mask) { if (Mask == fcNone) return *this; @@ -2350,7 +2381,8 @@ AttributeMask AttributeFuncs::typeIncompatible(Type *Ty, AttributeSet AS, .addAttribute(Attribute::DereferenceableOrNull) .addAttribute(Attribute::Writable) .addAttribute(Attribute::DeadOnUnwind) - .addAttribute(Attribute::Initializes); + .addAttribute(Attribute::Initializes) + .addAttribute(Attribute::Captures); if (ASK & ASK_UNSAFE_TO_DROP) Incompatible.addAttribute(Attribute::Nest) .addAttribute(Attribute::SwiftError) diff --git a/llvm/lib/Support/ModRef.cpp b/llvm/lib/Support/ModRef.cpp index a4eb70edd38d1..d3b3dd11171f1 100644 --- a/llvm/lib/Support/ModRef.cpp +++ b/llvm/lib/Support/ModRef.cpp @@ -12,6 +12,7 @@ #include "llvm/Support/ModRef.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringExtras.h" using namespace llvm; @@ -50,3 +51,36 @@ raw_ostream &llvm::operator<<(raw_ostream &OS, MemoryEffects ME) { }); return OS; } + +raw_ostream &llvm::operator<<(raw_ostream &OS, CaptureComponents CC) { + if (capturesNothing(CC)) { + OS << "none"; + return OS; + } + + ListSeparator LS; + if (capturesAddressIsNullOnly(CC)) + OS << LS << "address_is_null"; + else if (capturesAddress(CC)) + OS << LS << "address"; + if (capturesReadProvenanceOnly(CC)) + OS << LS << "read_provenance"; + if (capturesFullProvenance(CC)) + OS << LS << "provenance"; + + return OS; +} + +raw_ostream &llvm::operator<<(raw_ostream &OS, CaptureInfo CI) { + ListSeparator LS; + CaptureComponents Other = CI.getOtherComponents(); + CaptureComponents Ret = CI.getRetComponents(); + + OS << "captures("; + if (!capturesNothing(Other) || Other == Ret) + OS << LS << Other; + if (Other != Ret) + OS << LS << "ret: " << Ret; + OS << ")"; + return OS; +} diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp index 7ddb9e22c8344..af9813775f242 100644 --- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp +++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp @@ -975,6 +975,7 @@ Function *CodeExtractor::constructFunctionDeclaration( case Attribute::AllocatedPointer: case Attribute::AllocAlign: case Attribute::ByVal: + case Attribute::Captures: case Attribute::Dereferenceable: case Attribute::DereferenceableOrNull: case Attribute::ElementType: diff --git a/llvm/test/Assembler/captures-errors.ll b/llvm/test/Assembler/captures-errors.ll new file mode 100644 index 0000000000000..44788c79a2453 --- /dev/null +++ b/llvm/test/Assembler/captures-errors.ll @@ -0,0 +1,73 @@ +; RUN: split-file --leading-lines %s %t +; RUN: not llvm-as < %t/missing-lparen.ll 2>&1 | FileCheck %s --check-prefix=CHECK-MISSING-LPAREN +; RUN: not llvm-as < %t/missing-rparen.ll 2>&1 | FileCheck %s --check-prefix=CHECK-MISSING-RPAREN +; RUN: not llvm-as < %t/missing-rparen-none.ll 2>&1 | FileCheck %s --check-prefix=CHECK-MISSING-RPAREN-NONE +; RUN: not llvm-as < %t/missing-colon.ll 2>&1 | FileCheck %s --check-prefix=CHECK-MISSING-COLON +; RUN: not llvm-as < %t/invalid-component.ll 2>&1 | FileCheck %s --check-prefix=CHECK-INVALID-COMPONENT +; RUN: not llvm-as < %t/duplicate-ret.ll 2>&1 | FileCheck %s --check-prefix=CHECK-DUPLICATE-RET +; RUN: not llvm-as < %t/none-after.ll 2>&1 | FileCheck %s --check-prefix=CHECK-NONE-AFTER +; RUN: not llvm-as < %t/none-before.ll 2>&1 | FileCheck %s --check-prefix=CHECK-NONE-BEFORE +; RUN: not opt -disable-output < %t/non-pointer-type.ll 2>&1 | FileCheck %s --check-prefix=CHECK-NON-POINTER-TYPE + +;--- missing-lparen.ll + +; CHECK-MISSING-LPAREN: <stdin>:[[@LINE+1]]:32: error: expected '(' +define void @test(ptr captures %p) { + ret void +} + +;--- missing-rparen.ll + +; CHECK-MISSING-RPAREN: <stdin>:[[@LINE+1]]:40: error: expected ',' or ')' +define void @test(ptr captures(address %p) { + ret void +} + +;--- missing-rparen-none.ll + +; CHECK-MISSING-RPAREN-NONE: <stdin>:[[@LINE+1]]:37: error: expected ',' or ')' +define void @test(ptr captures(none %p) { + ret void +} + +;--- missing-colon.ll + +; CHECK-MISSING-COLON: <stdin>:[[@LINE+1]]:36: error: expected ':' +define void @test(ptr captures(ret address) %p) { + ret void +} + +;--- invalid-component.ll + +; CHECK-INVALID-COMPONENT: <stdin>:[[@LINE+1]]:32: error: expected one of 'none', 'address', 'address_is_null', 'provenance' or 'read_provenance' +define void @test(ptr captures(foo) %p) { + ret void +} + +;--- duplicate-ret.ll + +; CHECK-DUPLICATE-RET: <stdin>:[[@LINE+1]]:51: error: duplicate 'ret' location +define void @test(ptr captures(ret: address, ret: provenance) %p) { + ret void +} + +;--- none-after.ll + +; CHECK-NONE-AFTER: <stdin>:[[@LINE+1]]:45: error: cannot use 'none' with other component +define void @test(ptr captures(address, none) %p) { + ret void +} + +;--- none-before.ll + +; CHECK-NONE-BEFORE: <stdin>:[[@LINE+1]]:38: error: cannot use 'none' with other component +define void @test(ptr captures(none, address) %p) { + ret void +} + +;--- non-pointer-type.ll + +; CHECK-NON-POINTER-TYPE: Attribute 'captures(none)' applied to incompatible type! +define void @test(i32 captures(none) %p) { + ret void +} diff --git a/llvm/test/Assembler/captures.ll b/llvm/test/Assembler/captures.ll new file mode 100644 index 0000000000000..1521a9df0cb42 --- /dev/null +++ b/llvm/test/Assembler/captures.ll @@ -0,0 +1,103 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5 +; RUN: opt -S < %s | FileCheck %s +; RUN: llvm-as < %s | llvm-dis | FileCheck %s + +define void @test_none(ptr captures(none) %p) { +; CHECK-LABEL: define void @test_none( +; CHECK-SAME: ptr captures(none) [[P:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @test_address(ptr captures(address) %p) { +; CHECK-LABEL: define void @test_address( +; CHECK-SAME: ptr captures(address) [[P:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @test_address_is_null(ptr captures(address_is_null) %p) { +; CHECK-LABEL: define void @test_address_is_null( +; CHECK-SAME: ptr captures(address_is_null) [[P:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @test_address_provenance(ptr captures(address, provenance) %p) { +; CHECK-LABEL: define void @test_address_provenance( +; CHECK-SAME: ptr captures(address, provenance) [[P:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @test_address_read_provenance(ptr captures(address, read_provenance) %p) { +; CHECK-LABEL: define void @test_address_read_provenance( +; CHECK-SAME: ptr captures(address, read_provenance) [[P:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @test_ret(ptr captures(ret: address, provenance) %p) { +; CHECK-LABEL: define void @test_ret( +; CHECK-SAME: ptr captures(ret: address, provenance) [[P:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @test_address_is_null_and_ret(ptr captures(address_is_null, ret: address, provenance) %p) { +; CHECK-LABEL: define void @test_address_is_null_and_ret( +; CHECK-SAME: ptr captures(address_is_null, ret: address, provenance) [[P:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @test_address_and_ret_none(ptr captures(address, ret: none) %p) { +; CHECK-LABEL: define void @test_address_and_ret_none( +; CHECK-SAME: ptr captures(address, ret: none) [[P:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +; Duplicates callpse into one. +define void @test_duplicate(ptr captures(address, address) %p) { +; CHECK-LABEL: define void @test_duplicate( +; CHECK-SAME: ptr captures(address) [[P:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +; read_provenance is a subset of provenance. +define void @test_duplicate_read_provenance(ptr captures(read_provenance, provenance) %p) { +; CHECK-LABEL: define void @test_duplicate_read_provenance( +; CHECK-SAME: ptr captures(provenance) [[P:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +; address_is_null is a subset of address. +define void @test_duplicate_address_is_null(ptr captures(address_is_null, address) %p) { +; CHECK-LABEL: define void @test_duplicate_address_is_null( +; CHECK-SAME: ptr captures(address) [[P:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +; Return-only none is same as plain none. +define void @test_ret_none(ptr captures(ret: none) %p) { +; CHECK-LABEL: define void @test_ret_none( +; CHECK-SAME: ptr captures(none) [[P:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} diff --git a/llvm/test/Bitcode/attributes.ll b/llvm/test/Bitcode/attributes.ll index 492de663884df..1da9291c71996 100644 --- a/llvm/test/Bitcode/attributes.ll +++ b/llvm/test/Bitcode/attributes.ll @@ -562,6 +562,11 @@ define void @initializes(ptr initializes((-4, 0), (4, 8)) %a) { ret void } +; CHECK: define void @captures(ptr captures(address) %p) +define void @captures(ptr captures(address) %p) { + ret void +} + ; CHECK: attributes #0 = { noreturn } ; CHECK: attributes #1 = { nounwind } ; CHECK: attributes #2 = { memory(none) } diff --git a/llvm/unittests/IR/AttributesTest.cpp b/llvm/unittests/IR/AttributesTest.cpp index f73f2b20e9fea..f0e34aa273369 100644 --- a/llvm/unittests/IR/AttributesTest.cpp +++ b/llvm/unittests/IR/AttributesTest.cpp @@ -437,6 +437,14 @@ TEST(Attributes, SetIntersect) { break; case Attribute::Range: break; + case Attribute::Captures: + V0 = CaptureInfo(CaptureComponents::AddressIsNull, + CaptureComponents::None) + .toIntValue(); + V1 = CaptureInfo(CaptureComponents::None, + CaptureComponents::ReadProvenance) + .toIntValue(); + break; default: ASSERT_FALSE(true); } @@ -516,6 +524,11 @@ TEST(Attributes, SetIntersect) { ASSERT_EQ(Res->getAttribute(Kind).getRange(), ConstantRange(APInt(32, 0), APInt(32, 20))); break; + case Attribute::Captures: + ASSERT_EQ(Res->getCaptureInfo(), + CaptureInfo(CaptureComponents::AddressIsNull, + CaptureComponents::ReadProvenance)); + break; default: ASSERT_FALSE(true); }