Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2ee0c45

Browse files
eendebakptclaude
andcommitted
gh-86199: Fix CI: move kwargs conversion out of the instruction body
Taking the address of a local (&dupkey) inside _MAKE_CALLARGS_A_TUPLE prevents MSVC from emitting the required tail call in the tail-calling interpreter build (error C4737). Move the mapping-to-dict conversion into a new helper, _PyEval_KwargsToDict(), so the op body has no address-taken locals. Also update test_dict_merge: f(**d) no longer emits DICT_MERGE, so use f(**d, **e) there and add a test that f(**d) traces through _MAKE_CALLARGS_A_TUPLE without _DICT_MERGE. Co-Authored-By: Claude Fable 5.1 <[email protected]>
1 parent fc0af8f commit 2ee0c45

7 files changed

Lines changed: 61 additions & 221 deletions

File tree

Include/internal/pycore_ceval.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ PyAPI_FUNC(void) _PyEval_FormatAwaitableError(PyThreadState *tstate, PyTypeObjec
298298
PyAPI_FUNC(void) _PyEval_FormatExcCheckArg(PyThreadState *tstate, PyObject *exc, const char *format_str, PyObject *obj);
299299
PyAPI_FUNC(void) _PyEval_FormatExcUnbound(PyThreadState *tstate, PyCodeObject *co, int oparg);
300300
PyAPI_FUNC(void) _PyEval_FormatKwargsError(PyThreadState *tstate, PyObject *func, PyObject *kwargs, PyObject *dupkey);
301+
PyAPI_FUNC(PyObject *) _PyEval_KwargsToDict(PyThreadState *tstate, PyObject *func, PyObject *kwargs);
301302
PyAPI_FUNC(PyObject *) _PyEval_ImportFrom(PyThreadState *, PyObject *, PyObject *);
302303

303304
PyAPI_FUNC(PyObject *) _PyEval_LazyImportName(

Lib/test/test_capi/test_opt.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5800,11 +5800,12 @@ def testfunc(n):
58005800

58015801
def test_dict_merge(self):
58025802
def testfunc(n):
5803-
d = {"a": 1, "b": 2}
5803+
d = {"a": 1}
5804+
e = {"b": 2}
58045805
def f(**kwargs):
58055806
return kwargs
58065807
for _ in range(n):
5807-
x = f(**d)
5808+
x = f(**d, **e)
58085809
return x
58095810

58105811
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
@@ -5815,6 +5816,23 @@ def f(**kwargs):
58155816
self.assertGreaterEqual(count_ops(ex, "_POP_TOP_NOP"), 1)
58165817
self.assertLessEqual(count_ops(ex, "_POP_TOP"), 2)
58175818

5819+
def test_call_function_ex_single_kwargs_unpack(self):
5820+
# gh-86199: f(**d) no longer copies d with DICT_MERGE
5821+
def testfunc(n):
5822+
d = {"a": 1, "b": 2}
5823+
def f(**kwargs):
5824+
return kwargs
5825+
for _ in range(n):
5826+
x = f(**d)
5827+
return x
5828+
5829+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
5830+
self.assertEqual(res, {"a": 1, "b": 2})
5831+
uops = get_opnames(ex)
5832+
5833+
self.assertNotIn("_DICT_MERGE", uops)
5834+
self.assertIn("_MAKE_CALLARGS_A_TUPLE", uops)
5835+
58185836
def test_list_extend(self):
58195837
def testfunc(n):
58205838
a = [1, 2, 3]

Modules/_testinternalcapi/test_cases.c.h

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

Python/bytecodes.c

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5630,20 +5630,11 @@ dummy_func(
56305630
}
56315631
PyObject *kwargs_o = PyStackRef_AsPyObjectBorrow(kwargs);
56325632
if (kwargs_o != NULL && !PyDict_CheckExact(kwargs_o)) {
5633-
PyObject *dict_o = PyDict_New();
5633+
PyObject *dict_o = _PyEval_KwargsToDict(
5634+
tstate, PyStackRef_AsPyObjectBorrow(func), kwargs_o);
56345635
if (dict_o == NULL) {
56355636
ERROR_NO_POP();
56365637
}
5637-
PyObject *dupkey = NULL;
5638-
int err = _PyDict_MergeUniq(dict_o, kwargs_o, &dupkey);
5639-
if (err < 0) {
5640-
_PyEval_FormatKwargsError(tstate,
5641-
PyStackRef_AsPyObjectBorrow(func), kwargs_o, dupkey);
5642-
Py_XDECREF(dupkey);
5643-
Py_DECREF(dict_o);
5644-
ERROR_NO_POP();
5645-
}
5646-
assert(dupkey == NULL);
56475638
_PyStackRef temp = kwargs;
56485639
kwargs = PyStackRef_FromPyObjectSteal(dict_o);
56495640
PyStackRef_CLOSE(temp);

Python/ceval.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3493,6 +3493,26 @@ _PyEval_FormatKwargsError(PyThreadState *tstate, PyObject *func, PyObject *kwarg
34933493
}
34943494
}
34953495

3496+
/* Return a new exact dict with the items of the mapping 'kwargs',
3497+
raising the same TypeError as DICT_MERGE on failure. */
3498+
PyObject *
3499+
_PyEval_KwargsToDict(PyThreadState *tstate, PyObject *func, PyObject *kwargs)
3500+
{
3501+
PyObject *dict = PyDict_New();
3502+
if (dict == NULL) {
3503+
return NULL;
3504+
}
3505+
PyObject *dupkey = NULL;
3506+
if (_PyDict_MergeUniq(dict, kwargs, &dupkey) < 0) {
3507+
_PyEval_FormatKwargsError(tstate, func, kwargs, dupkey);
3508+
Py_XDECREF(dupkey);
3509+
Py_DECREF(dict);
3510+
return NULL;
3511+
}
3512+
assert(dupkey == NULL);
3513+
return dict;
3514+
}
3515+
34963516
void
34973517
_PyEval_FormatExcCheckArg(PyThreadState *tstate, PyObject *exc,
34983518
const char *format_str, PyObject *obj)

Python/executor_cases.c.h

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

0 commit comments

Comments
 (0)