Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,9 @@ struct _Py_global_strings {
STRUCT_FOR_ID(aggregate_class)
STRUCT_FOR_ID(alias)
STRUCT_FOR_ID(align)
STRUCT_FOR_ID(all)
STRUCT_FOR_ID(allow_code)
STRUCT_FOR_ID(any)
STRUCT_FOR_ID(append)
STRUCT_FOR_ID(arg)
STRUCT_FOR_ID(argdefs)
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_magic_number.h
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ PC/launcher.c must also be updated.

*/

#define PYC_MAGIC_NUMBER 3619
#define PYC_MAGIC_NUMBER 3620
/* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
(little-endian) and then appending b'\r\n'. */
#define PYC_MAGIC_NUMBER_TOKEN \
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_opcode_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Include/internal/pycore_opcode_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ extern "C" {
/* Values used as the oparg for LOAD_COMMON_CONSTANT */
#define CONSTANT_ASSERTIONERROR 0
#define CONSTANT_NOTIMPLEMENTEDERROR 1
#define NUM_COMMON_CONSTANTS 2
#define CONSTANT_BUILTIN_TUPLE 2
#define CONSTANT_BUILTIN_ALL 3
#define CONSTANT_BUILTIN_ANY 4
#define NUM_COMMON_CONSTANTS 5

/* Values used in the oparg for RESUME */
#define RESUME_AT_FUNC_START 0
Expand Down
2 changes: 2 additions & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Include/internal/pycore_uop_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"HAVE_ARGUMENT", "EXTENDED_ARG", "hasarg", "hasconst", "hasname",
"hasjump", "hasjrel", "hasjabs", "hasfree", "haslocal", "hasexc"]

import builtins
import _opcode
from _opcode import stack_effect

Expand Down Expand Up @@ -38,7 +39,8 @@
_intrinsic_1_descs = _opcode.get_intrinsic1_descs()
_intrinsic_2_descs = _opcode.get_intrinsic2_descs()
_special_method_names = _opcode.get_special_method_names()
_common_constants = [AssertionError, NotImplementedError]
_common_constants = [AssertionError, NotImplementedError,
builtins.tuple, builtins.all, builtins.any]
_nb_ops = _opcode.get_nb_ops()

hascompare = [opmap["COMPARE_OP"]]
Expand Down
42 changes: 42 additions & 0 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ def test_all(self):
self.assertEqual(all(x > 42 for x in S), True)
S = [50, 40, 60]
self.assertEqual(all(x > 42 for x in S), False)
S = [50, 40, 60, TestFailingBool()]
self.assertEqual(all(x > 42 for x in S), False)

def test_any(self):
self.assertEqual(any([None, None, None]), False)
Expand All @@ -238,9 +240,49 @@ def test_any(self):
self.assertEqual(any([1, TestFailingBool()]), True) # Short-circuit
S = [40, 60, 30]
self.assertEqual(any(x > 42 for x in S), True)
S = [40, 60, 30, TestFailingBool()]
self.assertEqual(any(x > 42 for x in S), True)
S = [10, 20, 30]
self.assertEqual(any(x > 42 for x in S), False)

def test_all_any_tuple_optimization(self):
def f_all():
return all(x-2 for x in [1,2,3])

def f_any():
return any(x-1 for x in [1,2,3])

def f_tuple():
return tuple(2*x for x in [1,2,3])

funcs = [f_all, f_any, f_tuple]

for f in funcs:
# check that generator code object is not duplicated
code_objs = [c for c in f.__code__.co_consts if isinstance(c, type(f.__code__))]
self.assertEqual(len(code_objs), 1)


# check the overriding the builtins works
bltin_outputs = [f() for f in funcs]

global all, any, tuple
saved = all, any, tuple
try:
all = lambda x : "all"
any = lambda x : "any"
tuple = lambda x : "tuple"

return [f() for f in funcs]
finally:
all, any, tuple = saved

overridden_outputs = run_with_overrides()

for f, out1, out2 in zip(funcs, bltin_outputs, overridden_outputs):
with self.subTest(func = f.__name__):
self.assertNotEqual(out1, out2)

def test_ascii(self):
self.assertEqual(ascii(''), '\'\'')
self.assertEqual(ascii(0), '0')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Compiler emits optimised code for builtin any/all/tuple calls over a generator expression.
17 changes: 14 additions & 3 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -1409,11 +1409,22 @@ dummy_func(
if (oparg == CONSTANT_ASSERTIONERROR) {
val = PyExc_AssertionError;
}
else {
assert(oparg == CONSTANT_NOTIMPLEMENTEDERROR);
else if (oparg == CONSTANT_NOTIMPLEMENTEDERROR) {
val = PyExc_NotImplementedError;
}
value = PyStackRef_FromPyObjectImmortal(val);
else if (oparg == CONSTANT_BUILTIN_TUPLE) {
val = (PyObject*)&PyTuple_Type;
}
else if (oparg == CONSTANT_BUILTIN_ALL) {
val = PyDict_GetItemWithError(BUILTINS(), &_Py_ID(all));
}
else if (oparg == CONSTANT_BUILTIN_ANY) {
val = PyDict_GetItemWithError(BUILTINS(), &_Py_ID(any));
}
else {
Py_UNREACHABLE();
}
value = PyStackRef_FromPyObjectNew(val);
}

inst(LOAD_BUILD_CLASS, ( -- bc)) {
Expand Down
110 changes: 107 additions & 3 deletions Python/codegen.c
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@
}

#define ADDOP_LOAD_CONST(C, LOC, O) \
RETURN_IF_ERROR(codegen_addop_load_const((C), (LOC), (O)))

Check warning on line 296 in Python/codegen.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04)

‘initial_res’ may be used uninitialized [-Wmaybe-uninitialized]

Check warning on line 296 in Python/codegen.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04-arm)

‘initial_res’ may be used uninitialized [-Wmaybe-uninitialized]

Check warning on line 296 in Python/codegen.c

View workflow job for this annotation

GitHub Actions / Hypothesis tests on Ubuntu

‘initial_res’ may be used uninitialized [-Wmaybe-uninitialized]

Check warning on line 296 in Python/codegen.c

View workflow job for this annotation

GitHub Actions / Ubuntu (bolt) / build and test (ubuntu-24.04)

‘initial_res’ may be used uninitialized [-Wmaybe-uninitialized]

Check warning on line 296 in Python/codegen.c

View workflow job for this annotation

GitHub Actions / Address sanitizer (ubuntu-24.04)

‘initial_res’ may be used uninitialized in this function [-Wmaybe-uninitialized]

Check warning on line 296 in Python/codegen.c

View workflow job for this annotation

GitHub Actions / Ubuntu (free-threading) / build and test (ubuntu-24.04-arm)

‘initial_res’ may be used uninitialized [-Wmaybe-uninitialized]

Check warning on line 296 in Python/codegen.c

View workflow job for this annotation

GitHub Actions / Ubuntu (free-threading) / build and test (ubuntu-24.04)

‘initial_res’ may be used uninitialized [-Wmaybe-uninitialized]

#define ADDOP_LOAD_CONST_IN_SCOPE(C, LOC, O) \
RETURN_IF_ERROR_IN_SCOPE((C), codegen_addop_load_const((C), (LOC), (O)))
Expand Down Expand Up @@ -3723,6 +3723,105 @@
return loc;
}

static int push_inlined_comprehension_state(compiler *c, location loc,
PySTEntryObject *comp,
_PyCompile_InlinedComprehensionState *state);
static int pop_inlined_comprehension_state(compiler *c, location loc,
_PyCompile_InlinedComprehensionState *state);

PyObject *_PyCompile_Filename(compiler *c);

static int
maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end)
{
asdl_expr_seq *args = e->v.Call.args;
asdl_keyword_seq *kwds = e->v.Call.keywords;
expr_ty func = e->v.Call.func;

if (! (func->kind == Name_kind &&
asdl_seq_LEN(args) == 1 &&
asdl_seq_LEN(kwds) == 0 &&
asdl_seq_GET(args, 0)->kind == GeneratorExp_kind))
{
return 0;
}

expr_ty generator_exp = asdl_seq_GET(args, 0);

if (asdl_seq_LEN(generator_exp->v.GeneratorExp.generators) != 1) {
return 0;
}

location loc = LOC(func);

int optimized = 0;
NEW_JUMP_TARGET_LABEL(c, skip_optimization);

int const_oparg = -1;
PyObject *initial_res;
int continue_jump_opcode = -1;
if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "all")) {
const_oparg = CONSTANT_BUILTIN_ALL;
initial_res = Py_True;
continue_jump_opcode = POP_JUMP_IF_TRUE;
}
else if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "any")) {
const_oparg = CONSTANT_BUILTIN_ANY;
initial_res = Py_False;
continue_jump_opcode = POP_JUMP_IF_FALSE;
}
else if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "tuple")) {
const_oparg = CONSTANT_BUILTIN_TUPLE;
}
if (const_oparg != -1) {
RETURN_IF_ERROR(codegen_nameop(c, loc, func->v.Name.id, Load));
ADDOP_I(c, loc, LOAD_COMMON_CONSTANT, const_oparg);
ADDOP_COMPARE(c, loc, Is);
ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, skip_optimization);

if (const_oparg == CONSTANT_BUILTIN_TUPLE) {
ADDOP_I(c, loc, BUILD_LIST, 0);
}
else {
ADDOP_LOAD_CONST(c, loc, initial_res);
}
VISIT(c, expr, generator_exp);

NEW_JUMP_TARGET_LABEL(c, loop);
NEW_JUMP_TARGET_LABEL(c, cleanup);

USE_LABEL(c, loop);
ADDOP_JUMP(c, loc, FOR_ITER, cleanup);
if (const_oparg == CONSTANT_BUILTIN_TUPLE) {
ADDOP_I(c, loc, LIST_APPEND, 2);
ADDOP_JUMP(c, loc, JUMP, loop);
}
else {
ADDOP(c, loc, TO_BOOL);
ADDOP_JUMP(c, loc, continue_jump_opcode, loop);
}

ADDOP(c, NO_LOCATION, POP_ITER);
if (const_oparg != CONSTANT_BUILTIN_TUPLE) {
ADDOP(c, loc, POP_TOP);
ADDOP_LOAD_CONST(c, loc, initial_res == Py_True ? Py_False : Py_True);
}
ADDOP_JUMP(c, loc, JUMP, end);

USE_LABEL(c, cleanup);
ADDOP(c, NO_LOCATION, END_FOR);
ADDOP(c, NO_LOCATION, POP_ITER);
if (const_oparg == CONSTANT_BUILTIN_TUPLE) {
ADDOP_I(c, loc, CALL_INTRINSIC_1, INTRINSIC_LIST_TO_TUPLE);
}

optimized = 1;
ADDOP_JUMP(c, loc, JUMP, end);
}
USE_LABEL(c, skip_optimization);
return optimized;
}

// Return 1 if the method call was optimized, 0 if not, and -1 on error.
static int
maybe_optimize_method_call(compiler *c, expr_ty e)
Expand Down Expand Up @@ -3829,14 +3928,18 @@
if (ret == 1) {
return SUCCESS;
}
NEW_JUMP_TARGET_LABEL(c, skip_normal_call);
RETURN_IF_ERROR(maybe_optimize_function_call(c, e, skip_normal_call));
RETURN_IF_ERROR(check_caller(c, e->v.Call.func));
VISIT(c, expr, e->v.Call.func);
location loc = LOC(e->v.Call.func);
ADDOP(c, loc, PUSH_NULL);
loc = LOC(e);
return codegen_call_helper(c, loc, 0,
e->v.Call.args,
e->v.Call.keywords);
ret = codegen_call_helper(c, loc, 0,
e->v.Call.args,
e->v.Call.keywords);
USE_LABEL(c, skip_normal_call);
return ret;
}

static int
Expand Down Expand Up @@ -4547,6 +4650,7 @@
}

ADDOP_I(c, loc, op, 0);

if (is_inlined) {
ADDOP_I(c, loc, SWAP, 2);
}
Expand Down
6 changes: 6 additions & 0 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,12 @@ _PyCompile_Metadata(compiler *c)
return &c->u->u_metadata;
}

PyObject *
_PyCompile_Filename(compiler *c)
{
return c->c_filename;
}

// Merge *obj* with constant cache, without recursion.
int
_PyCompile_ConstCacheMergeOne(PyObject *const_cache, PyObject **obj)
Expand Down
29 changes: 26 additions & 3 deletions Python/executor_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading