[IR] Add support for memory attribute

This implements IR and bitcode support for the memory attribute,
as specified in https://reviews.llvm.org/D135597.

The new attribute is not used for anything yet (and as such, the
old memory attributes are unaffected).

Differential Revision: https://reviews.llvm.org/D135592
This commit is contained in:
Nikita Popov 2022-10-10 14:33:13 +02:00
parent 9d9de5a5df
commit e9754f0211
14 changed files with 311 additions and 5 deletions

View File

@ -42,6 +42,7 @@ namespace llvm {
class Comdat;
class MDString;
class MDNode;
class MemoryEffects;
struct SlotMapping;
/// ValID - Represents a reference of a definition of some sort with no type.
@ -284,6 +285,7 @@ namespace llvm {
bool parseOptionalDerefAttrBytes(lltok::Kind AttrKind, uint64_t &Bytes);
bool parseOptionalUWTableKind(UWTableKind &Kind);
bool parseAllocKind(AllocFnKind &Kind);
Optional<MemoryEffects> parseMemoryAttr();
bool parseScopeAndOrdering(bool IsAtomic, SyncScope::ID &SSID,
AtomicOrdering &Ordering);
bool parseScope(SyncScope::ID &SSID);

View File

@ -183,6 +183,13 @@ enum Kind {
kw_##DISPLAY_NAME,
#include "llvm/IR/Attributes.inc"
// Memory attribute:
kw_read,
kw_write,
kw_readwrite,
kw_argmem,
kw_inaccessiblemem,
kw_type,
kw_opaque,

View File

@ -690,6 +690,7 @@ enum AttributeKindCodes {
ATTR_KIND_PRESPLIT_COROUTINE = 83,
ATTR_KIND_FNRETTHUNK_EXTERN = 84,
ATTR_KIND_SKIP_PROFILE = 85,
ATTR_KIND_MEMORY = 86,
};
enum ComdatSelectionKindCodes {

View File

@ -42,6 +42,7 @@ class AttributeSetNode;
class FoldingSetNodeID;
class Function;
class LLVMContext;
class MemoryEffects;
class Type;
enum class AllocFnKind : uint64_t {
@ -243,6 +244,9 @@ public:
// Returns the allocator function kind.
AllocFnKind getAllocKind() const;
/// Returns memory effects.
MemoryEffects getMemoryEffects() const;
/// The Attribute is converted to a string of equivalent mnemonic. This
/// is, presumably, for writing out the mnemonics for the assembly writer.
std::string getAsString(bool InAttrGrp = false) const;
@ -1220,6 +1224,9 @@ public:
// This turns the allocator kind into the form used internally in Attribute.
AttrBuilder &addAllocKindAttr(AllocFnKind Kind);
/// Add memory effect attribute.
AttrBuilder &addMemoryAttr(MemoryEffects ME);
ArrayRef<Attribute> attrs() const { return Attrs; }
bool operator==(const AttrBuilder &B) const;

View File

@ -126,6 +126,9 @@ def InReg : EnumAttr<"inreg", [ParamAttr, RetAttr]>;
/// Build jump-instruction tables and replace refs.
def JumpTable : EnumAttr<"jumptable", [FnAttr]>;
/// Memory effects of the function.
def Memory : IntAttr<"memory", [FnAttr]>;
/// Function must be optimized for size first.
def MinSize : EnumAttr<"minsize", [FnAttr]>;

View File

@ -82,11 +82,6 @@ private:
return (uint32_t)Loc * BitsPerLoc;
}
static auto locations() {
return enum_seq_inclusive(Location::ArgMem, Location::Other,
force_iteration_on_noniterable_enum);
}
MemoryEffects(uint32_t Data) : Data(Data) {}
void setModRef(Location Loc, ModRefInfo MR) {
@ -97,6 +92,12 @@ private:
friend raw_ostream &operator<<(raw_ostream &OS, MemoryEffects RMRB);
public:
/// Returns iterator over all supported location kinds.
static auto locations() {
return enum_seq_inclusive(Location::ArgMem, Location::Other,
force_iteration_on_noniterable_enum);
}
/// Create MemoryEffects that can access only the given location with the
/// given ModRefInfo.
MemoryEffects(Location Loc, ModRefInfo MR) { setModRef(Loc, MR); }
@ -147,6 +148,18 @@ public:
return FRMB;
}
/// Create MemoryEffects from an encoded integer value (used by memory
/// attribute).
static MemoryEffects createFromIntValue(uint32_t Data) {
return MemoryEffects(Data);
}
/// Convert MemoryEffects into an encoded integer value (used by memory
/// attribute).
uint32_t toIntValue() const {
return Data;
}
/// Get ModRefInfo for the given Location.
ModRefInfo getModRef(Location Loc) const {
return ModRefInfo((Data >> getLocationPos(Loc)) & LocMask);

View File

@ -644,6 +644,12 @@ lltok::Kind LLLexer::LexIdentifier() {
KEYWORD(DISPLAY_NAME);
#include "llvm/IR/Attributes.inc"
KEYWORD(read);
KEYWORD(write);
KEYWORD(readwrite);
KEYWORD(argmem);
KEYWORD(inaccessiblemem);
KEYWORD(type);
KEYWORD(opaque);

View File

@ -14,6 +14,7 @@
#include "llvm/ADT/APSInt.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/None.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/AsmParser/LLToken.h"
@ -36,6 +37,7 @@
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Metadata.h"
#include "llvm/IR/ModRef.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Operator.h"
#include "llvm/IR/Value.h"
@ -1456,6 +1458,13 @@ bool LLParser::parseEnumAttribute(Attribute::AttrKind Attr, AttrBuilder &B,
B.addAllocKindAttr(Kind);
return false;
}
case Attribute::Memory: {
Optional<MemoryEffects> ME = parseMemoryAttr();
if (!ME)
return true;
B.addMemoryAttr(*ME);
return false;
}
default:
B.addAttribute(Attr);
Lex.Lex();
@ -2177,6 +2186,87 @@ bool LLParser::parseAllocKind(AllocFnKind &Kind) {
return false;
}
static Optional<MemoryEffects::Location> keywordToLoc(lltok::Kind Tok) {
switch (Tok) {
case lltok::kw_argmem:
return MemoryEffects::ArgMem;
case lltok::kw_inaccessiblemem:
return MemoryEffects::InaccessibleMem;
default:
return None;
}
}
static Optional<ModRefInfo> keywordToModRef(lltok::Kind Tok) {
switch (Tok) {
case lltok::kw_none:
return ModRefInfo::NoModRef;
case lltok::kw_read:
return ModRefInfo::Ref;
case lltok::kw_write:
return ModRefInfo::Mod;
case lltok::kw_readwrite:
return ModRefInfo::ModRef;
default:
return None;
}
}
Optional<MemoryEffects> LLParser::parseMemoryAttr() {
MemoryEffects ME = MemoryEffects::none();
// We use syntax like memory(argmem: read), so the colon should not be
// interpreted as a label terminator.
Lex.setIgnoreColonInIdentifiers(true);
auto _ = make_scope_exit([&] { Lex.setIgnoreColonInIdentifiers(false); });
Lex.Lex();
if (!EatIfPresent(lltok::lparen)) {
tokError("expected '('");
return None;
}
bool SeenLoc = false;
do {
Optional<MemoryEffects::Location> Loc = keywordToLoc(Lex.getKind());
if (Loc) {
Lex.Lex();
if (!EatIfPresent(lltok::colon)) {
tokError("expected ':' after location");
return None;
}
}
Optional<ModRefInfo> MR = keywordToModRef(Lex.getKind());
if (!MR) {
if (!Loc)
tokError("expected memory location (argmem, inaccessiblemem) "
"or access kind (none, read, write, readwrite)");
else
tokError("expected access kind (none, read, write, readwrite)");
return None;
}
Lex.Lex();
if (Loc) {
SeenLoc = true;
ME = ME.getWithModRef(*Loc, *MR);
} else {
if (SeenLoc) {
tokError("default access kind must be specified first");
return None;
}
ME = MemoryEffects(*MR);
}
if (EatIfPresent(lltok::rparen))
return ME;
} while (EatIfPresent(lltok::comma));
tokError("unterminated memory attribute");
return None;
}
/// parseOptionalCommaAlign
/// ::=
/// ::= ',' align 4

View File

@ -55,6 +55,7 @@
#include "llvm/IR/IntrinsicsARM.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Metadata.h"
#include "llvm/IR/ModRef.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/ModuleSummaryIndex.h"
#include "llvm/IR/Operator.h"
@ -1878,6 +1879,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
return Attribute::InReg;
case bitc::ATTR_KIND_JUMP_TABLE:
return Attribute::JumpTable;
case bitc::ATTR_KIND_MEMORY:
return Attribute::Memory;
case bitc::ATTR_KIND_MIN_SIZE:
return Attribute::MinSize;
case bitc::ATTR_KIND_NAKED:
@ -2122,6 +2125,8 @@ Error BitcodeReader::parseAttributeGroupBlock() {
B.addUWTableAttr(UWTableKind(Record[++i]));
else if (Kind == Attribute::AllocKind)
B.addAllocKindAttr(static_cast<AllocFnKind>(Record[++i]));
else if (Kind == Attribute::Memory)
B.addMemoryAttr(MemoryEffects::createFromIntValue(Record[++i]));
} else if (Record[i] == 3 || Record[i] == 4) { // String attribute
bool HasValue = (Record[i++] == 4);
SmallString<64> KindStr;

View File

@ -656,6 +656,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
return bitc::ATTR_KIND_ALLOCATED_POINTER;
case Attribute::AllocKind:
return bitc::ATTR_KIND_ALLOC_KIND;
case Attribute::Memory:
return bitc::ATTR_KIND_MEMORY;
case Attribute::Naked:
return bitc::ATTR_KIND_NAKED;
case Attribute::Nest:

View File

@ -26,6 +26,7 @@
#include "llvm/Config/llvm-config.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/ModRef.h"
#include "llvm/IR/Type.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/ErrorHandling.h"
@ -383,6 +384,26 @@ AllocFnKind Attribute::getAllocKind() const {
return AllocFnKind(pImpl->getValueAsInt());
}
MemoryEffects Attribute::getMemoryEffects() const {
assert(hasAttribute(Attribute::Memory) &&
"Can only call getMemoryEffects() on memory attribute");
return MemoryEffects::createFromIntValue(pImpl->getValueAsInt());
}
static const char *getModRefStr(ModRefInfo MR) {
switch (MR) {
case ModRefInfo::NoModRef:
return "none";
case ModRefInfo::Ref:
return "read";
case ModRefInfo::Mod:
return "write";
case ModRefInfo::ModRef:
return "readwrite";
}
llvm_unreachable("Invalid ModRefInfo");
}
std::string Attribute::getAsString(bool InAttrGrp) const {
if (!pImpl) return {};
@ -474,6 +495,48 @@ std::string Attribute::getAsString(bool InAttrGrp) const {
.str();
}
if (hasAttribute(Attribute::Memory)) {
std::string Result;
raw_string_ostream OS(Result);
bool First = true;
OS << "memory(";
MemoryEffects ME = getMemoryEffects();
// Print access kind for "other" as the default access kind. This way it
// will apply to any new location kinds that get split out of "other".
ModRefInfo OtherMR = ME.getModRef(MemoryEffects::Other);
if (OtherMR != ModRefInfo::NoModRef || ME.getModRef() == OtherMR) {
First = false;
OS << getModRefStr(OtherMR);
}
for (auto Loc : MemoryEffects::locations()) {
ModRefInfo MR = ME.getModRef(Loc);
if (MR == OtherMR)
continue;
if (!First)
OS << ", ";
First = false;
switch (Loc) {
case MemoryEffects::ArgMem:
OS << "argmem: ";
break;
case MemoryEffects::InaccessibleMem:
OS << "inaccessiblemem: ";
break;
case MemoryEffects::Other:
llvm_unreachable("This is represented as the default access kind");
}
OS << getModRefStr(MR);
}
OS << ")";
OS.flush();
return Result;
}
// Convert target-dependent attributes to strings of the form:
//
// "kind"
@ -1723,6 +1786,10 @@ AttrBuilder &AttrBuilder::addUWTableAttr(UWTableKind Kind) {
return addRawIntAttr(Attribute::UWTable, uint64_t(Kind));
}
AttrBuilder &AttrBuilder::addMemoryAttr(MemoryEffects ME) {
return addRawIntAttr(Attribute::Memory, ME.toIntValue());
}
AttrBuilder &AttrBuilder::addAllocKindAttr(AllocFnKind Kind) {
return addRawIntAttr(Attribute::AllocKind, static_cast<uint64_t>(Kind));
}

View File

@ -922,6 +922,7 @@ Function *CodeExtractor::constructFunction(const ValueSet &inputs,
case Attribute::WriteOnly:
case Attribute::AllocKind:
case Attribute::PresplitCoroutine:
case Attribute::Memory:
continue;
// Those attributes should be safe to propagate to the extracted function.
case Attribute::AlwaysInline:

View File

@ -0,0 +1,34 @@
; RUN: split-file %s %t
; RUN: not llvm-as < %t/missing-args.ll 2>&1 | FileCheck %s --check-prefix=MISSING-ARGS
; RUN: not llvm-as < %t/empty.ll 2>&1 | FileCheck %s --check-prefix=EMPTY
; RUN: not llvm-as < %t/unterminated.ll 2>&1 | FileCheck %s --check-prefix=UNTERMINATED
; RUN: not llvm-as < %t/invalid-kind.ll 2>&1 | FileCheck %s --check-prefix=INVALID-KIND
; RUN: not llvm-as < %t/other.ll 2>&1 | FileCheck %s --check-prefix=OTHER
; RUN: not llvm-as < %t/missing-colon.ll 2>&1 | FileCheck %s --check-prefix=MISSING-COLON
; RUN: not llvm-as < %t/invalid-access-kind.ll 2>&1 | FileCheck %s --check-prefix=INVALID-ACCESS-KIND
; RUN: not llvm-as < %t/default-after-loc.ll 2>&1 | FileCheck %s --check-prefix=DEFAULT-AFTER-LOC
;--- missing-args.ll
; MISSING-ARGS: error: expected '('
declare void @fn() memory
;--- empty.ll
; EMPTY: error: expected memory location (argmem, inaccessiblemem) or access kind (none, read, write, readwrite)
declare void @fn() memory()
;--- unterminated.ll
; UNTERMINATED: error: unterminated memory attribute
declare void @fn() memory(read
;--- invalid-kind.ll
; INVALID-KIND: error: expected memory location (argmem, inaccessiblemem) or access kind (none, read, write, readwrite)
declare void @fn() memory(foo)
;--- other.ll
; OTHER: error: expected memory location (argmem, inaccessiblemem) or access kind (none, read, write, readwrite)
declare void @fn() memory(other: read)
;--- missing-colon.ll
; MISSING-COLON: error: expected ':' after location
declare void @fn() memory(argmem)
;--- invalid-access-kind.ll
; INVALID-ACCESS-KIND: error: expected access kind (none, read, write, readwrite)
declare void @fn() memory(argmem: foo)
;--- default-after-loc.ll
; DEFAULT-AFTER-LOC: error: default access kind must be specified first
declare void @fn() memory(argmem: read, write)

View File

@ -0,0 +1,68 @@
; RUN: llvm-as < %s | llvm-dis | FileCheck %s
; CHECK: Function Attrs: memory(none)
; CHECK: @fn_readnone2()
declare void @fn_readnone2() memory(none)
; CHECK: Function Attrs: memory(read)
; CHECK: @fn_readonly()
declare void @fn_readonly() memory(read)
; CHECK: Function Attrs: memory(write)
; CHECK: @fn_writeonly()
declare void @fn_writeonly() memory(write)
; CHECK: Function Attrs: memory(readwrite)
; CHECK: @fn_readwrite()
declare void @fn_readwrite() memory(readwrite)
; CHECK: Function Attrs: memory(argmem: read)
; CHECK: @fn_argmem_read()
declare void @fn_argmem_read() memory(argmem: read)
; CHECK: Function Attrs: memory(argmem: write)
; CHECK: @fn_argmem_write()
declare void @fn_argmem_write() memory(argmem: write)
; CHECK: Function Attrs: memory(argmem: readwrite)
; CHECK: @fn_argmem_readwrite()
declare void @fn_argmem_readwrite() memory(argmem: readwrite)
; CHECK: Function Attrs: memory(inaccessiblemem: read)
; CHECK: @fn_inaccessiblemem_read()
declare void @fn_inaccessiblemem_read() memory(inaccessiblemem: read)
; CHECK: Function Attrs: memory(inaccessiblemem: write)
; CHECK: @fn_inaccessiblemem_write()
declare void @fn_inaccessiblemem_write() memory(inaccessiblemem: write)
; CHECK: Function Attrs: memory(inaccessiblemem: readwrite)
; CHECK: @fn_inaccessiblemem_readwrite()
declare void @fn_inaccessiblemem_readwrite() memory(inaccessiblemem: readwrite)
; CHECK: Function Attrs: memory(read, argmem: readwrite)
; CHECK: @fn_read_argmem_readwrite()
declare void @fn_read_argmem_readwrite() memory(read, argmem: readwrite)
; CHECK: Function Attrs: memory(read, argmem: write)
; CHECK: @fn_read_argmem_write()
declare void @fn_read_argmem_write() memory(read, argmem: write)
; CHECK: Function Attrs: memory(read, argmem: none)
; CHECK: @fn_read_argmem_none()
declare void @fn_read_argmem_none() memory(read, argmem: none)
; CHECK: Function Attrs: memory(argmem: read, inaccessiblemem: read)
; CHECK: @fn_argmem_inaccessiblemem_read()
declare void @fn_argmem_inaccessiblemem_read()
memory(argmem: read, inaccessiblemem: read)
; CHECK: Function Attrs: memory(argmem: read, inaccessiblemem: write)
; CHECK: @fn_argmem_read_inaccessiblemem_write()
declare void @fn_argmem_read_inaccessiblemem_write()
memory(argmem: read, inaccessiblemem: write)
; CHECK: Function Attrs: memory(argmem: read, inaccessiblemem: write)
; CHECK: @fn_argmem_read_inaccessiblemem_write_reordered()
declare void @fn_argmem_read_inaccessiblemem_write_reordered()
memory(inaccessiblemem: write, argmem: read)