@@ -2496,11 +2496,11 @@ ref_stack_fini(ref_stack *stack)
24962496
24972497typedef 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
25062506static 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
25192519store_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+ */
25372574static int
25382575optimize_load_fast (cfg_builder * g )
25392576{
0 commit comments