[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:
parent
9d9de5a5df
commit
e9754f0211
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]>;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
Loading…
Reference in New Issue