diff --git a/panda/src/event/asyncFuture_ext.cxx b/panda/src/event/asyncFuture_ext.cxx index 0ac681f1b2..c181085280 100644 --- a/panda/src/event/asyncFuture_ext.cxx +++ b/panda/src/event/asyncFuture_ext.cxx @@ -332,8 +332,7 @@ add_done_callback(PyObject *self, PyObject *fn) { } PythonTask *task = new PythonTask(fn); - Py_DECREF(task->_args); - task->_args = PyTuple_Pack(1, self); + task->_args.assign(1, Py_NewRef(self)); task->_append_task = false; task->_ignore_return = true; diff --git a/panda/src/event/pythonTask.cxx b/panda/src/event/pythonTask.cxx index bc540a75ac..264442d27e 100644 --- a/panda/src/event/pythonTask.cxx +++ b/panda/src/event/pythonTask.cxx @@ -37,7 +37,6 @@ PythonTask:: PythonTask(PyObject *func_or_coro, const std::string &name) : AsyncTask(name), _function(nullptr), - _args(nullptr), _upon_death(nullptr), _owner(nullptr), _exception(nullptr), @@ -45,6 +44,7 @@ PythonTask(PyObject *func_or_coro, const std::string &name) : _exc_traceback(nullptr), _generator(nullptr), _fut_waiter(nullptr), + _append_task(true), _ignore_return(false), _registered_to_owner(false), _retrieved_exception(false) { @@ -63,7 +63,6 @@ PythonTask(PyObject *func_or_coro, const std::string &name) : nassert_raise("Invalid function passed to PythonTask"); } - set_args(Py_None, true); set_upon_death(Py_None); set_owner(Py_None); @@ -104,7 +103,9 @@ PythonTask:: // All of these may have already been cleared by __clear__. Py_XDECREF(_function); - Py_XDECREF(_args); + for (PyObject *arg : _args) { + Py_DECREF(arg); + } Py_XDECREF(__dict__); Py_XDECREF(_exception); Py_XDECREF(_exc_value); @@ -134,21 +135,23 @@ set_function(PyObject *function) { */ void PythonTask:: set_args(PyObject *args, bool append_task) { - Py_XDECREF(_args); - _args = nullptr; + pvector old_args = std::exchange(_args, {}); - if (args == Py_None) { - // None means no arguments; create an empty tuple. - _args = PyTuple_New(0); - } else { - if (PySequence_Check(args)) { - _args = PySequence_Tuple(args); + if (PySequence_Check(args)) { + Py_ssize_t len = PySequence_Size(args); + _args.resize(len); + + for (Py_ssize_t i = 0; i < len; ++i) { + _args[i] = PySequence_GetItem(args, i); } } - - if (_args == nullptr) { + else if (args != Py_None) { nassert_raise("Invalid args passed to PythonTask"); - _args = PyTuple_New(0); + return; + } + + for (PyObject *old_arg : old_args) { + Py_DECREF(old_arg); } _append_task = append_task; @@ -159,19 +162,18 @@ set_args(PyObject *args, bool append_task) { */ PyObject *PythonTask:: get_args() { + // If we want to append the task, we have to create a new tuple with space + // for one more at the end. We have to do this dynamically each time, to + // avoid storing the task itself in its own arguments list, and thereby + // creating a cyclical reference. + + size_t num_args = _args.size(); + PyObject *result = PyTuple_New(num_args + _append_task); + for (size_t i = 0; i < num_args; ++i) { + PyTuple_SET_ITEM(result, i, Py_NewRef(_args[i])); + } + if (_append_task) { - // If we want to append the task, we have to create a new tuple with space - // for one more at the end. We have to do this dynamically each time, to - // avoid storing the task itself in its own arguments list, and thereby - // creating a cyclical reference. - - int num_args = PyTuple_GET_SIZE(_args); - PyObject *with_task = PyTuple_New(num_args + 1); - for (int i = 0; i < num_args; ++i) { - PyObject *item = PyTuple_GET_ITEM(_args, i); - PyTuple_SET_ITEM(with_task, i, Py_NewRef(item)); - } - // Check whether we have a Python wrapper. This is not the case if the // object has been created by C++ and never been exposed to Python code. if (__self__ == nullptr) { @@ -180,12 +182,9 @@ get_args() { __self__ = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false); } - PyTuple_SET_ITEM(with_task, num_args, Py_NewRef(__self__)); - return with_task; - } - else { - return Py_NewRef(_args); + PyTuple_SET_ITEM(result, num_args, Py_NewRef(__self__)); } + return result; } /** @@ -376,7 +375,9 @@ int PythonTask:: __traverse__(visitproc visit, void *arg) { Py_VISIT(__self__); Py_VISIT(_function); - Py_VISIT(_args); + for (PyObject *arg : _args) { + Py_VISIT(arg); + } Py_VISIT(_upon_death); Py_VISIT(_owner); Py_VISIT(__dict__); @@ -390,7 +391,12 @@ __traverse__(visitproc visit, void *arg) { int PythonTask:: __clear__() { Py_CLEAR(_function); - Py_CLEAR(_args); + { + pvector old_args = std::exchange(_args, {}); + for (PyObject *arg : old_args) { + Py_DECREF(arg); + } + } Py_CLEAR(_upon_death); Py_CLEAR(_owner); Py_CLEAR(__dict__); @@ -590,9 +596,21 @@ do_python_task() { // We are calling the function directly. nassertr(_function != nullptr, DS_interrupt); - PyObject *args = get_args(); - result = PythonThread::call_python_func(_function, args); - Py_DECREF(args); + size_t nargs = _args.size(); + PyObject **args = (PyObject **)alloca(sizeof(PyObject *) * (nargs + 2)) + 1; + std::copy(_args.begin(), _args.end(), args); + + if (_append_task) { + // Check whether we have a Python wrapper. This is not the case if the + // object has been created by C++ and never been exposed to Python code. + if (__self__ == nullptr) { + // A __self__ instance does not exist, let's create one now. + ref(); + __self__ = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false); + } + args[nargs++] = __self__; + } + result = PythonThread::call_python_func(_function, args, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET); if (result != nullptr && PyGen_Check(result)) { // The function has yielded a generator. We will call into that diff --git a/panda/src/event/pythonTask.h b/panda/src/event/pythonTask.h index 148252e999..67ae3ed17a 100644 --- a/panda/src/event/pythonTask.h +++ b/panda/src/event/pythonTask.h @@ -114,7 +114,7 @@ private: private: PyObject *_function; - PyObject *_args; + pvector _args; PyObject *_upon_death; PyObject *_owner; diff --git a/panda/src/gobj/pythonTexturePoolFilter.cxx b/panda/src/gobj/pythonTexturePoolFilter.cxx index 0fc11b181d..f20cc094ce 100644 --- a/panda/src/gobj/pythonTexturePoolFilter.cxx +++ b/panda/src/gobj/pythonTexturePoolFilter.cxx @@ -101,17 +101,19 @@ pre_load(const Filename &orig_filename, const Filename &orig_alpha_filename, #endif // Wrap the arguments. - PyObject *args = Py_BuildValue("(OOiiNO)", - DTool_CreatePyInstance((void *)&orig_filename, Dtool_Filename, false, true), - DTool_CreatePyInstance((void *)&orig_alpha_filename, Dtool_Filename, false, true), - primary_file_num_channels, - alpha_file_channel, - PyBool_FromLong(read_mipmaps), - DTool_CreatePyInstance((void *)&options, Dtool_LoaderOptions, false, true) - ); + PyObject *args[7]; + args[1] = DTool_CreatePyInstance((void *)&orig_filename, Dtool_Filename, false, true); + args[2] = DTool_CreatePyInstance((void *)&orig_alpha_filename, Dtool_Filename, false, true); + args[3] = PyLong_FromLong(primary_file_num_channels); + args[4] = PyLong_FromLong(alpha_file_channel); + args[5] = PyBool_FromLong(read_mipmaps); + args[6] = DTool_CreatePyInstance((void *)&options, Dtool_LoaderOptions, false, true); - PyObject *result = PythonThread::call_python_func(_pre_load_func, args); - Py_DECREF(args); + PyObject *result = PythonThread::call_python_func(_pre_load_func, args + 1, 6 | PY_VECTORCALL_ARGUMENTS_OFFSET); + + for (size_t i = 1; i < 7; ++i) { + Py_DECREF(args[i]); + } PT(Texture) tex; if (result != nullptr) { @@ -162,11 +164,11 @@ post_load(Texture *tex) { #endif // Wrap the arguments. - PyObject *args = PyTuple_Pack(1, - DTool_CreatePyInstance(tex, Dtool_Texture, true, false) - ); - PyObject *result = PythonThread::call_python_func(_post_load_func, args); - Py_DECREF(args); + PyObject *args[2]; + args[1] = DTool_CreatePyInstance(tex, Dtool_Texture, true, false); + + PyObject *result = PythonThread::call_python_func(_post_load_func, args + 1, 1 | PY_VECTORCALL_ARGUMENTS_OFFSET); + Py_DECREF(args[1]); PT(Texture) result_tex; if (result != nullptr) { diff --git a/panda/src/pgraph/pythonLoaderFileType.cxx b/panda/src/pgraph/pythonLoaderFileType.cxx index aa4b84cceb..b33930cffe 100644 --- a/panda/src/pgraph/pythonLoaderFileType.cxx +++ b/panda/src/pgraph/pythonLoaderFileType.cxx @@ -305,19 +305,19 @@ load_file(const Filename &path, const LoaderOptions &options, #endif // Wrap the arguments. - PyObject *args = PyTuple_New(3); - PyTuple_SET_ITEM(args, 0, DTool_CreatePyInstance((void *)&path, Dtool_Filename, false, true)); - PyTuple_SET_ITEM(args, 1, DTool_CreatePyInstance((void *)&options, Dtool_LoaderOptions, false, true)); + PyObject *args[4]; + args[1] = DTool_CreatePyInstance((void *)&path, Dtool_Filename, false, true); + args[2] = DTool_CreatePyInstance((void *)&options, Dtool_LoaderOptions, false, true); if (record != nullptr) { record->ref(); - PyTuple_SET_ITEM(args, 2, DTool_CreatePyInstanceTyped((void *)record, Dtool_BamCacheRecord, true, false, record->get_type_index())); + args[3] = DTool_CreatePyInstanceTyped((void *)record, Dtool_BamCacheRecord, true, false, record->get_type_index()); } else { - PyTuple_SET_ITEM(args, 2, Py_NewRef(Py_None)); + args[3] = Py_NewRef(Py_None); } PT(PandaNode) node; - PyObject *result = PythonThread::call_python_func(_load_func, args); + PyObject *result = PythonThread::call_python_func(_load_func, args + 1, 3 | PY_VECTORCALL_ARGUMENTS_OFFSET); if (result != nullptr) { if (DtoolInstance_Check(result)) { node = (PandaNode *)DtoolInstance_UPCAST(result, Dtool_PandaNode); @@ -325,7 +325,9 @@ load_file(const Filename &path, const LoaderOptions &options, Py_DECREF(result); } - Py_DECREF(args); + Py_DECREF(args[1]); + Py_DECREF(args[2]); + Py_DECREF(args[3]); if (node == nullptr) { PyObject *exc_type = PyErr_Occurred(); @@ -372,13 +374,16 @@ save_file(const Filename &path, const LoaderOptions &options, #endif // Wrap the arguments. - PyObject *args = PyTuple_New(3); - PyTuple_SET_ITEM(args, 0, DTool_CreatePyInstance((void *)&path, Dtool_Filename, false, true)); - PyTuple_SET_ITEM(args, 1, DTool_CreatePyInstance((void *)&options, Dtool_LoaderOptions, false, true)); - PyTuple_SET_ITEM(args, 2, DTool_CreatePyInstanceTyped((void *)node, Dtool_PandaNode, true, false, node->get_type_index())); + PyObject *args[4]; + args[1] = DTool_CreatePyInstance((void *)&path, Dtool_Filename, false, true); + args[2] = DTool_CreatePyInstance((void *)&options, Dtool_LoaderOptions, false, true); + args[3] = DTool_CreatePyInstanceTyped((void *)node, Dtool_PandaNode, true, false, node->get_type_index()); + + PyObject *result = PythonThread::call_python_func(_save_func, args + 1, 3 | PY_VECTORCALL_ARGUMENTS_OFFSET); + Py_DECREF(args[1]); + Py_DECREF(args[2]); + Py_DECREF(args[3]); - PyObject *result = PythonThread::call_python_func(_load_func, args); - Py_DECREF(args); if (result != nullptr) { Py_DECREF(result); } else { diff --git a/panda/src/pipeline/pythonThread.cxx b/panda/src/pipeline/pythonThread.cxx index 84770c258e..eff5728aaf 100644 --- a/panda/src/pipeline/pythonThread.cxx +++ b/panda/src/pipeline/pythonThread.cxx @@ -123,7 +123,7 @@ set_args(PyObject *args) { * exception. */ PyObject *PythonThread:: -call_python_func(PyObject *function, PyObject *args) { +call_python_func(PyObject *function, PyObject **args, size_t nargsf) { Thread *current_thread = get_current_thread(); // Create a new Python thread state data structure, so Python can properly @@ -132,7 +132,7 @@ call_python_func(PyObject *function, PyObject *args) { if (current_thread == get_main_thread()) { // In the main thread, just call the function. - result = PyObject_Call(function, args, nullptr); + result = _PyObject_Vectorcall(function, args, nargsf, nullptr); if (result == nullptr) { if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_SystemExit)) { @@ -189,7 +189,7 @@ call_python_func(PyObject *function, PyObject *args) { PyThreadState_Swap(new_thread_state); // Call the user's function. - result = PyObject_Call(function, args, nullptr); + result = _PyObject_Vectorcall(function, args, nargsf, nullptr); if (result == nullptr && PyErr_Occurred()) { // We got an exception. Move the exception from the current thread into // the main thread, so it can be handled there. @@ -230,7 +230,7 @@ call_python_func(PyObject *function, PyObject *args) { gstate = PyGILState_Ensure(); // Call the user's function. - result = PyObject_Call(function, args, nullptr); + result = _PyObject_Vectorcall(function, args, nargsf, nullptr); if (result == nullptr && PyErr_Occurred()) { // We got an exception. Move the exception from the current thread into // the main thread, so it can be handled there. @@ -275,7 +275,9 @@ call_python_func(PyObject *function, PyObject *args) { */ void PythonThread:: thread_main() { - _result = call_python_func(_function, _args); + PyObject **args = &PyTuple_GET_ITEM(_args, 0); + Py_ssize_t nargs = PyTuple_GET_SIZE(_args); + _result = call_python_func(_function, args, (size_t)nargs); } #endif // HAVE_PYTHON diff --git a/panda/src/pipeline/pythonThread.h b/panda/src/pipeline/pythonThread.h index eb57351149..f9bf6c91e9 100644 --- a/panda/src/pipeline/pythonThread.h +++ b/panda/src/pipeline/pythonThread.h @@ -36,7 +36,8 @@ public: PyObject *get_args() const; void set_args(PyObject *); - static PyObject *call_python_func(PyObject *function, PyObject *args); + static PyObject *call_python_func(PyObject *function, PyObject **args, + size_t nargsf); PUBLISHED: MAKE_PROPERTY(args, get_args, set_args); diff --git a/panda/src/putil/bamReader_ext.cxx b/panda/src/putil/bamReader_ext.cxx index 84c5060e1e..e85606b440 100644 --- a/panda/src/putil/bamReader_ext.cxx +++ b/panda/src/putil/bamReader_ext.cxx @@ -41,15 +41,14 @@ static TypedWritable *factory_callback(const FactoryParams ¶ms){ BamReader *manager; parse_params(params, scan, manager); - PyObject *py_scan = DTool_CreatePyInstance(&scan, Dtool_DatagramIterator, false, false); - PyObject *py_manager = DTool_CreatePyInstance(manager, Dtool_BamReader, false, false); - PyObject *args = PyTuple_Pack(2, py_scan, py_manager); + PyObject *args[3]; + args[1] = DTool_CreatePyInstance(&scan, Dtool_DatagramIterator, false, false); + args[2] = DTool_CreatePyInstance(manager, Dtool_BamReader, false, false); // Now call the Python function. - PyObject *result = PythonThread::call_python_func(func, args); - Py_DECREF(args); - Py_DECREF(py_scan); - Py_DECREF(py_manager); + PyObject *result = PythonThread::call_python_func(func, args + 1, 2 | PY_VECTORCALL_ARGUMENTS_OFFSET); + Py_DECREF(args[1]); + Py_DECREF(args[2]); if (result == nullptr) { util_cat.error() diff --git a/panda/src/putil/pythonCallbackObject.cxx b/panda/src/putil/pythonCallbackObject.cxx index 4fc2da8173..8607273a6a 100644 --- a/panda/src/putil/pythonCallbackObject.cxx +++ b/panda/src/putil/pythonCallbackObject.cxx @@ -129,16 +129,13 @@ void PythonCallbackObject:: do_python_callback(CallbackData *cbdata) { nassertv(cbdata != nullptr); - // Wrap the cbdata up in a Python object, then put it in a tuple, for the - // argument list. - PyObject *pycbdata = - DTool_CreatePyInstanceTyped(cbdata, Dtool_TypedObject, - false, false, cbdata->get_type_index()); - PyObject *args = Py_BuildValue("(O)", pycbdata); - Py_DECREF(pycbdata); + // Wrap the cbdata up in a Python object. + PyObject *args[2]; + args[1] = DTool_CreatePyInstanceTyped(cbdata, Dtool_TypedObject, + false, false, cbdata->get_type_index()); - PyObject *result = PythonThread::call_python_func(_function, args); - Py_DECREF(args); + PyObject *result = PythonThread::call_python_func(_function, args + 1, 1 | PY_VECTORCALL_ARGUMENTS_OFFSET); + Py_DECREF(args[1]); if (result == nullptr) { if (PyErr_Occurred() != PyExc_SystemExit) {