Skip to content

Commit 60cd3eb

Browse files
kevinfreiKevin Frei
andauthored
Reduce llvm-gsymutil memory usage (#91023)
llvm-gsymutil eats a lot of RAM. On some large binaries, it causes OOM's on smaller hardware, consuming well over 64GB of RAM. This change frees line tables once we're done with them, and frees DWARFUnits's DIE's when we finish processing each DU, though they may get reconstituted if there are references from other DU's during processing. Once the conversion is complete, all DIE's are freed. The reduction in peak memory usage from these changes showed between 7-12% in my tests. The double-checked locking around the creation & freeing of the data structures was tested on a 166 core system. I validated that it trivially malfunctioned without the locks (and with stupid reordering of the locks) and worked reliably with them. --------- Co-authored-by: Kevin Frei <[email protected]>
1 parent e94a00c commit 60cd3eb

File tree

3 files changed

+105
-14
lines changed

3 files changed

+105
-14
lines changed

llvm/include/llvm/DebugInfo/DWARF/DWARFUnit.h

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "llvm/DebugInfo/DWARF/DWARFLocationExpression.h"
2323
#include "llvm/DebugInfo/DWARF/DWARFUnitIndex.h"
2424
#include "llvm/Support/DataExtractor.h"
25+
#include "llvm/Support/RWMutex.h"
2526
#include <cassert>
2627
#include <cstddef>
2728
#include <cstdint>
@@ -257,6 +258,10 @@ class DWARFUnit {
257258

258259
std::shared_ptr<DWARFUnit> DWO;
259260

261+
mutable llvm::sys::RWMutex FreeDIEsMutex;
262+
mutable llvm::sys::RWMutex ExtractCUDieMutex;
263+
mutable llvm::sys::RWMutex ExtractNonCUDIEsMutex;
264+
260265
protected:
261266
friend dwarf_linker::parallel::CompileUnit;
262267

@@ -566,6 +571,9 @@ class DWARFUnit {
566571

567572
Error tryExtractDIEsIfNeeded(bool CUDieOnly);
568573

574+
/// clearDIEs - Clear parsed DIEs to keep memory usage low.
575+
void clearDIEs(bool KeepCUDie);
576+
569577
private:
570578
/// Size in bytes of the .debug_info data associated with this compile unit.
571579
size_t getDebugInfoSize() const {
@@ -577,13 +585,22 @@ class DWARFUnit {
577585
/// hasn't already been done
578586
void extractDIEsIfNeeded(bool CUDieOnly);
579587

588+
/// extracCUDieIfNeeded - Parse CU DIE if it hasn't already been done.
589+
/// Only to be used from extractDIEsIfNeeded, which holds the correct locks.
590+
bool extractCUDieIfNeeded(bool CUDieOnly, bool &HasCUDie);
591+
592+
/// extractNonCUDIEsIfNeeded - Parses non-CU DIE's for a given CU if needed.
593+
/// Only to be used from extractDIEsIfNeeded, which holds the correct locks.
594+
Error extractNonCUDIEsIfNeeded(bool HasCUDie);
595+
596+
/// extractNonCUDIEsHelper - helper to be invoked *only* from inside
597+
/// tryExtractDIEsIfNeeded, which holds the correct locks.
598+
Error extractNonCUDIEsHelper();
599+
580600
/// extractDIEsToVector - Appends all parsed DIEs to a vector.
581601
void extractDIEsToVector(bool AppendCUDie, bool AppendNonCUDIEs,
582602
std::vector<DWARFDebugInfoEntry> &DIEs) const;
583603

584-
/// clearDIEs - Clear parsed DIEs to keep memory usage low.
585-
void clearDIEs(bool KeepCUDie);
586-
587604
/// parseDWO - Parses .dwo file for current compile unit. Returns true if
588605
/// it was actually constructed.
589606
/// The \p AlternativeLocation specifies an alternative location to get

llvm/lib/DebugInfo/DWARF/DWARFUnit.cpp

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -495,21 +495,78 @@ void DWARFUnit::extractDIEsIfNeeded(bool CUDieOnly) {
495495
Context.getRecoverableErrorHandler()(std::move(e));
496496
}
497497

498-
Error DWARFUnit::tryExtractDIEsIfNeeded(bool CUDieOnly) {
499-
if ((CUDieOnly && !DieArray.empty()) ||
500-
DieArray.size() > 1)
501-
return Error::success(); // Already parsed.
498+
static bool DoubleCheckedRWLocker(llvm::sys::RWMutex &Mutex,
499+
const std::function<bool()> &reader,
500+
const std::function<void()> &writer) {
501+
{
502+
llvm::sys::ScopedReader Lock(Mutex);
503+
if (reader())
504+
return true;
505+
}
506+
llvm::sys::ScopedWriter Lock(Mutex);
507+
if (reader())
508+
return true;
509+
// If we get here, then the reader function returned false. This means that
510+
// no one else is currently writing to this data structure and it's safe for
511+
// us to write to it now. The scoped writer lock guarantees there are no
512+
// other readers or writers at this point.
513+
writer();
514+
return false;
515+
}
502516

503-
bool HasCUDie = !DieArray.empty();
504-
extractDIEsToVector(!HasCUDie, !CUDieOnly, DieArray);
517+
// Helper to safely check if the Compile-Unit DIE has been extracted already.
518+
// If not, then extract it, and return false, indicating that it was *not*
519+
// already extracted.
520+
bool DWARFUnit::extractCUDieIfNeeded(bool CUDieOnly, bool &HasCUDie) {
521+
return DoubleCheckedRWLocker(
522+
ExtractCUDieMutex,
523+
// Calculate if the CU DIE has been extracted already.
524+
[&]() {
525+
return ((CUDieOnly && !DieArray.empty()) || DieArray.size() > 1);
526+
},
527+
// Lambda to extract the CU DIE.
528+
[&]() {
529+
HasCUDie = !DieArray.empty();
530+
extractDIEsToVector(!HasCUDie, !CUDieOnly, DieArray);
531+
});
532+
}
505533

506-
if (DieArray.empty())
507-
return Error::success();
534+
// Helper to safely check if the non-Compile-Unit DIEs have been parsed
535+
// already. If they haven't been parsed, go ahead and parse them.
536+
Error DWARFUnit::extractNonCUDIEsIfNeeded(bool HasCUDie) {
537+
Error Result = Error::success();
538+
DoubleCheckedRWLocker(
539+
ExtractNonCUDIEsMutex,
540+
// Lambda to check if all DIEs have been extracted already.
541+
[=]() { return (DieArray.empty() || HasCUDie); },
542+
// Lambda to extract all the DIEs using the helper function
543+
[&]() {
544+
if (Error E = extractNonCUDIEsHelper()) {
545+
// Consume the success placeholder and save the actual error
546+
consumeError(std::move(Result));
547+
Result = std::move(E);
548+
}
549+
});
550+
return Result;
551+
}
508552

509-
// If CU DIE was just parsed, copy several attribute values from it.
510-
if (HasCUDie)
553+
Error DWARFUnit::tryExtractDIEsIfNeeded(bool CUDieOnly) {
554+
// Acquire the FreeDIEsMutex lock (in read-mode) to prevent the Compile Unit
555+
// DIE from being freed by a thread calling clearDIEs() after the CU DIE was
556+
// parsed, but before the rest of the DIEs are parsed, as there are no other
557+
// locks held during that brief period.
558+
llvm::sys::ScopedReader FreeLock(FreeDIEsMutex);
559+
bool HasCUDie = false;
560+
if (extractCUDieIfNeeded(CUDieOnly, HasCUDie))
511561
return Error::success();
562+
// Right here is where the above-mentioned race condition exists.
563+
return extractNonCUDIEsIfNeeded(HasCUDie);
564+
}
512565

566+
// Helper used from the tryExtractDIEsIfNeeded function: it must already have
567+
// acquired the ExtractNonCUDIEsMutex for writing.
568+
Error DWARFUnit::extractNonCUDIEsHelper() {
569+
// If CU DIE was just parsed, copy several attribute values from it.
513570
DWARFDie UnitDie(this, &DieArray[0]);
514571
if (std::optional<uint64_t> DWOId =
515572
toUnsigned(UnitDie.find(DW_AT_GNU_dwo_id)))
@@ -653,6 +710,10 @@ bool DWARFUnit::parseDWO(StringRef DWOAlternativeLocation) {
653710
}
654711

655712
void DWARFUnit::clearDIEs(bool KeepCUDie) {
713+
// We need to acquire the FreeDIEsMutex lock in write-mode, because we are
714+
// going to free the DIEs, when other threads might be trying to create them.
715+
llvm::sys::ScopedWriter FreeLock(FreeDIEsMutex);
716+
656717
// Do not use resize() + shrink_to_fit() to free memory occupied by dies.
657718
// shrink_to_fit() is a *non-binding* request to reduce capacity() to size().
658719
// It depends on the implementation whether the request is fulfilled.

llvm/lib/DebugInfo/GSYM/DwarfTransformer.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,11 @@ Error DwarfTransformer::convert(uint32_t NumThreads, OutputAggregator &Out) {
587587
DWARFDie Die = getDie(*CU);
588588
CUInfo CUI(DICtx, dyn_cast<DWARFCompileUnit>(CU.get()));
589589
handleDie(Out, CUI, Die);
590+
// Release the line table, once we're done.
591+
DICtx.clearLineTableForUnit(CU.get());
592+
// Free any DIEs that were allocated by the DWARF parser.
593+
// If/when they're needed by other CU's, they'll be recreated.
594+
CU->clearDIEs(/*KeepCUDie=*/false);
590595
}
591596
} else {
592597
// LLVM Dwarf parser is not thread-safe and we need to parse all DWARF up
@@ -612,11 +617,16 @@ Error DwarfTransformer::convert(uint32_t NumThreads, OutputAggregator &Out) {
612617
DWARFDie Die = getDie(*CU);
613618
if (Die) {
614619
CUInfo CUI(DICtx, dyn_cast<DWARFCompileUnit>(CU.get()));
615-
pool.async([this, CUI, &LogMutex, &Out, Die]() mutable {
620+
pool.async([this, CUI, &CU, &LogMutex, &Out, Die]() mutable {
616621
std::string storage;
617622
raw_string_ostream StrStream(storage);
618623
OutputAggregator ThreadOut(Out.GetOS() ? &StrStream : nullptr);
619624
handleDie(ThreadOut, CUI, Die);
625+
// Release the line table once we're done.
626+
DICtx.clearLineTableForUnit(CU.get());
627+
// Free any DIEs that were allocated by the DWARF parser.
628+
// If/when they're needed by other CU's, they'll be recreated.
629+
CU->clearDIEs(/*KeepCUDie=*/false);
620630
// Print ThreadLogStorage lines into an actual stream under a lock
621631
std::lock_guard<std::mutex> guard(LogMutex);
622632
if (Out.GetOS()) {
@@ -629,6 +639,9 @@ Error DwarfTransformer::convert(uint32_t NumThreads, OutputAggregator &Out) {
629639
}
630640
pool.wait();
631641
}
642+
// Now get rid of all the DIEs that may have been recreated
643+
for (const auto &CU : DICtx.compile_units())
644+
CU->clearDIEs(/*KeepCUDie=*/false);
632645
size_t FunctionsAddedCount = Gsym.getNumFunctionInfos() - NumBefore;
633646
Out << "Loaded " << FunctionsAddedCount << " functions from DWARF.\n";
634647
return Error::success();

0 commit comments

Comments
 (0)