]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-131738: optimize builtin any/all/tuple calls with a generator expression arg ...
authorIrit Katriel <1055913+iritkatriel@users.noreply.github.com>
Fri, 28 Mar 2025 10:35:20 +0000 (10:35 +0000)
committerGitHub <noreply@github.com>
Fri, 28 Mar 2025 10:35:20 +0000 (10:35 +0000)
16 files changed:
Include/internal/pycore_global_objects_fini_generated.h
Include/internal/pycore_global_strings.h
Include/internal/pycore_interp_structs.h
Include/internal/pycore_magic_number.h
Include/internal/pycore_opcode_utils.h
Include/internal/pycore_runtime_init_generated.h
Include/internal/pycore_unicodeobject_generated.h
Lib/opcode.py
Lib/test/test_builtin.py
Misc/NEWS.d/next/Core_and_Builtins/2025-03-25-20-38-06.gh-issue-131738.eCb0OQ.rst [new file with mode: 0644]
Objects/genobject.c
Python/bytecodes.c
Python/codegen.c
Python/executor_cases.c.h
Python/generated_cases.c.h
Python/pylifecycle.c

index 1a2e387f894865ea2ab3fb5e6a971978bc9c32a4..605c9e1c480c6e6b7f366e067936e12d10cc6f05 100644 (file)
@@ -792,7 +792,9 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(aggregate_class));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(alias));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(align));
+    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(all));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(allow_code));
+    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(any));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(append));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(arg));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(argdefs));
index 2177f8dc1a6a06120de226c003c165c077ff3ac0..76bc00cfaecf9b2f0745cd8edc28c31d6b17b71e 100644 (file)
@@ -283,7 +283,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)
index ee92a1e6f2dec90c1e94e8bcb027f23e6d483ac2..44a4231fbc5e30c10834321d0e68a8014f9c89de 100644 (file)
@@ -9,6 +9,7 @@ extern "C" {
 
 #include "pycore_ast_state.h"     // struct ast_state
 #include "pycore_llist.h"         // struct llist_node
+#include "pycore_opcode_utils.h"  // NUM_COMMON_CONSTANTS
 #include "pycore_pymath.h"        // _PY_SHORT_FLOAT_REPR
 #include "pycore_structs.h"       // PyHamtObject
 #include "pycore_tstate.h"        // _PyThreadStateImpl
@@ -912,6 +913,7 @@ struct _is {
     struct ast_state ast;
     struct types_state types;
     struct callable_cache callable_cache;
+    PyObject *common_consts[NUM_COMMON_CONSTANTS];
     bool jit;
     struct _PyExecutorObject *executor_list_head;
     size_t trace_run_counter;
index 839a86e5830e78ea67d79a9a7dc46eafd4a6552d..edcc52b337364f265ee96e507c2f73b595820e3b 100644 (file)
@@ -272,6 +272,7 @@ Known values:
     Python 3.14a6 3617 (Branch monitoring for async for loops)
     Python 3.14a6 3618 (Add oparg to END_ASYNC_FOR)
     Python 3.14a6 3619 (Renumber RESUME opcode from 149 to 128)
+    Python 3.14a6 3620 (Optimize bytecode for all/any/tuple called on a genexp)
 
     Python 3.15 will start with 3650
 
@@ -284,7 +285,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 \
index 0872231d1f2d11c907ae59bacbaa59f72de226b4..b3056e7bb84c69cbbe6cbbd0d9ad2ba2020b9dd7 100644 (file)
@@ -8,8 +8,6 @@ extern "C" {
 #  error "this header requires Py_BUILD_CORE define"
 #endif
 
-#include "opcode_ids.h"
-
 #define MAX_REAL_OPCODE 254
 
 #define IS_WITHIN_OPCODE_RANGE(opcode) \
@@ -67,7 +65,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
index 2582d32b88372a1029238f9c5b1d7e6406cec662..3927cb8adf14c5c98b64d7ce56e38afd295d032c 100644 (file)
@@ -790,7 +790,9 @@ extern "C" {
     INIT_ID(aggregate_class), \
     INIT_ID(alias), \
     INIT_ID(align), \
+    INIT_ID(all), \
     INIT_ID(allow_code), \
+    INIT_ID(any), \
     INIT_ID(append), \
     INIT_ID(arg), \
     INIT_ID(argdefs), \
index 2d26ca6949551e3ffd16debb7193ffa272dead9f..67c71986410eef6713543e3bf24d057fd3295cd7 100644 (file)
@@ -920,10 +920,18 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
     assert(PyUnicode_GET_LENGTH(string) != 1);
+    string = &_Py_ID(all);
+    _PyUnicode_InternStatic(interp, &string);
+    assert(_PyUnicode_CheckConsistency(string, 1));
+    assert(PyUnicode_GET_LENGTH(string) != 1);
     string = &_Py_ID(allow_code);
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
     assert(PyUnicode_GET_LENGTH(string) != 1);
+    string = &_Py_ID(any);
+    _PyUnicode_InternStatic(interp, &string);
+    assert(_PyUnicode_CheckConsistency(string, 1));
+    assert(PyUnicode_GET_LENGTH(string) != 1);
     string = &_Py_ID(append);
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
index ea7c27698dd338eebc1819f55a2818ed2e16405d..0e9520b68324997ea1061cfaade68554d4b5a6f4 100644 (file)
@@ -9,6 +9,7 @@ __all__ = ["cmp_op", "stack_effect", "hascompare", "opname", "opmap",
            "HAVE_ARGUMENT", "EXTENDED_ARG", "hasarg", "hasconst", "hasname",
            "hasjump", "hasjrel", "hasjabs", "hasfree", "haslocal", "hasexc"]
 
+import builtins
 import _opcode
 from _opcode import stack_effect
 
@@ -38,7 +39,8 @@ hasexc = [op for op in opmap.values() if _opcode.has_exc(op)]
 _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 = [builtins.AssertionError, builtins.NotImplementedError,
+                     builtins.tuple, builtins.all, builtins.any]
 _nb_ops = _opcode.get_nb_ops()
 
 hascompare = [opmap["COMPARE_OP"]]
index 5546e6345e9208d9c686912435895d3a43637186..27b14c36b51b5083cf50a530412596db72894f6f 100644 (file)
@@ -225,6 +225,8 @@ class BuiltinTest(ComplexesAreIdenticalMixin, unittest.TestCase):
         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)
@@ -238,9 +240,59 @@ class BuiltinTest(ComplexesAreIdenticalMixin, unittest.TestCase):
         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
+
+        global all, any, tuple
+        saved = all, any, tuple
+        try:
+            all = lambda x : "all"
+            any = lambda x : "any"
+            tuple = lambda x : "tuple"
+
+            overridden_outputs = [f() for f in funcs]
+        finally:
+            all, any, tuple = saved
+
+        self.assertEqual(overridden_outputs, ['all', 'any', 'tuple'])
+
+        # Now repeat, overriding the builtins module as well
+        saved = all, any, tuple
+        try:
+            builtins.all = all = lambda x : "all"
+            builtins.any = any = lambda x : "any"
+            builtins.tuple = tuple = lambda x : "tuple"
+
+            overridden_outputs = [f() for f in funcs]
+        finally:
+            all, any, tuple = saved
+            builtins.all, builtins.any, builtins.tuple = saved
+
+        self.assertEqual(overridden_outputs, ['all', 'any', 'tuple'])
+
+
     def test_ascii(self):
         self.assertEqual(ascii(''), '\'\'')
         self.assertEqual(ascii(0), '0')
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-03-25-20-38-06.gh-issue-131738.eCb0OQ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-03-25-20-38-06.gh-issue-131738.eCb0OQ.rst
new file mode 100644 (file)
index 0000000..ac99b2a
--- /dev/null
@@ -0,0 +1 @@
+Compiler emits optimized code for builtin any/all/tuple calls over a generator expression.
index 0238613c2a19df2448745d4c83e02967d82643e6..98b2c5004df8ac2c8e0b7bd7652f01c19904f512 100644 (file)
@@ -19,6 +19,8 @@
 #include "pycore_warnings.h"      // _PyErr_WarnUnawaitedCoroutine()
 
 
+#include "opcode_ids.h"           // RESUME, etc
+
 // Forward declarations
 static PyObject* gen_close(PyObject *, PyObject *);
 static PyObject* async_gen_asend_new(PyAsyncGenObject *, PyObject *);
index b6a482183b1d0bcbb99e7fb03a7ff537a25cbac8..b3b7441c31569a39c4eafba20803dc9ce742fba7 100644 (file)
@@ -1402,16 +1402,8 @@ dummy_func(
 
         inst(LOAD_COMMON_CONSTANT, ( -- value)) {
             // Keep in sync with _common_constants in opcode.py
-            // If we ever have more than two constants, use a lookup table
-            PyObject *val;
-            if (oparg == CONSTANT_ASSERTIONERROR) {
-                val = PyExc_AssertionError;
-            }
-            else {
-                assert(oparg == CONSTANT_NOTIMPLEMENTEDERROR);
-                val = PyExc_NotImplementedError;
-            }
-            value = PyStackRef_FromPyObjectImmortal(val);
+            assert(oparg < NUM_COMMON_CONSTANTS);
+            value = PyStackRef_FromPyObjectNew(tstate->interp->common_consts[oparg]);
         }
 
         inst(LOAD_BUILD_CLASS, ( -- bc)) {
index 8cc484e98d64af6b9446f330f0f546d8f826b68b..dc50737840f002059b8ba8a3ed85bbd74f9ab789 100644 (file)
@@ -3820,6 +3820,92 @@ update_start_location_to_match_attr(compiler *c, location loc,
     return loc;
 }
 
+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;
+    }
+
+    location loc = LOC(func);
+
+    int optimized = 0;
+    NEW_JUMP_TARGET_LABEL(c, skip_optimization);
+
+    int const_oparg = -1;
+    PyObject *initial_res = NULL;
+    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) {
+        ADDOP_I(c, loc, COPY, 1); // the function
+        ADDOP_I(c, loc, LOAD_COMMON_CONSTANT, const_oparg);
+        ADDOP_COMPARE(c, loc, Is);
+        ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, skip_optimization);
+        ADDOP(c, loc, POP_TOP);
+
+        if (const_oparg == CONSTANT_BUILTIN_TUPLE) {
+            ADDOP_I(c, loc, BUILD_LIST, 0);
+        }
+        expr_ty generator_exp = asdl_seq_GET(args, 0);
+        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_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);
+        }
+        else {
+            ADDOP_LOAD_CONST(c, loc, initial_res);
+        }
+
+        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)
@@ -3926,14 +4012,18 @@ codegen_call(compiler *c, expr_ty e)
     if (ret == 1) {
         return SUCCESS;
     }
+    NEW_JUMP_TARGET_LABEL(c, skip_normal_call);
     RETURN_IF_ERROR(check_caller(c, e->v.Call.func));
     VISIT(c, expr, e->v.Call.func);
+    RETURN_IF_ERROR(maybe_optimize_function_call(c, e, skip_normal_call));
     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
index b3c32c5c5d029fbc64834c2b89fb7b12ec1ec6d6..9306a6aea354358974f5f16e1011bccd810dcf3b 100644 (file)
             _PyStackRef value;
             oparg = CURRENT_OPARG();
             // Keep in sync with _common_constants in opcode.py
-            // If we ever have more than two constants, use a lookup table
-            PyObject *val;
-            if (oparg == CONSTANT_ASSERTIONERROR) {
-                val = PyExc_AssertionError;
-            }
-            else {
-                assert(oparg == CONSTANT_NOTIMPLEMENTEDERROR);
-                val = PyExc_NotImplementedError;
-            }
-            value = PyStackRef_FromPyObjectImmortal(val);
+            assert(oparg < NUM_COMMON_CONSTANTS);
+            value = PyStackRef_FromPyObjectNew(tstate->interp->common_consts[oparg]);
             stack_pointer[0] = value;
             stack_pointer += 1;
             assert(WITHIN_STACK_BOUNDS());
index 0314f983506b0d1f5e09fc76721e1dedf388ab3d..f1e22f6d3dd70036cefb6c4606d59a576b402561 100644 (file)
             INSTRUCTION_STATS(LOAD_COMMON_CONSTANT);
             _PyStackRef value;
             // Keep in sync with _common_constants in opcode.py
-            // If we ever have more than two constants, use a lookup table
-            PyObject *val;
-            if (oparg == CONSTANT_ASSERTIONERROR) {
-                val = PyExc_AssertionError;
-            }
-            else {
-                assert(oparg == CONSTANT_NOTIMPLEMENTEDERROR);
-                val = PyExc_NotImplementedError;
-            }
-            value = PyStackRef_FromPyObjectImmortal(val);
+            assert(oparg < NUM_COMMON_CONSTANTS);
+            value = PyStackRef_FromPyObjectNew(tstate->interp->common_consts[oparg]);
             stack_pointer[0] = value;
             stack_pointer += 1;
             assert(WITHIN_STACK_BOUNDS());
index 484583e1c79579f33ead9b36f9d2bd1aa8d685cb..934614e73b56f9992160a7505e0fea54b0d0e2ca 100644 (file)
@@ -790,6 +790,26 @@ pycore_init_builtins(PyThreadState *tstate)
     }
     interp->callable_cache.len = len;
 
+    PyObject *all = PyDict_GetItemWithError(builtins_dict, &_Py_ID(all));
+    if (!all) {
+        goto error;
+    }
+
+    PyObject *any = PyDict_GetItemWithError(builtins_dict, &_Py_ID(any));
+    if (!any) {
+        goto error;
+    }
+
+    interp->common_consts[CONSTANT_ASSERTIONERROR] = PyExc_AssertionError;
+    interp->common_consts[CONSTANT_NOTIMPLEMENTEDERROR] = PyExc_NotImplementedError;
+    interp->common_consts[CONSTANT_BUILTIN_TUPLE] = (PyObject*)&PyTuple_Type;
+    interp->common_consts[CONSTANT_BUILTIN_ALL] = all;
+    interp->common_consts[CONSTANT_BUILTIN_ANY] = any;
+
+    for (int i=0; i < NUM_COMMON_CONSTANTS; i++) {
+        assert(interp->common_consts[i] != NULL);
+    }
+
     PyObject *list_append = _PyType_Lookup(&PyList_Type, &_Py_ID(append));
     if (list_append == NULL) {
         goto error;