Index: include/llvm/IR/ModuleSummaryIndex.h =================================================================== --- include/llvm/IR/ModuleSummaryIndex.h +++ include/llvm/IR/ModuleSummaryIndex.h @@ -162,7 +162,8 @@ /// Struct that holds a reference to a particular GUID in a global value /// summary. struct ValueInfo { - PointerIntPair + enum Flags { HaveGV = 1, ReadOnly = 2, WriteOnly = 4 }; + PointerIntPair RefAndFlags; ValueInfo() = default; @@ -188,9 +189,33 @@ : getRef()->second.U.Name; } - bool haveGVs() const { return RefAndFlags.getInt() & 0x1; } - bool isReadOnly() const { return RefAndFlags.getInt() & 0x2; } - void setReadOnly() { RefAndFlags.setInt(RefAndFlags.getInt() | 0x2); } + bool haveGVs() const { return RefAndFlags.getInt() & HaveGV; } + bool isReadOnly() const { + assert(isValidAccessSpecifier()); + return RefAndFlags.getInt() & ReadOnly; + } + bool isWriteOnly() const { + assert(isValidAccessSpecifier()); + return RefAndFlags.getInt() & WriteOnly; + } + unsigned getAccessSpecifier() const { + assert(isValidAccessSpecifier()); + return RefAndFlags.getInt() & (ReadOnly | WriteOnly); + } + bool isValidAccessSpecifier() const { + unsigned BadAccessMask = ReadOnly | WriteOnly; + return (RefAndFlags.getInt() & BadAccessMask) != BadAccessMask; + } + void setReadOnly() { + // We expect ro/wo attribute to set only once during + // ValueInfo lifetime. + assert(isValidAccessSpecifier()); + RefAndFlags.setInt(RefAndFlags.getInt() | ReadOnly); + } + void setWriteOnly() { + assert(isValidAccessSpecifier()); + RefAndFlags.setInt(RefAndFlags.getInt() | WriteOnly); + } const GlobalValueSummaryMapTy::value_type *getRef() const { return RefAndFlags.getPointer(); @@ -584,8 +609,8 @@ std::move(TypeTestAssumeConstVCalls), std::move(TypeCheckedLoadConstVCalls)}); } - // Gets the number of immutable refs in RefEdgeList - unsigned immutableRefCount() const; + // Gets the number of readonly and writeonly refs in RefEdgeList + std::pair specialRefCounts() const; /// Check if this is a function summary. static bool classof(const GlobalValueSummary *GVS) { @@ -701,15 +726,24 @@ /// Global variable summary information to aid decisions and /// implementation of importing. /// -/// Global variable summary has extra flag, telling if it is -/// modified during the program run or not. This affects ThinLTO -/// internalization +/// Global variable summary has two extra flag, telling if it is +/// readonly or writeonly. Both readonly and writeonly variables +/// can be optimized in the backed: readonly variables can be +/// const-folded, while writeonly vars can be completely eliminated +/// together with corresponding stores. We let both things happen +/// by means of internalizing such variables after ThinLTO import. class GlobalVarSummary : public GlobalValueSummary { public: struct GVarFlags { - GVarFlags(bool ReadOnly = false) : ReadOnly(ReadOnly) {} + GVarFlags(bool ReadOnly, bool WriteOnly) + : ReadOnly(ReadOnly), WriteOnly(WriteOnly) {} unsigned ReadOnly : 1; + // Unlike reference (ValueInfo), GlobalVarSummary has both + // ReadOnly and WriteOnly bits set in permodule summaries. + // This happens, because attribute propagation occurs on + // think link phase. + unsigned WriteOnly : 1; } VarFlags; GlobalVarSummary(GVFlags Flags, GVarFlags VarFlags, @@ -724,7 +758,9 @@ GVarFlags varflags() const { return VarFlags; } void setReadOnly(bool RO) { VarFlags.ReadOnly = RO; } + void setWriteOnly(bool WO) { VarFlags.WriteOnly = WO; } bool isReadOnly() const { return VarFlags.ReadOnly; } + bool isWriteOnly() const { return VarFlags.WriteOnly; } }; struct TypeTestResolution { @@ -1232,7 +1268,7 @@ void dumpSCCs(raw_ostream &OS); /// Analyze index and detect unmodified globals - void propagateConstants(const DenseSet &PreservedSymbols); + void propagateAttributes(const DenseSet &PreservedSymbols); }; /// GraphTraits definition to build SCC for the index Index: lib/Analysis/ModuleSummaryAnalysis.cpp =================================================================== --- lib/Analysis/ModuleSummaryAnalysis.cpp +++ lib/Analysis/ModuleSummaryAnalysis.cpp @@ -231,6 +231,13 @@ return false; } +static bool isNonVolatileStore(const Instruction *I) { + if (const auto *SI = dyn_cast(I)) + return !SI->isVolatile(); + + return false; +} + static void computeFunctionSummary(ModuleSummaryIndex &Index, const Module &M, const Function &F, BlockFrequencyInfo *BFI, ProfileSummaryInfo *PSI, DominatorTree &DT, @@ -245,7 +252,7 @@ // Map from callee ValueId to profile count. Used to accumulate profile // counts for all static calls to a given callee. MapVector CallGraphEdges; - SetVector RefEdges; + SetVector RefEdges, LoadRefEdges, StoreRefEdges; SetVector TypeTests; SetVector TypeTestAssumeVCalls, TypeCheckedLoadVCalls; @@ -258,6 +265,7 @@ // list. findRefEdges(Index, &F, RefEdges, Visited); std::vector NonVolatileLoads; + std::vector NonVolatileStores; bool HasInlineAsmMaybeReferencingInternal = false; for (const BasicBlock &BB : F) @@ -271,6 +279,19 @@ Visited.insert(&I); NonVolatileLoads.push_back(&I); continue; + } else if (isNonVolatileStore(&I)) { + Visited.insert(&I); + NonVolatileStores.push_back(&I); + // Add all reference edges from first operand of store. + // We can't tell if such refernces are read or write only. + Value *Stored = I.getOperand(0); + if (auto *GV = dyn_cast(Stored)) + // findRefEdges will try to examine GV operands, so instead + // of calling it we should add GV to RefEdges directly. + RefEdges.insert(Index.getOrInsertValueInfo(GV)); + else if (auto *U = dyn_cast(Stored)) + findRefEdges(Index, U, RefEdges, Visited); + continue; } findRefEdges(Index, &I, RefEdges, Visited); auto CS = ImmutableCallSite(&I); @@ -361,24 +382,50 @@ } } + auto AddRefEdges = [&](const std::vector &Instrs, + SetVector &Edges) { + for (const auto *I : Instrs) { + Visited.erase(I); + findRefEdges(Index, I, Edges, Visited); + } + }; + // By now we processed all instructions in a function, except - // non-volatile loads. All new refs we add in a loop below - // are obviously constant. All constant refs are grouped in the - // end of RefEdges vector, so we can use a single integer value - // to identify them. + // non-volatile loads and non-volatile value stores. Let's find + // ref edges for both of instruction sets + AddRefEdges(NonVolatileLoads, LoadRefEdges); + AddRefEdges(NonVolatileStores, StoreRefEdges); + + // If both load and store instruction reference the same variable + // we won't be able to optimize it. Add all such reference edges + // to RefEdges set. + for (auto &VI : StoreRefEdges) + if (LoadRefEdges.remove(VI)) + RefEdges.insert(VI); + unsigned RefCnt = RefEdges.size(); - for (const Instruction *I : NonVolatileLoads) { - Visited.erase(I); - findRefEdges(Index, I, RefEdges, Visited); - } + // All new reference edges inserted in two loops below are either + // read or write only. They will be grouped in the end of RefEdges + // vector, so we can use a single integer value to identify them. + for (auto &VI : LoadRefEdges) + RefEdges.insert(VI); + + unsigned FirstWORef = RefEdges.size(); + for (auto &VI : StoreRefEdges) + RefEdges.insert(VI); + std::vector Refs = RefEdges.takeVector(); // Regular LTO module doesn't participate in ThinLTO import, // so no reference from it can be readonly, since this would // require importing variable as local copy - if (IsThinLTO) - for (; RefCnt < Refs.size(); ++RefCnt) + if (IsThinLTO) { + for (; RefCnt < FirstWORef; ++RefCnt) Refs[RefCnt].setReadOnly(); + for (; RefCnt < Refs.size(); ++RefCnt) + Refs[RefCnt].setWriteOnly(); + } + // Explicit add hot edges to enforce importing for designated GUIDs for // sample PGO, to enable the same inlines as the profiled optimized binary. for (auto &I : F.getImportGUIDs()) @@ -422,10 +469,11 @@ /* Live = */ false, V.isDSOLocal(), V.hasLinkOnceODRLinkage() && V.hasGlobalUnnamedAddr()); - // Don't mark variables we won't be able to internalize as read-only. - GlobalVarSummary::GVarFlags VarFlags( + // Don't mark variables we won't be able to internalize as read/write-only. + bool CanBeInternalized = !V.hasComdat() && !V.hasAppendingLinkage() && !V.isInterposable() && - !V.hasAvailableExternallyLinkage() && !V.hasDLLExportStorageClass()); + !V.hasAvailableExternallyLinkage() && !V.hasDLLExportStorageClass(); + GlobalVarSummary::GVarFlags VarFlags(CanBeInternalized, CanBeInternalized); auto GVarSummary = llvm::make_unique(Flags, VarFlags, RefEdges.takeVector()); if (NonRenamableLocal) @@ -541,7 +589,7 @@ } else { std::unique_ptr Summary = llvm::make_unique( - GVFlags, GlobalVarSummary::GVarFlags(), + GVFlags, GlobalVarSummary::GVarFlags(false, false), ArrayRef{}); Index.addGlobalValueSummary(*GV, std::move(Summary)); } Index: lib/AsmParser/LLParser.cpp =================================================================== --- lib/AsmParser/LLParser.cpp +++ lib/AsmParser/LLParser.cpp @@ -7759,9 +7759,13 @@ static void resolveFwdRef(ValueInfo *Fwd, ValueInfo &Resolved) { bool ReadOnly = Fwd->isReadOnly(); + bool WriteOnly = Fwd->isWriteOnly(); + assert(!(ReadOnly && WriteOnly)); *Fwd = Resolved; if (ReadOnly) Fwd->setReadOnly(); + if (WriteOnly) + Fwd->setReadOnly(); } /// Stores the given Name/GUID and associated summary into the Index. @@ -7991,7 +7995,8 @@ GlobalValueSummary::GVFlags GVFlags = GlobalValueSummary::GVFlags( /*Linkage=*/GlobalValue::ExternalLinkage, /*NotEligibleToImport=*/false, /*Live=*/false, /*IsLocal=*/false, /*CanAutoHide=*/false); - GlobalVarSummary::GVarFlags GVarFlags(/*ReadOnly*/ false); + GlobalVarSummary::GVarFlags GVarFlags(/*ReadOnly*/ false, + /* WriteOnly */ false); std::vector Refs; if (ParseToken(lltok::colon, "expected ':' here") || ParseToken(lltok::lparen, "expected '(' here") || @@ -8259,10 +8264,11 @@ VContexts.push_back(VC); } while (EatIfPresent(lltok::comma)); - // Sort value contexts so that ones with readonly ValueInfo are at the end - // of VContexts vector. This is needed to match immutableRefCount() behavior. + // Sort value contexts so that ones with writeonly + // and readonly ValueInfo are at the end of VContexts vector. + // See FunctionSummary::specialRefCounts() llvm::sort(VContexts, [](const ValueContext &VC1, const ValueContext &VC2) { - return VC1.VI.isReadOnly() < VC2.VI.isReadOnly(); + return VC1.VI.getAccessSpecifier() < VC2.VI.getAccessSpecifier(); }); IdToIndexMapType IdToIndexMap; @@ -8580,7 +8586,8 @@ } /// GVarFlags -/// ::= 'varFlags' ':' '(' 'readonly' ':' Flag ')' +/// ::= 'varFlags' ':' '(' 'readonly' ':' Flag +/// [ ',' 'writeonly' ':' Flag ] ')' bool LLParser::ParseGVarFlags(GlobalVarSummary::GVarFlags &GVarFlags) { assert(Lex.getKind() == lltok::kw_varFlags); Lex.Lex(); @@ -8594,10 +8601,17 @@ ParseFlag(Flag); GVarFlags.ReadOnly = Flag; - - if (ParseToken(lltok::rparen, "expected ')' here")) - return true; - return false; + GVarFlags.WriteOnly = false; + if (Lex.getKind() == lltok::comma) { + Lex.Lex(); + // Optional 'writeonly' attribute + if (ParseToken(lltok::kw_writeonly, "expected 'writeonly' here") || + ParseToken(lltok::colon, "expected ':' here")) + return true; + ParseFlag(Flag); + GVarFlags.WriteOnly = Flag; + } + return ParseToken(lltok::rparen, "expected ')' here"); } /// ModuleReference @@ -8620,7 +8634,9 @@ /// GVReference /// ::= SummaryID bool LLParser::ParseGVReference(ValueInfo &VI, unsigned &GVId) { - bool ReadOnly = EatIfPresent(lltok::kw_readonly); + bool WriteOnly = false, ReadOnly = EatIfPresent(lltok::kw_readonly); + if (!ReadOnly) + WriteOnly = EatIfPresent(lltok::kw_writeonly); if (ParseToken(lltok::SummaryID, "expected GV ID")) return true; @@ -8635,5 +8651,7 @@ if (ReadOnly) VI.setReadOnly(); + if (WriteOnly) + VI.setWriteOnly(); return false; } Index: lib/Bitcode/Reader/BitcodeReader.cpp =================================================================== --- lib/Bitcode/Reader/BitcodeReader.cpp +++ lib/Bitcode/Reader/BitcodeReader.cpp @@ -904,7 +904,8 @@ // Decode the flags for GlobalVariable in the summary static GlobalVarSummary::GVarFlags getDecodedGVarFlags(uint64_t RawFlags) { - return GlobalVarSummary::GVarFlags((RawFlags & 0x1) ? true : false); + return GlobalVarSummary::GVarFlags((RawFlags & 0x1) ? true : false, + (RawFlags & 0x2) ? true : false); } static GlobalValue::VisibilityTypes getDecodedVisibility(unsigned Val) { @@ -5375,10 +5376,16 @@ parseWholeProgramDevirtResolution(Record, Strtab, Slot, TypeId); } -static void setImmutableRefs(std::vector &Refs, unsigned Count) { +static void setSpecialRefs(std::vector &Refs, unsigned ROCnt, + unsigned WOCnt) { // Read-only refs are in the end of the refs list. - for (unsigned RefNo = Refs.size() - Count; RefNo < Refs.size(); ++RefNo) + assert(ROCnt + WOCnt <= Refs.size()); + unsigned FirstWORef = Refs.size() - WOCnt; + unsigned RefNo = FirstWORef - ROCnt; + for (; RefNo < FirstWORef; ++RefNo) Refs[RefNo].setReadOnly(); + for (; RefNo < Refs.size(); ++RefNo) + Refs[RefNo].setWriteOnly(); } // Eagerly parse the entire summary block. This populates the GlobalValueSummary @@ -5398,9 +5405,9 @@ } const uint64_t Version = Record[0]; const bool IsOldProfileFormat = Version == 1; - if (Version < 1 || Version > 6) + if (Version < 1 || Version > 7) return error("Invalid summary version " + Twine(Version) + - ". Version should be in the range [1-6]."); + ". Version should be in the range [1-7]."); Record.clear(); // Keep around the last seen summary to be used when we see an optional @@ -5494,15 +5501,19 @@ unsigned InstCount = Record[2]; uint64_t RawFunFlags = 0; unsigned NumRefs = Record[3]; - unsigned NumImmutableRefs = 0; + unsigned NumRORefs = 0, NumWORefs = 0; int RefListStartIndex = 4; if (Version >= 4) { RawFunFlags = Record[3]; NumRefs = Record[4]; RefListStartIndex = 5; if (Version >= 5) { - NumImmutableRefs = Record[5]; + NumRORefs = Record[5]; RefListStartIndex = 6; + if (Version >= 7) { + NumWORefs = Record[6]; + RefListStartIndex = 7; + } } } @@ -5522,7 +5533,7 @@ std::vector Calls = makeCallList( ArrayRef(Record).slice(CallGraphEdgeStartIndex), IsOldProfileFormat, HasProfile, HasRelBF); - setImmutableRefs(Refs, NumImmutableRefs); + setSpecialRefs(Refs, NumRORefs, NumWORefs); auto FS = llvm::make_unique( Flags, InstCount, getDecodedFFlags(RawFunFlags), /*EntryCount=*/0, std::move(Refs), std::move(Calls), std::move(PendingTypeTests), @@ -5573,7 +5584,7 @@ unsigned ValueID = Record[0]; uint64_t RawFlags = Record[1]; unsigned RefArrayStart = 2; - GlobalVarSummary::GVarFlags GVF; + GlobalVarSummary::GVarFlags GVF(false, false); auto Flags = getDecodedGVSummaryFlags(RawFlags, Version); if (Version >= 5) { GVF = getDecodedGVarFlags(Record[2]); @@ -5602,7 +5613,7 @@ uint64_t RawFunFlags = 0; uint64_t EntryCount = 0; unsigned NumRefs = Record[4]; - unsigned NumImmutableRefs = 0; + unsigned NumRORefs = 0, NumWORefs = 0; int RefListStartIndex = 5; if (Version >= 4) { @@ -5610,13 +5621,19 @@ RefListStartIndex = 6; size_t NumRefsIndex = 5; if (Version >= 5) { + unsigned NumRORefsOffset = 1; RefListStartIndex = 7; if (Version >= 6) { NumRefsIndex = 6; EntryCount = Record[5]; RefListStartIndex = 8; + if (Version >= 7) { + RefListStartIndex = 9; + NumWORefs = Record[8]; + NumRORefsOffset = 2; + } } - NumImmutableRefs = Record[RefListStartIndex - 1]; + NumRORefs = Record[RefListStartIndex - NumRORefsOffset]; } NumRefs = Record[NumRefsIndex]; } @@ -5632,7 +5649,7 @@ ArrayRef(Record).slice(CallGraphEdgeStartIndex), IsOldProfileFormat, HasProfile, false); ValueInfo VI = getValueInfoFromValueId(ValueID).first; - setImmutableRefs(Refs, NumImmutableRefs); + setSpecialRefs(Refs, NumRORefs, NumWORefs); auto FS = llvm::make_unique( Flags, InstCount, getDecodedFFlags(RawFunFlags), EntryCount, std::move(Refs), std::move(Edges), std::move(PendingTypeTests), @@ -5679,7 +5696,7 @@ uint64_t ModuleId = Record[1]; uint64_t RawFlags = Record[2]; unsigned RefArrayStart = 3; - GlobalVarSummary::GVarFlags GVF; + GlobalVarSummary::GVarFlags GVF(false, false); auto Flags = getDecodedGVSummaryFlags(RawFlags, Version); if (Version >= 5) { GVF = getDecodedGVarFlags(Record[3]); Index: lib/Bitcode/Writer/BitcodeWriter.cpp =================================================================== --- lib/Bitcode/Writer/BitcodeWriter.cpp +++ lib/Bitcode/Writer/BitcodeWriter.cpp @@ -1014,7 +1014,7 @@ } static uint64_t getEncodedGVarFlags(GlobalVarSummary::GVarFlags Flags) { - uint64_t RawFlags = Flags.ReadOnly; + uint64_t RawFlags = Flags.ReadOnly | (Flags.WriteOnly << 1); return RawFlags; } @@ -3613,11 +3613,13 @@ FunctionSummary *FS = cast(Summary); writeFunctionTypeMetadataRecords(Stream, FS); + auto SpecialRefCnts = FS->specialRefCounts(); NameVals.push_back(getEncodedGVSummaryFlags(FS->flags())); NameVals.push_back(FS->instCount()); NameVals.push_back(getEncodedFFlags(FS->fflags())); NameVals.push_back(FS->refs().size()); - NameVals.push_back(FS->immutableRefCount()); + NameVals.push_back(SpecialRefCnts.first); // rorefcnt + NameVals.push_back(SpecialRefCnts.second); // worefcnt for (auto &RI : FS->refs()) NameVals.push_back(VE.getValueID(RI.getValue())); @@ -3676,7 +3678,7 @@ // Current version for the summary. // This is bumped whenever we introduce changes in the way some record are // interpreted, like flags for instance. -static const uint64_t INDEX_VERSION = 6; +static const uint64_t INDEX_VERSION = 7; /// Emit the per-module summary section alongside the rest of /// the module's bitcode. @@ -3718,7 +3720,8 @@ Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // instcount Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // fflags Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // numrefs - Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // immutablerefcnt + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // rorefcnt + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // worefcnt // numrefs x valueid, n x (valueid, hotness) Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Array)); Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); @@ -3735,7 +3738,8 @@ Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // instcount Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // fflags Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // numrefs - Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // immutablerefcnt + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // rorefcnt + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // worefcnt // numrefs x valueid, n x (valueid [, rel_block_freq]) Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Array)); Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); @@ -3837,7 +3841,8 @@ Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // fflags Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // entrycount Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // numrefs - Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // immutablerefcnt + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // rorefcnt + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // worefcnt // numrefs x valueid, n x (valueid) Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Array)); Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); @@ -3853,7 +3858,8 @@ Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // fflags Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // entrycount Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // numrefs - Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // immutablerefcnt + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // rorefcnt + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // worefcnt // numrefs x valueid, n x (valueid, hotness) Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Array)); Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); @@ -3955,20 +3961,24 @@ // Fill in below NameVals.push_back(0); // numrefs - NameVals.push_back(0); // immutablerefcnt + NameVals.push_back(0); // rorefcnt + NameVals.push_back(0); // worefcnt - unsigned Count = 0, ImmutableRefCnt = 0; + unsigned Count = 0, RORefCnt = 0, WORefCnt = 0; for (auto &RI : FS->refs()) { auto RefValueId = getValueId(RI.getGUID()); if (!RefValueId) continue; NameVals.push_back(*RefValueId); if (RI.isReadOnly()) - ImmutableRefCnt++; + RORefCnt++; + else if (RI.isWriteOnly()) + WORefCnt++; Count++; } NameVals[6] = Count; - NameVals[7] = ImmutableRefCnt; + NameVals[7] = RORefCnt; + NameVals[8] = WORefCnt; bool HasProfileData = false; for (auto &EI : FS->calls()) { Index: lib/IR/AsmWriter.cpp =================================================================== --- lib/IR/AsmWriter.cpp +++ lib/IR/AsmWriter.cpp @@ -2862,7 +2862,8 @@ } void AssemblyWriter::printGlobalVarSummary(const GlobalVarSummary *GS) { - Out << ", varFlags: (readonly: " << GS->VarFlags.ReadOnly << ")"; + Out << ", varFlags: (readonly: " << GS->VarFlags.ReadOnly << ", " + << "writeonly: " << GS->VarFlags.WriteOnly << ")"; } static std::string getLinkageName(GlobalValue::LinkageTypes LT) { @@ -3059,6 +3060,8 @@ Out << FS; if (Ref.isReadOnly()) Out << "readonly "; + else if (Ref.isWriteOnly()) + Out << "writeonly "; Out << "^" << Machine.getGUIDSlot(Ref.getGUID()); } Out << ")"; Index: lib/IR/ModuleSummaryIndex.cpp =================================================================== --- lib/IR/ModuleSummaryIndex.cpp +++ lib/IR/ModuleSummaryIndex.cpp @@ -23,6 +23,8 @@ STATISTIC(ReadOnlyLiveGVars, "Number of live global variables marked read only"); +STATISTIC(WriteOnlyLiveGVars, + "Number of live global variables marked write only"); FunctionSummary FunctionSummary::ExternalNode = FunctionSummary::makeDummyFunctionSummary({}); @@ -45,15 +47,18 @@ }); } -// Gets the number of immutable refs in RefEdgeList -unsigned FunctionSummary::immutableRefCount() const { - // Here we take advantage of having all readonly references +// Gets the number of readonly and writeonly refs in RefEdgeList +std::pair FunctionSummary::specialRefCounts() const { + // Here we take advantage of having all readonly and writeonly references // located in the end of the RefEdgeList. auto Refs = refs(); - unsigned ImmutableRefCnt = 0; - for (int I = Refs.size() - 1; I >= 0 && Refs[I].isReadOnly(); --I) - ImmutableRefCnt++; - return ImmutableRefCnt; + unsigned RORefCnt = 0, WORefCnt = 0; + int I; + for (I = Refs.size() - 1; I >= 0 && Refs[I].isWriteOnly(); --I) + WORefCnt++; + for (; I >= 0 && Refs[I].isReadOnly(); --I) + RORefCnt++; + return {RORefCnt, WORefCnt}; } // Collect for the given module the list of function it defines @@ -99,48 +104,54 @@ return false; } -static void propagateConstantsToRefs(GlobalValueSummary *S) { - // If reference is not readonly then referenced summary is not - // readonly either. Note that: +static void propagateAttributesToRefs(GlobalValueSummary *S) { + // If reference is not readonly or writeonly then referenced summary is not + // read/writeonly either. Note that: // - All references from GlobalVarSummary are conservatively considered as - // not readonly. Tracking them properly requires more complex analysis - // then we have now. + // not readonly or writeonly. Tracking them properly requires more complex + // analysis then we have now. // // - AliasSummary objects have no refs at all so this function is a no-op // for them. for (auto &VI : S->refs()) { - if (VI.isReadOnly()) { - // We only mark refs as readonly when computing function summaries on - // analysis phase. - assert(isa(S)); - continue; - } + assert(VI.getAccessSpecifier() == 0 || isa(S)); for (auto &Ref : VI.getSummaryList()) - // If references to alias is not readonly then aliasee is not readonly - if (auto *GVS = dyn_cast(Ref->getBaseObject())) - GVS->setReadOnly(false); + // If references to alias is not read/writeonly then aliasee + // is not read/writeonly + if (auto *GVS = dyn_cast(Ref->getBaseObject())) { + if (!VI.isReadOnly()) + GVS->setReadOnly(false); + if (!VI.isWriteOnly()) + GVS->setWriteOnly(false); + } } } -// Do the constant propagation in combined index. -// The goal of constant propagation is internalization of readonly -// variables. To determine which variables are readonly and which -// are not we take following steps: -// - During analysis we speculatively assign readonly attribute to -// all variables which can be internalized. When computing function -// summary we also assign readonly attribute to a reference if -// function doesn't modify referenced variable. +// Do the access attribute propagation in combined index. +// The goal of attribute propagation is internalization of readonly +// or writeonly variables. To determine which variables are readonly +// (or writeonly) and which are not we take following steps: +// - During analysis we speculatively assign readonly and writeonly +// attribute to all variables which can be internalized. When computing +// function summary we also assign readonly or writeonly attribute to a +// reference if function doesn't modify referenced variable (readonly) +// or doesn't read it (writeonly). // -// - After computing dead symbols in combined index we do the constant -// propagation. During this step we clear readonly attribute from +// - After computing dead symbols in combined index we do the attribute +// propagation. During this step we clear read/writeonly attribute from // all variables which: // a. are preserved or can't be imported // b. referenced by any global variable initializer // c. referenced by a function and reference is not readonly // +// Also we clear writeonly attribute is reference to a variable is readonly +// and readonly attribute if reference is writeonly. This means that having +// two different functions of which one is reading variable 'foo' and other +// is modifying it, we effectively clear all access attribute of 'foo'. +// // Internalization itself happens in the backend after import is finished -// See internalizeImmutableGVs. -void ModuleSummaryIndex::propagateConstants( +// See internalizeGVsAfterImport. +void ModuleSummaryIndex::propagateAttributes( const DenseSet &GUIDPreservedSymbols) { for (auto &P : *this) for (auto &S : P.second.SummaryList) { @@ -160,17 +171,24 @@ if (auto *GVS = dyn_cast(S->getBaseObject())) // Here we intentionally pass S.get() not GVS, because S could be // an alias. - if (!canImportGlobalVar(S.get()) || GUIDPreservedSymbols.count(P.first)) + if (!canImportGlobalVar(S.get()) || + GUIDPreservedSymbols.count(P.first)) { GVS->setReadOnly(false); - propagateConstantsToRefs(S.get()); + GVS->setWriteOnly(false); + } + propagateAttributesToRefs(S.get()); } if (llvm::AreStatisticsEnabled()) for (auto &P : *this) if (P.second.SummaryList.size()) if (auto *GVS = dyn_cast( P.second.SummaryList[0]->getBaseObject())) - if (isGlobalValueLive(GVS) && GVS->isReadOnly()) - ReadOnlyLiveGVars++; + if (isGlobalValueLive(GVS)) { + if (GVS->isReadOnly()) + ReadOnlyLiveGVars++; + if (GVS->isWriteOnly()) + WriteOnlyLiveGVars++; + } } // TODO: write a graphviz dumper for SCCs (see ModuleSummaryIndex::exportToDot) @@ -337,6 +355,12 @@ return false; } +static bool hasWriteOnlyFlag(const GlobalValueSummary *S) { + if (auto *GVS = dyn_cast(S)) + return GVS->isWriteOnly(); + return false; +} + void ModuleSummaryIndex::exportToDot(raw_ostream &OS) const { std::vector CrossModuleEdges; DenseMap> NodeMap; @@ -358,12 +382,14 @@ // 0 - alias // 1 - reference // 2 - constant reference - // Other value: (hotness - 3). - TypeOrHotness += 3; + // 3 - writeonly reference + // Other value: (hotness - 4). + TypeOrHotness += 4; static const char *EdgeAttrs[] = { " [style=dotted]; // alias", " [style=dashed]; // ref", " [style=dashed,color=forestgreen]; // const-ref", + " [style=dashed,color=violetred]; // writeOnly-ref", " // call (hotness : Unknown)", " [color=blue]; // call (hotness : Cold)", " // call (hotness : None)", @@ -408,6 +434,8 @@ A.add("shape", "Mrecord", "variable"); if (Flags.Live && hasReadOnlyFlag(SummaryIt.second)) A.addComment("immutable"); + if (Flags.Live && hasWriteOnlyFlag(SummaryIt.second)) + A.addComment("writeOnly"); } if (Flags.DSOLocal) A.addComment("dsoLocal"); @@ -429,10 +457,11 @@ for (auto &SummaryIt : GVSMap) { auto *GVS = SummaryIt.second; for (auto &R : GVS->refs()) - Draw(SummaryIt.first, R.getGUID(), R.isReadOnly() ? -1 : -2); + Draw(SummaryIt.first, R.getGUID(), + R.isWriteOnly() ? -1 : (R.isReadOnly() ? -2 : -3)); if (auto *AS = dyn_cast_or_null(SummaryIt.second)) { - Draw(SummaryIt.first, AS->getAliaseeGUID(), -3); + Draw(SummaryIt.first, AS->getAliaseeGUID(), -4); continue; } Index: lib/LTO/LTO.cpp =================================================================== --- lib/LTO/LTO.cpp +++ lib/LTO/LTO.cpp @@ -192,8 +192,9 @@ AddUnsigned(VI.isDSOLocal()); AddUsedCfiGlobal(VI.getGUID()); } - if (auto *GVS = dyn_cast(GS)) - AddUnsigned(GVS->isReadOnly()); + if (auto *GVS = dyn_cast(GS)) { + AddUnsigned(GVS->isReadOnly() | GVS->isWriteOnly()); + } if (auto *FS = dyn_cast(GS)) { for (auto &TT : FS->type_tests()) UsedTypeIds.insert(TT); @@ -371,9 +372,9 @@ GUIDPreservedSymbols); } -static bool isWeakWriteableObject(GlobalValueSummary *GVS) { +static bool isWeakObjectWithRWAccess(GlobalValueSummary *GVS) { if (auto *VarSummary = dyn_cast(GVS->getBaseObject())) - return !VarSummary->isReadOnly() && + return !VarSummary->isReadOnly() && !VarSummary->isWriteOnly() && (VarSummary->linkage() == GlobalValue::WeakODRLinkage || VarSummary->linkage() == GlobalValue::LinkOnceODRLinkage); return false; @@ -394,11 +395,12 @@ // We can't internalize available_externally globals because this // can break function pointer equality. S->linkage() != GlobalValue::AvailableExternallyLinkage && - // Functions and read-only variables with linkonce_odr and weak_odr - // linkage can be internalized. We can't internalize linkonce_odr - // and weak_odr variables which are modified somewhere in the - // program because reads and writes will become inconsistent. - !isWeakWriteableObject(S.get())) + // Functions and read-only variables with linkonce_odr and + // weak_odr linkage can be internalized. We can't internalize + // linkonce_odr and weak_odr variables which are both modified + // and read somewhere in the program because reads and writes + // will become inconsistent. + !isWeakObjectWithRWAccess(S.get())) S->setLinkage(GlobalValue::InternalLinkage); } } Index: lib/Transforms/IPO/FunctionImport.cpp =================================================================== --- lib/Transforms/IPO/FunctionImport.cpp +++ lib/Transforms/IPO/FunctionImport.cpp @@ -850,14 +850,16 @@ bool ImportEnabled) { computeDeadSymbols(Index, GUIDPreservedSymbols, isPrevailing); if (ImportEnabled) { - Index.propagateConstants(GUIDPreservedSymbols); + Index.propagateAttributes(GUIDPreservedSymbols); } else { - // If import is disabled we should drop read-only attribute + // If import is disabled we should drop read/write-only attribute // from all summaries to prevent internalization. for (auto &P : Index) for (auto &S : P.second.SummaryList) - if (auto *GVS = dyn_cast(S.get())) + if (auto *GVS = dyn_cast(S.get())) { GVS->setReadOnly(false); + GVS->setWriteOnly(false); + } } } @@ -1064,7 +1066,7 @@ // Internalize values that we marked with specific attribute // in processGlobalForThinLTO. -static void internalizeImmutableGVs(Module &M) { +static void internalizeGVsAfterImport(Module &M) { for (auto &GV : M.globals()) // Skip GVs which have been converted to declarations // by dropDeadSymbols. @@ -1197,7 +1199,7 @@ NumImportedModules++; } - internalizeImmutableGVs(DestModule); + internalizeGVsAfterImport(DestModule); NumImportedFunctions += (ImportedCount - ImportedGVCount); NumImportedGlobalVars += ImportedGVCount; Index: lib/Transforms/Utils/FunctionImportUtils.cpp =================================================================== --- lib/Transforms/Utils/FunctionImportUtils.cpp +++ lib/Transforms/Utils/FunctionImportUtils.cpp @@ -229,11 +229,11 @@ } } - // Mark read-only variables which can be imported with specific attribute. - // We can't internalize them now because IRMover will fail to link variable - // definitions to their external declarations during ThinLTO import. We'll - // internalize read-only variables later, after import is finished. - // See internalizeImmutableGVs. + // Mark read/write-only variables which can be imported with specific + // attribute. We can't internalize them now because IRMover will fail + // to link variable definitions to their external declarations during + // ThinLTO import. We'll internalize read-only variables later, after + // import is finished. See internalizeGVsAfterImport. // // If global value dead stripping is not enabled in summary then // propagateConstants hasn't been run. We can't internalize GV @@ -241,7 +241,7 @@ if (!GV.isDeclaration() && VI && ImportIndex.withGlobalValueDeadStripping()) { const auto &SL = VI.getSummaryList(); auto *GVS = SL.empty() ? nullptr : dyn_cast(SL[0].get()); - if (GVS && GVS->isReadOnly()) + if (GVS && (GVS->isReadOnly() || GVS->isWriteOnly())) cast(&GV)->addAttribute("thinlto-internalize"); } Index: test/Assembler/thinlto-summary.ll =================================================================== --- test/Assembler/thinlto-summary.ll +++ test/Assembler/thinlto-summary.ll @@ -76,10 +76,10 @@ ; CHECK: ^8 = gv: (guid: 7, summaries: (function: (module: ^0, flags: (linkage: linkonce_odr, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 1))) ; CHECK: ^9 = gv: (guid: 8, summaries: (function: (module: ^0, flags: (linkage: weak_odr, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 1), insts: 1))) ; CHECK: ^10 = gv: (guid: 9, summaries: (function: (module: ^0, flags: (linkage: weak, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 1))) -; CHECK: ^11 = gv: (guid: 10, summaries: (variable: (module: ^0, flags: (linkage: common, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), varFlags: (readonly: 0)))) -; CHECK: ^12 = gv: (guid: 11, summaries: (variable: (module: ^0, flags: (linkage: appending, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), varFlags: (readonly: 0), refs: (^4)))) -; CHECK: ^13 = gv: (guid: 12, summaries: (variable: (module: ^0, flags: (linkage: external, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), varFlags: (readonly: 1)))) -; CHECK: ^14 = gv: (guid: 13, summaries: (variable: (module: ^0, flags: (linkage: external, notEligibleToImport: 0, live: 0, dsoLocal: 1, canAutoHide: 0), varFlags: (readonly: 0)))) +; CHECK: ^11 = gv: (guid: 10, summaries: (variable: (module: ^0, flags: (linkage: common, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), varFlags: (readonly: 0, writeonly: 0)))) +; CHECK: ^12 = gv: (guid: 11, summaries: (variable: (module: ^0, flags: (linkage: appending, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), varFlags: (readonly: 0, writeonly: 0), refs: (^4)))) +; CHECK: ^13 = gv: (guid: 12, summaries: (variable: (module: ^0, flags: (linkage: external, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), varFlags: (readonly: 1, writeonly: 0)))) +; CHECK: ^14 = gv: (guid: 13, summaries: (variable: (module: ^0, flags: (linkage: external, notEligibleToImport: 0, live: 0, dsoLocal: 1, canAutoHide: 0), varFlags: (readonly: 0, writeonly: 0)))) ; CHECK: ^15 = gv: (guid: 14, summaries: (function: (module: ^1, flags: (linkage: external, notEligibleToImport: 1, live: 1, dsoLocal: 0, canAutoHide: 0), insts: 1))) ; CHECK: ^16 = gv: (guid: 15, summaries: (function: (module: ^1, flags: (linkage: external, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 1, funcFlags: (readNone: 1, readOnly: 0, noRecurse: 1, returnDoesNotAlias: 0, noInline: 0)))) ; CHECK: ^17 = gv: (guid: 16, summaries: (function: (module: ^1, flags: (linkage: external, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 1, funcFlags: (readNone: 0, readOnly: 1, noRecurse: 0, returnDoesNotAlias: 1, noInline: 0), calls: ((callee: ^15))))) Index: test/Bitcode/summary_version.ll =================================================================== --- test/Bitcode/summary_version.ll +++ test/Bitcode/summary_version.ll @@ -2,7 +2,7 @@ ; RUN: opt -module-summary %s -o - | llvm-bcanalyzer -dump | FileCheck %s ; CHECK: +; CHECK: Index: test/Bitcode/thinlto-alias.ll =================================================================== --- test/Bitcode/thinlto-alias.ll +++ test/Bitcode/thinlto-alias.ll @@ -21,7 +21,7 @@ ; CHECK-NEXT: +; CHECK-NEXT: ; CHECK-NEXT: ; CHECK: ; COMBINED-NEXT: -; COMBINED-NEXT: +; COMBINED-NEXT: ; COMBINED-NEXT: +; CHECK-NEXT: ; CHECK-NEXT: ; CHECK-NEXT: Index: test/Bitcode/thinlto-function-summary-callgraph-cast.ll =================================================================== --- test/Bitcode/thinlto-function-summary-callgraph-cast.ll +++ test/Bitcode/thinlto-function-summary-callgraph-cast.ll @@ -7,9 +7,9 @@ ; CHECK-NEXT: +; CHECK-NEXT: ; "another_caller" has only references but no calls. -; CHECK-NEXT: +; CHECK-NEXT: ; CHECK-NEXT: ; CHECK-NEXT: Index: test/Bitcode/thinlto-function-summary-callgraph-pgo.ll =================================================================== --- test/Bitcode/thinlto-function-summary-callgraph-pgo.ll +++ test/Bitcode/thinlto-function-summary-callgraph-pgo.ll @@ -18,7 +18,7 @@ ; CHECK-NEXT: +; CHECK-NEXT: ; CHECK-NEXT: ; CHECK: +; COMBINED-NEXT: ; COMBINED-NEXT: ; ModuleID = 'thinlto-function-summary-callgraph.ll' Index: test/Bitcode/thinlto-function-summary-callgraph-profile-summary.ll =================================================================== --- test/Bitcode/thinlto-function-summary-callgraph-profile-summary.ll +++ test/Bitcode/thinlto-function-summary-callgraph-profile-summary.ll @@ -49,7 +49,7 @@ ; CHECK-NEXT: ; op4=hot1 op6=cold op8=hot2 op10=hot4 op12=none1 op14=hot3 op16=none2 op18=none3 op20=123 -; CHECK-NEXT: +; CHECK-NEXT: ; CHECK-NEXT: ; CHECK: +; COMBINED-NEXT: ; COMBINED_NEXT: Index: test/Bitcode/thinlto-function-summary-callgraph-relbf.ll =================================================================== --- test/Bitcode/thinlto-function-summary-callgraph-relbf.ll +++ test/Bitcode/thinlto-function-summary-callgraph-relbf.ll @@ -14,7 +14,7 @@ ; CHECK-NEXT: ; CHECK: ; op4=none1 op6=hot1 op8=cold1 op10=none2 op12=hot2 op14=cold2 op16=none3 op18=hot3 op20=cold3 op22=123 -; CHECK-NEXT: +; CHECK-NEXT: ; CHECK-NEXT: ; CHECK: +; COMBINED-NEXT: ; COMBINED_NEXT: Index: test/Bitcode/thinlto-function-summary-callgraph.ll =================================================================== --- test/Bitcode/thinlto-function-summary-callgraph.ll +++ test/Bitcode/thinlto-function-summary-callgraph.ll @@ -34,7 +34,7 @@ ; COMBINED-NEXT: +; COMBINED-NEXT: ; COMBINED-NEXT: ; ModuleID = 'thinlto-function-summary-callgraph.ll' Index: test/Bitcode/thinlto-function-summary-refgraph.ll =================================================================== --- test/Bitcode/thinlto-function-summary-refgraph.ll +++ test/Bitcode/thinlto-function-summary-refgraph.ll @@ -41,27 +41,27 @@ ; CHECK: +; CHECK-DAG: ; Function W contains a call to func3 as well as a reference to globalvar: ; op0=W op4=globalvar op5=func3 -; CHECK-DAG: +; CHECK-DAG: ; Function X contains call to foo, as well as address reference to foo ; which is in the same instruction as the call: ; op0=X op4=foo op5=foo -; CHECK-DAG: +; CHECK-DAG: ; Function Y contains call to func2, and ensures we don't incorrectly add ; a reference to it when reached while earlier analyzing the phi using its ; return value: ; op0=Y op4=func2 -; CHECK-DAG: +; CHECK-DAG: ; Function Z contains call to func2, and ensures we don't incorrectly add ; a reference to it when reached while analyzing subsequent use of its return ; value: ; op0=Z op4=func2 -; CHECK-DAG: +; CHECK-DAG: ; Variable bar initialization contains address reference to func: ; op0=bar op2=func -; CHECK-DAG: +; CHECK-DAG: ; CHECK: ; CHECK: M1_[[A]] [style=dashed,color=violetred]; // writeOnly-ref +; COMBINED-NEXT: } + +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +@A = external local_unnamed_addr global i32, align 4 + +; Function Attrs: nounwind uwtable +define i32 @main() local_unnamed_addr { + store i32 42, i32* @A, align 4 + ret i32 0 +} Index: test/ThinLTO/X86/index-const-prop.ll =================================================================== --- test/ThinLTO/X86/index-const-prop.ll +++ test/ThinLTO/X86/index-const-prop.ll @@ -13,6 +13,11 @@ ; RUN: llvm-dis %t1.imported.bc -o - | FileCheck %s --check-prefix=IMPORT ; RUN: llvm-lto -thinlto-action=optimize %t1.imported.bc -o - | llvm-dis - -o - | FileCheck %s --check-prefix=OPTIMIZE +; Check that we optimize write-only variables +; RUN: llvm-lto -thinlto-action=import -exported-symbol=main2 %t1.bc -thinlto-index=%t3.index.bc -o %t1.imported.bc -stats 2>&1 | FileCheck %s --check-prefix=STATS2 +; RUN: llvm-dis %t1.imported.bc -o - | FileCheck %s --check-prefix=IMPORT +; RUN: llvm-lto -thinlto-action=optimize %t1.imported.bc -o - | llvm-dis - -o - | FileCheck %s --check-prefix=OPTIMIZE2 + ; STATS: 2 module-summary-index - Number of live global variables marked read only ; Check that we don't internalize gBar when it is exported @@ -28,6 +33,14 @@ ; IMPORT2: @gBar = available_externally local_unnamed_addr global i32 2, align 4, !dbg !5 +; STATS2: 2 module-summary-index - Number of live global variables marked write only + +; Check that we've optimized out stores +; OPTIMIZE2: define i32 @main2 +; OPTIMIZE2-NEXT: %1 = tail call i32 @rand() +; OPTIMIZE2-NEXT: %2 = tail call i32 @rand() +; OPTIMIZE2-NEXT: ret i32 0 + target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-pc-linux-gnu" @@ -44,6 +57,13 @@ ret i32 %add } +define i32 @main2() local_unnamed_addr { + tail call void @baz() + ret i32 0 +} + declare i32 @foo(...) local_unnamed_addr declare i32 @bar(...) local_unnamed_addr + +declare void @baz() local_unnamed_addr Index: test/ThinLTO/X86/index-const-prop2.ll =================================================================== --- test/ThinLTO/X86/index-const-prop2.ll +++ test/ThinLTO/X86/index-const-prop2.ll @@ -11,8 +11,10 @@ ; RUN: -r=%t2.bc,rand, \ ; RUN: -r=%t2.bc,gBar,pl \ ; RUN: -r=%t1.bc,main,plx \ +; RUN: -r=%t1.bc,main2,pl \ ; RUN: -r=%t1.bc,foo, \ ; RUN: -r=%t1.bc,bar, \ +; RUN: -r=%t1.bc,baz, \ ; RUN: -r=%t1.bc,gBar, \ ; RUN: -o %t3 ; RUN: llvm-dis %t3.1.3.import.bc -o - | FileCheck %s --check-prefix=IMPORT @@ -26,11 +28,32 @@ ; RUN: -r=%t2.bc,rand, \ ; RUN: -r=%t2.bc,gBar,plx \ ; RUN: -r=%t1.bc,main,plx \ +; RUN: -r=%t1.bc,main2,pl \ ; RUN: -r=%t1.bc,foo, \ ; RUN: -r=%t1.bc,bar, \ +; RUN: -r=%t1.bc,baz, \ ; RUN: -r=%t1.bc,gBar, \ -; RUN: -o %t3 -; RUN: llvm-dis %t3.1.3.import.bc -o - | FileCheck %s --check-prefix=IMPORT2 +; RUN: -o %t4 +; RUN: llvm-dis %t4.1.3.import.bc -o - | FileCheck %s --check-prefix=IMPORT2 + +; RUN: llvm-lto2 run %t1.bc %t2.bc -save-temps \ +; RUN: -r=%t2.bc,foo,pl \ +; RUN: -r=%t2.bc,bar,pl \ +; RUN: -r=%t2.bc,baz,pl \ +; RUN: -r=%t2.bc,rand, \ +; RUN: -r=%t2.bc,gBar,pl \ +; RUN: -r=%t1.bc,main,pl \ +; RUN: -r=%t1.bc,main2,plx \ +; RUN: -r=%t1.bc,foo, \ +; RUN: -r=%t1.bc,bar, \ +; RUN: -r=%t1.bc,baz, \ +; RUN: -r=%t1.bc,gBar, \ +; RUN: -o %t5 +; RUN: llvm-dis %t5.1.3.import.bc -o - | FileCheck %s --check-prefix=IMPORT +; RUN: llvm-dis %t5.1.5.precodegen.bc -o - | FileCheck %s --check-prefix=CODEGEN2 +; Check that gFoo and gBar were eliminated from source module together +; with corresponsing stores +; RUN: llvm-dis %t5.2.5.precodegen.bc -o - | FileCheck %s --check-prefix=CODEGEN2-SRC ; IMPORT: @gFoo.llvm.0 = internal unnamed_addr global i32 1, align 4 ; IMPORT-NEXT: @gBar = internal local_unnamed_addr global i32 2, align 4 @@ -41,6 +64,16 @@ ; IMPORT2: @gBar = available_externally dso_local local_unnamed_addr global i32 2, align 4 +; CODEGEN2: i32 @main2 +; CODEGEN2-NEXT: %1 = tail call i32 @rand() +; CODEGEN2-NEXT: %2 = tail call i32 @rand() +; CODEGEN2-NEXT: ret i32 0 + +; CODEGEN2-SRC: void @baz() +; CODEGEN2-SRC-NEXT: %1 = tail call i32 @rand() +; CODEGEN2-SRC-NEXT: %2 = tail call i32 @rand() +; CODEGEN2-SRC-NEXT: ret void + target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-pc-linux-gnu" @@ -54,6 +87,13 @@ ret i32 %add } +define i32 @main2() local_unnamed_addr { + tail call void @baz() + ret i32 0 +} + declare i32 @foo(...) local_unnamed_addr declare i32 @bar(...) local_unnamed_addr + +declare void @baz() local_unnamed_addr