Skip to content

Commit 76a75a7

Browse files
committed
Add a high level comment explaining our approach
1 parent c332912 commit 76a75a7

File tree

1 file changed

+42
-5
lines changed

1 file changed

+42
-5
lines changed

Python/flowgraph.c

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2496,11 +2496,11 @@ ref_stack_fini(ref_stack *stack)
24962496

24972497
typedef enum {
24982498
// The loaded reference is still on the stack when the local is killed
2499-
LOCAL_KILLED_ON_STACK = 1,
2499+
SUPPORT_KILLED = 1,
25002500
// The loaded reference is stored into a local
2501-
STORED_AS_LOCAL = 2,
2501+
STORED_AS_LOCAL = 2,
25022502
// The loaded reference is still on the stack at the end of the basic block
2503-
REF_UNCONSUMED = 4,
2503+
REF_UNCONSUMED = 4,
25042504
} LoadFastInstrFlag;
25052505

25062506
static void
@@ -2510,7 +2510,7 @@ kill_local(uint8_t *instr_flags, ref_stack *refs, int local)
25102510
ref r = ref_stack_at(refs, i);
25112511
if (r.local == local) {
25122512
assert(r.instr >= 0);
2513-
instr_flags[r.instr] |= LOCAL_KILLED_ON_STACK;
2513+
instr_flags[r.instr] |= SUPPORT_KILLED;
25142514
}
25152515
}
25162516
}
@@ -2519,7 +2519,7 @@ static void
25192519
store_local(uint8_t *instr_flags, ref_stack *refs, int local, ref r)
25202520
{
25212521
kill_local(instr_flags, refs, local);
2522-
if (r.instr != -1) {
2522+
if (r.instr != DUMMY_INSTR) {
25232523
instr_flags[r.instr] |= STORED_AS_LOCAL;
25242524
}
25252525
}
@@ -2534,6 +2534,43 @@ load_fast_push_block(basicblock ***sp, basicblock *target, int start_depth)
25342534
}
25352535
}
25362536

2537+
/*
2538+
* Strength reduce LOAD_FAST{_LOAD_FAST} instructions into weaker variants that
2539+
* load borrowed references onto the operand stack.
2540+
*
2541+
* This is only safe when we can prove that the reference in the frame outlives
2542+
* the borrowed reference produced by the instruction. We make this tractable
2543+
* by enforcing the following lifetimes:
2544+
*
2545+
* 1. Borrowed references loaded onto the operand stack live until the end of
2546+
* the instruction that consumes them from the stack. Any borrowed
2547+
* references that would escape into the heap (e.g. into frame objects or
2548+
* generators) are converted into new, strong references.
2549+
*
2550+
* 2. Locals live until they are either killed by an instruction
2551+
* (e.g. STORE_FAST) or the frame is unwound. Any local that is overwritten
2552+
* via `f_locals` is added to a list owned by the frame object.
2553+
*
2554+
* To simplify the problem of detecting which supporting references in the
2555+
* frame are killed by instructions that overwrite locals, we only allow
2556+
* borrowed references to be stored as a local in the frame if they were passed
2557+
* as an argument. {RETURN,YIELD}_VALUE convert borrowed references into new,
2558+
* strong references.
2559+
*
2560+
* Using the above, we can optimize any LOAD_FAST{_LOAD_FAST} instructions
2561+
* that meet the following criteria:
2562+
*
2563+
* 1. The produced reference must be consumed from the stack before the
2564+
* supporting reference in the frame is killed.
2565+
*
2566+
* 2. The produced reference cannot be stored as a local.
2567+
*
2568+
* We use abstract interpretation to identify instructions that meet these
2569+
* criteria. For each basic block, we simulate the effect the bytecode has on a
2570+
* stack of abstract references and note any instructions that violate the
2571+
* criteria above. Once we've processed all the instructions in a block, any
2572+
* non-violating LOAD_FAST{_LOAD_FAST} can be optimized.
2573+
*/
25372574
static int
25382575
optimize_load_fast(cfg_builder *g)
25392576
{

0 commit comments

Comments
 (0)