From ff585feacf58b384d7525d2b1368298435132fb4 Mon Sep 17 00:00:00 2001 From: Antonio Frighetto Date: Mon, 10 Feb 2025 15:30:35 +0100 Subject: [PATCH] [IR][ModRef] Introduce `errno` memory location Model C/C++ `errno` macro by adding a corresponding `errno` memory location kind to the IR. Preliminary work to separate `errno` writes from other memory accesses, to the benefit of alias analyses and optimization correctness. Previous discussion: https://discourse.llvm.org/t/rfc-modelling-errno-memory-effects/82972. --- llvm/docs/LangRef.rst | 7 +++-- llvm/include/llvm/AsmParser/LLToken.h | 1 + llvm/include/llvm/Support/ModRef.h | 19 +++++++++++- llvm/lib/AsmParser/LLLexer.cpp | 1 + llvm/lib/AsmParser/LLParser.cpp | 4 ++- llvm/lib/Bitcode/Reader/BitcodeReader.cpp | 28 ++++++++++++++---- llvm/lib/Bitcode/Writer/BitcodeWriter.cpp | 11 +++++-- llvm/lib/IR/Attributes.cpp | 3 ++ llvm/lib/Support/ModRef.cpp | 3 ++ llvm/lib/Transforms/IPO/FunctionAttrs.cpp | 4 +++ llvm/lib/Transforms/IPO/SCCP.cpp | 6 ++-- .../test/Assembler/memory-attribute-errors.ll | 6 ++-- llvm/test/Assembler/memory-attribute.ll | 12 ++++++++ .../Inputs/memory-attribute-upgrade.bc | Bin 0 -> 1736 bytes llvm/test/Bitcode/memory-attribute-upgrade.ll | 7 +++++ llvm/unittests/Support/ModRefTest.cpp | 4 +-- mlir/test/Target/LLVMIR/llvmir.mlir | 10 +++---- 17 files changed, 103 insertions(+), 23 deletions(-) create mode 100644 llvm/test/Bitcode/Inputs/memory-attribute-upgrade.bc create mode 100644 llvm/test/Bitcode/memory-attribute-upgrade.ll diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index d615514e5a72b..b9f681f2feed8 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -2046,8 +2046,8 @@ For example: This attribute specifies the possible memory effects of the call-site or function. It allows specifying the possible access kinds (``none``, ``read``, ``write``, or ``readwrite``) for the possible memory location - kinds (``argmem``, ``inaccessiblemem``, as well as a default). It is best - understood by example: + kinds (``argmem``, ``inaccessiblemem``, ``errnomem``, as well as a default). + It is best understood by example: - ``memory(none)``: Does not access any memory. - ``memory(read)``: May read (but not write) any memory. @@ -2056,6 +2056,8 @@ For example: - ``memory(argmem: read)``: May only read argument memory. - ``memory(argmem: read, inaccessiblemem: write)``: May only read argument memory and only write inaccessible memory. + - ``memory(argmem: read, errnomem: write)``: May only read argument memory + and only write errno. - ``memory(read, argmem: readwrite)``: May read any memory (default mode) and additionally write argument memory. - ``memory(readwrite, argmem: none)``: May access any memory apart from @@ -2085,6 +2087,7 @@ For example: allocator function may return newly accessible memory while only accessing inaccessible memory itself). Inaccessible memory is often used to model control dependencies of intrinsics. + - ``errnomem``: This refers to accesses to the ``errno`` variable. - The default access kind (specified without a location prefix) applies to all locations that haven't been specified explicitly, including those that don't currently have a dedicated location kind (e.g. accesses to globals diff --git a/llvm/include/llvm/AsmParser/LLToken.h b/llvm/include/llvm/AsmParser/LLToken.h index 4a431383e0a1c..a53d471f70271 100644 --- a/llvm/include/llvm/AsmParser/LLToken.h +++ b/llvm/include/llvm/AsmParser/LLToken.h @@ -201,6 +201,7 @@ enum Kind { kw_readwrite, kw_argmem, kw_inaccessiblemem, + kw_errnomem, // Legacy attributes: kw_argmemonly, diff --git a/llvm/include/llvm/Support/ModRef.h b/llvm/include/llvm/Support/ModRef.h index ee71ee803b554..7f58f5236aedd 100644 --- a/llvm/include/llvm/Support/ModRef.h +++ b/llvm/include/llvm/Support/ModRef.h @@ -61,8 +61,10 @@ enum class IRMemLocation { ArgMem = 0, /// Memory that is inaccessible via LLVM IR. InaccessibleMem = 1, + /// Errno memory. + ErrnoMem = 2, /// Any other memory. - Other = 2, + Other = 3, /// Helpers to iterate all locations in the MemoryEffectsBase class. First = ArgMem, @@ -139,6 +141,16 @@ template class MemoryEffectsBase { return MemoryEffectsBase(Location::InaccessibleMem, MR); } + /// Create MemoryEffectsBase that can only access errno memory. + static MemoryEffectsBase errnoMemOnly(ModRefInfo MR = ModRefInfo::ModRef) { + return MemoryEffectsBase(Location::ErrnoMem, MR); + } + + /// Create MemoryEffectsBase that can only access other memory. + static MemoryEffectsBase otherMemOnly(ModRefInfo MR = ModRefInfo::ModRef) { + return MemoryEffectsBase(Location::Other, MR); + } + /// Create MemoryEffectsBase that can only access inaccessible or argument /// memory. static MemoryEffectsBase @@ -212,6 +224,11 @@ template class MemoryEffectsBase { return getWithoutLoc(Location::InaccessibleMem).doesNotAccessMemory(); } + /// Whether this function only (at most) accesses errno memory. + bool onlyAccessesErrnoMem() const { + return getWithoutLoc(Location::ErrnoMem).doesNotAccessMemory(); + } + /// Whether this function only (at most) accesses argument and inaccessible /// memory. bool onlyAccessesInaccessibleOrArgMem() const { diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp index 438824f84e2d0..c867a68518e4d 100644 --- a/llvm/lib/AsmParser/LLLexer.cpp +++ b/llvm/lib/AsmParser/LLLexer.cpp @@ -701,6 +701,7 @@ lltok::Kind LLLexer::LexIdentifier() { KEYWORD(readwrite); KEYWORD(argmem); KEYWORD(inaccessiblemem); + KEYWORD(errnomem); KEYWORD(argmemonly); KEYWORD(inaccessiblememonly); KEYWORD(inaccessiblemem_or_argmemonly); diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp index ad52a9f493eae..0817851bd408a 100644 --- a/llvm/lib/AsmParser/LLParser.cpp +++ b/llvm/lib/AsmParser/LLParser.cpp @@ -2497,6 +2497,8 @@ static std::optional keywordToLoc(lltok::Kind Tok) { return IRMemLocation::ArgMem; case lltok::kw_inaccessiblemem: return IRMemLocation::InaccessibleMem; + case lltok::kw_errnomem: + return IRMemLocation::ErrnoMem; default: return std::nullopt; } @@ -2545,7 +2547,7 @@ std::optional LLParser::parseMemoryAttr() { std::optional MR = keywordToModRef(Lex.getKind()); if (!MR) { if (!Loc) - tokError("expected memory location (argmem, inaccessiblemem) " + tokError("expected memory location (argmem, inaccessiblemem, errnomem) " "or access kind (none, read, write, readwrite)"); else tokError("expected access kind (none, read, write, readwrite)"); diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp index 1a09e80c4fbb2..d687495c42de6 100644 --- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -1937,8 +1937,7 @@ static void addRawAttributeValue(AttrBuilder &B, uint64_t Val) { } /// This fills an AttrBuilder object with the LLVM attributes that have -/// been decoded from the given integer. This function must stay in sync with -/// 'encodeLLVMAttributesForBitcode'. +/// been decoded from the given integer. static void decodeLLVMAttributesForBitcode(AttrBuilder &B, uint64_t EncodedAttrs, uint64_t AttrIdx) { @@ -2398,9 +2397,28 @@ Error BitcodeReader::parseAttributeGroupBlock() { B.addUWTableAttr(UWTableKind(Record[++i])); else if (Kind == Attribute::AllocKind) B.addAllocKindAttr(static_cast(Record[++i])); - else if (Kind == Attribute::Memory) - B.addMemoryAttr(MemoryEffects::createFromIntValue(Record[++i])); - else if (Kind == Attribute::Captures) + else if (Kind == Attribute::Memory) { + uint64_t EncodedME = Record[++i]; + const uint8_t Version = (EncodedME >> 56); + if (Version == 0) { + // Errno memory location was previously encompassed into default + // memory. Ensure this is taken into account while reconstructing + // the memory attribute prior to its introduction. + ModRefInfo ArgMem = ModRefInfo((EncodedME >> 0) & 3); + ModRefInfo InaccessibleMem = ModRefInfo((EncodedME >> 2) & 3); + ModRefInfo OtherMem = ModRefInfo((EncodedME >> 4) & 3); + auto ME = MemoryEffects::inaccessibleMemOnly(InaccessibleMem) | + MemoryEffects::argMemOnly(ArgMem) | + MemoryEffects::errnoMemOnly(OtherMem) | + MemoryEffects::otherMemOnly(OtherMem); + B.addMemoryAttr(ME); + } else { + // Construct the memory attribute directly from the encoded base + // on newer versions. + B.addMemoryAttr(MemoryEffects::createFromIntValue( + EncodedME & 0x00FFFFFFFFFFFFFFULL)); + } + } else if (Kind == Attribute::Captures) B.addCapturesAttr(CaptureInfo::createFromIntValue(Record[++i])); else if (Kind == Attribute::NoFPClass) B.addNoFPClassAttr( diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp index 7ca63c2c7251d..450b8066540e5 100644 --- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -977,8 +977,15 @@ void ModuleBitcodeWriter::writeAttributeGroupTable() { Record.push_back(getAttrKindEncoding(Attr.getKindAsEnum())); } else if (Attr.isIntAttribute()) { Record.push_back(1); - Record.push_back(getAttrKindEncoding(Attr.getKindAsEnum())); - Record.push_back(Attr.getValueAsInt()); + Attribute::AttrKind Kind = Attr.getKindAsEnum(); + Record.push_back(getAttrKindEncoding(Kind)); + if (Kind == Attribute::Memory) { + // Version field for upgrading old memory effects. + const uint64_t Version = 1; + Record.push_back((Version << 56) | Attr.getValueAsInt()); + } else { + Record.push_back(Attr.getValueAsInt()); + } } else if (Attr.isStringAttribute()) { StringRef Kind = Attr.getKindAsString(); StringRef Val = Attr.getValueAsString(); diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp index ef0591ef31744..8da1dfe914818 100644 --- a/llvm/lib/IR/Attributes.cpp +++ b/llvm/lib/IR/Attributes.cpp @@ -647,6 +647,9 @@ std::string Attribute::getAsString(bool InAttrGrp) const { case IRMemLocation::InaccessibleMem: OS << "inaccessiblemem: "; break; + case IRMemLocation::ErrnoMem: + OS << "errnomem: "; + break; case IRMemLocation::Other: llvm_unreachable("This is represented as the default access kind"); } diff --git a/llvm/lib/Support/ModRef.cpp b/llvm/lib/Support/ModRef.cpp index d3b3dd11171f1..2bb9bc945bd2e 100644 --- a/llvm/lib/Support/ModRef.cpp +++ b/llvm/lib/Support/ModRef.cpp @@ -43,6 +43,9 @@ raw_ostream &llvm::operator<<(raw_ostream &OS, MemoryEffects ME) { case IRMemLocation::InaccessibleMem: OS << "InaccessibleMem: "; break; + case IRMemLocation::ErrnoMem: + OS << "ErrnoMem: "; + break; case IRMemLocation::Other: OS << "Other: "; break; diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp index 4445d56146da7..a66d7ce9c3f50 100644 --- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp +++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp @@ -141,6 +141,7 @@ static void addLocAccess(MemoryEffects &ME, const MemoryLocation &Loc, // If it's not an identified object, it might be an argument. if (!isIdentifiedObject(UO)) ME |= MemoryEffects::argMemOnly(MR); + ME |= MemoryEffects(IRMemLocation::ErrnoMem, MR); ME |= MemoryEffects(IRMemLocation::Other, MR); } @@ -219,6 +220,9 @@ checkFunctionMemoryAccess(Function &F, bool ThisBody, AAResults &AAR, if (isa(I)) continue; + // Merge callee's memory effects into caller's ones, including + // inaccessible and errno memory, but excluding argument memory, which is + // handled separately. ME |= CallME.getWithoutLoc(IRMemLocation::ArgMem); // If the call accesses captured memory (currently part of "other") and diff --git a/llvm/lib/Transforms/IPO/SCCP.cpp b/llvm/lib/Transforms/IPO/SCCP.cpp index 2afcdf09af016..639e3039108a7 100644 --- a/llvm/lib/Transforms/IPO/SCCP.cpp +++ b/llvm/lib/Transforms/IPO/SCCP.cpp @@ -191,8 +191,10 @@ static bool runIPSCCP( if (ME == MemoryEffects::unknown()) return AL; - ME |= MemoryEffects(IRMemLocation::Other, - ME.getModRef(IRMemLocation::ArgMem)); + ModRefInfo ArgMemMR = ME.getModRef(IRMemLocation::ArgMem); + ME |= MemoryEffects(IRMemLocation::ErrnoMem, ArgMemMR); + ME |= MemoryEffects(IRMemLocation::Other, ArgMemMR); + return AL.addFnAttribute( F.getContext(), Attribute::getWithMemoryEffects(F.getContext(), ME)); diff --git a/llvm/test/Assembler/memory-attribute-errors.ll b/llvm/test/Assembler/memory-attribute-errors.ll index 1fba90362e79b..2eed11d9465d5 100644 --- a/llvm/test/Assembler/memory-attribute-errors.ll +++ b/llvm/test/Assembler/memory-attribute-errors.ll @@ -12,16 +12,16 @@ ; MISSING-ARGS: error: expected '(' declare void @fn() memory ;--- empty.ll -; EMPTY: error: expected memory location (argmem, inaccessiblemem) or access kind (none, read, write, readwrite) +; EMPTY: error: expected memory location (argmem, inaccessiblemem, errnomem) 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) +; INVALID-KIND: error: expected memory location (argmem, inaccessiblemem, errnomem) 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) +; OTHER: error: expected memory location (argmem, inaccessiblemem, errnomem) or access kind (none, read, write, readwrite) declare void @fn() memory(other: read) ;--- missing-colon.ll ; MISSING-COLON: error: expected ':' after location diff --git a/llvm/test/Assembler/memory-attribute.ll b/llvm/test/Assembler/memory-attribute.ll index 2f7d3980eb378..effd4ce7c4548 100644 --- a/llvm/test/Assembler/memory-attribute.ll +++ b/llvm/test/Assembler/memory-attribute.ll @@ -40,6 +40,18 @@ declare void @fn_inaccessiblemem_write() memory(inaccessiblemem: write) ; CHECK: @fn_inaccessiblemem_readwrite() declare void @fn_inaccessiblemem_readwrite() memory(inaccessiblemem: readwrite) +; CHECK: Function Attrs: memory(errnomem: read) +; CHECK: @fn_errnomem_read() +declare void @fn_errnomem_read() memory(errnomem: read) + +; CHECK: Function Attrs: memory(errnomem: write) +; CHECK: @fn_errnomem_write() +declare void @fn_errnomem_write() memory(errnomem: write) + +; CHECK: Function Attrs: memory(errnomem: readwrite) +; CHECK: @fn_errnomem_readwrite() +declare void @fn_errnomem_readwrite() memory(errnomem: readwrite) + ; CHECK: Function Attrs: memory(read, argmem: readwrite) ; CHECK: @fn_read_argmem_readwrite() declare void @fn_read_argmem_readwrite() memory(read, argmem: readwrite) diff --git a/llvm/test/Bitcode/Inputs/memory-attribute-upgrade.bc b/llvm/test/Bitcode/Inputs/memory-attribute-upgrade.bc new file mode 100644 index 0000000000000000000000000000000000000000..52a38d27b10327243e1b7ba34f2e7762a8ebf722 GIT binary patch literal 1736 zcmYjRZ%h+s7=MpKuVTGB6t&g6+nu1{gMW@9(Asusdv4av#p+zbk{Mc{1vX^`I{vx1 zmUbT7`k|VB=m$(-Gt2x23m^DE7TW7v)y6TiEF>eO9e>0Pl}xsnXtq69)a*^}eeQYR z=Xu`e`8|Ie;`DrNIYK1}p(d?R`PRGNeSP-n#L3;6%A=T}1o2vgE@B96F5*!U$mrq2 zLlu|pnd0MW%3!*JMYMZMG9FVwIaur&5jnzP;S)d-Yhj3hMfL1UvYG+6s7D} zidr(7@p4XleTch>84QsIB6gWS8XFz_vPduEx3}vJ(db@^ITFYq^cjaxDZI7ES4@5D z7`#cHhAq~aayd&k?Gta5I1KwVrMs{yhd>F(f(V_`Aq1|X-Jj?Fg(Xezxa$`AXUab{ zo0=Ha{R4lWXwKQU)Rm`|Q!;em49Fh`Jr&SxgKh_RLUU;ohM_k|P1#o4rDz;shaHJw zcFJ^hxaX;c;&g83_BKEP@Yq<>YVGy9lL*;f9H7}C{|;*_q6$U}r;5z!d|422FNNxC zO<|iSiBNDq1&0du1~#G9mz{`3H|;xiqgYI7;A|XxyA8BP6Qt95+EiZGoO)Sn3Lf4D|G%n^T06RU!% zDKknhj?&q}KIfrt@pL-G-14Y@iHW9+Xi60JN!b_|jYBGPTs{PkJ{vUutTK)8f>g~h(5~ohA$4CVM_I>u2hhE{C`6P1@V-<&aX3e8M z^J&M-@PR74D-n8cfg)C?i6s~DSg1(ewX=)) zowIpn9vpD$&0}HfM0j^ZHbzBLR<=wgO&J+tv}BzVof;gx>I48AB>k^9h*dZFAg5mA z$R&YX;>f>UL8#cGY5cfF72vmb`C8Z-my14tCZXr@^c+VodX#-7#HKv-%qX4NNQNhgk?Rlu#1kYo4{d-_@}m(T zn1w=@Ath$00O-$*%B=YQ6AS}2P&{k77B&w<1E{PcVGA@vOjbsYhl4wy+lrGzb}Y#0 zH0klYZDSnyC=1+d$S8AgTd}<`TNJ%?8A7r;LKROxSBs1(YK1ZOLa&qCn1cp_+7rS4 zD5|m@0ttl9g0U)~TUGID#a3&;+Z|~1bo4j&cC`n*jh?R7^WO7~U0zSi%NvFH|3yKv z*>mok*Sn$hVaK`t=e#1Tu!ib`!a @@ -21,7 +20,8 @@ TEST(ModRefTest, PrintMemoryEffects) { std::string S; raw_string_ostream OS(S); OS << MemoryEffects::none(); - EXPECT_EQ(S, "ArgMem: NoModRef, InaccessibleMem: NoModRef, Other: NoModRef"); + EXPECT_EQ(S, "ArgMem: NoModRef, InaccessibleMem: NoModRef, ErrnoMem: " + "NoModRef, Other: NoModRef"); } } // namespace diff --git a/mlir/test/Target/LLVMIR/llvmir.mlir b/mlir/test/Target/LLVMIR/llvmir.mlir index c0b3a9cb43022..7f9a3ba79d724 100644 --- a/mlir/test/Target/LLVMIR/llvmir.mlir +++ b/mlir/test/Target/LLVMIR/llvmir.mlir @@ -2347,7 +2347,7 @@ llvm.func @readonly_function(%arg0: !llvm.ptr {llvm.readonly}) llvm.func @arg_mem_none_func() attributes { memory_effects = #llvm.memory_effects} -// CHECK: attributes #[[ATTR]] = { memory(readwrite, argmem: none) } +// CHECK: attributes #[[ATTR]] = { memory(readwrite, argmem: none, errnomem: none) } // ----- @@ -2355,7 +2355,7 @@ llvm.func @arg_mem_none_func() attributes { llvm.func @readwrite_func() attributes { memory_effects = #llvm.memory_effects} -// CHECK: attributes #[[ATTR]] = { memory(readwrite) } +// CHECK: attributes #[[ATTR]] = { memory(readwrite, errnomem: none) } // ----- @@ -2613,11 +2613,11 @@ llvm.func @mem_effects_call() { // CHECK: #[[ATTRS_0]] // CHECK-SAME: memory(none) // CHECK: #[[ATTRS_1]] -// CHECK-SAME: memory(read, argmem: none, inaccessiblemem: write) +// CHECK-SAME: memory(read, argmem: none, inaccessiblemem: write, errnomem: none) // CHECK: #[[ATTRS_2]] -// CHECK-SAME: memory(read, inaccessiblemem: write) +// CHECK-SAME: memory(read, inaccessiblemem: write, errnomem: none) // CHECK: #[[ATTRS_3]] -// CHECK-SAME: memory(readwrite, argmem: read) +// CHECK-SAME: memory(readwrite, argmem: read, errnomem: none) // -----