[IR] Introduce captures attribute (#116990)
This introduces the `captures` attribute as described in: https://discourse.llvm.org/t/rfc-improvements-to-capture-tracking/81420 This initial patch only introduces the IR/bitcode support for the attribute and its in-memory representation as `CaptureInfo`. This will be followed by a patch to upgrade and remove the `nocapture` attribute, and then by actual inference/analysis support. Based on the RFC feedback, I've used a syntax similar to the `memory` attribute, though the only "location" that can be specified is `ret`. I've added some pretty extensive documentation to LangRef on the semantics. One non-obvious bit here is that using ptrtoint will not result in a "return-only" capture, even if the ptrtoint result is only used in the return value. Without this requirement we wouldn't be able to continue ordinary capture analysis on the return value.
This commit is contained in:
@@ -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:
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -284,6 +284,9 @@ public:
|
||||
/// 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 @@ public:
|
||||
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 @@ public:
|
||||
/// Add memory effect attribute.
|
||||
AttrBuilder &addMemoryAttr(MemoryEffects ME);
|
||||
|
||||
/// Add captures attribute.
|
||||
AttrBuilder &addCapturesAttr(CaptureInfo CI);
|
||||
|
||||
// Add nofpclass attribute
|
||||
AttrBuilder &addNoFPClassAttr(FPClassTest NoFPClassMask);
|
||||
|
||||
|
||||
@@ -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]>;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 ]* ']'
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -346,6 +346,7 @@ public:
|
||||
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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
73
llvm/test/Assembler/captures-errors.ll
Normal file
73
llvm/test/Assembler/captures-errors.ll
Normal file
@@ -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
|
||||
}
|
||||
103
llvm/test/Assembler/captures.ll
Normal file
103
llvm/test/Assembler/captures.ll
Normal file
@@ -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
|
||||
}
|
||||
@@ -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) }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user