From b78eb3924179a5d4eb1b184596a809459beb8afc Mon Sep 17 00:00:00 2001 From: Matt Page Date: Sun, 4 May 2025 09:15:07 -0700 Subject: [PATCH 1/3] Don't optimize `LOAD_FAST` instructions whose local is killed by `DEL_FAST` In certain cases it's possible for locals loaded by `LOAD_FAST` instructions to be on the stack when the local is killed by `DEL_FAST`. These `LOAD_FAST` instructions should not be optimized into `LOAD_FAST_BORROW` as the strong reference in the frame is killed while there is still a reference on the stack. --- Lib/test/test_peepholer.py | 7 +++++++ Python/flowgraph.c | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 565e42b04a68d0..7f7998f5a1f8d5 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -2472,6 +2472,13 @@ def test_unoptimized_if_support_killed(self): ] self.check(insts, insts) + insts = [ + ("LOAD_FAST", 0, 1), + ("DELETE_FAST", 0, 2), + ("POP_TOP", None, 3), + ] + self.check(insts, insts) + def test_unoptimized_if_aliased(self): insts = [ ("LOAD_FAST", 0, 1), diff --git a/Python/flowgraph.c b/Python/flowgraph.c index e0bb5615db3e0b..78ef02a911a72b 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -2795,6 +2795,11 @@ optimize_load_fast(cfg_builder *g) assert(opcode != EXTENDED_ARG); switch (opcode) { // Opcodes that load and store locals + case DELETE_FAST: { + kill_local(instr_flags, &refs, oparg); + break; + } + case LOAD_FAST: { PUSH_REF(i, oparg); break; From e1182c74aa5f7aa4df2e2d144922bfd36f633455 Mon Sep 17 00:00:00 2001 From: Matt Page Date: Sun, 4 May 2025 18:03:13 -0700 Subject: [PATCH 2/3] Bump the magic number --- Include/internal/pycore_magic_number.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Include/internal/pycore_magic_number.h b/Include/internal/pycore_magic_number.h index a96cb6236f78b7..1cc897c5a69469 100644 --- a/Include/internal/pycore_magic_number.h +++ b/Include/internal/pycore_magic_number.h @@ -276,6 +276,7 @@ Known values: Python 3.14a7 3621 (Optimize LOAD_FAST opcodes into LOAD_FAST_BORROW) Python 3.14a7 3622 (Store annotations in different class dict keys) Python 3.14a7 3623 (Add BUILD_INTERPOLATION & BUILD_TEMPLATE opcodes) + Python 3.14b1 3624 (Don't optimize LOAD_FAST when local is killed by DELETE_FAST) Python 3.15 will start with 3650 @@ -288,7 +289,7 @@ PC/launcher.c must also be updated. */ -#define PYC_MAGIC_NUMBER 3623 +#define PYC_MAGIC_NUMBER 3624 /* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes (little-endian) and then appending b'\r\n'. */ #define PYC_MAGIC_NUMBER_TOKEN \ From 193c16c3283260bad14bb45d0c275d785e0101df Mon Sep 17 00:00:00 2001 From: Matt Page Date: Sun, 4 May 2025 20:16:48 -0700 Subject: [PATCH 3/3] Add minimal repro of crash as a test --- Lib/test/test_peepholer.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 7f7998f5a1f8d5..47f51f1979faab 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -1,4 +1,5 @@ import dis +import gc from itertools import combinations, product import opcode import sys @@ -2613,6 +2614,22 @@ def test_send(self): ] self.cfg_optimization_test(insts, expected, consts=[None]) + def test_del_in_finally(self): + # This loads `obj` onto the stack, executes `del obj`, then returns the + # `obj` from the stack. See gh-133371 for more details. + def create_obj(): + obj = [42] + try: + return obj + finally: + del obj + + obj = create_obj() + # The crash in the linked issue happens while running GC during + # interpreter finalization, so run it here manually. + gc.collect() + self.assertEqual(obj, [42]) + if __name__ == "__main__":