diff --git a/docs/api-reference/function-index.rst b/docs/api-reference/function-index.rst index b75861cf2..cefcc63e2 100644 --- a/docs/api-reference/function-index.rst +++ b/docs/api-reference/function-index.rst @@ -108,6 +108,8 @@ HPy Core API Function Index * :c:func:`HPy_And` * :c:func:`HPy_AsPyObject` * :c:func:`HPy_Bytes` +* :c:func:`HPy_Call` +* :c:func:`HPy_CallMethod` * :c:func:`HPy_CallTupleDict` * :c:func:`HPy_Close` * :c:func:`HPy_Compile_s` diff --git a/docs/api-reference/hpy-call.rst b/docs/api-reference/hpy-call.rst new file mode 100644 index 000000000..d685898f1 --- /dev/null +++ b/docs/api-reference/hpy-call.rst @@ -0,0 +1,5 @@ +HPy Call API +============ + +.. autocmodule:: autogen/public_api.h + :members: HPy_Call,HPy_CallMethod,HPy_CallTupleDict diff --git a/docs/api-reference/index.rst b/docs/api-reference/index.rst index fe81487c3..44055d76f 100644 --- a/docs/api-reference/index.rst +++ b/docs/api-reference/index.rst @@ -28,6 +28,7 @@ between the modes. hpy-ctx hpy-object hpy-type + hpy-call hpy-field hpy-global hpy-gil diff --git a/docs/porting-guide.rst b/docs/porting-guide.rst index 36aebf1c5..5614f5993 100644 --- a/docs/porting-guide.rst +++ b/docs/porting-guide.rst @@ -240,6 +240,7 @@ with the code for the :term:`CPython ABI` mode, so it is guaranteed to be correc `PyNumber_Xor `_ :c:func:`HPy_Xor` `PyObject_ASCII `_ :c:func:`HPy_ASCII` `PyObject_Bytes `_ :c:func:`HPy_Bytes` + `PyObject_Call `_ :c:func:`HPy_CallTupleDict` `PyObject_DelItem `_ :c:func:`HPy_DelItem` `PyObject_GetAttr `_ :c:func:`HPy_GetAttr` `PyObject_GetAttrString `_ :c:func:`HPy_GetAttr_s` @@ -258,6 +259,8 @@ with the code for the :term:`CPython ABI` mode, so it is guaranteed to be correc `PyObject_Str `_ :c:func:`HPy_Str` `PyObject_Type `_ :c:func:`HPy_Type` `PyObject_TypeCheck `_ :c:func:`HPy_TypeCheck` + `PyObject_Vectorcall `_ :c:func:`HPy_Call` + `PyObject_VectorcallMethod `_ :c:func:`HPy_CallMethod` `PySequence_Contains `_ :c:func:`HPy_Contains` `PySlice_AdjustIndices `_ :c:func:`HPySlice_AdjustIndices` `PySlice_Unpack `_ :c:func:`HPySlice_Unpack` diff --git a/hpy/debug/src/autogen_debug_ctx_init.h b/hpy/debug/src/autogen_debug_ctx_init.h index 93670b601..6c242cfe4 100644 --- a/hpy/debug/src/autogen_debug_ctx_init.h +++ b/hpy/debug/src/autogen_debug_ctx_init.h @@ -69,6 +69,8 @@ DHPy debug_ctx_InPlaceXor(HPyContext *dctx, DHPy h1, DHPy h2); DHPy debug_ctx_InPlaceOr(HPyContext *dctx, DHPy h1, DHPy h2); int debug_ctx_Callable_Check(HPyContext *dctx, DHPy h); DHPy debug_ctx_CallTupleDict(HPyContext *dctx, DHPy callable, DHPy args, DHPy kw); +DHPy debug_ctx_Call(HPyContext *dctx, DHPy callable, const DHPy *args, size_t nargs, DHPy kwnames); +DHPy debug_ctx_CallMethod(HPyContext *dctx, DHPy name, const DHPy *args, size_t nargs, DHPy kwnames); void debug_ctx_FatalError(HPyContext *dctx, const char *message); void debug_ctx_Err_SetString(HPyContext *dctx, DHPy h_type, const char *utf8_message); void debug_ctx_Err_SetObject(HPyContext *dctx, DHPy h_type, DHPy h_value); @@ -333,6 +335,8 @@ static inline void debug_ctx_init_fields(HPyContext *dctx, HPyContext *uctx) dctx->ctx_InPlaceOr = &debug_ctx_InPlaceOr; dctx->ctx_Callable_Check = &debug_ctx_Callable_Check; dctx->ctx_CallTupleDict = &debug_ctx_CallTupleDict; + dctx->ctx_Call = &debug_ctx_Call; + dctx->ctx_CallMethod = &debug_ctx_CallMethod; dctx->ctx_FatalError = &debug_ctx_FatalError; dctx->ctx_Err_SetString = &debug_ctx_Err_SetString; dctx->ctx_Err_SetObject = &debug_ctx_Err_SetObject; diff --git a/hpy/debug/src/debug_ctx.c b/hpy/debug/src/debug_ctx.c index 6fa038cf1..fbcb0b21e 100644 --- a/hpy/debug/src/debug_ctx.c +++ b/hpy/debug/src/debug_ctx.c @@ -610,3 +610,75 @@ DHPy debug_ctx_Unicode_Substring(HPyContext *dctx, DHPy str, HPy_ssize_t start, ctx_info->is_valid = true; return DHPy_open(dctx, universal_result); } + +DHPy debug_ctx_Call(HPyContext *dctx, DHPy dh_callable, const DHPy *dh_args, size_t nargs, DHPy dh_kwnames) +{ + HPyDebugCtxInfo *ctx_info; + HPyContext *uctx; + + ctx_info = get_ctx_info(dctx); + if (!ctx_info->is_valid) { + report_invalid_debug_context(); + } + + UHPy uh_callable = DHPy_unwrap(dctx, dh_callable); + UHPy uh_kwnames = DHPy_unwrap(dctx, dh_kwnames); + uctx = ctx_info->info->uctx; + HPy_ssize_t nkw; + if (!HPy_IsNull(uh_kwnames)) { + if (!HPyTuple_Check(uctx, uh_kwnames)) { + HPy_FatalError(uctx, "HPy_Call arg 'kwnames' must be a tuple object or HPy_NULL"); + } + nkw = HPy_Length(uctx, uh_kwnames); + if (nkw < 0) { + return HPy_NULL; + } + } else { + nkw = 0; + } + const size_t n_all_args = nargs + nkw; + UHPy *uh_args = (UHPy *)alloca(n_all_args * sizeof(UHPy)); + for(size_t i=0; i < n_all_args; i++) { + uh_args[i] = DHPy_unwrap(dctx, dh_args[i]); + } + ctx_info->is_valid = false; + DHPy dh_result = DHPy_open(dctx, HPy_Call(uctx, uh_callable, uh_args, nargs, uh_kwnames)); + ctx_info->is_valid = true; + return dh_result; +} + +DHPy debug_ctx_CallMethod(HPyContext *dctx, DHPy dh_name, const DHPy *dh_args, size_t nargs, DHPy dh_kwnames) +{ + HPyDebugCtxInfo *ctx_info; + HPyContext *uctx; + + ctx_info = get_ctx_info(dctx); + if (!ctx_info->is_valid) { + report_invalid_debug_context(); + } + + UHPy uh_name = DHPy_unwrap(dctx, dh_name); + UHPy uh_kwnames = DHPy_unwrap(dctx, dh_kwnames); + uctx = ctx_info->info->uctx; + HPy_ssize_t nkw; + if (!HPy_IsNull(uh_kwnames)) { + if (!HPyTuple_Check(uctx, uh_kwnames)) { + HPy_FatalError(uctx, "HPy_CallMethod arg 'kwnames' must be a tuple object or HPy_NULL"); + } + nkw = HPy_Length(uctx, uh_kwnames); + if (nkw < 0) { + return HPy_NULL; + } + } else { + nkw = 0; + } + const size_t n_all_args = nargs + nkw; + UHPy *uh_args = (UHPy *)alloca(n_all_args * sizeof(UHPy)); + for(size_t i=0; i < n_all_args; i++) { + uh_args[i] = DHPy_unwrap(dctx, dh_args[i]); + } + ctx_info->is_valid = false; + DHPy dh_result = DHPy_open(dctx, HPy_CallMethod(uctx, uh_name, uh_args, nargs, uh_kwnames)); + ctx_info->is_valid = true; + return dh_result; +} diff --git a/hpy/devel/include/hpy/cpython/misc.h b/hpy/devel/include/hpy/cpython/misc.h index d236b4a91..5665460de 100644 --- a/hpy/devel/include/hpy/cpython/misc.h +++ b/hpy/devel/include/hpy/cpython/misc.h @@ -276,6 +276,30 @@ HPyAPI_FUNC HPy HPy_CallTupleDict(HPyContext *ctx, HPy callable, HPy args, HPy k return ctx_CallTupleDict(ctx, callable, args, kw); } +#if PY_VERSION_HEX < 0x03090000 +# define PyObject_Vectorcall _PyObject_Vectorcall +#endif + +HPyAPI_FUNC HPy HPy_Call(HPyContext *ctx, HPy callable, const HPy *args, size_t nargs, HPy kwnames) +{ + assert(sizeof(HPy) == sizeof(PyObject *)); + return _py2h(PyObject_Vectorcall(_h2py(callable), (PyObject *const *)args, nargs, _h2py(kwnames))); +} + +#if PY_VERSION_HEX < 0x03090000 +# undef PyObject_Vectorcall +#endif + +HPyAPI_FUNC HPy HPy_CallMethod(HPyContext *ctx, HPy name, const HPy *args, size_t nargs, HPy kwnames) +{ +#if PY_VERSION_HEX >= 0x03090000 + assert(sizeof(HPy) == sizeof(PyObject *)); + return _py2h(PyObject_VectorcallMethod(_h2py(name), (PyObject *const *)args, nargs, _h2py(kwnames))); +#else + return ctx_CallMethod(ctx, name, args, nargs, kwnames); +#endif +} + HPyAPI_FUNC void _HPy_Dump(HPyContext *ctx, HPy h) { ctx_Dump(ctx, h); diff --git a/hpy/devel/include/hpy/inline_helpers.h b/hpy/devel/include/hpy/inline_helpers.h index d642af869..ba09657d5 100644 --- a/hpy/devel/include/hpy/inline_helpers.h +++ b/hpy/devel/include/hpy/inline_helpers.h @@ -459,4 +459,79 @@ HPySlice_AdjustIndices(HPyContext *_HPy_UNUSED_ARG(ctx), HPy_ssize_t length, return 0; } +/** + * Call a method of a Python object. + * + * This is a convenience function for calling a method. It uses + * :c:func:`HPy_GetAttr_s` and :c:func:`HPy_CallTupleDict` to perform the method + * call. + * + * :param ctx: + * The execution context. + * :param utf8_name: + * The name (UTF-8 encoded C string) of the method. Must not be ``NULL``. + * :param receiver: + * A handle to the receiver of the call (i.e. the ``self``). Must not be + * ``HPy_NULL``. + * :param args: + * A handle to a tuple containing the positional arguments (must not be + * ``HPy_NULL`` but can, of course, be empty). + * :param kw: + * A handle to a Python dictionary containing the keyword arguments (may be + * ``HPy_NULL``). + * + * :returns: + * The result of the call on success, or ``HPy_NULL`` in case of an error. + */ +HPyAPI_INLINE_HELPER HPy +HPy_CallMethodTupleDict_s(HPyContext *ctx, const char *utf8_name, HPy receiver, HPy args, HPy kw) +{ + HPy method = HPy_GetAttr_s(ctx, receiver, utf8_name); + if (HPy_IsNull(method)) { + return HPy_NULL; + } + + HPy result = HPy_CallTupleDict(ctx, method, args, kw); + HPy_Close(ctx, method); + return result; +} + +/** + * Call a method of a Python object. + * + * This is a convenience function for calling a method. It uses + * :c:func:`HPy_GetAttr` and :c:func:`HPy_CallTupleDict` to perform the method + * call. + * + * :param ctx: + * The execution context. + * :param name: + * A handle to the name (a Unicode object) of the method. Must not be + * ``HPy_NULL``. + * :param receiver: + * A handle to the receiver of the call (i.e. the ``self``). Must not be + * ``HPy_NULL``. + * :param args: + * A handle to a tuple containing the positional arguments (must not be + * ``HPy_NULL`` but can, of course, be empty). + * :param kw: + * A handle to a Python dictionary containing the keyword arguments (may be + * ``HPy_NULL``). + * + * :returns: + * The result of the call on success, or ``HPy_NULL`` in case of an error. + */ +HPyAPI_INLINE_HELPER HPy +HPy_CallMethodTupleDict(HPyContext *ctx, HPy name, HPy receiver, HPy args, HPy kw) +{ + HPy method = HPy_GetAttr(ctx, receiver, name); + if (HPy_IsNull(method)) { + return HPy_NULL; + } + + HPy result = HPy_CallTupleDict(ctx, method, args, kw); + HPy_Close(ctx, method); + return result; +} + #endif //HPY_INLINE_HELPERS_H diff --git a/hpy/devel/include/hpy/runtime/ctx_funcs.h b/hpy/devel/include/hpy/runtime/ctx_funcs.h index 959782cfa..4fc1f319f 100644 --- a/hpy/devel/include/hpy/runtime/ctx_funcs.h +++ b/hpy/devel/include/hpy/runtime/ctx_funcs.h @@ -12,6 +12,8 @@ _HPy_HIDDEN HPy ctx_Bytes_FromStringAndSize(HPyContext *ctx, const char *v, // ctx_call.c _HPy_HIDDEN HPy ctx_CallTupleDict(HPyContext *ctx, HPy callable, HPy args, HPy kw); +_HPy_HIDDEN HPy ctx_Call(HPyContext *ctx, HPy callable, const HPy *args, size_t nargs, HPy kwnames); +_HPy_HIDDEN HPy ctx_CallMethod(HPyContext *ctx, HPy name, const HPy *args, size_t nargs, HPy kwnames); // ctx_err.c _HPy_HIDDEN int ctx_Err_Occurred(HPyContext *ctx); diff --git a/hpy/devel/include/hpy/universal/autogen_ctx.h b/hpy/devel/include/hpy/universal/autogen_ctx.h index b45c126f5..6133fc633 100644 --- a/hpy/devel/include/hpy/universal/autogen_ctx.h +++ b/hpy/devel/include/hpy/universal/autogen_ctx.h @@ -275,4 +275,6 @@ struct _HPyContext_s { HPy (*ctx_Dict_Copy)(HPyContext *ctx, HPy h); int (*ctx_Slice_Unpack)(HPyContext *ctx, HPy slice, HPy_ssize_t *start, HPy_ssize_t *stop, HPy_ssize_t *step); int (*ctx_SetCallFunction)(HPyContext *ctx, HPy h, HPyCallFunction *func); + HPy (*ctx_Call)(HPyContext *ctx, HPy callable, const HPy *args, size_t nargs, HPy kwnames); + HPy (*ctx_CallMethod)(HPyContext *ctx, HPy name, const HPy *args, size_t nargs, HPy kwnames); }; diff --git a/hpy/devel/include/hpy/universal/autogen_trampolines.h b/hpy/devel/include/hpy/universal/autogen_trampolines.h index 9b463d04a..0e57d3f1b 100644 --- a/hpy/devel/include/hpy/universal/autogen_trampolines.h +++ b/hpy/devel/include/hpy/universal/autogen_trampolines.h @@ -246,6 +246,14 @@ HPyAPI_FUNC HPy HPy_CallTupleDict(HPyContext *ctx, HPy callable, HPy args, HPy k return ctx->ctx_CallTupleDict ( ctx, callable, args, kw ); } +HPyAPI_FUNC HPy HPy_Call(HPyContext *ctx, HPy callable, const HPy *args, size_t nargs, HPy kwnames) { + return ctx->ctx_Call ( ctx, callable, args, nargs, kwnames ); +} + +HPyAPI_FUNC HPy HPy_CallMethod(HPyContext *ctx, HPy name, const HPy *args, size_t nargs, HPy kwnames) { + return ctx->ctx_CallMethod ( ctx, name, args, nargs, kwnames ); +} + HPyAPI_FUNC HPy HPyErr_SetString(HPyContext *ctx, HPy h_type, const char *utf8_message) { ctx->ctx_Err_SetString ( ctx, h_type, utf8_message ); return HPy_NULL; } diff --git a/hpy/devel/src/runtime/ctx_call.c b/hpy/devel/src/runtime/ctx_call.c index a6f6b3ee3..e85fd236b 100644 --- a/hpy/devel/src/runtime/ctx_call.c +++ b/hpy/devel/src/runtime/ctx_call.c @@ -1,5 +1,8 @@ #include #include "hpy.h" +#if defined(_MSC_VER) +# include /* for alloca() */ +#endif #ifndef HPY_ABI_CPYTHON // for _h2py and _py2h @@ -36,3 +39,81 @@ ctx_CallTupleDict(HPyContext *ctx, HPy callable, HPy args, HPy kw) } return _py2h(obj); } + +#if PY_VERSION_HEX < 0x03090000 +# define PyObject_Vectorcall _PyObject_Vectorcall +#endif + +_HPy_HIDDEN HPy +ctx_Call(HPyContext *ctx, HPy h_callable, const HPy *h_args, size_t nargs, HPy h_kwnames) +{ + PyObject *kwnames; + size_t n_all_args; + + if (HPy_IsNull(h_kwnames)) { + kwnames = NULL; + n_all_args = nargs; + } else { + kwnames = _h2py(h_kwnames); + assert(kwnames != NULL); + assert(PyTuple_Check(kwnames)); + n_all_args = nargs + PyTuple_GET_SIZE(kwnames); + assert(n_all_args >= nargs); + } + + /* Since we already allocate a fresh args array, we make it one element + larger and set PY_VECTORCALL_ARGUMENTS_OFFSET to avoid further + allocations from CPython. */ + PyObject **args = (PyObject **) alloca((n_all_args + 1) * sizeof(PyObject *)); + for (size_t i = 0; i < n_all_args; i++) { + args[i+1] = _h2py(h_args[i]); + } + + return _py2h(PyObject_Vectorcall(_h2py(h_callable), args+1, + nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames)); +} + +#if PY_VERSION_HEX < 0x03090000 +# undef PyObject_Vectorcall +#endif + +_HPy_HIDDEN HPy +ctx_CallMethod(HPyContext *ctx, HPy h_name, const HPy *h_args, size_t nargs, + HPy h_kwnames) +{ + PyObject *result, *kwnames; + size_t n_all_args; + + if (HPy_IsNull(h_kwnames)) { + kwnames = NULL; + n_all_args = nargs; + } else { + kwnames = _h2py(h_kwnames); + assert(kwnames != NULL); + assert(PyTuple_Check(kwnames)); + n_all_args = nargs + PyTuple_GET_SIZE(kwnames); + assert(n_all_args >= nargs); + } + + /* Since we already allocate a fresh args array, we make it one element + larger and set PY_VECTORCALL_ARGUMENTS_OFFSET to avoid further + allocations from CPython. */ + PyObject **args = (PyObject **) alloca( + (n_all_args + 1) * sizeof(PyObject *)); + for (size_t i = 0; i < n_all_args; i++) { + args[i+1] = _h2py(h_args[i]); + } + +#if PY_VERSION_HEX < 0x03090000 + PyObject *method = PyObject_GetAttr(args[1], _h2py(h_name)); + if (method == NULL) + return HPy_NULL; + result = _PyObject_Vectorcall(method, &args[2], + (nargs-1) | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames); + Py_DECREF(method); +#else + result = PyObject_VectorcallMethod(_h2py(h_name), args+1, + nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames); +#endif + return _py2h(result); +} diff --git a/hpy/tools/autogen/conf.py b/hpy/tools/autogen/conf.py index 333583535..4a7a001e9 100644 --- a/hpy/tools/autogen/conf.py +++ b/hpy/tools/autogen/conf.py @@ -41,6 +41,8 @@ 'HPy_Contains': 'PySequence_Contains', 'HPy_Length': 'PyObject_Length', 'HPy_CallTupleDict': None, + 'HPy_Call': None, # 'PyObject_Vectorcall', no auto arg conversion + 'HPy_CallMethod': None, # 'PyObject_VectorcallMethod',no auto arg conversion 'HPy_FromPyObject': None, 'HPy_AsPyObject': None, '_HPy_AsStruct_Object': None, diff --git a/hpy/tools/autogen/debug.py b/hpy/tools/autogen/debug.py index 9ee28857e..b2d3bd065 100644 --- a/hpy/tools/autogen/debug.py +++ b/hpy/tools/autogen/debug.py @@ -93,6 +93,8 @@ class autogen_debug_wrappers(AutoGenFile): 'HPyType_GetName', 'HPyType_IsSubtype', 'HPyUnicode_Substring', + 'HPy_Call', + 'HPy_CallMethod', } def generate(self): diff --git a/hpy/tools/autogen/public_api.h b/hpy/tools/autogen/public_api.h index 0879e3289..a25062eda 100644 --- a/hpy/tools/autogen/public_api.h +++ b/hpy/tools/autogen/public_api.h @@ -233,9 +233,79 @@ HPy HPy_InPlaceOr(HPyContext *ctx, HPy h1, HPy h2); HPy_ID(134) int HPyCallable_Check(HPyContext *ctx, HPy h); + +/** + * Call a Python object. + * + * :param ctx: + * The execution context. + * :param callable: + * A handle to the Python object to call (must not be ``HPy_NULL``). + * :param args: + * A handle to a tuple containing the positional arguments (must not be + * ``HPy_NULL`` but can, of course, be empty). + * :param kw: + * A handle to a Python dictionary containing the keyword arguments (may be + * ``HPy_NULL``). + * + * :returns: + * The result of the call on success, or ``HPy_NULL`` in case of an error. + */ HPy_ID(135) HPy HPy_CallTupleDict(HPyContext *ctx, HPy callable, HPy args, HPy kw); +/** + * Call a Python object. + * + * :param ctx: + * The execution context. + * :param callable: + * A handle to the Python object to call (must not be ``HPy_NULL``). + * :param args: + * A pointer to an array of positional and keyword arguments. This argument + * must not be ``NULL`` if ``nargs > 0`` or + * ``HPy_Length(ctx, kwnames) > 0``. + * :param nargs: + * The number of positional arguments in ``args``. + * :param kwnames: + * A handle to the tuple of keyword argument names (may be ``HPy_NULL``). + * The values of the keyword arguments are also passed in ``args`` appended + * to the positional arguments. Argument ``nargs`` does not include the + * keyword argument count. + * + * :returns: + * The result of the call on success, or ``HPy_NULL`` in case of an error. + */ +HPy_ID(261) +HPy HPy_Call(HPyContext *ctx, HPy callable, const HPy *args, size_t nargs, HPy kwnames); + +/** + * Call a method of a Python object. + * + * :param ctx: + * The execution context. + * :param name: + * A handle to the name (a Unicode object) of the method. Must not be + * ``HPy_NULL``. + * :param args: + * A pointer to an array of the arguments. The receiver is ``args[0]``, and + * the positional and keyword arguments are starting at ``args[1]``. This + * argument must not be ``NULL`` since a receiver is always required. + * :param nargs: + * The number of positional arguments in ``args`` including the receiver at + * ``args[0]`` (therefore, ``nargs`` must be at least ``1``). + * :param kwnames: + * A handle to the tuple of keyword argument names (may be ``HPy_NULL``). + * The values of the keyword arguments are also passed in ``args`` appended + * to the positional arguments. Argument ``nargs`` does not include the + * keyword argument count. + * + * :returns: + * The result of the call on success, or ``HPy_NULL`` in case of an error. + */ +HPy_ID(262) +HPy HPy_CallMethod(HPyContext *ctx, HPy name, const HPy *args, size_t nargs, HPy kwnames); + /* pyerrors.h */ HPy_ID(136) void HPy_FatalError(HPyContext *ctx, const char *message); diff --git a/hpy/trace/src/autogen_trace_ctx_init.h b/hpy/trace/src/autogen_trace_ctx_init.h index 3e7e64981..4e452b7e2 100644 --- a/hpy/trace/src/autogen_trace_ctx_init.h +++ b/hpy/trace/src/autogen_trace_ctx_init.h @@ -69,6 +69,8 @@ HPy trace_ctx_InPlaceXor(HPyContext *tctx, HPy h1, HPy h2); HPy trace_ctx_InPlaceOr(HPyContext *tctx, HPy h1, HPy h2); int trace_ctx_Callable_Check(HPyContext *tctx, HPy h); HPy trace_ctx_CallTupleDict(HPyContext *tctx, HPy callable, HPy args, HPy kw); +HPy trace_ctx_Call(HPyContext *tctx, HPy callable, const HPy *args, size_t nargs, HPy kwnames); +HPy trace_ctx_CallMethod(HPyContext *tctx, HPy name, const HPy *args, size_t nargs, HPy kwnames); void trace_ctx_Err_SetString(HPyContext *tctx, HPy h_type, const char *utf8_message); void trace_ctx_Err_SetObject(HPyContext *tctx, HPy h_type, HPy h_value); HPy trace_ctx_Err_SetFromErrnoWithFilename(HPyContext *tctx, HPy h_type, const char *filename_fsencoded); @@ -191,8 +193,8 @@ static inline void trace_ctx_init_info(HPyTraceInfo *info, HPyContext *uctx) { info->magic_number = HPY_TRACE_MAGIC; info->uctx = uctx; - info->call_counts = (uint64_t *)calloc(261, sizeof(uint64_t)); - info->durations = (_HPyTime_t *)calloc(261, sizeof(_HPyTime_t)); + info->call_counts = (uint64_t *)calloc(263, sizeof(uint64_t)); + info->durations = (_HPyTime_t *)calloc(263, sizeof(_HPyTime_t)); info->on_enter_func = HPy_NULL; info->on_exit_func = HPy_NULL; } @@ -350,6 +352,8 @@ static inline void trace_ctx_init_fields(HPyContext *tctx, HPyContext *uctx) tctx->ctx_InPlaceOr = &trace_ctx_InPlaceOr; tctx->ctx_Callable_Check = &trace_ctx_Callable_Check; tctx->ctx_CallTupleDict = &trace_ctx_CallTupleDict; + tctx->ctx_Call = &trace_ctx_Call; + tctx->ctx_CallMethod = &trace_ctx_CallMethod; tctx->ctx_FatalError = uctx->ctx_FatalError; tctx->ctx_Err_SetString = &trace_ctx_Err_SetString; tctx->ctx_Err_SetObject = &trace_ctx_Err_SetObject; diff --git a/hpy/trace/src/autogen_trace_func_table.c b/hpy/trace/src/autogen_trace_func_table.c index 0df1adc90..7a1a16332 100644 --- a/hpy/trace/src/autogen_trace_func_table.c +++ b/hpy/trace/src/autogen_trace_func_table.c @@ -12,7 +12,7 @@ #include "trace_internal.h" -#define TRACE_NFUNC 178 +#define TRACE_NFUNC 180 #define NO_FUNC "" static const char *trace_func_table[] = { @@ -277,6 +277,8 @@ static const char *trace_func_table[] = { "ctx_Dict_Copy", "ctx_Slice_Unpack", "ctx_SetCallFunction", + "ctx_Call", + "ctx_CallMethod", NULL /* sentinel */ }; @@ -287,7 +289,7 @@ int hpy_trace_get_nfunc(void) const char * hpy_trace_get_func_name(int idx) { - if (idx >= 0 && idx < 261) + if (idx >= 0 && idx < 263) return trace_func_table[idx]; return NULL; } diff --git a/hpy/trace/src/autogen_trace_wrappers.c b/hpy/trace/src/autogen_trace_wrappers.c index 6d19954aa..cd8a4b130 100644 --- a/hpy/trace/src/autogen_trace_wrappers.c +++ b/hpy/trace/src/autogen_trace_wrappers.c @@ -778,6 +778,32 @@ HPy trace_ctx_CallTupleDict(HPyContext *tctx, HPy callable, HPy args, HPy kw) return res; } +HPy trace_ctx_Call(HPyContext *tctx, HPy callable, const HPy *args, size_t nargs, HPy kwnames) +{ + HPyTraceInfo *info = hpy_trace_on_enter(tctx, 261); + HPyContext *uctx = info->uctx; + _HPyTime_t _ts_start, _ts_end; + _HPyClockStatus_t r0, r1; + r0 = get_monotonic_clock(&_ts_start); + HPy res = HPy_Call(uctx, callable, args, nargs, kwnames); + r1 = get_monotonic_clock(&_ts_end); + hpy_trace_on_exit(info, 261, r0, r1, &_ts_start, &_ts_end); + return res; +} + +HPy trace_ctx_CallMethod(HPyContext *tctx, HPy name, const HPy *args, size_t nargs, HPy kwnames) +{ + HPyTraceInfo *info = hpy_trace_on_enter(tctx, 262); + HPyContext *uctx = info->uctx; + _HPyTime_t _ts_start, _ts_end; + _HPyClockStatus_t r0, r1; + r0 = get_monotonic_clock(&_ts_start); + HPy res = HPy_CallMethod(uctx, name, args, nargs, kwnames); + r1 = get_monotonic_clock(&_ts_end); + hpy_trace_on_exit(info, 262, r0, r1, &_ts_start, &_ts_end); + return res; +} + void trace_ctx_Err_SetString(HPyContext *tctx, HPy h_type, const char *utf8_message) { HPyTraceInfo *info = hpy_trace_on_enter(tctx, 137); diff --git a/hpy/universal/src/autogen_ctx_def.h b/hpy/universal/src/autogen_ctx_def.h index 6c1e5d0dc..f8a455a61 100644 --- a/hpy/universal/src/autogen_ctx_def.h +++ b/hpy/universal/src/autogen_ctx_def.h @@ -74,6 +74,8 @@ struct _HPyContext_s g_universal_ctx = { .ctx_InPlaceOr = &ctx_InPlaceOr, .ctx_Callable_Check = &ctx_Callable_Check, .ctx_CallTupleDict = &ctx_CallTupleDict, + .ctx_Call = &ctx_Call, + .ctx_CallMethod = &ctx_CallMethod, .ctx_FatalError = &ctx_FatalError, .ctx_Err_SetString = &ctx_Err_SetString, .ctx_Err_SetObject = &ctx_Err_SetObject, diff --git a/test/test_call.py b/test/test_call.py index 2cb6971f7..370206394 100644 --- a/test/test_call.py +++ b/test/test_call.py @@ -18,6 +18,23 @@ def argument_combinations(self, **items): if not args and not kw: yield {} + def argument_combinations_tuple(self, **items): + """ Same as 'argument_combinations' but returns a tuple where + the first element is the argument tuple and the second is + a dict that may contain the keywords dict. + """ + items = list(items.items()) + for i in range(len(items) + 1): + args = tuple(item[1] for item in items[:i]) + kw = dict(items[i:]) + yield args, kw + if not args: + yield tuple(), kw + if not kw: + yield args, {} + if not args and not kw: + yield tuple(), {} + def test_hpy_calltupledict(self): import pytest mod = self.make_module(""" @@ -88,6 +105,208 @@ def g(): with pytest.raises(TypeError): mod.call(f, kw=None) + def test_hpy_callmethodtupledict(self): + import pytest + mod = self.make_module(""" + HPyDef_METH(call, "call", HPyFunc_KEYWORDS) + static HPy call_impl(HPyContext *ctx, HPy self, + const HPy *args, size_t nargs, HPy kwnames) + { + HPy result, result_0, result_1; + HPy receiver = HPy_NULL; + HPy h_name = HPy_NULL; + HPy m_args = HPy_NULL; + const char *s_name = ""; + HPyTracker ht; + static const char *kwlist[] = { "receiver", "name", "args", NULL }; + if (!HPyArg_ParseKeywords(ctx, &ht, args, nargs, kwnames, "OO|O", + kwlist, &receiver, &h_name, &m_args)) { + return HPy_NULL; + } + s_name = HPyUnicode_AsUTF8AndSize(ctx, h_name, NULL); + if (s_name == NULL) { + HPyTracker_Close(ctx, ht); + return HPy_NULL; + } + + result_0 = HPy_CallMethodTupleDict(ctx, h_name, receiver, m_args, HPy_NULL); + if (HPy_IsNull(result_0)) { + HPyTracker_Close(ctx, ht); + return HPy_NULL; + } + + result_1 = HPy_CallMethodTupleDict_s(ctx, s_name, receiver, m_args, HPy_NULL); + if (HPy_IsNull(result_1)) { + HPyTracker_Close(ctx, ht); + HPy_Close(ctx, result_0); + return HPy_NULL; + } + + HPyTracker_Close(ctx, ht); + result = HPyTuple_Pack(ctx, 2, result_0, result_1); + HPy_Close(ctx, result_0); + HPy_Close(ctx, result_1); + return result; + } + @EXPORT(call) + @INIT + """) + + test_args = ( + # (receiver, method, args_tuple) + dict(receiver={"hello": 1, "world": 2}, name="keys", args=tuple()), + dict(receiver="Hello, World", name="find", args=("Wo", )), + ) + + for kw in test_args: + res = getattr(kw["receiver"], kw["name"])(*kw["args"]) + assert mod.call(**kw) == (res, res) + + with pytest.raises(AttributeError): + mod.call(receiver=dict(), name="asdf", args=tuple()) + + with pytest.raises(TypeError): + mod.call(receiver="Hello, World", name="find") + + with pytest.raises(TypeError): + mod.call(receiver="Hello, World", name="find", args=("1", ) * 100) + + def test_hpy_call(self): + import pytest + mod = self.make_module(""" + #define SELF 1 + + HPyDef_METH(call, "call", HPyFunc_KEYWORDS) + static HPy call_impl(HPyContext *ctx, HPy self, + const HPy *args, size_t nargs, HPy kwnames) + { + if (nargs < SELF) { + HPyErr_SetString(ctx, ctx->h_TypeError, "HPy_Call requires a receiver"); + return HPy_NULL; + } + return HPy_Call(ctx, args[0], args + SELF, nargs - SELF, kwnames); + } + @EXPORT(call) + @INIT + """) + + def foo(): + raise ValueError + + def listify(*args): + return args + + def f(a, b): + return a + b + + def g(): + return "this is g" + + class KwDict(dict): + def __getitem__(self, key): + return "key=" + str(key); + + test_args = ( + # (receiver, args_tuple, kwd) + (dict, (dict(a=0, b=1), ), {}), + (dict, tuple(), dict(a=0, b=1)), + ) + for receiver, args_tuple, kwd in test_args: + assert mod.call(receiver, *args_tuple, **kwd) == receiver(*args_tuple, **kwd) + + # NULL dict for keywords + mod.call(dict) + + # dict subclass for keywords + kwdict = KwDict(x=11, y=12, z=13) + assert mod.call(dict, **kwdict) == dict(kwdict) + + with pytest.raises(ValueError): + mod.call(foo) + with pytest.raises(TypeError): + mod.call() + + # large amount of args + r = range(1000) + assert mod.call(listify, *r) == listify(*r) + + # test passing arguments with handles of the correct type -- + # i.e. args is a tuple or a null handle, kw is a dict or a null handle. + for args, kwd in self.argument_combinations_tuple(a=1, b=2): + assert mod.call(f, *args, **kwd) == 3 + for args, kwd in self.argument_combinations_tuple(a=1): + with pytest.raises(TypeError): + mod.call(f, *args, **kwd) + for args, kwd in self.argument_combinations_tuple(): + with pytest.raises(TypeError): + mod.call(f, *args, **kwd) + for args, kwd in self.argument_combinations_tuple(): + assert mod.call(g, *args, **kwd) == "this is g" + for args, kwd in self.argument_combinations_tuple(object=2): + assert mod.call(str, *args, **kwd) == "2" + for args, kwd in self.argument_combinations_tuple(): + with pytest.raises(TypeError): + mod.call("not callable", *args, **kwd) + for args, kwd in self.argument_combinations_tuple(unknown=2): + with pytest.raises(TypeError): + mod.call("not callable", *args, **kwd) + + def test_hpy_callmethod(self): + import pytest + mod = self.make_module(""" + #define NAME 1 + + HPyDef_METH(call, "call", HPyFunc_KEYWORDS) + static HPy call_impl(HPyContext *ctx, HPy self, + const HPy *args, size_t nargs, HPy kwnames) + { + if (nargs < NAME) { + HPyErr_SetString(ctx, ctx->h_TypeError, "HPy_CallMethod requires a receiver and a method name"); + return HPy_NULL; + } + // 'args[0]' is the name + return HPy_CallMethod(ctx, args[0], args + NAME, nargs - NAME, kwnames); + } + @EXPORT(call) + @INIT + """) + + class Dummy: + not_callable = 123 + + def f(self, a, b): + return a + b + + def g(self): + return 'this is g' + + test_obj = Dummy() + + # test passing arguments with handles of the correct type -- + # i.e. args is a tuple or a null handle, kw is a dict or a null handle. + for args, kwd in self.argument_combinations_tuple(a=1, b=2): + assert mod.call('f', test_obj, *args, **kwd) == 3 + for args, kwd in self.argument_combinations_tuple(a=1): + with pytest.raises(TypeError): + mod.call('f', test_obj, *args, **kwd) + for args, kwd in self.argument_combinations_tuple(a=1, b=2, c=3): + with pytest.raises(TypeError): + mod.call('f', test_obj, *args, **kwd) + for args, kwd in self.argument_combinations_tuple(): + with pytest.raises(TypeError): + mod.call('f', test_obj, *args, **kwd) + for args, kwd in self.argument_combinations_tuple(): + assert mod.call('g', test_obj, *args, **kwd) == 'this is g' + for args, kwd in self.argument_combinations_tuple(): + with pytest.raises(TypeError): + mod.call('not_callable', test_obj, *args, **kwd) + for args, kwd in self.argument_combinations_tuple(unknown=2): + with pytest.raises(TypeError): + mod.call('not_callable', test_obj, *args, **kwd) + for args, kwd in self.argument_combinations_tuple(): + with pytest.raises(AttributeError): + mod.call('embedded null byte', test_obj, *args, **kwd) + def test_hpycallable_check(self): mod = self.make_module(""" HPyDef_METH(f, "f", HPyFunc_O)