//===-- InstrProfCorrelator.cpp -------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "llvm/ProfileData/InstrProfCorrelator.h" #include "llvm/DebugInfo/DIContext.h" #include "llvm/DebugInfo/DWARF/DWARFContext.h" #include "llvm/DebugInfo/DWARF/DWARFDie.h" #include "llvm/DebugInfo/DWARF/DWARFExpression.h" #include "llvm/DebugInfo/DWARF/DWARFFormValue.h" #include "llvm/DebugInfo/DWARF/DWARFLocationExpression.h" #include "llvm/DebugInfo/DWARF/DWARFUnit.h" #include "llvm/Object/MachO.h" #include "llvm/Support/Debug.h" #include #define DEBUG_TYPE "correlator" using namespace llvm; /// Get the __llvm_prf_cnts section. Expected getCountersSection(const object::ObjectFile &Obj) { for (auto &Section : Obj.sections()) if (auto SectionName = Section.getName()) if (SectionName.get() == INSTR_PROF_CNTS_SECT_NAME) return Section; return make_error( instrprof_error::unable_to_correlate_profile, "could not find counter section (" INSTR_PROF_CNTS_SECT_NAME ")"); } const char *InstrProfCorrelator::FunctionNameAttributeName = "Function Name"; const char *InstrProfCorrelator::CFGHashAttributeName = "CFG Hash"; const char *InstrProfCorrelator::NumCountersAttributeName = "Num Counters"; llvm::Expected> InstrProfCorrelator::Context::get(std::unique_ptr Buffer, const object::ObjectFile &Obj) { auto CountersSection = getCountersSection(Obj); if (auto Err = CountersSection.takeError()) return std::move(Err); auto C = std::make_unique(); C->Buffer = std::move(Buffer); C->CountersSectionStart = CountersSection->getAddress(); C->CountersSectionEnd = C->CountersSectionStart + CountersSection->getSize(); C->ShouldSwapBytes = Obj.isLittleEndian() != sys::IsLittleEndianHost; return Expected>(std::move(C)); } llvm::Expected> InstrProfCorrelator::get(StringRef DebugInfoFilename) { auto DsymObjectsOrErr = object::MachOObjectFile::findDsymObjectMembers(DebugInfoFilename); if (auto Err = DsymObjectsOrErr.takeError()) return std::move(Err); if (!DsymObjectsOrErr->empty()) { // TODO: Enable profile correlation when there are multiple objects in a // dSYM bundle. if (DsymObjectsOrErr->size() > 1) return make_error( instrprof_error::unable_to_correlate_profile, "using multiple objects is not yet supported"); DebugInfoFilename = *DsymObjectsOrErr->begin(); } auto BufferOrErr = errorOrToExpected(MemoryBuffer::getFile(DebugInfoFilename)); if (auto Err = BufferOrErr.takeError()) return std::move(Err); return get(std::move(*BufferOrErr)); } llvm::Expected> InstrProfCorrelator::get(std::unique_ptr Buffer) { auto BinOrErr = object::createBinary(*Buffer); if (auto Err = BinOrErr.takeError()) return std::move(Err); if (auto *Obj = dyn_cast(BinOrErr->get())) { auto CtxOrErr = Context::get(std::move(Buffer), *Obj); if (auto Err = CtxOrErr.takeError()) return std::move(Err); auto T = Obj->makeTriple(); if (T.isArch64Bit()) return InstrProfCorrelatorImpl::get(std::move(*CtxOrErr), *Obj); if (T.isArch32Bit()) return InstrProfCorrelatorImpl::get(std::move(*CtxOrErr), *Obj); } return make_error( instrprof_error::unable_to_correlate_profile, "not an object file"); } std::optional InstrProfCorrelator::getDataSize() const { if (auto *C = dyn_cast>(this)) { return C->getDataSize(); } else if (auto *C = dyn_cast>(this)) { return C->getDataSize(); } return {}; } namespace llvm { template <> InstrProfCorrelatorImpl::InstrProfCorrelatorImpl( std::unique_ptr Ctx) : InstrProfCorrelatorImpl(InstrProfCorrelatorKind::CK_32Bit, std::move(Ctx)) {} template <> InstrProfCorrelatorImpl::InstrProfCorrelatorImpl( std::unique_ptr Ctx) : InstrProfCorrelatorImpl(InstrProfCorrelatorKind::CK_64Bit, std::move(Ctx)) {} template <> bool InstrProfCorrelatorImpl::classof(const InstrProfCorrelator *C) { return C->getKind() == InstrProfCorrelatorKind::CK_32Bit; } template <> bool InstrProfCorrelatorImpl::classof(const InstrProfCorrelator *C) { return C->getKind() == InstrProfCorrelatorKind::CK_64Bit; } } // end namespace llvm template llvm::Expected>> InstrProfCorrelatorImpl::get( std::unique_ptr Ctx, const object::ObjectFile &Obj) { if (Obj.isELF() || Obj.isMachO()) { auto DICtx = DWARFContext::create(Obj); return std::make_unique>(std::move(DICtx), std::move(Ctx)); } return make_error( instrprof_error::unable_to_correlate_profile, "unsupported debug info format (only DWARF is supported)"); } template Error InstrProfCorrelatorImpl::correlateProfileData() { assert(Data.empty() && Names.empty() && NamesVec.empty()); correlateProfileDataImpl(); if (Data.empty() || NamesVec.empty()) return make_error( instrprof_error::unable_to_correlate_profile, "could not find any profile metadata in debug info"); auto Result = collectPGOFuncNameStrings(NamesVec, /*doCompression=*/false, Names); CounterOffsets.clear(); NamesVec.clear(); return Result; } template <> struct yaml::MappingTraits { static void mapping(yaml::IO &io, InstrProfCorrelator::CorrelationData &Data) { io.mapRequired("Probes", Data.Probes); } }; template <> struct yaml::MappingTraits { static void mapping(yaml::IO &io, InstrProfCorrelator::Probe &P) { io.mapRequired("Function Name", P.FunctionName); io.mapOptional("Linkage Name", P.LinkageName); io.mapRequired("CFG Hash", P.CFGHash); io.mapRequired("Counter Offset", P.CounterOffset); io.mapRequired("Num Counters", P.NumCounters); io.mapOptional("File", P.FilePath); io.mapOptional("Line", P.LineNumber); } }; template <> struct yaml::SequenceElementTraits { static const bool flow = false; }; template Error InstrProfCorrelatorImpl::dumpYaml(raw_ostream &OS) { InstrProfCorrelator::CorrelationData Data; correlateProfileDataImpl(&Data); if (Data.Probes.empty()) return make_error( instrprof_error::unable_to_correlate_profile, "could not find any profile metadata in debug info"); yaml::Output YamlOS(OS); YamlOS << Data; return Error::success(); } template void InstrProfCorrelatorImpl::addProbe(StringRef FunctionName, uint64_t CFGHash, IntPtrT CounterOffset, IntPtrT FunctionPtr, uint32_t NumCounters) { // Check if a probe was already added for this counter offset. if (!CounterOffsets.insert(CounterOffset).second) return; Data.push_back({ maybeSwap(IndexedInstrProf::ComputeHash(FunctionName)), maybeSwap(CFGHash), // In this mode, CounterPtr actually stores the section relative address // of the counter. maybeSwap(CounterOffset), maybeSwap(FunctionPtr), // TODO: Value profiling is not yet supported. /*ValuesPtr=*/maybeSwap(0), maybeSwap(NumCounters), /*NumValueSites=*/{maybeSwap(0), maybeSwap(0)}, }); NamesVec.push_back(FunctionName.str()); } template std::optional DwarfInstrProfCorrelator::getLocation(const DWARFDie &Die) const { auto Locations = Die.getLocations(dwarf::DW_AT_location); if (!Locations) { consumeError(Locations.takeError()); return {}; } auto &DU = *Die.getDwarfUnit(); auto AddressSize = DU.getAddressByteSize(); for (auto &Location : *Locations) { DataExtractor Data(Location.Expr, DICtx->isLittleEndian(), AddressSize); DWARFExpression Expr(Data, AddressSize); for (auto &Op : Expr) { if (Op.getCode() == dwarf::DW_OP_addr) { return Op.getRawOperand(0); } else if (Op.getCode() == dwarf::DW_OP_addrx) { uint64_t Index = Op.getRawOperand(0); if (auto SA = DU.getAddrOffsetSectionItem(Index)) return SA->Address; } } } return {}; } template bool DwarfInstrProfCorrelator::isDIEOfProbe(const DWARFDie &Die) { const auto &ParentDie = Die.getParent(); if (!Die.isValid() || !ParentDie.isValid() || Die.isNULL()) return false; if (Die.getTag() != dwarf::DW_TAG_variable) return false; if (!ParentDie.isSubprogramDIE()) return false; if (!Die.hasChildren()) return false; if (const char *Name = Die.getName(DINameKind::ShortName)) return StringRef(Name).startswith(getInstrProfCountersVarPrefix()); return false; } template void DwarfInstrProfCorrelator::correlateProfileDataImpl( InstrProfCorrelator::CorrelationData *Data) { auto maybeAddProbe = [&](DWARFDie Die) { if (!isDIEOfProbe(Die)) return; std::optional FunctionName; std::optional CFGHash; std::optional CounterPtr = getLocation(Die); auto FnDie = Die.getParent(); auto FunctionPtr = dwarf::toAddress(FnDie.find(dwarf::DW_AT_low_pc)); std::optional NumCounters; for (const DWARFDie &Child : Die.children()) { if (Child.getTag() != dwarf::DW_TAG_LLVM_annotation) continue; auto AnnotationFormName = Child.find(dwarf::DW_AT_name); auto AnnotationFormValue = Child.find(dwarf::DW_AT_const_value); if (!AnnotationFormName || !AnnotationFormValue) continue; auto AnnotationNameOrErr = AnnotationFormName->getAsCString(); if (auto Err = AnnotationNameOrErr.takeError()) { consumeError(std::move(Err)); continue; } StringRef AnnotationName = *AnnotationNameOrErr; if (AnnotationName.compare( InstrProfCorrelator::FunctionNameAttributeName) == 0) { if (auto EC = AnnotationFormValue->getAsCString().moveInto(FunctionName)) consumeError(std::move(EC)); } else if (AnnotationName.compare( InstrProfCorrelator::CFGHashAttributeName) == 0) { CFGHash = AnnotationFormValue->getAsUnsignedConstant(); } else if (AnnotationName.compare( InstrProfCorrelator::NumCountersAttributeName) == 0) { NumCounters = AnnotationFormValue->getAsUnsignedConstant(); } } if (!FunctionName || !CFGHash || !CounterPtr || !NumCounters) { LLVM_DEBUG(dbgs() << "Incomplete DIE for probe\n\tFunctionName: " << FunctionName << "\n\tCFGHash: " << CFGHash << "\n\tCounterPtr: " << CounterPtr << "\n\tNumCounters: " << NumCounters); LLVM_DEBUG(Die.dump(dbgs())); return; } uint64_t CountersStart = this->Ctx->CountersSectionStart; uint64_t CountersEnd = this->Ctx->CountersSectionEnd; if (*CounterPtr < CountersStart || *CounterPtr >= CountersEnd) { LLVM_DEBUG( dbgs() << "CounterPtr out of range for probe\n\tFunction Name: " << FunctionName << "\n\tExpected: [0x" << Twine::utohexstr(CountersStart) << ", 0x" << Twine::utohexstr(CountersEnd) << ")\n\tActual: 0x" << Twine::utohexstr(*CounterPtr)); LLVM_DEBUG(Die.dump(dbgs())); return; } if (!FunctionPtr) { LLVM_DEBUG(dbgs() << "Could not find address of " << *FunctionName << "\n"); LLVM_DEBUG(Die.dump(dbgs())); } IntPtrT CounterOffset = *CounterPtr - CountersStart; if (Data) { InstrProfCorrelator::Probe P; P.FunctionName = *FunctionName; if (auto Name = FnDie.getName(DINameKind::LinkageName)) P.LinkageName = Name; P.CFGHash = *CFGHash; P.CounterOffset = CounterOffset; P.NumCounters = *NumCounters; auto FilePath = FnDie.getDeclFile( DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath); if (!FilePath.empty()) P.FilePath = FilePath; if (auto LineNumber = FnDie.getDeclLine()) P.LineNumber = LineNumber; Data->Probes.push_back(P); } else { this->addProbe(*FunctionName, *CFGHash, CounterOffset, FunctionPtr.value_or(0), *NumCounters); } }; for (auto &CU : DICtx->normal_units()) for (const auto &Entry : CU->dies()) maybeAddProbe(DWARFDie(CU.get(), &Entry)); for (auto &CU : DICtx->dwo_units()) for (const auto &Entry : CU->dies()) maybeAddProbe(DWARFDie(CU.get(), &Entry)); }