From d4d0b875775eba44fea3950ba787a9900102222d Mon Sep 17 00:00:00 2001 From: cuishuang Date: Fri, 14 Aug 2026 19:20:51 +0800 Subject: [PATCH 1/2] gh-155141: Improve stack-use estimation for keyword calls --- Lib/test/test_compile.py | 24 +++++++++++++++++++ ...08-14-19-00-00.gh-issue-155141.call-kw.rst | 2 ++ Python/codegen.c | 2 +- 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-08-14-19-00-00.gh-issue-155141.call-kw.rst diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index df473d59fff3d8e..959732fc6e4a83d 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -63,6 +63,30 @@ def test_argument_handling(self): self.assertRaises(SyntaxError, exec, 'def f(a = 0, a = 1): pass') self.assertRaises(SyntaxError, exec, 'def f(a): global a; a = 1') + def test_call_opcode_stack_use_limit(self): + def get_call_opcode(positional_count, keyword_count): + args = ["0"] * positional_count + args.extend(f"a{i}=0" for i in range(keyword_count)) + code = compile(f"f({', '.join(args)})", "", "exec") + return next( + instr.opname for instr in dis.get_instructions(code) + if instr.opname.startswith("CALL") + ) + + for positional_count, keyword_count, expected_opcode in [ + (0, 16, "CALL_KW"), + (15, 14, "CALL_KW"), + (15, 15, "CALL_FUNCTION_EX"), + ]: + with self.subTest( + positional_count=positional_count, + keyword_count=keyword_count, + ): + self.assertEqual( + get_call_opcode(positional_count, keyword_count), + expected_opcode, + ) + def test_syntax_error(self): self.assertRaises(SyntaxError, compile, "1+*3", "filename", "exec") diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-14-19-00-00.gh-issue-155141.call-kw.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-14-19-00-00.gh-issue-155141.call-kw.rst new file mode 100644 index 000000000000000..6e814294ecd0a22 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-14-19-00-00.gh-issue-155141.call-kw.rst @@ -0,0 +1,2 @@ +Improve the compiler's stack-use estimate for calls with keyword arguments, +allowing more calls to use the faster ``CALL_KW`` instruction. diff --git a/Python/codegen.c b/Python/codegen.c index bedf3b17c52ce44..0cb35980fd8f086 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -4427,7 +4427,7 @@ codegen_call_helper_impl(compiler *c, location loc, nelts = asdl_seq_LEN(args); nkwelts = asdl_seq_LEN(keywords); - if (nelts + nkwelts*2 > _PY_STACK_USE_GUIDELINE) { + if (nelts + nkwelts + (nkwelts != 0) > _PY_STACK_USE_GUIDELINE) { goto ex_call; } for (i = 0; i < nelts; i++) { From 319b159d416c60297a4de90efcf3061121ea3276 Mon Sep 17 00:00:00 2001 From: cuishuang Date: Sat, 22 Aug 2026 09:00:57 +0800 Subject: [PATCH 2/2] gh-155141: Address review comments --- .../2026-08-14-19-00-00.gh-issue-155141.call-kw.rst | 5 +++-- Python/codegen.c | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-14-19-00-00.gh-issue-155141.call-kw.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-14-19-00-00.gh-issue-155141.call-kw.rst index 6e814294ecd0a22..caee6e10168eabd 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-14-19-00-00.gh-issue-155141.call-kw.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-14-19-00-00.gh-issue-155141.call-kw.rst @@ -1,2 +1,3 @@ -Improve the compiler's stack-use estimate for calls with keyword arguments, -allowing more calls to use the faster ``CALL_KW`` instruction. +Compile pure-keyword calls with 16 to 29 keyword arguments using the faster +``CALL_KW`` instruction. This includes common cases such as dataclass +constructors with many fields. diff --git a/Python/codegen.c b/Python/codegen.c index 0cb35980fd8f086..ffff88f1c99ba0a 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -87,6 +87,9 @@ typedef _PyCompile_FBlockInfo fblockinfo; #define LOC(x) SRC_LOCATION_FROM_AST(x) +#define CALL_STACK_USE(nargs, nkwds) \ + ((nargs) + (nkwds) + ((nkwds) != 0)) + #define NEW_JUMP_TARGET_LABEL(C, NAME) \ jump_target_label NAME = _PyInstructionSequence_NewLabel(INSTR_SEQUENCE(C)); \ if (!IS_JUMP_TARGET_LABEL(NAME)) { \ @@ -4134,7 +4137,7 @@ maybe_optimize_method_call(compiler *c, expr_ty e) /* Check that there aren't too many arguments */ argsl = asdl_seq_LEN(args); kwdsl = asdl_seq_LEN(kwds); - if (argsl + kwdsl + (kwdsl != 0) >= _PY_STACK_USE_GUIDELINE) { + if (CALL_STACK_USE(argsl, kwdsl) >= _PY_STACK_USE_GUIDELINE) { return 0; } /* Check that there are no *varargs types of arguments. */ @@ -4427,7 +4430,7 @@ codegen_call_helper_impl(compiler *c, location loc, nelts = asdl_seq_LEN(args); nkwelts = asdl_seq_LEN(keywords); - if (nelts + nkwelts + (nkwelts != 0) > _PY_STACK_USE_GUIDELINE) { + if (CALL_STACK_USE(nelts, nkwelts) > _PY_STACK_USE_GUIDELINE) { goto ex_call; } for (i = 0; i < nelts; i++) {