Skip to content

Commit cb17124

Browse files
kddnewtonmeta-codesync[bot]
authored andcommitted
Un-revert D110114775: [CinderX] Relax AArch64 b/bl with branch stubs
Summary: Re-apply D109607577 by backing out its revert, D110114775. The linker issue that caused the revert has been fixed separately, so this restores the original branch-stub relaxation change exactly as it stood before the revert. Reviewed By: mpage Differential Revision: D110202421 fbshipit-source-id: d67afbd56b5be499f369110e785fec21be221f5c
1 parent 417ff45 commit cb17124

9 files changed

Lines changed: 1028 additions & 136 deletions

File tree

cinderx/Jit/code_allocator.cpp

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -335,48 +335,59 @@ void CodeAllocatorCinder::ensureSplitSpace(
335335
}
336336

337337
AllocateResult CodeAllocatorCinder::addSplitCode(asmjit::CodeHolder* code) {
338-
// Compute how much space each section type needs.
339338
size_t hot_size = 0;
340339
size_t cold_size = 0;
341-
for (asmjit::Section* section : code->sections()) {
342-
CodeSection cs = codeSectionFromName(section->name());
343-
if (cs == CodeSection::kCold) {
344-
cold_size += section->realSize();
345-
} else {
346-
hot_size += section->realSize();
340+
341+
for (;;) {
342+
// Compute how much space each section type needs.
343+
hot_size = 0;
344+
cold_size = 0;
345+
for (asmjit::Section* section : code->sections()) {
346+
CodeSection cs = codeSectionFromName(section->name());
347+
if (cs == CodeSection::kCold) {
348+
cold_size += section->realSize();
349+
} else {
350+
hot_size += section->realSize();
351+
}
347352
}
348-
}
349353

350-
// Ensure we have enough space for both hot and cold code.
354+
// Ensure we have enough space for both hot and cold code.
351355
#if defined(__aarch64__)
352-
// On ARM64, branch displacements are limited (±128MB for B/BL, ±1MB for
353-
// B.cond). Allocate hot and cold from a single contiguous region so
354-
// cross-section jumps are always in range.
355-
ensureSplitSpace(hot_size, cold_size);
356+
// On ARM64, branch displacements are limited (±128MB for B/BL, ±1MB for
357+
// B.cond). Allocate hot and cold from a single contiguous region so
358+
// cross-section jumps are always in range.
359+
ensureSplitSpace(hot_size, cold_size);
356360
#else
357-
// On x86-64, RIP-relative addressing has a ±2GB range which is large enough
358-
// that independent allocations are unlikely to exceed it in practice.
359-
ensureSpace(hot_alloc_, hot_alloc_free_, hot_size, true);
360-
ensureSpace(
361-
cold_alloc_,
362-
cold_alloc_free_,
363-
cold_size,
364-
getConfig().cold_code_huge_pages);
361+
// On x86-64, RIP-relative addressing has a ±2GB range which is large enough
362+
// that independent allocations are unlikely to exceed it in practice.
363+
ensureSpace(hot_alloc_, hot_alloc_free_, hot_size, true);
364+
ensureSpace(
365+
cold_alloc_,
366+
cold_alloc_free_,
367+
cold_size,
368+
getConfig().cold_code_huge_pages);
365369
#endif
366370

367-
// Fix up offsets for each code section before resolving links.
368-
// All offsets are relative to the hot allocation base so that asmjit can
369-
// resolve cross-section jumps correctly.
370-
size_t hot_offset = 0;
371-
size_t cold_offset = static_cast<size_t>(cold_alloc_ - hot_alloc_);
372-
for (asmjit::Section* section : code->sections()) {
373-
CodeSection cs = codeSectionFromName(section->name());
374-
if (cs == CodeSection::kCold) {
375-
section->setOffset(cold_offset);
376-
cold_offset += section->realSize();
377-
} else {
378-
section->setOffset(hot_offset);
379-
hot_offset += section->realSize();
371+
// Fix up offsets for each code section before resolving links.
372+
// All offsets are relative to the hot allocation base so that asmjit can
373+
// resolve cross-section jumps correctly.
374+
size_t hot_offset = 0;
375+
size_t cold_offset = static_cast<size_t>(cold_alloc_ - hot_alloc_);
376+
for (asmjit::Section* section : code->sections()) {
377+
CodeSection cs = codeSectionFromName(section->name());
378+
if (cs == CodeSection::kCold) {
379+
section->setOffset(cold_offset);
380+
cold_offset += section->realSize();
381+
} else {
382+
section->setOffset(hot_offset);
383+
hot_offset += section->realSize();
384+
}
385+
}
386+
387+
bool changed = false;
388+
PROPAGATE_ERROR(code->ensureBranchStubIslands(&changed));
389+
if (!changed) {
390+
break;
380391
}
381392
}
382393

cinderx/Jit/codegen/autogen.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1708,8 +1708,8 @@ void translateCall(Environ* env, const Instruction* instr) {
17081708
auto input = instr->getInput(0);
17091709

17101710
if (input->isImm()) {
1711-
// Use bl(imm) which leverages asmjit's relaxation to pick the optimal
1712-
// encoding: direct bl if within ±128MB, or ldr+blr via address table.
1711+
// Use bl(imm) so asmjit can pick the final encoding at relocation time:
1712+
// direct bl if within ±128MB, or a branch to an out-of-line stub.
17131713
as->bl(static_cast<uint64_t>(input->getConstant()));
17141714
} else if (input->isReg()) {
17151715
as->blr(AT::getGp(input));

cinderx/Jit/codegen/code_section.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ const char* codeSectionName(CodeSection section) {
1616
}
1717

1818
CodeSection codeSectionFromName(const char* name) {
19-
if (strcmp(name, ".text") == 0 || strcmp(name, ".addrtab") == 0) {
19+
if (strcmp(name, ".text") == 0 || strcmp(name, ".addrtab") == 0 ||
20+
strcmp(name, ".a64stubs") == 0) {
2021
return CodeSection::kHot;
2122
}
2223
if (strcmp(name, ".coldtext") == 0) {

cinderx/RuntimeTests/branch_relaxation_test.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ using namespace cinderx::jit::codegen;
1515

1616
namespace {
1717

18+
extern "C" uint64_t branchRelaxationReturn42() {
19+
return 42;
20+
}
21+
22+
extern "C" uint64_t branchRelaxationAddOne(uint64_t value) {
23+
return value + 1;
24+
}
25+
1826
class BranchRelaxationTest : public ::testing::Test {
1927
public:
2028
void SetUp() override {
@@ -75,6 +83,40 @@ TEST_F(BranchRelaxationTest, InRangeCondBranch) {
7583
EXPECT_EQ(func(1), 42u);
7684
}
7785

86+
TEST_F(BranchRelaxationTest, AbsoluteCallThroughCodeAllocator) {
87+
asmjit::CodeHolder code;
88+
code.init(code_allocator_->asmJitEnvironment());
89+
arch::Builder as(&code);
90+
91+
as.stp(arch::fp, arch::lr, asmjit::a64::ptr_pre(asmjit::a64::sp, -16));
92+
as.mov(arch::fp, asmjit::a64::sp);
93+
as.bl(reinterpret_cast<uint64_t>(branchRelaxationAddOne));
94+
as.add(asmjit::a64::x0, asmjit::a64::x0, 1);
95+
as.mov(asmjit::a64::sp, arch::fp);
96+
as.ldp(arch::fp, arch::lr, asmjit::a64::ptr_post(asmjit::a64::sp, 16));
97+
as.ret(arch::lr);
98+
99+
void* fn = compileBuilder(as, code);
100+
ASSERT_NE(fn, nullptr);
101+
102+
auto func = reinterpret_cast<uint64_t (*)(uint64_t)>(fn);
103+
EXPECT_EQ(func(41), 43u);
104+
}
105+
106+
TEST_F(BranchRelaxationTest, AbsoluteBranchThroughCodeAllocator) {
107+
asmjit::CodeHolder code;
108+
code.init(code_allocator_->asmJitEnvironment());
109+
arch::Builder as(&code);
110+
111+
as.b(reinterpret_cast<uint64_t>(branchRelaxationReturn42));
112+
113+
void* fn = compileBuilder(as, code);
114+
ASSERT_NE(fn, nullptr);
115+
116+
auto func = reinterpret_cast<uint64_t (*)()>(fn);
117+
EXPECT_EQ(func(), 42u);
118+
}
119+
78120
// tbz has a 14-bit signed immediate (+/-32KB).
79121
TEST_F(BranchRelaxationTest, OutOfRangeTbzIsRelaxed) {
80122
asmjit::CodeHolder code;

cinderx/ThirdParty/asmjit/src/asmjit/arm/a64assembler.cpp

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,7 @@ Error Assembler::_emit(InstId instId, const Operand_& o0, const Operand_& o1, co
815815
// is valid. All options that require special handling (including invalid
816816
// instruction) are handled by the next branch.
817817
// Check for < 8 bytes remaining because branch relaxation (cbz/cbnz,
818-
// tbz/tbnz, b.cond, bl/b, adr) may emit two 32-bit words (8 bytes).
818+
// tbz/tbnz, b.cond, adr) may emit two 32-bit words (8 bytes).
819819
InstOptions options = InstOptions(instId - 1 >= Inst::_kIdCount - 1) | InstOptions((size_t)(_bufferEnd - writer.cursor()) < 8) | instOptions() | forcedInstOptions();
820820

821821
CondCode instCC = BaseInst::extractARMCondCode(instId);
@@ -854,7 +854,7 @@ Error Assembler::_emit(InstId instId, const Operand_& o0, const Operand_& o1, co
854854
goto InvalidInstruction;
855855

856856
// Grow request, happens rarely. Reserve 8 bytes because branch relaxation
857-
// (cbz/cbnz, tbz/tbnz, b.cond, bl/b, adr) may emit two 32-bit words.
857+
// (cbz/cbnz, tbz/tbnz, b.cond, adr) may emit two 32-bit words.
858858
err = writer.ensureSpace(this, 8);
859859
if (ASMJIT_UNLIKELY(err))
860860
goto Failed;
@@ -5207,9 +5207,9 @@ Error Assembler::_emit(InstId instId, const Operand_& o0, const Operand_& o1, co
52075207

52085208
size_t codeOffset = writer.offsetFrom(_bufferData);
52095209

5210-
// For b(imm) and bl(imm), use address table mechanism to handle out-of-range
5211-
// targets. At emit time we reserve 8 bytes, and during relocation we choose
5212-
// the optimal encoding based on the actual displacement.
5210+
// For b(imm) and bl(imm), use a branch stub to handle out-of-range
5211+
// targets. At emit time we reserve one branch instruction, and during
5212+
// relocation we choose either the target or the stub as its destination.
52135213
if ((instId == Inst::kIdBl || instId == Inst::kIdB) &&
52145214
offsetFormat.immBitCount() == 26 &&
52155215
(baseAddress == Globals::kNoBaseAddress || _section->id() != 0))
@@ -5235,7 +5235,7 @@ Error Assembler::_emit(InstId instId, const Operand_& o0, const Operand_& o1, co
52355235
pc &= ~uint64_t(4096 - 1);
52365236

52375237
// For b(imm) and bl(imm) when base address is known, try the direct
5238-
// encoding first. If it doesn't fit, use the address table mechanism.
5238+
// encoding first. If it doesn't fit, use the branch stub mechanism.
52395239
if ((instId == Inst::kIdBl || instId == Inst::kIdB) && offsetFormat.immBitCount() == 26) {
52405240
int64_t displacement = int64_t(targetOffset - pc);
52415241
int64_t dispImm = displacement >> 2;
@@ -5281,7 +5281,7 @@ Error Assembler::_emit(InstId instId, const Operand_& o0, const Operand_& o1, co
52815281
}
52825282

52835283
// --------------------------------------------------------------------------
5284-
// [EmitOp - b/bl Relaxation via Address Table]
5284+
// [EmitOp - b/bl Relaxation via Branch Stub]
52855285
// --------------------------------------------------------------------------
52865286

52875287
EmitOp_BranchReloc:
@@ -5295,21 +5295,21 @@ Error Assembler::_emit(InstId instId, const Operand_& o0, const Operand_& o1, co
52955295
if (err)
52965296
goto Failed;
52975297

5298+
size_t codeOffset = writer.offsetFrom(_bufferData);
52985299
uint64_t targetOffset = rmRel->as<Imm>().valueAs<uint64_t>();
5299-
err = _code->addAddressToAddressTable(targetOffset);
5300+
err = _code->addAddressToA64BranchStubTable(targetOffset);
53005301
if (err)
53015302
goto Failed;
53025303

5303-
size_t codeOffset = writer.offsetFrom(_bufferData);
53045304
re->_sourceSectionId = _section->id();
53055305
re->_sourceOffset = codeOffset;
53065306
re->_format = offsetFormat;
53075307
re->_payload = targetOffset;
53085308

5309-
// Emit two 32-bit words (placeholder: nop; nop). These will be patched
5310-
// during relocation to either `b/bl target; nop` or `ldr x16, [pc+off]; br/blr x16`.
5311-
writer.emit32uLE(0xD503201F); // NOP
5312-
writer.emit32uLE(0xD503201F); // NOP
5309+
// Emit a 32-bit branch placeholder. During relocation this is patched to
5310+
// either `b/bl target` or `b/bl stub`, where the stub loads the full target
5311+
// address and branches to it.
5312+
writer.emit32uLE(opcode.get());
53135313
goto EmitDone;
53145314
}
53155315

cinderx/ThirdParty/asmjit/src/asmjit/arm/a64builder.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,6 @@ static uint32_t estimateNodeSize(BaseNode* node, uint32_t currentOffset) noexcep
9696
return 8;
9797
if (realId == Inst::kIdLdr && op.isMem() && op.as<Mem>().hasBaseLabel())
9898
return 8;
99-
if ((realId == Inst::kIdB || realId == Inst::kIdBl) && op.isImm())
100-
return 8;
10199
if (realId == Inst::kIdMov && op.isImm() && op.as<Imm>().valueAs<uint64_t>() > 0xFFFF)
102100
return 16;
103101
}
@@ -149,9 +147,10 @@ Error Builder::relaxBranches() {
149147
ZoneVector<uint32_t> labelSections;
150148
ASMJIT_PROPAGATE(labelSections.resize(&_allocator, labelCount));
151149

152-
for (;;) {
150+
auto updateNodePositions = [&]() noexcept {
153151
uint32_t sectionId = 0;
154152
uint32_t offset = 0;
153+
155154
memset(labelSections.data(), 0, labelCount * sizeof(uint32_t));
156155

157156
for (BaseNode* node = firstNode(); node; node = node->next()) {
@@ -171,9 +170,13 @@ Error Builder::relaxBranches() {
171170

172171
offset += size;
173172
}
173+
};
174+
175+
for (;;) {
176+
updateNodePositions();
174177

175178
bool changed = false;
176-
sectionId = 0;
179+
uint32_t sectionId = 0;
177180

178181
for (BaseNode* node = firstNode(); node; node = node->next()) {
179182
if (node->isSection()) {

0 commit comments

Comments
 (0)