Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 80b7148

Browse filesBrowse files
authored
gh-87092: Expose assembler to unit tests (#103988)
1 parent a474e04 commit 80b7148
Copy full SHA for 80b7148

11 files changed

+329
-48
lines changed

‎Include/internal/pycore_compile.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_compile.h
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ PyAPI_FUNC(PyObject*) _PyCompile_OptimizeCfg(
103103
PyObject *instructions,
104104
PyObject *consts);
105105

106+
PyAPI_FUNC(PyCodeObject*)
107+
_PyCompile_Assemble(_PyCompile_CodeUnitMetadata *umd, PyObject *filename,
108+
PyObject *instructions);
109+
106110
#ifdef __cplusplus
107111
}
108112
#endif

‎Include/internal/pycore_global_objects_fini_generated.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_global_objects_fini_generated.h
+1Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Include/internal/pycore_global_strings.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_global_strings.h
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,7 @@ struct _Py_global_strings {
517517
STRUCT_FOR_ID(memlimit)
518518
STRUCT_FOR_ID(message)
519519
STRUCT_FOR_ID(metaclass)
520+
STRUCT_FOR_ID(metadata)
520521
STRUCT_FOR_ID(method)
521522
STRUCT_FOR_ID(mod)
522523
STRUCT_FOR_ID(mode)

‎Include/internal/pycore_runtime_init_generated.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_runtime_init_generated.h
+1Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Include/internal/pycore_unicodeobject_generated.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_unicodeobject_generated.h
+3Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Lib/test/support/bytecode_helper.py

Copy file name to clipboardExpand all lines: Lib/test/support/bytecode_helper.py
+19-13Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import unittest
44
import dis
55
import io
6-
from _testinternalcapi import compiler_codegen, optimize_cfg
6+
from _testinternalcapi import compiler_codegen, optimize_cfg, assemble_code_object
77

88
_UNSPECIFIED = object()
99

@@ -108,6 +108,18 @@ def normalize_insts(self, insts):
108108
res.append((opcode, arg, *loc))
109109
return res
110110

111+
def complete_insts_info(self, insts):
112+
# fill in omitted fields in location, and oparg 0 for ops with no arg.
113+
res = []
114+
for item in insts:
115+
assert isinstance(item, tuple)
116+
inst = list(item)
117+
opcode = dis.opmap[inst[0]]
118+
oparg = inst[1]
119+
loc = inst[2:] + [-1] * (6 - len(inst))
120+
res.append((opcode, oparg, *loc))
121+
return res
122+
111123

112124
class CodegenTestCase(CompilationStepTestCase):
113125

@@ -118,20 +130,14 @@ def generate_code(self, ast):
118130

119131
class CfgOptimizationTestCase(CompilationStepTestCase):
120132

121-
def complete_insts_info(self, insts):
122-
# fill in omitted fields in location, and oparg 0 for ops with no arg.
123-
res = []
124-
for item in insts:
125-
assert isinstance(item, tuple)
126-
inst = list(reversed(item))
127-
opcode = dis.opmap[inst.pop()]
128-
oparg = inst.pop()
129-
loc = inst + [-1] * (4 - len(inst))
130-
res.append((opcode, oparg, *loc))
131-
return res
132-
133133
def get_optimized(self, insts, consts):
134134
insts = self.normalize_insts(insts)
135135
insts = self.complete_insts_info(insts)
136136
insts = optimize_cfg(insts, consts)
137137
return insts, consts
138+
139+
class AssemblerTestCase(CompilationStepTestCase):
140+
141+
def get_code_object(self, filename, insts, metadata):
142+
co = assemble_code_object(filename, insts, metadata)
143+
return co

‎Lib/test/test_compiler_assemble.py

Copy file name to clipboard
+71Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
2+
import ast
3+
import types
4+
5+
from test.support.bytecode_helper import AssemblerTestCase
6+
7+
8+
# Tests for the code-object creation stage of the compiler.
9+
10+
class IsolatedAssembleTests(AssemblerTestCase):
11+
12+
def complete_metadata(self, metadata, filename="myfile.py"):
13+
if metadata is None:
14+
metadata = {}
15+
for key in ['name', 'qualname']:
16+
metadata.setdefault(key, key)
17+
for key in ['consts']:
18+
metadata.setdefault(key, [])
19+
for key in ['names', 'varnames', 'cellvars', 'freevars']:
20+
metadata.setdefault(key, {})
21+
for key in ['argcount', 'posonlyargcount', 'kwonlyargcount']:
22+
metadata.setdefault(key, 0)
23+
metadata.setdefault('firstlineno', 1)
24+
metadata.setdefault('filename', filename)
25+
return metadata
26+
27+
def assemble_test(self, insts, metadata, expected):
28+
metadata = self.complete_metadata(metadata)
29+
insts = self.complete_insts_info(insts)
30+
31+
co = self.get_code_object(metadata['filename'], insts, metadata)
32+
self.assertIsInstance(co, types.CodeType)
33+
34+
expected_metadata = {}
35+
for key, value in metadata.items():
36+
if isinstance(value, list):
37+
expected_metadata[key] = tuple(value)
38+
elif isinstance(value, dict):
39+
expected_metadata[key] = tuple(value.keys())
40+
else:
41+
expected_metadata[key] = value
42+
43+
for key, value in expected_metadata.items():
44+
self.assertEqual(getattr(co, "co_" + key), value)
45+
46+
f = types.FunctionType(co, {})
47+
for args, res in expected.items():
48+
self.assertEqual(f(*args), res)
49+
50+
def test_simple_expr(self):
51+
metadata = {
52+
'filename' : 'avg.py',
53+
'name' : 'avg',
54+
'qualname' : 'stats.avg',
55+
'consts' : [2],
56+
'argcount' : 2,
57+
'varnames' : {'x' : 0, 'y' : 1},
58+
}
59+
60+
# code for "return (x+y)/2"
61+
insts = [
62+
('RESUME', 0),
63+
('LOAD_FAST', 0, 1), # 'x'
64+
('LOAD_FAST', 1, 1), # 'y'
65+
('BINARY_OP', 0, 1), # '+'
66+
('LOAD_CONST', 0, 1), # 2
67+
('BINARY_OP', 11, 1), # '/'
68+
('RETURN_VALUE', 1),
69+
]
70+
expected = {(3, 4) : 3.5, (-100, 200) : 50, (10, 18) : 14}
71+
self.assemble_test(insts, metadata, expected)

‎Modules/_testinternalcapi.c

Copy file name to clipboardExpand all lines: Modules/_testinternalcapi.c
+64-1Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
#include "Python.h"
1515
#include "pycore_atomic_funcs.h" // _Py_atomic_int_get()
1616
#include "pycore_bitutils.h" // _Py_bswap32()
17-
#include "pycore_compile.h" // _PyCompile_CodeGen, _PyCompile_OptimizeCfg
17+
#include "pycore_compile.h" // _PyCompile_CodeGen, _PyCompile_OptimizeCfg, _PyCompile_Assemble
1818
#include "pycore_fileutils.h" // _Py_normpath
1919
#include "pycore_frame.h" // _PyInterpreterFrame
2020
#include "pycore_gc.h" // PyGC_Head
@@ -625,6 +625,68 @@ _testinternalcapi_optimize_cfg_impl(PyObject *module, PyObject *instructions,
625625
return _PyCompile_OptimizeCfg(instructions, consts);
626626
}
627627

628+
static int
629+
get_nonnegative_int_from_dict(PyObject *dict, const char *key) {
630+
PyObject *obj = PyDict_GetItemString(dict, key);
631+
if (obj == NULL) {
632+
return -1;
633+
}
634+
return PyLong_AsLong(obj);
635+
}
636+
637+
/*[clinic input]
638+
639+
_testinternalcapi.assemble_code_object -> object
640+
641+
filename: object
642+
instructions: object
643+
metadata: object
644+
645+
Create a code object for the given instructions.
646+
[clinic start generated code]*/
647+
648+
static PyObject *
649+
_testinternalcapi_assemble_code_object_impl(PyObject *module,
650+
PyObject *filename,
651+
PyObject *instructions,
652+
PyObject *metadata)
653+
/*[clinic end generated code: output=38003dc16a930f48 input=e713ad77f08fb3a8]*/
654+
655+
{
656+
assert(PyDict_Check(metadata));
657+
_PyCompile_CodeUnitMetadata umd;
658+
659+
umd.u_name = PyDict_GetItemString(metadata, "name");
660+
umd.u_qualname = PyDict_GetItemString(metadata, "qualname");
661+
662+
assert(PyUnicode_Check(umd.u_name));
663+
assert(PyUnicode_Check(umd.u_qualname));
664+
665+
umd.u_consts = PyDict_GetItemString(metadata, "consts");
666+
umd.u_names = PyDict_GetItemString(metadata, "names");
667+
umd.u_varnames = PyDict_GetItemString(metadata, "varnames");
668+
umd.u_cellvars = PyDict_GetItemString(metadata, "cellvars");
669+
umd.u_freevars = PyDict_GetItemString(metadata, "freevars");
670+
671+
assert(PyList_Check(umd.u_consts));
672+
assert(PyDict_Check(umd.u_names));
673+
assert(PyDict_Check(umd.u_varnames));
674+
assert(PyDict_Check(umd.u_cellvars));
675+
assert(PyDict_Check(umd.u_freevars));
676+
677+
umd.u_argcount = get_nonnegative_int_from_dict(metadata, "argcount");
678+
umd.u_posonlyargcount = get_nonnegative_int_from_dict(metadata, "posonlyargcount");
679+
umd.u_kwonlyargcount = get_nonnegative_int_from_dict(metadata, "kwonlyargcount");
680+
umd.u_firstlineno = get_nonnegative_int_from_dict(metadata, "firstlineno");
681+
682+
assert(umd.u_argcount >= 0);
683+
assert(umd.u_posonlyargcount >= 0);
684+
assert(umd.u_kwonlyargcount >= 0);
685+
assert(umd.u_firstlineno >= 0);
686+
687+
return (PyObject*)_PyCompile_Assemble(&umd, filename, instructions);
688+
}
689+
628690

629691
static PyObject *
630692
get_interp_settings(PyObject *self, PyObject *args)
@@ -705,6 +767,7 @@ static PyMethodDef module_functions[] = {
705767
{"set_eval_frame_record", set_eval_frame_record, METH_O, NULL},
706768
_TESTINTERNALCAPI_COMPILER_CODEGEN_METHODDEF
707769
_TESTINTERNALCAPI_OPTIMIZE_CFG_METHODDEF
770+
_TESTINTERNALCAPI_ASSEMBLE_CODE_OBJECT_METHODDEF
708771
{"get_interp_settings", get_interp_settings, METH_VARARGS, NULL},
709772
{"clear_extension", clear_extension, METH_VARARGS, NULL},
710773
{NULL, NULL} /* sentinel */

‎Modules/clinic/_testinternalcapi.c.h

Copy file name to clipboardExpand all lines: Modules/clinic/_testinternalcapi.c.h
+63-1Lines changed: 63 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.