Skip to content

Commit edd2e55

Browse files
alexmalyshevmeta-codesync[bot]
authored andcommitted
Add a configurable spill-everything register allocator
Summary: Adds `SpillAllocator`, a deliberately naive register allocator that spills every virtual register to its own stack slot, and makes the register allocator selectable from the command line / environment. It's meant for testing and isolating the rest of the JIT pipeline from the optimizing linear scan allocator. Configuration: - New `-X cinderx-jit-reg-alloc=<linear-scan|spill>` option (and `CINDERX_JIT_REG_ALLOC` env var), parsed in `pyjit.cpp` into a new `RegAllocKind` config enum. The default is `linear-scan`. Any other value raises a `ValueError` during CinderX initialization. - `gen_asm.cpp` selects the allocator through the `RegisterAllocator` interface based on `getConfig().reg_alloc`. SpillAllocator (`cinderx/Jit/lir/spill_alloc.{cpp,h}`, new `:spill-alloc` library): - Gives every vreg a home stack slot it keeps for the whole function, so a value defined in one block and used in another is just re-read from the same slot -- no live intervals, interval splitting, or cross-block location bookkeeping. - Rewrites each instruction to refer only to physical locations, loading operands that must be in registers (per `getInputPhyRegUse` / `getOutputPhyRegUse`) into caller-save scratch registers around the instruction and leaving everything else in its slot. - Lowers phis to copies on the incoming edges (phi-output slots are unique, so no critical-edge splitting is needed) and strips the `kReturn` / `kBranchToYieldExit` pseudo-terminators for PostRegAllocRewrite. - Handles `kBind` (store the bound register into the slot), calls (operands stay in slots; PostRegAllocRewrite applies the calling convention), and read-modify-write `kInc`/`kDec`. - Because nothing is ever live in a register across an instruction boundary, caller-save registers are always free at calls and no callee-saved register is ever clobbered. Generators and coroutines are supported. A generator runs with the frame pointer swapped between the machine stack and a heap-allocated generator frame, so a value produced on one side of a `Move` into the frame-pointer register but read on the other is stale once spilled. `handleFramePointerSwitch` carries those values across the switch in a register: the frame-setup call's result on the forward switch (stack -> heap), and the return-value exit phi on the reverse switch (heap -> stack, including the extra deferred-refcount step on free-threaded builds). Reviewed By: DinoV Differential Revision: D112574130 fbshipit-source-id: 06c4c72bd9ebc3480dcd67533cd13071e190d940
1 parent aeb4fed commit edd2e55

5 files changed

Lines changed: 704 additions & 4 deletions

File tree

cinderx/Jit/codegen/gen_asm.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
#include "cinderx/Jit/lir/postalloc.h"
3434
#include "cinderx/Jit/lir/postgen.h"
3535
#include "cinderx/Jit/lir/printer.h"
36+
#include "cinderx/Jit/lir/regalloc.h"
37+
#include "cinderx/Jit/lir/spill_alloc.h"
3638
#include "cinderx/Jit/lir/target_select.h"
3739
#include "cinderx/Jit/lir/verify.h"
3840
#include "cinderx/Jit/perf_jitdump.h"
@@ -845,15 +847,25 @@ void* NativeGenerator::getVectorcallEntry() {
845847
reserved_stack_space += 16;
846848
#endif
847849

848-
LinearScanAllocator lsalloc(lir_func.get(), reserved_stack_space);
850+
std::unique_ptr<RegisterAllocator> allocator;
851+
switch (getConfig().reg_alloc) {
852+
case RegAllocKind::kLinearScan:
853+
allocator = std::make_unique<LinearScanAllocator>(
854+
lir_func.get(), reserved_stack_space);
855+
break;
856+
case RegAllocKind::kSpill:
857+
allocator = std::make_unique<SpillAllocator>(
858+
lir_func.get(), reserved_stack_space);
859+
break;
860+
}
849861

850862
COMPILE_TIMER(
851863
getFunction()->compilation_phase_timer,
852864
"Register Allocation",
853-
lsalloc.run())
865+
allocator->run())
854866

855-
env_.shadow_frames_and_spill_size = lsalloc.getFrameSize();
856-
env_.changed_regs = lsalloc.getChangedRegs();
867+
env_.shadow_frames_and_spill_size = allocator->getFrameSize();
868+
env_.changed_regs = allocator->getChangedRegs();
857869
env_.exit_label = as_->newLabel();
858870
env_.can_deopt = getFunction()->canDeopt();
859871

cinderx/Jit/config.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,16 @@ enum class AsmSyntax : uint8_t {
115115
Intel,
116116
};
117117

118+
// Which register allocator the JIT should use to lower LIR virtual registers to
119+
// physical locations.
120+
enum class RegAllocKind : uint8_t {
121+
// Optimizing linear scan allocator (the default).
122+
kLinearScan,
123+
// Trivial allocator that spills everything to the stack. Intended for
124+
// testing and isolating the rest of the JIT pipeline.
125+
kSpill,
126+
};
127+
118128
// Collection of configuration values for the JIT.
119129
//
120130
// Note: It's fine to store non-trivially destructible objects like std::string
@@ -221,6 +231,9 @@ struct Config {
221231
// The ASM syntax the JIT should use when disassembling.
222232
AsmSyntax asm_syntax{AsmSyntax::ATT};
223233

234+
// The register allocator the JIT should use.
235+
RegAllocKind reg_alloc{RegAllocKind::kLinearScan};
236+
224237
// List of function name patterns for which to capture compilation times.
225238
std::vector<std::string> capture_compilation_times_for;
226239

0 commit comments

Comments
 (0)