Skip to content

Commit 182114e

Browse files
DinoVmeta-codesync[bot]
authored andcommitted
Add a LoadPair LIR instruction
Summary: Adds `kLoadPair`, the load-side counterpart to the existing `kStorePair`, so adjacent 64-bit loads can be emitted as a single `ldp`. It takes the same (offset, base) shape as `kStorePair`, with the first destination as the output and the second as an extra input. On x86 it lowers to two `mov`s. The first user is `PopulateEntryBlock`, which loads the vectorcall arguments from consecutive slots of the argument array on every JIT function entry: ``` mov x10, x1 mov x10, x1 ldr x1, [x10] ldp x1, x2, [x10] ldr x2, [x10, #8] -> ldp x3, x4, [x10, #0x10] ldr x3, [x10, #0x10] ldp x5, x6, [x10, #0x20] ldr x4, [x10, #0x18] ldr x5, [x10, #0x20] ldr x6, [x10, #0x28] ``` One important caveat about this - we have no support for instructions w/ multiple outputs. So this instruction is only available after register allocation where we've previously allocated the output register as part of a load instruction. The current usage runs after register allocation and the next diff adds a post-alloc pass as well. Pairing is limited to aarch64 so x86 output is unchanged. Reviewed By: alexmalyshev Differential Revision: D114969843 fbshipit-source-id: 6f77f125a67b19201aac2d52dbcace9fc8bc3837
1 parent 4a4f09c commit 182114e

6 files changed

Lines changed: 104 additions & 9 deletions

File tree

cinderx/Jit/codegen/autogen.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,35 @@ void translateStorePair(Environ* env, const Instruction* instr) {
15271527
#endif
15281528
}
15291529

1530+
void translateLoadPair(Environ* env, const Instruction* instr) {
1531+
arch::Builder* as = env->as;
1532+
JIT_DCHECK(
1533+
instr->getNumInputs() == 3,
1534+
"LoadPair expects exactly 3 inputs (offset, base, dst1)");
1535+
int32_t offset = static_cast<int32_t>(instr->getInput(0)->getConstant());
1536+
auto base_reg = instr->getInput(1)->getPhyRegister();
1537+
auto dst0_loc = instr->output()->getPhyRegister();
1538+
auto dst1_loc = instr->getInput(2)->getPhyRegister();
1539+
1540+
#if defined(CINDER_X86_64)
1541+
auto base = x86::gpq(base_reg.loc);
1542+
as->mov(x86::gpq(dst0_loc.loc), x86::qword_ptr(base, offset));
1543+
as->mov(x86::gpq(dst1_loc.loc), x86::qword_ptr(base, offset + kPointerSize));
1544+
#elif defined(CINDER_AARCH64)
1545+
auto dst0 = a64::x(dst0_loc.loc);
1546+
auto dst1 = a64::x(dst1_loc.loc);
1547+
1548+
if (auto ptr = getPairPtr(env, base_reg, offset)) {
1549+
as->ldp(dst0, dst1, *ptr);
1550+
} else {
1551+
as->ldr(dst0, getPairElementPtr(env, base_reg, offset));
1552+
as->ldr(dst1, getPairElementPtr(env, base_reg, offset + kPointerSize));
1553+
}
1554+
#else
1555+
CINDER_UNSUPPORTED
1556+
#endif
1557+
}
1558+
15301559
// Tear down the frame. On x86, this executes 'leave' (mov rsp, rbp; pop rbp).
15311560
// On aarch64, this restores sp from fp and pops the frame record (fp + lr).
15321561
//
@@ -3225,6 +3254,9 @@ void AutoTranslator::translateInstr(Environ* env, const Instruction* instr)
32253254
case Instruction::kStorePair:
32263255
translateStorePair(env, instr);
32273256
return;
3257+
case Instruction::kLoadPair:
3258+
translateLoadPair(env, instr);
3259+
return;
32283260
case Instruction::kLeave:
32293261
translateLeave(env);
32303262
return;
@@ -3503,6 +3535,9 @@ void AutoTranslator::translateInstr(Environ* env, const Instruction* instr)
35033535
case Instruction::kStorePair:
35043536
translateStorePair(env, instr);
35053537
return;
3538+
case Instruction::kLoadPair:
3539+
translateLoadPair(env, instr);
3540+
return;
35063541
case Instruction::kLeave:
35073542
translateLeave(env);
35083543
return;

cinderx/Jit/lir/generator.cpp

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -586,28 +586,79 @@ void PopulateEntryBlock(
586586
auto kPointerSize = static_cast<int32_t>(sizeof(void*));
587587

588588
bool has_extra_args = false;
589+
590+
// Consecutive general-purpose arguments live in adjacent slots, so they can
591+
// be loaded a pair at a time. This block is built after the post-allocation
592+
// pairing pass has run, so the pairs are formed here instead. A double
593+
// argument, a gap left by an overflow argument, or a destination that would
594+
// clobber the array pointer ends the run.
595+
#if defined(CINDER_AARCH64)
596+
constexpr bool kCanPairArgLoads = true;
597+
#else
598+
constexpr bool kCanPairArgLoads = false;
599+
#endif
600+
601+
std::optional<std::pair<int32_t, PhyLocation>> pending;
602+
auto flushPending = [&]() {
603+
if (!pending.has_value()) {
604+
return;
605+
}
606+
entry_block->allocateInstr(
607+
Instruction::kMove,
608+
nullptr,
609+
OutPhyReg(pending->second),
610+
Ind(args_reg, pending->first));
611+
pending.reset();
612+
};
613+
589614
for (size_t i = 0; i < arg_locations.size(); i++) {
590615
PhyLocation arg = arg_locations[i];
591616
if (arg == PhyLocation::REG_INVALID) {
592617
has_extra_args = true;
618+
flushPending();
593619
continue;
594620
}
595-
if (arg.isGpRegister()) {
621+
622+
auto offset = static_cast<int32_t>(i * kPointerSize);
623+
624+
if (!arg.isGpRegister()) {
625+
flushPending();
596626
entry_block->allocateInstr(
597627
Instruction::kMove,
598628
nullptr,
599-
OutPhyReg(arg),
600-
Ind(args_reg, static_cast<int32_t>(i * kPointerSize)));
601-
} else {
629+
OutPhyReg(arg, Operand::kDouble),
630+
Ind(args_reg, offset, Operand::kDouble));
631+
continue;
632+
}
633+
634+
// ldp with a destination that is also its base is unpredictable, and any
635+
// following load would be reading through a clobbered pointer anyway.
636+
if (arg == args_reg) {
637+
flushPending();
602638
entry_block->allocateInstr(
603-
Instruction::kMove,
639+
Instruction::kMove, nullptr, OutPhyReg(arg), Ind(args_reg, offset));
640+
continue;
641+
}
642+
643+
if (kCanPairArgLoads && pending.has_value() && pending->second != arg) {
644+
JIT_DCHECK(
645+
offset == pending->first + kPointerSize,
646+
"pending arg load should be for the preceding slot");
647+
entry_block->allocateInstr(
648+
Instruction::kLoadPair,
604649
nullptr,
605-
OutPhyReg(arg, Operand::kDouble),
606-
Ind(args_reg,
607-
static_cast<int32_t>(i * kPointerSize),
608-
Operand::kDouble));
650+
OutPhyReg(pending->second),
651+
Imm{static_cast<uint64_t>(pending->first)},
652+
PhyReg{args_reg},
653+
PhyReg{arg});
654+
pending.reset();
655+
continue;
609656
}
657+
658+
flushPending();
659+
pending = std::make_pair(offset, arg);
610660
}
661+
flushPending();
611662
if (has_extra_args) {
612663
// Point extra_args register past the register-bound args to the
613664
// start of the overflow args in the vectorcall array.

cinderx/Jit/lir/instruction.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ enum OperandSizeType {
153153
X(SetupFrame, false, FlagEffects::kInvalidate, kDefault, 0, {}, 1) \
154154
X(VariadicPush, false, FlagEffects::kNone, kDefault, 0, {}, 1) \
155155
X(StorePair, false, FlagEffects::kNone, kDefault, 0, {0, 1, 1, 1}, 1) \
156+
X(LoadPair, false, FlagEffects::kNone, kDefault, 1, {0, 1, 1}, 1) \
156157
X(Leave, false, FlagEffects::kInvalidate, kDefault, 0, {}, 1) \
157158
X(Ret, false, FlagEffects::kInvalidate, kDefault, 0, {}, 1) \
158159
X(CmpBranchZero, false, FlagEffects::kNone, kDefault, 0, {1}, 1) \

cinderx/Jit/lir/linear_scan.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,9 @@ void LinearScanAllocator::calculateLiveIntervals() {
469469
continue;
470470
}
471471

472+
JIT_DCHECK(
473+
instr_opcode != Instruction::kLoadPair,
474+
"load pair can only be generated after register allocation");
472475
// output
473476
auto output_opnd = instr->output();
474477
if (output_opnd->isVreg()) {

cinderx/Jit/lir/postalloc.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,7 @@ RewriteResult rewriteMemoryInputsToReg(instr_iter_t instr_iter) {
11511151
case Instruction::kReserveStack:
11521152
case Instruction::kVariadicPush:
11531153
case Instruction::kStorePair:
1154+
case Instruction::kLoadPair:
11541155
case Instruction::kLeave:
11551156
case Instruction::kRet:
11561157
case Instruction::kCmpBranchZero:

cinderx/Jit/lir/spill_alloc.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ void SpillAllocator::rewriteInstr(BasicBlock* block, instr_iter_t iter) {
174174
return;
175175
}
176176

177+
JIT_DCHECK(
178+
!instr->isLoadPair(),
179+
"load pair can only be generated after register allocation");
180+
177181
// Generator frame migration: a Move into the frame-pointer register swaps the
178182
// frame pointer between the machine stack and the heap-allocated generator
179183
// data. Handle values that must cross the switch, then fall through so the

0 commit comments

Comments
 (0)