Skip to content

Commit df6bccf

Browse files
committed
implement all/any/tuple optimization
1 parent 8de7276 commit df6bccf

11 files changed

+179
-13
lines changed

Include/internal/pycore_global_objects_fini_generated.h

Lines changed: 2 additions & 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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,9 @@ struct _Py_global_strings {
278278
STRUCT_FOR_ID(aggregate_class)
279279
STRUCT_FOR_ID(alias)
280280
STRUCT_FOR_ID(align)
281+
STRUCT_FOR_ID(all)
281282
STRUCT_FOR_ID(allow_code)
283+
STRUCT_FOR_ID(any)
282284
STRUCT_FOR_ID(append)
283285
STRUCT_FOR_ID(arg)
284286
STRUCT_FOR_ID(argdefs)

Include/internal/pycore_magic_number.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ PC/launcher.c must also be updated.
284284
285285
*/
286286

287-
#define PYC_MAGIC_NUMBER 3619
287+
#define PYC_MAGIC_NUMBER 3620
288288
/* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
289289
(little-endian) and then appending b'\r\n'. */
290290
#define PYC_MAGIC_NUMBER_TOKEN \

Include/internal/pycore_runtime_init_generated.h

Lines changed: 2 additions & 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

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_builtin.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ def test_all(self):
225225
self.assertEqual(all(x > 42 for x in S), True)
226226
S = [50, 40, 60]
227227
self.assertEqual(all(x > 42 for x in S), False)
228+
S = [50, 40, 60, TestFailingBool()]
229+
self.assertEqual(all(x > 42 for x in S), False)
228230

229231
def test_any(self):
230232
self.assertEqual(any([None, None, None]), False)
@@ -238,9 +240,49 @@ def test_any(self):
238240
self.assertEqual(any([1, TestFailingBool()]), True) # Short-circuit
239241
S = [40, 60, 30]
240242
self.assertEqual(any(x > 42 for x in S), True)
243+
S = [40, 60, 30, TestFailingBool()]
244+
self.assertEqual(any(x > 42 for x in S), True)
241245
S = [10, 20, 30]
242246
self.assertEqual(any(x > 42 for x in S), False)
243247

248+
def test_all_any_tuple_optimization(self):
249+
def f_all():
250+
return all(x-2 for x in [1,2,3])
251+
252+
def f_any():
253+
return any(x-1 for x in [1,2,3])
254+
255+
def f_tuple():
256+
return tuple(2*x for x in [1,2,3])
257+
258+
funcs = [f_all, f_any, f_tuple]
259+
260+
for f in funcs:
261+
# check that generator code object is not duplicated
262+
code_objs = [c for c in f.__code__.co_consts if isinstance(c, type(f.__code__))]
263+
self.assertEqual(len(code_objs), 1)
264+
265+
266+
# check the overriding the builtins works
267+
bltin_outputs = [f() for f in funcs]
268+
269+
global all, any, tuple
270+
saved = all, any, tuple
271+
try:
272+
all = lambda x : "all"
273+
any = lambda x : "any"
274+
tuple = lambda x : "tuple"
275+
276+
return [f() for f in funcs]
277+
finally:
278+
all, any, tuple = saved
279+
280+
overridden_outputs = run_with_overrides()
281+
282+
for f, out1, out2 in zip(funcs, bltin_outputs, overridden_outputs):
283+
with self.subTest(func = f.__name__):
284+
self.assertNotEqual(out1, out2)
285+
244286
def test_ascii(self):
245287
self.assertEqual(ascii(''), '\'\'')
246288
self.assertEqual(ascii(0), '0')

Python/bytecodes.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,15 +1416,15 @@ dummy_func(
14161416
val = (PyObject*)&PyTuple_Type;
14171417
}
14181418
else if (oparg == CONSTANT_BUILTIN_ALL) {
1419-
val = PyDict_GetItemWithError(builtins_dict, &_Py_ID(all));
1419+
val = PyDict_GetItemWithError(BUILTINS(), &_Py_ID(all));
14201420
}
14211421
else if (oparg == CONSTANT_BUILTIN_ANY) {
1422-
val = PyDict_GetItemWithError(builtins_dict, &_Py_ID(any));
1422+
val = PyDict_GetItemWithError(BUILTINS(), &_Py_ID(any));
14231423
}
14241424
else {
14251425
Py_UNREACHABLE();
14261426
}
1427-
value = PyStackRef_FromPyObjectImmortal(val);
1427+
value = PyStackRef_FromPyObjectNew(val);
14281428
}
14291429

14301430
inst(LOAD_BUILD_CLASS, ( -- bc)) {

Python/codegen.c

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3723,6 +3723,105 @@ update_start_location_to_match_attr(compiler *c, location loc,
37233723
return loc;
37243724
}
37253725

3726+
static int push_inlined_comprehension_state(compiler *c, location loc,
3727+
PySTEntryObject *comp,
3728+
_PyCompile_InlinedComprehensionState *state);
3729+
static int pop_inlined_comprehension_state(compiler *c, location loc,
3730+
_PyCompile_InlinedComprehensionState *state);
3731+
3732+
PyObject *_PyCompile_Filename(compiler *c);
3733+
3734+
static int
3735+
maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end)
3736+
{
3737+
asdl_expr_seq *args = e->v.Call.args;
3738+
asdl_keyword_seq *kwds = e->v.Call.keywords;
3739+
expr_ty func = e->v.Call.func;
3740+
3741+
if (! (func->kind == Name_kind &&
3742+
asdl_seq_LEN(args) == 1 &&
3743+
asdl_seq_LEN(kwds) == 0 &&
3744+
asdl_seq_GET(args, 0)->kind == GeneratorExp_kind))
3745+
{
3746+
return 0;
3747+
}
3748+
3749+
expr_ty generator_exp = asdl_seq_GET(args, 0);
3750+
3751+
if (asdl_seq_LEN(generator_exp->v.GeneratorExp.generators) != 1) {
3752+
return 0;
3753+
}
3754+
3755+
location loc = LOC(func);
3756+
3757+
int optimized = 0;
3758+
NEW_JUMP_TARGET_LABEL(c, skip_optimization);
3759+
3760+
int const_oparg = -1;
3761+
PyObject *initial_res;
3762+
int continue_jump_opcode = -1;
3763+
if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "all")) {
3764+
const_oparg = CONSTANT_BUILTIN_ALL;
3765+
initial_res = Py_True;
3766+
continue_jump_opcode = POP_JUMP_IF_TRUE;
3767+
}
3768+
else if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "any")) {
3769+
const_oparg = CONSTANT_BUILTIN_ANY;
3770+
initial_res = Py_False;
3771+
continue_jump_opcode = POP_JUMP_IF_FALSE;
3772+
}
3773+
else if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "tuple")) {
3774+
const_oparg = CONSTANT_BUILTIN_TUPLE;
3775+
}
3776+
if (const_oparg != -1) {
3777+
RETURN_IF_ERROR(codegen_nameop(c, loc, func->v.Name.id, Load));
3778+
ADDOP_I(c, loc, LOAD_COMMON_CONSTANT, const_oparg);
3779+
ADDOP_COMPARE(c, loc, Is);
3780+
ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, skip_optimization);
3781+
3782+
if (const_oparg == CONSTANT_BUILTIN_TUPLE) {
3783+
ADDOP_I(c, loc, BUILD_LIST, 0);
3784+
}
3785+
else {
3786+
ADDOP_LOAD_CONST(c, loc, initial_res);
3787+
}
3788+
VISIT(c, expr, generator_exp);
3789+
3790+
NEW_JUMP_TARGET_LABEL(c, loop);
3791+
NEW_JUMP_TARGET_LABEL(c, cleanup);
3792+
3793+
USE_LABEL(c, loop);
3794+
ADDOP_JUMP(c, loc, FOR_ITER, cleanup);
3795+
if (const_oparg == CONSTANT_BUILTIN_TUPLE) {
3796+
ADDOP_I(c, loc, LIST_APPEND, 2);
3797+
ADDOP_JUMP(c, loc, JUMP, loop);
3798+
}
3799+
else {
3800+
ADDOP(c, loc, TO_BOOL);
3801+
ADDOP_JUMP(c, loc, continue_jump_opcode, loop);
3802+
}
3803+
3804+
ADDOP(c, NO_LOCATION, POP_ITER);
3805+
if (const_oparg != CONSTANT_BUILTIN_TUPLE) {
3806+
ADDOP(c, loc, POP_TOP);
3807+
ADDOP_LOAD_CONST(c, loc, initial_res == Py_True ? Py_False : Py_True);
3808+
}
3809+
ADDOP_JUMP(c, loc, JUMP, end);
3810+
3811+
USE_LABEL(c, cleanup);
3812+
ADDOP(c, NO_LOCATION, END_FOR);
3813+
ADDOP(c, NO_LOCATION, POP_ITER);
3814+
if (const_oparg == CONSTANT_BUILTIN_TUPLE) {
3815+
ADDOP_I(c, loc, CALL_INTRINSIC_1, INTRINSIC_LIST_TO_TUPLE);
3816+
}
3817+
3818+
optimized = 1;
3819+
ADDOP_JUMP(c, loc, JUMP, end);
3820+
}
3821+
USE_LABEL(c, skip_optimization);
3822+
return optimized;
3823+
}
3824+
37263825
// Return 1 if the method call was optimized, 0 if not, and -1 on error.
37273826
static int
37283827
maybe_optimize_method_call(compiler *c, expr_ty e)
@@ -3829,14 +3928,18 @@ codegen_call(compiler *c, expr_ty e)
38293928
if (ret == 1) {
38303929
return SUCCESS;
38313930
}
3931+
NEW_JUMP_TARGET_LABEL(c, skip_normal_call);
3932+
RETURN_IF_ERROR(maybe_optimize_function_call(c, e, skip_normal_call));
38323933
RETURN_IF_ERROR(check_caller(c, e->v.Call.func));
38333934
VISIT(c, expr, e->v.Call.func);
38343935
location loc = LOC(e->v.Call.func);
38353936
ADDOP(c, loc, PUSH_NULL);
38363937
loc = LOC(e);
3837-
return codegen_call_helper(c, loc, 0,
3838-
e->v.Call.args,
3839-
e->v.Call.keywords);
3938+
ret = codegen_call_helper(c, loc, 0,
3939+
e->v.Call.args,
3940+
e->v.Call.keywords);
3941+
USE_LABEL(c, skip_normal_call);
3942+
return ret;
38403943
}
38413944

38423945
static int
@@ -4547,6 +4650,7 @@ codegen_comprehension(compiler *c, expr_ty e, int type,
45474650
}
45484651

45494652
ADDOP_I(c, loc, op, 0);
4653+
45504654
if (is_inlined) {
45514655
ADDOP_I(c, loc, SWAP, 2);
45524656
}

Python/compile.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1231,6 +1231,12 @@ _PyCompile_Metadata(compiler *c)
12311231
return &c->u->u_metadata;
12321232
}
12331233

1234+
PyObject *
1235+
_PyCompile_Filename(compiler *c)
1236+
{
1237+
return c->c_filename;
1238+
}
1239+
12341240
// Merge *obj* with constant cache, without recursion.
12351241
int
12361242
_PyCompile_ConstCacheMergeOne(PyObject *const_cache, PyObject **obj)

Python/executor_cases.c.h

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)