79 changes: 72 additions & 7 deletions llvm/include/llvm/ExecutionEngine/JITLink/JITLink.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ class Block : public Addressable {
/// Create a zero-fill defined addressable.
Block(Section &Parent, JITTargetAddress Size, JITTargetAddress Address,
uint64_t Alignment, uint64_t AlignmentOffset)
: Addressable(Address, true), Parent(Parent), Size(Size) {
: Addressable(Address, true), Parent(&Parent), Size(Size) {
assert(isPowerOf2_64(Alignment) && "Alignment must be power of 2");
assert(AlignmentOffset < Alignment &&
"Alignment offset cannot exceed alignment");
Expand All @@ -170,7 +170,7 @@ class Block : public Addressable {
/// mutations are required.
Block(Section &Parent, ArrayRef<char> Content, JITTargetAddress Address,
uint64_t Alignment, uint64_t AlignmentOffset)
: Addressable(Address, true), Parent(Parent), Data(Content.data()),
: Addressable(Address, true), Parent(&Parent), Data(Content.data()),
Size(Content.size()) {
assert(isPowerOf2_64(Alignment) && "Alignment must be power of 2");
assert(AlignmentOffset < Alignment &&
Expand All @@ -189,7 +189,7 @@ class Block : public Addressable {
/// allocator.
Block(Section &Parent, MutableArrayRef<char> Content,
JITTargetAddress Address, uint64_t Alignment, uint64_t AlignmentOffset)
: Addressable(Address, true), Parent(Parent), Data(Content.data()),
: Addressable(Address, true), Parent(&Parent), Data(Content.data()),
Size(Content.size()) {
assert(isPowerOf2_64(Alignment) && "Alignment must be power of 2");
assert(AlignmentOffset < Alignment &&
Expand All @@ -212,7 +212,7 @@ class Block : public Addressable {
Block &operator=(Block &&) = delete;

/// Return the parent section for this block.
Section &getSection() const { return Parent; }
Section &getSection() const { return *Parent; }

/// Returns true if this is a zero-fill block.
///
Expand Down Expand Up @@ -331,7 +331,9 @@ class Block : public Addressable {
private:
static constexpr uint64_t MaxAlignmentOffset = (1ULL << 56) - 1;

Section &Parent;
void setSection(Section &Parent) { this->Parent = &Parent; }

Section *Parent;
const char *Data = nullptr;
size_t Size = 0;
std::vector<Edge> Edges;
Expand Down Expand Up @@ -684,6 +686,8 @@ class Section {
return make_range(Blocks.begin(), Blocks.end());
}

BlockSet::size_type blocks_size() const { return Blocks.size(); }

/// Returns an iterator over the symbols defined in this section.
iterator_range<symbol_iterator> symbols() {
return make_range(Symbols.begin(), Symbols.end());
Expand All @@ -695,7 +699,7 @@ class Section {
}

/// Return the number of symbols in this section.
SymbolSet::size_type symbols_size() { return Symbols.size(); }
SymbolSet::size_type symbols_size() const { return Symbols.size(); }

private:
void addSymbol(Symbol &Sym) {
Expand All @@ -718,6 +722,17 @@ class Section {
Blocks.erase(&B);
}

void transferContentTo(Section &DstSection) {
if (&DstSection == this)
return;
for (auto *S : Symbols)
DstSection.addSymbol(*S);
for (auto *B : Blocks)
DstSection.addBlock(*B);
Symbols.clear();
Blocks.clear();
}

StringRef Name;
sys::Memory::ProtectionFlags Prot;
SectionOrdinal SecOrdinal = 0;
Expand Down Expand Up @@ -1102,6 +1117,8 @@ class LinkGraph {
section_iterator(Sections.end()));
}

SectionList::size_type sections_size() const { return Sections.size(); }

/// Returns the section with the given name if it exists, otherwise returns
/// null.
Section *findSectionByName(StringRef Name) {
Expand Down Expand Up @@ -1231,6 +1248,43 @@ class LinkGraph {
}
}

/// Transfers the given Block and all Symbols pointing to it to the given
/// Section.
///
/// No attempt is made to check compatibility of the source and destination
/// sections. Blocks may be moved between sections with incompatible
/// permissions (e.g. from data to text). The client is responsible for
/// ensuring that this is safe.
void transferBlock(Block &B, Section &NewSection) {
auto &OldSection = B.getSection();
if (&OldSection == &NewSection)
return;
SmallVector<Symbol *> AttachedSymbols;
for (auto *S : OldSection.symbols())
if (&S->getBlock() == &B)
AttachedSymbols.push_back(S);
for (auto *S : AttachedSymbols) {
OldSection.removeSymbol(*S);
NewSection.addSymbol(*S);
}
OldSection.removeBlock(B);
NewSection.addBlock(B);
}

/// Move all blocks and symbols from the source section to the destination
/// section.
///
/// If PreserveSrcSection is true (or SrcSection and DstSection are the same)
/// then SrcSection is preserved, otherwise it is removed (the default).
void mergeSections(Section &DstSection, Section &SrcSection,
bool PreserveSrcSection = false) {
if (&DstSection == &SrcSection)
return;
SrcSection.transferContentTo(DstSection);
if (!PreserveSrcSection)
removeSection(SrcSection);
}

/// Removes an external symbol. Also removes the underlying Addressable.
void removeExternalSymbol(Symbol &Sym) {
assert(!Sym.isDefined() && !Sym.isAbsolute() &&
Expand Down Expand Up @@ -1269,7 +1323,8 @@ class LinkGraph {
destroySymbol(Sym);
}

/// Remove a block.
/// Remove a block. The block reference is defunct after calling this
/// function and should no longer be used.
void removeBlock(Block &B) {
assert(llvm::none_of(B.getSection().symbols(),
[&](const Symbol *Sym) {
Expand All @@ -1280,6 +1335,16 @@ class LinkGraph {
destroyBlock(B);
}

/// Remove a section. The section reference is defunct after calling this
/// function and should no longer be used.
void removeSection(Section &Sec) {
auto I = llvm::find_if(Sections, [&Sec](const std::unique_ptr<Section> &S) {
return S.get() == &Sec;
});
assert(I != Sections.end() && "Section does not appear in this graph");
Sections.erase(I);
}

/// Dump the graph.
void dump(raw_ostream &OS);

Expand Down
20 changes: 14 additions & 6 deletions llvm/include/llvm/ExecutionEngine/Orc/MachOPlatform.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ namespace orc {

struct MachOPerObjectSectionsToRegister {
ExecutorAddressRange EHFrameSection;
ExecutorAddressRange ThreadDataSection;
};

struct MachOJITDylibInitializers {
Expand Down Expand Up @@ -158,14 +159,16 @@ class MachOPlatform : public Platform {
void addMachOHeaderSupportPasses(MaterializationResponsibility &MR,
jitlink::PassConfiguration &Config);

void addEHSupportPasses(MaterializationResponsibility &MR,
jitlink::PassConfiguration &Config);
void addEHAndTLVSupportPasses(MaterializationResponsibility &MR,
jitlink::PassConfiguration &Config);

Error preserveInitSections(jitlink::LinkGraph &G,
MaterializationResponsibility &MR);

Error registerInitSections(jitlink::LinkGraph &G, JITDylib &JD);

Error fixTLVSectionsAndEdges(jitlink::LinkGraph &G, JITDylib &JD);

std::mutex PluginMutex;
MachOPlatform &MP;
InitSymbolDepMap InitSymbolDeps;
Expand Down Expand Up @@ -213,6 +216,8 @@ class MachOPlatform : public Platform {

Error registerPerObjectSections(const MachOPerObjectSectionsToRegister &POSR);

Expected<uint64_t> createPThreadKey();

ExecutionSession &ES;
ObjectLinkingLayer &ObjLinkingLayer;
ExecutorProcessControl &EPC;
Expand All @@ -223,6 +228,7 @@ class MachOPlatform : public Platform {
ExecutorAddress orc_rt_macho_platform_bootstrap;
ExecutorAddress orc_rt_macho_platform_shutdown;
ExecutorAddress orc_rt_macho_register_object_sections;
ExecutorAddress orc_rt_macho_create_pthread_key;

DenseMap<JITDylib *, SymbolLookupSet> RegisteredInitSymbols;

Expand All @@ -233,11 +239,13 @@ class MachOPlatform : public Platform {
std::vector<MachOPerObjectSectionsToRegister> BootstrapPOSRs;

DenseMap<JITTargetAddress, JITDylib *> HeaderAddrToJITDylib;
DenseMap<JITDylib *, uint64_t> JITDylibToPThreadKey;
};

namespace shared {

using SPSMachOPerObjectSectionsToRegister = SPSTuple<SPSExecutorAddressRange>;
using SPSMachOPerObjectSectionsToRegister =
SPSTuple<SPSExecutorAddressRange, SPSExecutorAddressRange>;

template <>
class SPSSerializationTraits<SPSMachOPerObjectSectionsToRegister,
Expand All @@ -246,19 +254,19 @@ class SPSSerializationTraits<SPSMachOPerObjectSectionsToRegister,
public:
static size_t size(const MachOPerObjectSectionsToRegister &MOPOSR) {
return SPSMachOPerObjectSectionsToRegister::AsArgList::size(
MOPOSR.EHFrameSection);
MOPOSR.EHFrameSection, MOPOSR.ThreadDataSection);
}

static bool serialize(SPSOutputBuffer &OB,
const MachOPerObjectSectionsToRegister &MOPOSR) {
return SPSMachOPerObjectSectionsToRegister::AsArgList::serialize(
OB, MOPOSR.EHFrameSection);
OB, MOPOSR.EHFrameSection, MOPOSR.ThreadDataSection);
}

static bool deserialize(SPSInputBuffer &IB,
MachOPerObjectSectionsToRegister &MOPOSR) {
return SPSMachOPerObjectSectionsToRegister::AsArgList::deserialize(
IB, MOPOSR.EHFrameSection);
IB, MOPOSR.EHFrameSection, MOPOSR.ThreadDataSection);
}
};

Expand Down
5 changes: 4 additions & 1 deletion llvm/lib/ExecutionEngine/JITLink/JITLink.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,10 @@ void LinkGraph::dump(raw_ostream &OS) {
OS << " block " << formatv("{0:x16}", B->getAddress())
<< " size = " << formatv("{0:x8}", B->getSize())
<< ", align = " << B->getAlignment()
<< ", alignment-offset = " << B->getAlignmentOffset() << "\n";
<< ", alignment-offset = " << B->getAlignmentOffset();
if (B->isZeroFill())
OS << ", zero-fill";
OS << "\n";

auto BlockSymsI = BlockSymbols.find(B);
if (BlockSymsI != BlockSymbols.end()) {
Expand Down
26 changes: 16 additions & 10 deletions llvm/lib/ExecutionEngine/JITLink/MachOLinkGraphBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@ bool MachOLinkGraphBuilder::isDebugSection(const NormalizedSection &NSec) {
strcmp(NSec.SegName, "__DWARF") == 0);
}

bool MachOLinkGraphBuilder::isZeroFillSection(const NormalizedSection &NSec) {
switch (NSec.Flags & MachO::SECTION_TYPE) {
case MachO::S_ZEROFILL:
case MachO::S_GB_ZEROFILL:
case MachO::S_THREAD_LOCAL_ZEROFILL:
return true;
default:
return false;
}
}

unsigned
MachOLinkGraphBuilder::getPointerSize(const object::MachOObjectFile &Obj) {
return Obj.is64Bit() ? 8 : 4;
Expand Down Expand Up @@ -154,17 +165,12 @@ Error MachOLinkGraphBuilder::createNormalizedSections() {
});

// Get the section data if any.
{
unsigned SectionType = NSec.Flags & MachO::SECTION_TYPE;
if (SectionType != MachO::S_ZEROFILL &&
SectionType != MachO::S_GB_ZEROFILL) {
if (!isZeroFillSection(NSec)) {
if (DataOffset + NSec.Size > Obj.getData().size())
return make_error<JITLinkError>(
"Section data extends past end of file");

if (DataOffset + NSec.Size > Obj.getData().size())
return make_error<JITLinkError>(
"Section data extends past end of file");

NSec.Data = Obj.getData().data() + DataOffset;
}
NSec.Data = Obj.getData().data() + DataOffset;
}

// Get prot flags.
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/ExecutionEngine/JITLink/MachOLinkGraphBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ class MachOLinkGraphBuilder {
static bool isAltEntry(const NormalizedSymbol &NSym);

static bool isDebugSection(const NormalizedSection &NSec);
static bool isZeroFillSection(const NormalizedSection &NSec);

MachO::relocation_info
getRelocationInfo(const object::relocation_iterator RelItr) {
Expand Down
11 changes: 8 additions & 3 deletions llvm/lib/ExecutionEngine/JITLink/MachO_x86_64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,14 @@ class MachOLinkGraphBuilder_x86_64 : public MachOLinkGraphBuilder {
Addend = *(const little32_t *)FixupContent - 4;
Kind = x86_64::RequestGOTAndTransformToDelta32;
break;
case MachOPCRel32TLV:
if (auto TargetSymbolOrErr = findSymbolByIndex(RI.r_symbolnum))
TargetSymbol = TargetSymbolOrErr->GraphSymbol;
else
return TargetSymbolOrErr.takeError();
Addend = *(const little32_t *)FixupContent;
Kind = x86_64::RequestTLVPAndTransformToPCRel32TLVPLoadRelaxable;
break;
case MachOPointer32:
if (auto TargetSymbolOrErr = findSymbolByIndex(RI.r_symbolnum))
TargetSymbol = TargetSymbolOrErr->GraphSymbol;
Expand Down Expand Up @@ -392,9 +400,6 @@ class MachOLinkGraphBuilder_x86_64 : public MachOLinkGraphBuilder {
assert(TargetSymbol && "No target symbol from parsePairRelocation?");
break;
}
case MachOPCRel32TLV:
return make_error<JITLinkError>(
"MachO TLV relocations not yet supported");
}

LLVM_DEBUG({
Expand Down
120 changes: 114 additions & 6 deletions llvm/lib/ExecutionEngine/Orc/MachOPlatform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ constexpr MachOHeaderMaterializationUnit::HeaderSymbol

StringRef EHFrameSectionName = "__TEXT,__eh_frame";
StringRef ModInitFuncSectionName = "__DATA,__mod_init_func";
StringRef ThreadBSSSectionName = "__DATA,__thread_bss";
StringRef ThreadDataSectionName = "__DATA,__thread_data";
StringRef ThreadVarsSectionName = "__DATA,__thread_vars";

StringRef InitSectionNames[] = {ModInitFuncSectionName};

Expand Down Expand Up @@ -467,7 +470,8 @@ Error MachOPlatform::bootstrapMachORuntime(JITDylib &PlatformJD) {
{"___orc_rt_macho_platform_bootstrap", &orc_rt_macho_platform_bootstrap},
{"___orc_rt_macho_platform_shutdown", &orc_rt_macho_platform_shutdown},
{"___orc_rt_macho_register_object_sections",
&orc_rt_macho_register_object_sections}};
&orc_rt_macho_register_object_sections},
{"___orc_rt_macho_create_pthread_key", &orc_rt_macho_create_pthread_key}};

SymbolLookupSet RuntimeSymbols;
std::vector<std::pair<SymbolStringPtr, ExecutorAddress *>> AddrsToRecord;
Expand Down Expand Up @@ -562,6 +566,20 @@ Error MachOPlatform::registerPerObjectSections(
return ErrResult;
}

Expected<uint64_t> MachOPlatform::createPThreadKey() {
if (!orc_rt_macho_create_pthread_key)
return make_error<StringError>(
"Attempting to create pthread key in target, but runtime support has "
"not been loaded yet",
inconvertibleErrorCode());

uint64_t Result = 0;
if (auto Err = EPC.runSPSWrapper<uint64_t(void)>(
orc_rt_macho_create_pthread_key.getValue(), Result))
return std::move(Err);
return Result;
}

void MachOPlatform::MachOPlatformPlugin::modifyPassConfig(
MaterializationResponsibility &MR, jitlink::LinkGraph &LG,
jitlink::PassConfiguration &Config) {
Expand All @@ -579,8 +597,8 @@ void MachOPlatform::MachOPlatformPlugin::modifyPassConfig(
if (MR.getInitializerSymbol())
addInitializerSupportPasses(MR, Config);

// Add passes for eh-frame support.
addEHSupportPasses(MR, Config);
// Add passes for eh-frame and TLV support.
addEHAndTLVSupportPasses(MR, Config);
}

ObjectLinkingLayer::Plugin::SyntheticSymbolDependenciesMap
Expand Down Expand Up @@ -634,10 +652,18 @@ void MachOPlatform::MachOPlatformPlugin::addMachOHeaderSupportPasses(
});
}

void MachOPlatform::MachOPlatformPlugin::addEHSupportPasses(
void MachOPlatform::MachOPlatformPlugin::addEHAndTLVSupportPasses(
MaterializationResponsibility &MR, jitlink::PassConfiguration &Config) {

// Add a pass to register the final addresses of the eh-frame sections
// Insert TLV lowering at the start of the PostPrunePasses, since we want
// it to run before GOT/PLT lowering.
Config.PostPrunePasses.insert(
Config.PostPrunePasses.begin(),
[this, &JD = MR.getTargetJITDylib()](jitlink::LinkGraph &G) {
return fixTLVSectionsAndEdges(G, JD);
});

// Add a pass to register the final addresses of the eh-frame and TLV sections
// with the runtime.
Config.PostFixupPasses.push_back([this](jitlink::LinkGraph &G) -> Error {
MachOPerObjectSectionsToRegister POSR;
Expand All @@ -649,7 +675,33 @@ void MachOPlatform::MachOPlatformPlugin::addEHSupportPasses(
ExecutorAddress(R.getEnd())};
}

if (POSR.EHFrameSection.StartAddress) {
// Get a pointer to the thread data section if there is one. It will be used
// below.
jitlink::Section *ThreadDataSection =
G.findSectionByName(ThreadDataSectionName);

// Handle thread BSS section if there is one.
if (auto *ThreadBSSSection = G.findSectionByName(ThreadBSSSectionName)) {
// If there's already a thread data section in this graph then merge the
// thread BSS section content into it, otherwise just treat the thread
// BSS section as the thread data section.
if (ThreadDataSection)
G.mergeSections(*ThreadDataSection, *ThreadBSSSection);
else
ThreadDataSection = ThreadBSSSection;
}

// Having merged thread BSS (if present) and thread data (if present),
// record the resulting section range.
if (ThreadDataSection) {
jitlink::SectionRange R(*ThreadDataSection);
if (!R.empty())
POSR.ThreadDataSection = {ExecutorAddress(R.getStart()),
ExecutorAddress(R.getEnd())};
}

if (POSR.EHFrameSection.StartAddress ||
POSR.ThreadDataSection.StartAddress) {

// If we're still bootstrapping the runtime then just record this
// frame for now.
Expand Down Expand Up @@ -727,5 +779,61 @@ Error MachOPlatform::MachOPlatformPlugin::registerInitSections(
return MP.registerInitInfo(JD, InitSections);
}

Error MachOPlatform::MachOPlatformPlugin::fixTLVSectionsAndEdges(
jitlink::LinkGraph &G, JITDylib &JD) {

// Rename external references to __tlv_bootstrap to ___orc_rt_tlv_get_addr.
for (auto *Sym : G.external_symbols())
if (Sym->getName() == "__tlv_bootstrap") {
Sym->setName("___orc_rt_macho_tlv_get_addr");
break;
}

// Store key in __thread_vars struct fields.
if (auto *ThreadDataSec = G.findSectionByName(ThreadVarsSectionName)) {
Optional<uint64_t> Key;
{
std::lock_guard<std::mutex> Lock(MP.PlatformMutex);
auto I = MP.JITDylibToPThreadKey.find(&JD);
if (I != MP.JITDylibToPThreadKey.end())
Key = I->second;
}

if (!Key) {
if (auto KeyOrErr = MP.createPThreadKey())
Key = *KeyOrErr;
else
return KeyOrErr.takeError();
}

uint64_t PlatformKeyBits =
support::endian::byte_swap(*Key, G.getEndianness());

for (auto *B : ThreadDataSec->blocks()) {
if (B->getSize() != 3 * G.getPointerSize())
return make_error<StringError>("__thread_vars block at " +
formatv("{0:x}", B->getAddress()) +
" has unexpected size",
inconvertibleErrorCode());

auto NewBlockContent = G.allocateBuffer(B->getSize());
llvm::copy(B->getContent(), NewBlockContent.data());
memcpy(NewBlockContent.data() + G.getPointerSize(), &PlatformKeyBits,
G.getPointerSize());
B->setContent(NewBlockContent);
}
}

// Transform any TLV edges into GOT edges.
for (auto *B : G.blocks())
for (auto &E : B->edges())
if (E.getKind() ==
jitlink::x86_64::RequestTLVPAndTransformToPCRel32TLVPLoadRelaxable)
E.setKind(
jitlink::x86_64::RequestGOTAndTransformToPCRel32GOTLoadRelaxable);

return Error::success();
}

} // End namespace orc.
} // End namespace llvm.
19 changes: 19 additions & 0 deletions llvm/test/ExecutionEngine/JITLink/X86/MachO_thread_bss.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# RUN: llvm-mc -triple=x86_64-apple-macos10.9 -filetype=obj -o %t %s
# RUN: llvm-jitlink -noexec -check=%s %t
#
# Check that __thread_bss sections are handled as zero-fill.
#
# jitlink-check: *{4}X = 0

.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 15 sdk_version 10, 15
.globl _main
.p2align 4, 0x90
_main:
retq

.globl X
.tbss X, 4, 2


.subsections_via_symbols
119 changes: 119 additions & 0 deletions llvm/unittests/ExecutionEngine/JITLink/LinkGraphTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,125 @@ TEST(LinkGraphTest, TransferDefinedSymbol) {
EXPECT_EQ(S1.getSize(), 16U) << "Size was not updated";
}

TEST(LinkGraphTest, TransferBlock) {
// Check that we can transfer a block (and all associated symbols) from one
// section to another.
LinkGraph G("foo", Triple("x86_64-apple-darwin"), 8, support::little,
getGenericEdgeKindName);
auto &Sec1 = G.createSection("__data.1", RWFlags);
auto &Sec2 = G.createSection("__data.2", RWFlags);

// Create an initial block.
auto &B1 = G.createContentBlock(Sec1, BlockContent, 0x1000, 8, 0);
auto &B2 = G.createContentBlock(Sec1, BlockContent, 0x2000, 8, 0);

// Add some symbols on B1...
G.addDefinedSymbol(B1, 0, "S1", B1.getSize(), Linkage::Strong, Scope::Default,
false, false);
G.addDefinedSymbol(B1, 1, "S2", B1.getSize() - 1, Linkage::Strong,
Scope::Default, false, false);

// ... and on B2.
G.addDefinedSymbol(B2, 0, "S3", B2.getSize(), Linkage::Strong, Scope::Default,
false, false);
G.addDefinedSymbol(B2, 1, "S4", B2.getSize() - 1, Linkage::Strong,
Scope::Default, false, false);

EXPECT_EQ(Sec1.blocks_size(), 2U) << "Expected two blocks in Sec1 initially";
EXPECT_EQ(Sec1.symbols_size(), 4U)
<< "Expected four symbols in Sec1 initially";
EXPECT_EQ(Sec2.blocks_size(), 0U) << "Expected zero blocks in Sec2 initially";
EXPECT_EQ(Sec2.symbols_size(), 0U)
<< "Expected zero symbols in Sec2 initially";

// Transfer with zero offset, explicit size.
G.transferBlock(B1, Sec2);

EXPECT_EQ(Sec1.blocks_size(), 1U)
<< "Expected one blocks in Sec1 after transfer";
EXPECT_EQ(Sec1.symbols_size(), 2U)
<< "Expected two symbols in Sec1 after transfer";
EXPECT_EQ(Sec2.blocks_size(), 1U)
<< "Expected one blocks in Sec2 after transfer";
EXPECT_EQ(Sec2.symbols_size(), 2U)
<< "Expected two symbols in Sec2 after transfer";
}

TEST(LinkGraphTest, MergeSections) {
// Check that we can transfer a block (and all associated symbols) from one
// section to another.
LinkGraph G("foo", Triple("x86_64-apple-darwin"), 8, support::little,
getGenericEdgeKindName);
auto &Sec1 = G.createSection("__data.1", RWFlags);
auto &Sec2 = G.createSection("__data.2", RWFlags);
auto &Sec3 = G.createSection("__data.3", RWFlags);

// Create an initial block.
auto &B1 = G.createContentBlock(Sec1, BlockContent, 0x1000, 8, 0);
auto &B2 = G.createContentBlock(Sec2, BlockContent, 0x2000, 8, 0);
auto &B3 = G.createContentBlock(Sec3, BlockContent, 0x3000, 8, 0);

// Add a symbols for each block.
G.addDefinedSymbol(B1, 0, "S1", B1.getSize(), Linkage::Strong, Scope::Default,
false, false);
G.addDefinedSymbol(B2, 0, "S2", B2.getSize(), Linkage::Strong, Scope::Default,
false, false);
G.addDefinedSymbol(B3, 0, "S3", B2.getSize(), Linkage::Strong, Scope::Default,
false, false);

EXPECT_EQ(G.sections_size(), 3U) << "Expected three sections initially";
EXPECT_EQ(Sec1.blocks_size(), 1U) << "Expected one block in Sec1 initially";
EXPECT_EQ(Sec1.symbols_size(), 1U) << "Expected one symbol in Sec1 initially";
EXPECT_EQ(Sec2.blocks_size(), 1U) << "Expected one block in Sec2 initially";
EXPECT_EQ(Sec2.symbols_size(), 1U) << "Expected one symbol in Sec2 initially";
EXPECT_EQ(Sec3.blocks_size(), 1U) << "Expected one block in Sec3 initially";
EXPECT_EQ(Sec3.symbols_size(), 1U) << "Expected one symbol in Sec3 initially";

// Check that self-merge is a no-op.
G.mergeSections(Sec1, Sec1);

EXPECT_EQ(G.sections_size(), 3U)
<< "Expected three sections after first merge";
EXPECT_EQ(Sec1.blocks_size(), 1U)
<< "Expected one block in Sec1 after first merge";
EXPECT_EQ(Sec1.symbols_size(), 1U)
<< "Expected one symbol in Sec1 after first merge";
EXPECT_EQ(Sec2.blocks_size(), 1U)
<< "Expected one block in Sec2 after first merge";
EXPECT_EQ(Sec2.symbols_size(), 1U)
<< "Expected one symbol in Sec2 after first merge";
EXPECT_EQ(Sec3.blocks_size(), 1U)
<< "Expected one block in Sec3 after first merge";
EXPECT_EQ(Sec3.symbols_size(), 1U)
<< "Expected one symbol in Sec3 after first merge";

// Merge Sec2 into Sec1, removing Sec2.
G.mergeSections(Sec1, Sec2);

EXPECT_EQ(G.sections_size(), 2U)
<< "Expected two sections after section merge";
EXPECT_EQ(Sec1.blocks_size(), 2U)
<< "Expected two blocks in Sec1 after section merge";
EXPECT_EQ(Sec1.symbols_size(), 2U)
<< "Expected two symbols in Sec1 after section merge";
EXPECT_EQ(Sec3.blocks_size(), 1U)
<< "Expected one block in Sec3 after section merge";
EXPECT_EQ(Sec3.symbols_size(), 1U)
<< "Expected one symbol in Sec3 after section merge";

G.mergeSections(Sec1, Sec3, true);

EXPECT_EQ(G.sections_size(), 2U) << "Expected two sections after third merge";
EXPECT_EQ(Sec1.blocks_size(), 3U)
<< "Expected three blocks in Sec1 after third merge";
EXPECT_EQ(Sec1.symbols_size(), 3U)
<< "Expected three symbols in Sec1 after third merge";
EXPECT_EQ(Sec3.blocks_size(), 0U)
<< "Expected one block in Sec3 after third merge";
EXPECT_EQ(Sec3.symbols_size(), 0U)
<< "Expected one symbol in Sec3 after third merge";
}

TEST(LinkGraphTest, SplitBlock) {
// Check that the LinkGraph::splitBlock test works as expected.
LinkGraph G("foo", Triple("x86_64-apple-darwin"), 8, support::little,
Expand Down