Skip to content

Commit 417ff45

Browse files
mpagemeta-codesync[bot]
authored andcommitted
Remove the TempAllocator from HIRBuilder
Summary: We must have the same stack layout (which registers are in which stack slots) at control flow merge points to correctly translate between CPython's stack-based bytecode and our register-based IR. Preserving this invariant was previously split across two classes: `TempAllocator` and `BlockCanonicalizer`. `TempAllocator` held the canonical stack layout in its `_cache` member and provided methods (`AllocateStack` and `GetOrAllocateStack`) for manipulating the layout. `BlockCanonicalizer` inserted register moves at block exit to put the stack into canonical form. It's unsafe for a register that occupies a canonical stack slot to alias a local or a cell: a register move might inadvertently overwrite the the aliased local/cell. `TempAllocator` provided `AllocateNonStack` as a means of allocating registers that would never be used in stack slots. However, this is a foot-gun: it forces callers to know when to choose between `AllocateStack` and `AllocateNonStack`. This diff eliminates the foot-gun while preserving the function-wide canonical stack layout: - The layout now lives on its consumer, `BlockCanonicalizer`. These registers are fully disjoint from those allocated by the rest of `HIRBuilder`, making them safe to overwrite. - `BlockCanonicalizer` is now a member of `HIRBuilder` and is reused across all blocks, preserving the per function canonical layout. - `TempAllocator` is removed, elminating the foot-gun. Reviewed By: alexmalyshev Differential Revision: D109868165 fbshipit-source-id: a35ac66b7f277f8e8a07827b69dd521f4fd628c4
1 parent 6f82247 commit 417ff45

13 files changed

Lines changed: 2138 additions & 2161 deletions

cinderx/Jit/hir/builder.cpp

Lines changed: 201 additions & 219 deletions
Large diffs are not rendered by default.

cinderx/Jit/hir/builder.h

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "cinderx/Jit/hir/preload.h"
1212

1313
#include <memory>
14+
#include <optional>
1415
#include <unordered_map>
1516
#include <unordered_set>
1617
#include <vector>
@@ -22,27 +23,6 @@ class Environment;
2223
class Function;
2324
class Register;
2425

25-
// Helper class for managing temporary variables
26-
class TempAllocator {
27-
public:
28-
explicit TempAllocator(Environment* env) : env_(env) {}
29-
30-
// Allocate a temp register that may be used for the stack. It should not be a
31-
// register that will be treated specially in the FrameState (e.g. tracked as
32-
// containing a local or cell.)
33-
Register* AllocateStack();
34-
35-
// Get the i-th stack temporary or allocate one
36-
Register* GetOrAllocateStack(std::size_t idx);
37-
38-
// Allocate a temp register that will not be used for a stack value.
39-
Register* AllocateNonStack();
40-
41-
private:
42-
Environment* env_;
43-
std::vector<Register*> cache_;
44-
};
45-
4626
// We expect that on exit from a basic block the stack only contains temporaries
4727
// in increasing order (called the canonical form). For example,
4828
//
@@ -53,21 +33,33 @@ class TempAllocator {
5333
// It may be the case that temporaries are re-ordered, duplicated, or the stack
5434
// contains locals. This class is responsible for inserting the necessary
5535
// register moves such that the stack is in canonical form.
36+
//
37+
// It owns the per-function bank of canonical stack registers (one per
38+
// operand-stack depth). A single instance is reused for the whole function so
39+
// that stack depth i always lands in the same register at every block exit.
5640
class BlockCanonicalizer {
5741
public:
58-
BlockCanonicalizer() : processing_(), done_(), copies_(), moved_() {}
42+
explicit BlockCanonicalizer(Environment* env) : env_(env) {}
43+
44+
void Run(BasicBlock* block, OperandStack& stack);
5945

60-
void Run(BasicBlock* block, TempAllocator& temps, OperandStack& stack);
46+
// Get the register reserved for operand-stack depth idx, allocating it if it
47+
// does not yet exist. These canonical stack registers are used only to put
48+
// the operand stack in canonical form at block boundaries; they are never
49+
// handed out as general temporaries and so stay disjoint from locals/cells
50+
// and intermediates.
51+
Register* getOrAllocateCanonicalStack(std::size_t idx);
6152

6253
private:
6354
DISALLOW_COPY_AND_ASSIGN(BlockCanonicalizer);
6455

6556
void InsertCopies(
6657
Register* reg,
67-
TempAllocator& temps,
6858
Instr& terminator,
6959
std::vector<Register*>& alloced);
7060

61+
Environment* env_;
62+
std::vector<Register*> canonical_stack_;
7163
std::unordered_set<Register*> processing_;
7264
std::unordered_set<Register*> done_;
7365
std::unordered_map<Register*, std::vector<Register*>> copies_;
@@ -541,11 +533,19 @@ class HIRBuilder {
541533
// Check that a code object can be compiled into HIR.
542534
void checkTranslate();
543535

536+
// Allocate a fresh temporary register from the function's Environment.
537+
Register* allocateTemp();
538+
544539
BorrowedRef<PyCodeObject> code_;
545540
BlockMap block_map_;
546541
const Preloader& preloader_;
547542

548-
TempAllocator temps_{nullptr};
543+
// The function's register Environment, set in buildHIRImpl.
544+
Environment* env_{nullptr};
545+
546+
// Reused for the whole function so the canonical stack layout is preserved
547+
// across all blocks. Constructed in buildHIRImpl once env_ is known.
548+
std::optional<BlockCanonicalizer> block_canonicalizer_;
549549

550550
// Tracks the function for compilations that require it.
551551
Register* func_{nullptr};

cinderx/RuntimeTests/block_canonicalizer_test.cpp

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ using namespace cinderx::jit::hir;
1010
TEST(BlockCanonicalizerTest, BreaksCycles) {
1111
CFG cfg;
1212
Environment env;
13-
TempAllocator temps(&env);
13+
BlockCanonicalizer bc(&env);
1414
OperandStack stack;
1515

16-
auto t0 = temps.AllocateStack();
17-
auto t1 = temps.AllocateStack();
18-
auto t2 = temps.AllocateStack();
16+
auto t0 = bc.getOrAllocateCanonicalStack(0);
17+
auto t1 = bc.getOrAllocateCanonicalStack(1);
18+
auto t2 = bc.getOrAllocateCanonicalStack(2);
1919

2020
stack.push(t1);
2121
stack.push(t2);
@@ -24,8 +24,7 @@ TEST(BlockCanonicalizerTest, BreaksCycles) {
2424
auto block = cfg.AllocateBlock();
2525
block->append<Return>(env.AllocateRegister());
2626

27-
BlockCanonicalizer bc;
28-
bc.Run(block, temps, stack);
27+
bc.Run(block, stack);
2928

3029
HIRPrinter printer;
3130
const char* expected = R"(bb 0 {
@@ -42,12 +41,12 @@ TEST(BlockCanonicalizerTest, BreaksCycles) {
4241
TEST(BlockCanonicalizerTest, HandlesMultipleOccurrencesOfSingleReg) {
4342
CFG cfg;
4443
Environment env;
45-
TempAllocator temps(&env);
44+
BlockCanonicalizer bc(&env);
4645
OperandStack stack;
4746

48-
auto t0 = temps.AllocateStack();
49-
auto t1 = temps.AllocateStack();
50-
auto t2 = temps.AllocateStack();
47+
auto t0 = bc.getOrAllocateCanonicalStack(0);
48+
auto t1 = bc.getOrAllocateCanonicalStack(1);
49+
auto t2 = bc.getOrAllocateCanonicalStack(2);
5150

5251
stack.push(t1);
5352
stack.push(t2);
@@ -59,8 +58,7 @@ TEST(BlockCanonicalizerTest, HandlesMultipleOccurrencesOfSingleReg) {
5958
auto block = cfg.AllocateBlock();
6059
block->append<Return>(env.AllocateRegister());
6160

62-
BlockCanonicalizer bc;
63-
bc.Run(block, temps, stack);
61+
bc.Run(block, stack);
6462

6563
HIRPrinter printer;
6664
const char* expected = R"(bb 0 {
@@ -80,11 +78,11 @@ TEST(BlockCanonicalizerTest, HandlesMultipleOccurrencesOfSingleReg) {
8078
TEST(BlockCanonicalizerTest, HandlesMixOfLocalsAndTemporaries) {
8179
CFG cfg;
8280
Environment env;
83-
TempAllocator temps(&env);
81+
BlockCanonicalizer bc(&env);
8482
OperandStack stack;
8583

86-
auto t0 = temps.AllocateStack();
87-
auto t1 = temps.AllocateStack();
84+
auto t0 = bc.getOrAllocateCanonicalStack(0);
85+
auto t1 = bc.getOrAllocateCanonicalStack(1);
8886

8987
auto x = env.AllocateRegister();
9088
auto y = env.AllocateRegister();
@@ -98,8 +96,7 @@ TEST(BlockCanonicalizerTest, HandlesMixOfLocalsAndTemporaries) {
9896
auto block = cfg.AllocateBlock();
9997
block->append<Return>(env.AllocateRegister());
10098

101-
BlockCanonicalizer bc;
102-
bc.Run(block, temps, stack);
99+
bc.Run(block, stack);
103100

104101
HIRPrinter printer;
105102
const char* expected = R"(bb 0 {

0 commit comments

Comments
 (0)