Skip to content

Commit 0aa0fc3

Browse files
authored
gh-117901: Add option for compiler's codegen to save nested instruction sequences for introspection (#118007)
1 parent 692e902 commit 0aa0fc3

File tree

4 files changed

+115
-1
lines changed

4 files changed

+115
-1
lines changed

Include/internal/pycore_instruction_sequence.h

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ _PyJumpTargetLabel _PyInstructionSequence_NewLabel(_PyInstructionSequence *seq);
6161
int _PyInstructionSequence_ApplyLabelMap(_PyInstructionSequence *seq);
6262
int _PyInstructionSequence_InsertInstruction(_PyInstructionSequence *seq, int pos,
6363
int opcode, int oparg, _Py_SourceLocation loc);
64+
int _PyInstructionSequence_AddNested(_PyInstructionSequence *seq, _PyInstructionSequence *nested);
6465
void PyInstructionSequence_Fini(_PyInstructionSequence *seq);
6566

6667
extern PyTypeObject _PyInstructionSequence_Type;

Lib/test/test_compiler_codegen.py

+95-1
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11

2+
import textwrap
23
from test.support.bytecode_helper import CodegenTestCase
34

45
# Tests for the code-generation stage of the compiler.
56
# Examine the un-optimized code generated from the AST.
67

78
class IsolatedCodeGenTests(CodegenTestCase):
89

10+
def assertInstructionsMatch_recursive(self, insts, expected_insts):
11+
expected_nested = [i for i in expected_insts if isinstance(i, list)]
12+
expected_insts = [i for i in expected_insts if not isinstance(i, list)]
13+
self.assertInstructionsMatch(insts, expected_insts)
14+
self.assertEqual(len(insts.get_nested()), len(expected_nested))
15+
for n_insts, n_expected in zip(insts.get_nested(), expected_nested):
16+
self.assertInstructionsMatch_recursive(n_insts, n_expected)
17+
918
def codegen_test(self, snippet, expected_insts):
1019
import ast
1120
a = ast.parse(snippet, "my_file.py", "exec")
1221
insts = self.generate_code(a)
13-
self.assertInstructionsMatch(insts, expected_insts)
22+
self.assertInstructionsMatch_recursive(insts, expected_insts)
1423

1524
def test_if_expression(self):
1625
snippet = "42 if True else 24"
@@ -55,6 +64,91 @@ def test_for_loop(self):
5564
]
5665
self.codegen_test(snippet, expected)
5766

67+
def test_function(self):
68+
snippet = textwrap.dedent("""
69+
def f(x):
70+
return x + 42
71+
""")
72+
expected = [
73+
# Function definition
74+
('RESUME', 0),
75+
('LOAD_CONST', 0),
76+
('MAKE_FUNCTION', None),
77+
('STORE_NAME', 0),
78+
('LOAD_CONST', 1),
79+
('RETURN_VALUE', None),
80+
[
81+
# Function body
82+
('RESUME', 0),
83+
('LOAD_FAST', 0),
84+
('LOAD_CONST', 1),
85+
('BINARY_OP', 0),
86+
('RETURN_VALUE', None),
87+
('LOAD_CONST', 0),
88+
('RETURN_VALUE', None),
89+
]
90+
]
91+
self.codegen_test(snippet, expected)
92+
93+
def test_nested_functions(self):
94+
snippet = textwrap.dedent("""
95+
def f():
96+
def h():
97+
return 12
98+
def g():
99+
x = 1
100+
y = 2
101+
z = 3
102+
u = 4
103+
return 42
104+
""")
105+
expected = [
106+
# Function definition
107+
('RESUME', 0),
108+
('LOAD_CONST', 0),
109+
('MAKE_FUNCTION', None),
110+
('STORE_NAME', 0),
111+
('LOAD_CONST', 1),
112+
('RETURN_VALUE', None),
113+
[
114+
# Function body
115+
('RESUME', 0),
116+
('LOAD_CONST', 1),
117+
('MAKE_FUNCTION', None),
118+
('STORE_FAST', 0),
119+
('LOAD_CONST', 2),
120+
('MAKE_FUNCTION', None),
121+
('STORE_FAST', 1),
122+
('LOAD_CONST', 0),
123+
('RETURN_VALUE', None),
124+
[
125+
('RESUME', 0),
126+
('NOP', None),
127+
('LOAD_CONST', 1),
128+
('RETURN_VALUE', None),
129+
('LOAD_CONST', 0),
130+
('RETURN_VALUE', None),
131+
],
132+
[
133+
('RESUME', 0),
134+
('LOAD_CONST', 1),
135+
('STORE_FAST', 0),
136+
('LOAD_CONST', 2),
137+
('STORE_FAST', 1),
138+
('LOAD_CONST', 3),
139+
('STORE_FAST', 2),
140+
('LOAD_CONST', 4),
141+
('STORE_FAST', 3),
142+
('NOP', None),
143+
('LOAD_CONST', 5),
144+
('RETURN_VALUE', None),
145+
('LOAD_CONST', 0),
146+
('RETURN_VALUE', None),
147+
],
148+
],
149+
]
150+
self.codegen_test(snippet, expected)
151+
58152
def test_syntax_error__return_not_in_function(self):
59153
snippet = "return 42"
60154
with self.assertRaisesRegex(SyntaxError, "'return' outside function"):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add option for compiler's codegen to save nested instruction sequences for introspection.

Python/compile.c

+18
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,10 @@ struct compiler {
285285
struct compiler_unit *u; /* compiler state for current block */
286286
PyObject *c_stack; /* Python list holding compiler_unit ptrs */
287287
PyArena *c_arena; /* pointer to memory allocation arena */
288+
289+
bool c_save_nested_seqs; /* if true, construct recursive instruction sequences
290+
* (including instructions for nested code objects)
291+
*/
288292
};
289293

290294
#define INSTR_SEQUENCE(C) ((C)->u->u_instr_sequence)
@@ -402,6 +406,7 @@ compiler_setup(struct compiler *c, mod_ty mod, PyObject *filename,
402406
c->c_flags = *flags;
403407
c->c_optimize = (optimize == -1) ? _Py_GetConfig()->optimization_level : optimize;
404408
c->c_nestlevel = 0;
409+
c->c_save_nested_seqs = false;
405410

406411
if (!_PyAST_Optimize(mod, arena, c->c_optimize, merged)) {
407412
return ERROR;
@@ -1290,6 +1295,11 @@ compiler_exit_scope(struct compiler *c)
12901295
// Don't call PySequence_DelItem() with an exception raised
12911296
PyObject *exc = PyErr_GetRaisedException();
12921297

1298+
instr_sequence *nested_seq = NULL;
1299+
if (c->c_save_nested_seqs) {
1300+
nested_seq = c->u->u_instr_sequence;
1301+
Py_INCREF(nested_seq);
1302+
}
12931303
c->c_nestlevel--;
12941304
compiler_unit_free(c->u);
12951305
/* Restore c->u to the parent unit. */
@@ -1303,10 +1313,17 @@ compiler_exit_scope(struct compiler *c)
13031313
PyErr_FormatUnraisable("Exception ignored on removing "
13041314
"the last compiler stack item");
13051315
}
1316+
if (nested_seq != NULL) {
1317+
if (_PyInstructionSequence_AddNested(c->u->u_instr_sequence, nested_seq) < 0) {
1318+
PyErr_FormatUnraisable("Exception ignored on appending "
1319+
"nested instruction sequence");
1320+
}
1321+
}
13061322
}
13071323
else {
13081324
c->u = NULL;
13091325
}
1326+
Py_XDECREF(nested_seq);
13101327

13111328
PyErr_SetRaisedException(exc);
13121329
}
@@ -7734,6 +7751,7 @@ _PyCompile_CodeGen(PyObject *ast, PyObject *filename, PyCompilerFlags *pflags,
77347751
_PyArena_Free(arena);
77357752
return NULL;
77367753
}
7754+
c->c_save_nested_seqs = true;
77377755

77387756
metadata = PyDict_New();
77397757
if (metadata == NULL) {

0 commit comments

Comments
 (0)