From fd5d1e77ec399dfaeb8153b7d3b687ed4baa9877 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 27 Feb 2025 10:44:21 +0000 Subject: [PATCH 1/9] Use case body location for POP_TOP after case test, to give better locations for BRANCH events. --- Lib/test/test_monitoring.py | 31 +++++++++++++++++++++++++++++++ Python/codegen.c | 4 +++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index 305ba6fdeed96b..472b8f28ec43bd 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -1681,6 +1681,37 @@ async def foo(): ('branch left', 'func', 12, 12)]) + def test_async_for(self): + + def func(v=1): + x = 0 + for v in range(4): + match v: + case 1: + x += 1 + case 2: + x += 2 + case 3: + x += 3 + return x + + self.check_events(func, recorders = BRANCHES_RECORDERS, expected = [ + ('branch left', 'func', 2, 2), + ('branch right', 'func', 4, 6), + ('branch right', 'func', 6, 8), + ('branch left', 'func', 8, 8), + ('branch left', 'func', 2, 2), + ('branch left', 'func', 4, 5), + ('branch left', 'func', 2, 2), + ('branch right', 'func', 4, 6), + ('branch left', 'func', 6, 7), + ('branch left', 'func', 2, 2), + ('branch right', 'func', 4, 6), + ('branch right', 'func', 6, 8), + ('branch right', 'func', 8, 9), + ('branch right', 'func', 2, 10)]) + + class TestBranchConsistency(MonitoringTestBase, unittest.TestCase): def check_branches(self, func, tool=TEST_TOOL, recorders=BRANCH_OFFSET_RECORDERS): diff --git a/Python/codegen.c b/Python/codegen.c index ecad8c22bdf51e..1eed4122aea4c1 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -6121,7 +6121,9 @@ codegen_match_inner(compiler *c, stmt_ty s, pattern_context *pc) } // Success! Pop the subject off, we're done with it: if (i != cases - has_default - 1) { - ADDOP(c, LOC(m->pattern), POP_TOP); + /* Use the body location to give better locations for branch events */ + assert(asdl_seq_LEN(m->body) > 0); + ADDOP(c, LOC(asdl_seq_GET(m->body, 0)), POP_TOP); } VISIT_SEQ(c, stmt, m->body); ADDOP_JUMP(c, NO_LOCATION, JUMP, end); From 1ce7f7f2e4a1295ddc050417f4ca8935f33a976f Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 27 Feb 2025 10:47:16 +0000 Subject: [PATCH 2/9] Add news --- .../2025-02-27-10-47-09.gh-issue-123044.8182Un.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-02-27-10-47-09.gh-issue-123044.8182Un.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-27-10-47-09.gh-issue-123044.8182Un.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-27-10-47-09.gh-issue-123044.8182Un.rst new file mode 100644 index 00000000000000..d5515ba07f504d --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-27-10-47-09.gh-issue-123044.8182Un.rst @@ -0,0 +1,2 @@ +Make sure that the location of branch targets in `match` cases is in the +body, not the pattern. From f98fe7a99d9f922a442098ecf70689ac42a39623 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 27 Feb 2025 10:57:12 +0000 Subject: [PATCH 3/9] Include a default case in test --- Lib/test/test_monitoring.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index 472b8f28ec43bd..55b665c20e3fab 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -1691,7 +1691,7 @@ def func(v=1): x += 1 case 2: x += 2 - case 3: + case _: x += 3 return x @@ -1699,7 +1699,6 @@ def func(v=1): ('branch left', 'func', 2, 2), ('branch right', 'func', 4, 6), ('branch right', 'func', 6, 8), - ('branch left', 'func', 8, 8), ('branch left', 'func', 2, 2), ('branch left', 'func', 4, 5), ('branch left', 'func', 2, 2), @@ -1708,7 +1707,6 @@ def func(v=1): ('branch left', 'func', 2, 2), ('branch right', 'func', 4, 6), ('branch right', 'func', 6, 8), - ('branch right', 'func', 8, 9), ('branch right', 'func', 2, 10)]) From 7084af0e099a49137aadc710adead63f2548feb2 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 27 Feb 2025 11:02:15 +0000 Subject: [PATCH 4/9] Fix copy and paste --- Lib/test/test_monitoring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index 55b665c20e3fab..7c729ebbc4182d 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -1681,7 +1681,7 @@ async def foo(): ('branch left', 'func', 12, 12)]) - def test_async_for(self): + def test_match(self): def func(v=1): x = 0 From 9332b1991278ff670d7128539dd4f9976b97cd03 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 27 Feb 2025 11:04:35 +0000 Subject: [PATCH 5/9] Fix formatting in NEWS file for match cases --- .../2025-02-27-10-47-09.gh-issue-123044.8182Un.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-27-10-47-09.gh-issue-123044.8182Un.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-27-10-47-09.gh-issue-123044.8182Un.rst index d5515ba07f504d..75ad311d7cd7e4 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-27-10-47-09.gh-issue-123044.8182Un.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-27-10-47-09.gh-issue-123044.8182Un.rst @@ -1,2 +1,2 @@ -Make sure that the location of branch targets in `match` cases is in the +Make sure that the location of branch targets in ``match`` cases is in the body, not the pattern. From 0d82f1b0f0ce97219821b192272b5407b7dc8c95 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 28 Feb 2025 12:55:20 +0000 Subject: [PATCH 6/9] Use pseudo location to mark instruction as having the location of the next instruction --- Include/internal/pycore_symtable.h | 1 + Python/assemble.c | 9 +++++++++ Python/codegen.c | 5 ++--- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Include/internal/pycore_symtable.h b/Include/internal/pycore_symtable.h index 3b87a7f869c4d7..deccfd98d7f021 100644 --- a/Include/internal/pycore_symtable.h +++ b/Include/internal/pycore_symtable.h @@ -58,6 +58,7 @@ typedef struct { .end_col_offset = (n)->end_col_offset } static const _Py_SourceLocation NO_LOCATION = {-1, -1, -1, -1}; +static const _Py_SourceLocation NEXT_LOCATION = {INT_MAX, INT_MAX, INT_MAX, INT_MAX}; /* __future__ information */ typedef struct { diff --git a/Python/assemble.c b/Python/assemble.c index f7b88b519f5f71..839dd2bb5efefd 100644 --- a/Python/assemble.c +++ b/Python/assemble.c @@ -343,6 +343,15 @@ assemble_location_info(struct assembler *a, instr_sequence *instrs, a->a_lineno = firstlineno; location loc = NO_LOCATION; int size = 0; + if (same_location(instrs->s_instrs[instrs->s_used-1].i_loc, NEXT_LOCATION)) { + instrs->s_instrs[instrs->s_used-1].i_loc = NO_LOCATION; + } + for (int i = instrs->s_used-1; i > 0; i--) { + instruction *instr = &instrs->s_instrs[i]; + if (same_location(instr[-1].i_loc, NEXT_LOCATION)) { + instr[-1].i_loc = instr->i_loc; + } + } for (int i = 0; i < instrs->s_used; i++) { instruction *instr = &instrs->s_instrs[i]; if (!same_location(loc, instr->i_loc)) { diff --git a/Python/codegen.c b/Python/codegen.c index 1eed4122aea4c1..792e2000f3d967 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -6121,9 +6121,8 @@ codegen_match_inner(compiler *c, stmt_ty s, pattern_context *pc) } // Success! Pop the subject off, we're done with it: if (i != cases - has_default - 1) { - /* Use the body location to give better locations for branch events */ - assert(asdl_seq_LEN(m->body) > 0); - ADDOP(c, LOC(asdl_seq_GET(m->body, 0)), POP_TOP); + /* Use the next location to give better locations for branch events */ + ADDOP(c, NEXT_LOCATION, POP_TOP); } VISIT_SEQ(c, stmt, m->body); ADDOP_JUMP(c, NO_LOCATION, JUMP, end); From ab6fa4c680dd6af73ad9601f9c3f837d626ed4a2 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 6 Mar 2025 10:00:08 +0000 Subject: [PATCH 7/9] Use -2s for NEXT_LOCATION. Modify basicblock_remove_redundant_nops and propagate_line_numbers accordingly. --- Include/internal/pycore_symtable.h | 2 +- Python/flowgraph.c | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Include/internal/pycore_symtable.h b/Include/internal/pycore_symtable.h index deccfd98d7f021..fd6b9bcbe5abd8 100644 --- a/Include/internal/pycore_symtable.h +++ b/Include/internal/pycore_symtable.h @@ -58,7 +58,7 @@ typedef struct { .end_col_offset = (n)->end_col_offset } static const _Py_SourceLocation NO_LOCATION = {-1, -1, -1, -1}; -static const _Py_SourceLocation NEXT_LOCATION = {INT_MAX, INT_MAX, INT_MAX, INT_MAX}; +static const _Py_SourceLocation NEXT_LOCATION = {-2, -2, -2, -2}; /* __future__ information */ typedef struct { diff --git a/Python/flowgraph.c b/Python/flowgraph.c index c5bdf105545459..c71036a109097f 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -1079,8 +1079,8 @@ basicblock_remove_redundant_nops(basicblock *bb) { location next_loc = NO_LOCATION; for (int next_i=0; next_i < next->b_iused; next_i++) { cfg_instr *instr = &next->b_instr[next_i]; - if (instr->i_opcode == NOP && instr->i_loc.lineno == NO_LOCATION.lineno) { - /* Skip over NOPs without location, they will be removed */ + if (instr->i_opcode == NOP && instr->i_loc.lineno < 0) { + /* Skip over NOPs without a location, they will be removed */ continue; } next_loc = instr->i_loc; @@ -2977,7 +2977,7 @@ propagate_line_numbers(basicblock *entryblock) { location prev_location = NO_LOCATION; for (int i = 0; i < b->b_iused; i++) { - if (b->b_instr[i].i_loc.lineno < 0) { + if (b->b_instr[i].i_loc.lineno == NO_LOCATION.lineno) { b->b_instr[i].i_loc = prev_location; } else { @@ -2986,7 +2986,7 @@ propagate_line_numbers(basicblock *entryblock) { } if (BB_HAS_FALLTHROUGH(b) && b->b_next->b_predecessors == 1) { if (b->b_next->b_iused > 0) { - if (b->b_next->b_instr[0].i_loc.lineno < 0) { + if (b->b_next->b_instr[0].i_loc.lineno == NO_LOCATION.lineno) { b->b_next->b_instr[0].i_loc = prev_location; } } @@ -2994,7 +2994,7 @@ propagate_line_numbers(basicblock *entryblock) { if (is_jump(last)) { basicblock *target = last->i_target; if (target->b_predecessors == 1) { - if (target->b_instr[0].i_loc.lineno < 0) { + if (target->b_instr[0].i_loc.lineno == NO_LOCATION.lineno) { target->b_instr[0].i_loc = prev_location; } } From 1229037783979e8b5bb2de22e2bfbbcb42ddfe3a Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 6 Mar 2025 10:12:12 +0000 Subject: [PATCH 8/9] Add a couple of asserts for NEXT_LOCATION --- Python/assemble.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Python/assemble.c b/Python/assemble.c index 839dd2bb5efefd..0877e35c0f1710 100644 --- a/Python/assemble.c +++ b/Python/assemble.c @@ -343,12 +343,15 @@ assemble_location_info(struct assembler *a, instr_sequence *instrs, a->a_lineno = firstlineno; location loc = NO_LOCATION; int size = 0; + // The last location should not be NEXT_LOCATION, but don't crash non-debug builds if (same_location(instrs->s_instrs[instrs->s_used-1].i_loc, NEXT_LOCATION)) { + assert(0 && "last instruction has NEXT_LOCATION"); instrs->s_instrs[instrs->s_used-1].i_loc = NO_LOCATION; } for (int i = instrs->s_used-1; i > 0; i--) { instruction *instr = &instrs->s_instrs[i]; if (same_location(instr[-1].i_loc, NEXT_LOCATION)) { + assert(!IS_TERMINATOR_OPCODE(instr[-1].i_opcode)); instr[-1].i_loc = instr->i_loc; } } From a077433faaec6f20343c38ccc006d493f9f04b29 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 7 Mar 2025 13:56:45 +0000 Subject: [PATCH 9/9] Replace NEXT_LOCATION with NO_LOCATION if there is no meaningful location to use. Assert NEXT_LOCATION never gets emitted --- Python/assemble.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Python/assemble.c b/Python/assemble.c index 0877e35c0f1710..71de63534fb592 100644 --- a/Python/assemble.c +++ b/Python/assemble.c @@ -292,6 +292,7 @@ write_location_info_entry(struct assembler* a, location loc, int isize) RETURN_IF_ERROR(_PyBytes_Resize(&a->a_linetable, len*2)); } if (loc.lineno < 0) { + assert(loc.lineno == NO_LOCATION.lineno); write_location_info_none(a, isize); return SUCCESS; } @@ -342,19 +343,19 @@ assemble_location_info(struct assembler *a, instr_sequence *instrs, { a->a_lineno = firstlineno; location loc = NO_LOCATION; - int size = 0; - // The last location should not be NEXT_LOCATION, but don't crash non-debug builds - if (same_location(instrs->s_instrs[instrs->s_used-1].i_loc, NEXT_LOCATION)) { - assert(0 && "last instruction has NEXT_LOCATION"); - instrs->s_instrs[instrs->s_used-1].i_loc = NO_LOCATION; - } - for (int i = instrs->s_used-1; i > 0; i--) { + for (int i = instrs->s_used-1; i >= 0; i--) { instruction *instr = &instrs->s_instrs[i]; - if (same_location(instr[-1].i_loc, NEXT_LOCATION)) { - assert(!IS_TERMINATOR_OPCODE(instr[-1].i_opcode)); - instr[-1].i_loc = instr->i_loc; + if (same_location(instr->i_loc, NEXT_LOCATION)) { + if (IS_TERMINATOR_OPCODE(instr->i_opcode)) { + instr->i_loc = NO_LOCATION; + } + else { + assert(i < instrs->s_used-1); + instr->i_loc = instr[1].i_loc; + } } } + int size = 0; for (int i = 0; i < instrs->s_used; i++) { instruction *instr = &instrs->s_instrs[i]; if (!same_location(loc, instr->i_loc)) {