Skip to content

Commit 255870d

Browse files
committed
[JITLink] Update splitBlock to support splitting into multiple blocks.
LinkGraph::splitBlock used to take a single split-point to split a Block into two. In the common case where a block needs to be split repeatedly (e.g. in eh-frame and compact-unwind sections), iterative calls to splitBlock could lead to poor performance as symbols and edges are repeatedly shuffled to new blocks. This commit updates LinkGraph::splitBlock to take a sequence of split offsets, allowing a block to be split into an arbitrary number of new blocks. Internally, Symbols and Edges only need to be moved once (directly to whichever new block they will be associated with), leading to better performance. On some large MachO object files in an out of tree project this change improved the performance of splitBlock by several orders of magnitude. rdar://135820493
1 parent 85681d4 commit 255870d

File tree

10 files changed

+203
-133
lines changed

10 files changed

+203
-133
lines changed

llvm/include/llvm/ExecutionEngine/JITLink/JITLink.h

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,12 @@ class Block : public Addressable {
231231
/// Returns the size of this defined addressable.
232232
size_t getSize() const { return Size; }
233233

234+
/// Turns this block into a zero-fill block of the given size.
235+
void setZeroFillSize(size_t Size) {
236+
Data = nullptr;
237+
this->Size = Size;
238+
}
239+
234240
/// Returns the address range of this defined addressable.
235241
orc::ExecutorAddrRange getRange() const {
236242
return orc::ExecutorAddrRange(getAddress(), getSize());
@@ -1166,17 +1172,22 @@ class LinkGraph {
11661172
/// Cache type for the splitBlock function.
11671173
using SplitBlockCache = std::optional<SmallVector<Symbol *, 8>>;
11681174

1169-
/// Splits block B at the given index which must be greater than zero.
1170-
/// If SplitIndex == B.getSize() then this function is a no-op and returns B.
1171-
/// If SplitIndex < B.getSize() then this function returns a new block
1172-
/// covering the range [ 0, SplitIndex ), and B is modified to cover the range
1173-
/// [ SplitIndex, B.size() ).
1175+
/// Splits block B into a sequence of smaller blocks.
1176+
///
1177+
/// SplitOffsets should be a sequence of ascending offsets in B. The starting
1178+
/// offset should be greater than zero, and the final offset less than
1179+
/// B.getSize() - 1.
1180+
///
1181+
/// The resulting seqeunce of blocks will start with the original block B
1182+
/// (truncated to end at the first split offset) followed by newly introduced
1183+
/// blocks starting at the subsequent split points.
11741184
///
11751185
/// The optional Cache parameter can be used to speed up repeated calls to
1176-
/// splitBlock for a single block. If the value is None the cache will be
1177-
/// treated as uninitialized and splitBlock will populate it. Otherwise it
1178-
/// is assumed to contain the list of Symbols pointing at B, sorted in
1179-
/// descending order of offset.
1186+
/// splitBlock for blocks within a single Section. If the value is None then
1187+
/// the cache will be treated as uninitialized and splitBlock will populate
1188+
/// it. Otherwise it is assumed to contain the list of Symbols pointing at B,
1189+
/// sorted in descending order of offset.
1190+
///
11801191
///
11811192
/// Notes:
11821193
///
@@ -1189,18 +1200,56 @@ class LinkGraph {
11891200
/// LinkGraph does not have. Clients are responsible for ensuring that
11901201
/// splitBlock is not used in a way that invalidates edges.
11911202
///
1192-
/// 2. The newly introduced block will have a new ordinal which will be
1193-
/// higher than any other ordinals in the section. Clients are responsible
1194-
/// for re-assigning block ordinals to restore a compatible order if
1195-
/// needed.
1203+
/// 2. The newly introduced blocks will have new ordinals that will be higher
1204+
/// than any other ordinals in the section. Clients are responsible for
1205+
/// re-assigning block ordinals to restore a compatible order if needed.
11961206
///
11971207
/// 3. The cache is not automatically updated if new symbols are introduced
11981208
/// between calls to splitBlock. Any newly introduced symbols may be
11991209
/// added to the cache manually (descending offset order must be
12001210
/// preserved), or the cache can be set to None and rebuilt by
12011211
/// splitBlock on the next call.
1202-
Block &splitBlock(Block &B, size_t SplitIndex,
1203-
SplitBlockCache *Cache = nullptr);
1212+
template <typename SplitOffsetRange>
1213+
std::vector<Block *> splitBlock(Block &B, SplitOffsetRange &&SplitOffsets,
1214+
LinkGraph::SplitBlockCache *Cache = nullptr) {
1215+
std::vector<Block *> Blocks;
1216+
Blocks.push_back(&B);
1217+
1218+
if (std::empty(SplitOffsets))
1219+
return Blocks;
1220+
1221+
// Special case zero-fill:
1222+
if (B.isZeroFill()) {
1223+
size_t OrigSize = B.getSize();
1224+
for (Edge::OffsetT Offset : SplitOffsets) {
1225+
assert(Offset > 0 && Offset < B.getSize() &&
1226+
"Split offset must be inside block content");
1227+
Blocks.back()->setZeroFillSize(
1228+
Offset - (Blocks.back()->getAddress() - B.getAddress()));
1229+
Blocks.push_back(&createZeroFillBlock(
1230+
B.getSection(), B.getSize(), B.getAddress() + Offset,
1231+
B.getAlignment(),
1232+
(B.getAlignmentOffset() + Offset) % B.getAlignment()));
1233+
}
1234+
Blocks.back()->setZeroFillSize(
1235+
OrigSize - (Blocks.back()->getAddress() - B.getAddress()));
1236+
return Blocks;
1237+
}
1238+
1239+
// Handle content blocks. We'll just create the blocks with their starting
1240+
// address and no content here. The bulk of the work is deferred to
1241+
// splitBlockImpl.
1242+
for (Edge::OffsetT Offset : SplitOffsets) {
1243+
assert(Offset > 0 && Offset < B.getSize() &&
1244+
"Split offset must be inside block content");
1245+
Blocks.push_back(&createContentBlock(
1246+
B.getSection(), ArrayRef<char>(), B.getAddress() + Offset,
1247+
B.getAlignment(),
1248+
(B.getAlignmentOffset() + Offset) % B.getAlignment()));
1249+
}
1250+
1251+
return splitBlockImpl(std::move(Blocks), Cache);
1252+
}
12041253

12051254
/// Add an external symbol.
12061255
/// Some formats (e.g. ELF) allow Symbols to have sizes. For Symbols whose
@@ -1534,6 +1583,9 @@ class LinkGraph {
15341583
void dump(raw_ostream &OS);
15351584

15361585
private:
1586+
std::vector<Block *> splitBlockImpl(std::vector<Block *> Blocks,
1587+
SplitBlockCache *Cache);
1588+
15371589
// Put the BumpPtrAllocator first so that we don't free any of the underlying
15381590
// memory until the Symbol/Addressable destructors have been run.
15391591
BumpPtrAllocator Allocator;

llvm/lib/ExecutionEngine/JITLink/DWARFRecordSectionSplitter.cpp

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,11 @@ Error DWARFRecordSectionSplitter::processBlock(
7878
StringRef(B.getContent().data(), B.getContent().size()),
7979
G.getEndianness());
8080

81+
std::vector<Edge::OffsetT> SplitOffsets;
8182
while (true) {
82-
uint64_t RecordStartOffset = BlockReader.getOffset();
83-
8483
LLVM_DEBUG({
8584
dbgs() << " Processing CFI record at "
86-
<< formatv("{0:x16}", B.getAddress()) << "\n";
85+
<< (B.getAddress() + BlockReader.getOffset()) << "\n";
8786
});
8887

8988
uint32_t Length;
@@ -100,17 +99,16 @@ Error DWARFRecordSectionSplitter::processBlock(
10099
return Err;
101100
}
102101

103-
// If this was the last block then there's nothing to split
104-
if (BlockReader.empty()) {
105-
LLVM_DEBUG(dbgs() << " Extracted " << B << "\n");
106-
return Error::success();
107-
}
102+
// If this was the last block then there's nothing more to split
103+
if (BlockReader.empty())
104+
break;
108105

109-
uint64_t BlockSize = BlockReader.getOffset() - RecordStartOffset;
110-
auto &NewBlock = G.splitBlock(B, BlockSize, &Cache);
111-
(void)NewBlock;
112-
LLVM_DEBUG(dbgs() << " Extracted " << NewBlock << "\n");
106+
SplitOffsets.push_back(BlockReader.getOffset());
113107
}
108+
109+
G.splitBlock(B, SplitOffsets);
110+
111+
return Error::success();
114112
}
115113

116114
} // namespace jitlink

llvm/lib/ExecutionEngine/JITLink/JITLink.cpp

Lines changed: 81 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -167,84 +167,103 @@ Section::~Section() {
167167
B->~Block();
168168
}
169169

170-
Block &LinkGraph::splitBlock(Block &B, size_t SplitIndex,
171-
SplitBlockCache *Cache) {
172-
173-
assert(SplitIndex > 0 && "splitBlock can not be called with SplitIndex == 0");
174-
175-
// If the split point covers all of B then just return B.
176-
if (SplitIndex == B.getSize())
177-
return B;
178-
179-
assert(SplitIndex < B.getSize() && "SplitIndex out of range");
180-
181-
// Create the new block covering [ 0, SplitIndex ).
182-
auto &NewBlock =
183-
B.isZeroFill()
184-
? createZeroFillBlock(B.getSection(), SplitIndex, B.getAddress(),
185-
B.getAlignment(), B.getAlignmentOffset())
186-
: createContentBlock(
187-
B.getSection(), B.getContent().slice(0, SplitIndex),
188-
B.getAddress(), B.getAlignment(), B.getAlignmentOffset());
189-
190-
// Modify B to cover [ SplitIndex, B.size() ).
191-
B.setAddress(B.getAddress() + SplitIndex);
192-
B.setContent(B.getContent().slice(SplitIndex));
193-
B.setAlignmentOffset((B.getAlignmentOffset() + SplitIndex) %
194-
B.getAlignment());
195-
196-
// Handle edge transfer/update.
197-
{
198-
// Copy edges to NewBlock (recording their iterators so that we can remove
199-
// them from B), and update of Edges remaining on B.
200-
std::vector<Block::edge_iterator> EdgesToRemove;
201-
for (auto I = B.edges().begin(); I != B.edges().end();) {
202-
if (I->getOffset() < SplitIndex) {
203-
NewBlock.addEdge(*I);
204-
I = B.removeEdge(I);
205-
} else {
206-
I->setOffset(I->getOffset() - SplitIndex);
207-
++I;
208-
}
209-
}
170+
std::vector<Block *> LinkGraph::splitBlockImpl(std::vector<Block *> Blocks,
171+
SplitBlockCache *Cache) {
172+
assert(!Blocks.empty() && "Blocks must at least contain the original block");
173+
174+
// Fix up content of all blocks.
175+
ArrayRef<char> Content = Blocks.front()->getContent();
176+
for (size_t I = 0; I != Blocks.size() - 1; ++I) {
177+
Blocks[I]->setContent(
178+
Content.slice(Blocks[I]->getAddress() - Blocks[0]->getAddress(),
179+
Blocks[I + 1]->getAddress() - Blocks[I]->getAddress()));
210180
}
181+
Blocks.back()->setContent(
182+
Content.slice(Blocks.back()->getAddress() - Blocks[0]->getAddress()));
183+
bool IsMutable = Blocks[0]->ContentMutable;
184+
for (auto *B : Blocks)
185+
B->ContentMutable = IsMutable;
211186

212-
// Handle symbol transfer/update.
187+
// Transfer symbols.
213188
{
214-
// Initialize the symbols cache if necessary.
215189
SplitBlockCache LocalBlockSymbolsCache;
216190
if (!Cache)
217191
Cache = &LocalBlockSymbolsCache;
192+
193+
// Build cache if required.
218194
if (*Cache == std::nullopt) {
219195
*Cache = SplitBlockCache::value_type();
220-
for (auto *Sym : B.getSection().symbols())
221-
if (&Sym->getBlock() == &B)
222-
(*Cache)->push_back(Sym);
223196

197+
for (auto *Sym : Blocks[0]->getSection().symbols())
198+
if (&Sym->getBlock() == Blocks[0])
199+
(*Cache)->push_back(Sym);
224200
llvm::sort(**Cache, [](const Symbol *LHS, const Symbol *RHS) {
225-
return LHS->getOffset() > RHS->getOffset();
201+
return LHS->getAddress() > RHS->getAddress();
226202
});
227203
}
228-
auto &BlockSymbols = **Cache;
229-
230-
// Transfer all symbols with offset less than SplitIndex to NewBlock.
231-
while (!BlockSymbols.empty() &&
232-
BlockSymbols.back()->getOffset() < SplitIndex) {
233-
auto *Sym = BlockSymbols.back();
234-
// If the symbol extends beyond the split, update the size to be within
235-
// the new block.
236-
if (Sym->getOffset() + Sym->getSize() > SplitIndex)
237-
Sym->setSize(SplitIndex - Sym->getOffset());
238-
Sym->setBlock(NewBlock);
239-
BlockSymbols.pop_back();
204+
205+
auto TransferSymbol = [](Symbol &Sym, Block &B) {
206+
Sym.setOffset(Sym.getAddress() - B.getAddress());
207+
if (Sym.getSize() > B.getSize())
208+
Sym.setSize(B.getSize() - Sym.getOffset());
209+
Sym.setBlock(B);
210+
};
211+
212+
// Transfer symbols to all blocks except the last one.
213+
for (size_t I = 0; I != Blocks.size() - 1; ++I) {
214+
if ((*Cache)->empty())
215+
break;
216+
while (!(*Cache)->empty() &&
217+
(*Cache)->back()->getAddress() < Blocks[I + 1]->getAddress()) {
218+
TransferSymbol(*(*Cache)->back(), *Blocks[I]);
219+
(*Cache)->pop_back();
220+
}
221+
}
222+
// Transfer symbols to the last block, checking that all are in-range.
223+
while (!(*Cache)->empty()) {
224+
auto &Sym = *(*Cache)->back();
225+
(*Cache)->pop_back();
226+
assert(Sym.getAddress() >= Blocks.back()->getAddress() &&
227+
"Symbol address preceeds block");
228+
assert(Sym.getAddress() <= Blocks.back()->getRange().End &&
229+
"Symbol address starts past end of block");
230+
TransferSymbol(Sym, *Blocks.back());
231+
}
232+
}
233+
234+
// Transfer edges.
235+
auto &Edges = Blocks[0]->Edges;
236+
llvm::sort(Edges, [](const Edge &LHS, const Edge &RHS) {
237+
return LHS.getOffset() < RHS.getOffset();
238+
});
239+
240+
for (size_t I = Blocks.size() - 1; I != 0; --I) {
241+
242+
// If all edges have been transferred then bail out.
243+
if (Edges.empty())
244+
break;
245+
246+
Edge::OffsetT Delta = Blocks[I]->getAddress() - Blocks[0]->getAddress();
247+
248+
// If no edges to move for this block then move to the next one.
249+
if (Edges.back().getOffset() < Delta)
250+
continue;
251+
252+
size_t EI = Edges.size() - 1;
253+
while (EI != 0 && Edges[EI - 1].getOffset() >= Delta)
254+
--EI;
255+
256+
for (size_t J = EI; J != Edges.size(); ++J) {
257+
Blocks[I]->Edges.push_back(std::move(Edges[J]));
258+
Blocks[I]->Edges.back().setOffset(Blocks[I]->Edges.back().getOffset() -
259+
Delta);
240260
}
241261

242-
// Update offsets for all remaining symbols in B.
243-
for (auto *Sym : BlockSymbols)
244-
Sym->setOffset(Sym->getOffset() - SplitIndex);
262+
while (Edges.size() > EI)
263+
Edges.pop_back();
245264
}
246265

247-
return NewBlock;
266+
return Blocks;
248267
}
249268

250269
void LinkGraph::dump(raw_ostream &OS) {

0 commit comments

Comments
 (0)