Skip to content

Commit 67742d9

Browse files
committed
Optimize leading empty tuple unpack idioms in codegen
Includes the branch-local test expectations for the leading-only behavior.
1 parent 61ec3c2 commit 67742d9

2 files changed

Lines changed: 155 additions & 7 deletions

File tree

Lib/test/test_compile.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,139 @@ def or_false(x):
11191119
self.assertIn('LOAD_', opcodes[-2].opname)
11201120
self.assertEqual('RETURN_VALUE', opcodes[-1].opname)
11211121

1122+
def test_empty_set_unpack_literal_bytecode_optimization(self):
1123+
cases = {
1124+
# optimized cases
1125+
'{*()}': [
1126+
('RESUME', 0),
1127+
('BUILD_SET', 0),
1128+
('RETURN_VALUE', None),
1129+
],
1130+
'{*(), 1}': [
1131+
('RESUME', 0),
1132+
('LOAD_SMALL_INT', 1),
1133+
('BUILD_SET', 1),
1134+
('RETURN_VALUE', None),
1135+
],
1136+
'{*(), 1, 2, 3}': [
1137+
('RESUME', 0),
1138+
('BUILD_SET', 0),
1139+
('LOAD_CONST', frozenset({1, 2, 3})),
1140+
('SET_UPDATE', 1),
1141+
('RETURN_VALUE', None),
1142+
],
1143+
# unoptimized cases
1144+
'{*(1,)}': [
1145+
('RESUME', 0),
1146+
('BUILD_SET', 0),
1147+
('LOAD_CONST', (1,)),
1148+
('SET_UPDATE', 1),
1149+
('RETURN_VALUE', None),
1150+
],
1151+
'{*(x,)}': [
1152+
('RESUME', 0),
1153+
('BUILD_SET', 0),
1154+
('LOAD_NAME', 'x'),
1155+
('BUILD_TUPLE', 1),
1156+
('SET_UPDATE', 1),
1157+
('RETURN_VALUE', None),
1158+
],
1159+
'{1, *()}': [
1160+
('RESUME', 0),
1161+
('LOAD_SMALL_INT', 1),
1162+
('BUILD_SET', 1),
1163+
('LOAD_COMMON_CONSTANT', ()),
1164+
('SET_UPDATE', 1),
1165+
('RETURN_VALUE', None),
1166+
],
1167+
'{1, 2, 3, *()}': [
1168+
('RESUME', 0),
1169+
('BUILD_SET', 0),
1170+
('LOAD_CONST', frozenset({1, 2, 3})),
1171+
('SET_UPDATE', 1),
1172+
('LOAD_COMMON_CONSTANT', ()),
1173+
('SET_UPDATE', 1),
1174+
('RETURN_VALUE', None),
1175+
],
1176+
}
1177+
1178+
for source, expected in cases.items():
1179+
with self.subTest(source=source):
1180+
code = compile(source, '<test>', 'eval')
1181+
instructions = [
1182+
(instruction.opname, instruction.argval)
1183+
for instruction in dis.get_instructions(code)
1184+
]
1185+
self.assertEqual(instructions, expected)
1186+
1187+
def test_empty_leading_tuple_unpack_list_and_tuple_bytecode_optimization(self):
1188+
cases = {
1189+
# optimized cases
1190+
'[*()]': [
1191+
('RESUME', 0),
1192+
('BUILD_LIST', 0),
1193+
('RETURN_VALUE', None),
1194+
],
1195+
'[*(), 1]': [
1196+
('RESUME', 0),
1197+
('LOAD_SMALL_INT', 1),
1198+
('BUILD_LIST', 1),
1199+
('RETURN_VALUE', None),
1200+
],
1201+
'(*(),)': [
1202+
('RESUME', 0),
1203+
('LOAD_COMMON_CONSTANT', ()),
1204+
('RETURN_VALUE', None),
1205+
],
1206+
'(*(), 1)': [
1207+
('RESUME', 0),
1208+
('LOAD_CONST', (1,)),
1209+
('RETURN_VALUE', None),
1210+
],
1211+
# unoptimized cases
1212+
'[*(1,)]': [
1213+
('RESUME', 0),
1214+
('BUILD_LIST', 0),
1215+
('LOAD_CONST', (1,)),
1216+
('LIST_EXTEND', 1),
1217+
('RETURN_VALUE', None),
1218+
],
1219+
'[*(x,)]': [
1220+
('RESUME', 0),
1221+
('BUILD_LIST', 0),
1222+
('LOAD_NAME', 'x'),
1223+
('BUILD_TUPLE', 1),
1224+
('LIST_EXTEND', 1),
1225+
('RETURN_VALUE', None),
1226+
],
1227+
'[1, *()]': [
1228+
('RESUME', 0),
1229+
('LOAD_SMALL_INT', 1),
1230+
('BUILD_LIST', 1),
1231+
('LOAD_COMMON_CONSTANT', ()),
1232+
('LIST_EXTEND', 1),
1233+
('RETURN_VALUE', None),
1234+
],
1235+
'[1, 2, 3, *()]': [
1236+
('RESUME', 0),
1237+
('BUILD_LIST', 0),
1238+
('LOAD_CONST', (1, 2, 3)),
1239+
('LIST_EXTEND', 1),
1240+
('LOAD_COMMON_CONSTANT', ()),
1241+
('LIST_EXTEND', 1),
1242+
('RETURN_VALUE', None),
1243+
],
1244+
}
1245+
1246+
for source, expected in cases.items():
1247+
with self.subTest(source=source):
1248+
code = compile(source, '<test>', 'eval')
1249+
instructions = [
1250+
(instruction.opname, instruction.argval)
1251+
for instruction in dis.get_instructions(code)
1252+
]
1253+
self.assertEqual(instructions, expected)
1254+
11221255
def test_imported_load_method(self):
11231256
sources = [
11241257
"""\

Python/codegen.c

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3414,21 +3414,23 @@ codegen_boolop(compiler *c, expr_ty e)
34143414

34153415
static int
34163416
starunpack_helper_impl(compiler *c, location loc,
3417-
asdl_expr_seq *elts, PyObject *injected_arg, int pushed,
3417+
asdl_expr_seq *elts, Py_ssize_t start,
3418+
PyObject *injected_arg, int pushed,
34183419
int build, int add, int extend, int tuple)
34193420
{
3420-
Py_ssize_t n = asdl_seq_LEN(elts);
3421+
Py_ssize_t end = asdl_seq_LEN(elts);
3422+
Py_ssize_t n = end - start;
34213423
int big = n + pushed + (injected_arg ? 1 : 0) > _PY_STACK_USE_GUIDELINE;
34223424
int seen_star = 0;
3423-
for (Py_ssize_t i = 0; i < n; i++) {
3425+
for (Py_ssize_t i = start; i < end; i++) {
34243426
expr_ty elt = asdl_seq_GET(elts, i);
34253427
if (elt->kind == Starred_kind) {
34263428
seen_star = 1;
34273429
break;
34283430
}
34293431
}
34303432
if (!seen_star && !big) {
3431-
for (Py_ssize_t i = 0; i < n; i++) {
3433+
for (Py_ssize_t i = start; i < end; i++) {
34323434
expr_ty elt = asdl_seq_GET(elts, i);
34333435
VISIT(c, expr, elt);
34343436
}
@@ -3448,7 +3450,7 @@ starunpack_helper_impl(compiler *c, location loc,
34483450
ADDOP_I(c, loc, build, pushed);
34493451
sequence_built = 1;
34503452
}
3451-
for (Py_ssize_t i = 0; i < n; i++) {
3453+
for (Py_ssize_t i = start; i < end; i++) {
34523454
expr_ty elt = asdl_seq_GET(elts, i);
34533455
if (elt->kind == Starred_kind) {
34543456
if (sequence_built == 0) {
@@ -3476,12 +3478,25 @@ starunpack_helper_impl(compiler *c, location loc,
34763478
return SUCCESS;
34773479
}
34783480

3481+
static bool
3482+
is_empty_starred_tuple(expr_ty elt)
3483+
{
3484+
if (elt->kind != Starred_kind) {
3485+
return false;
3486+
}
3487+
expr_ty value = elt->v.Starred.value;
3488+
return value->kind == Tuple_kind &&
3489+
value->v.Tuple.ctx == Load &&
3490+
asdl_seq_LEN(value->v.Tuple.elts) == 0;
3491+
}
3492+
34793493
static int
34803494
starunpack_helper(compiler *c, location loc,
34813495
asdl_expr_seq *elts, int pushed,
34823496
int build, int add, int extend, int tuple)
34833497
{
3484-
return starunpack_helper_impl(c, loc, elts, NULL, pushed,
3498+
Py_ssize_t start = asdl_seq_LEN(elts) && is_empty_starred_tuple(asdl_seq_GET(elts, 0));
3499+
return starunpack_helper_impl(c, loc, elts, start, NULL, pushed,
34853500
build, add, extend, tuple);
34863501
}
34873502

@@ -4446,7 +4461,7 @@ codegen_call_helper_impl(compiler *c, location loc,
44464461
VISIT(c, expr, ((expr_ty)asdl_seq_GET(args, 0))->v.Starred.value);
44474462
}
44484463
else {
4449-
RETURN_IF_ERROR(starunpack_helper_impl(c, loc, args, injected_arg, n,
4464+
RETURN_IF_ERROR(starunpack_helper_impl(c, loc, args, 0, injected_arg, n,
44504465
BUILD_LIST, LIST_APPEND, LIST_EXTEND, 1));
44514466
}
44524467
/* Then keyword arguments */

0 commit comments

Comments
 (0)