From bf6dde8eede545af8dc35f7cb20c5e4cba1498d2 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 19 Oct 2026 10:23:15 +0100 Subject: [PATCH] [mypyc] Refactor: avoid the new for CallWrapper op Use LoadStatic + CallC instead of the dedicated CallWrapper op added in #22191. --- mypyc/analysis/dataflow.py | 4 -- mypyc/analysis/ircheck.py | 4 -- mypyc/analysis/selfleaks.py | 4 -- mypyc/codegen/emitfunc.py | 24 +++-------- mypyc/codegen/emitmodule.py | 9 ++-- mypyc/codegen/emitwrapper.py | 13 ------ mypyc/ir/func_ir.py | 17 ++++++++ mypyc/ir/ops.py | 48 +++------------------ mypyc/ir/pprint.py | 10 ++--- mypyc/irbuild/ll_builder.py | 20 +++++++-- mypyc/lib-rt/CPy.h | 8 ++-- mypyc/lib-rt/generic_ops.c | 11 +++-- mypyc/primitives/generic_ops.py | 21 +++++++++ mypyc/test-data/irbuild-classes.test | 64 +++++++++++++++++++++++----- mypyc/test/test_emitfunc.py | 7 +++ mypyc/transform/ir_transform.py | 9 ---- 16 files changed, 144 insertions(+), 129 deletions(-) diff --git a/mypyc/analysis/dataflow.py b/mypyc/analysis/dataflow.py index 95d7f9cea9759..babf7fbc6c87d 100644 --- a/mypyc/analysis/dataflow.py +++ b/mypyc/analysis/dataflow.py @@ -15,7 +15,6 @@ Branch, Call, CallC, - CallWrapper, Cast, ComparisonOp, ControlOp, @@ -210,9 +209,6 @@ def visit_dec_ref(self, op: DecRef) -> GenAndKill[T]: def visit_call(self, op: Call) -> GenAndKill[T]: return self.visit_register_op(op) - def visit_call_wrapper(self, op: CallWrapper) -> GenAndKill[T]: - return self.visit_register_op(op) - def visit_method_call(self, op: MethodCall) -> GenAndKill[T]: return self.visit_register_op(op) diff --git a/mypyc/analysis/ircheck.py b/mypyc/analysis/ircheck.py index b10fbf17d7011..384bcdbe5cd80 100644 --- a/mypyc/analysis/ircheck.py +++ b/mypyc/analysis/ircheck.py @@ -12,7 +12,6 @@ Branch, Call, CallC, - CallWrapper, Cast, ComparisonOp, ControlOp, @@ -388,9 +387,6 @@ def visit_call(self, op: Call) -> None: for arg_value, arg_runtime in zip(op.args, op.fn.sig.args): self.check_type_coercion(op, arg_value.type, arg_runtime.type) - def visit_call_wrapper(self, op: CallWrapper) -> None: - pass - def visit_method_call(self, op: MethodCall) -> None: # Similar to above, but we must look up method first. method_decl = op.receiver_type.class_ir.method_decl(op.method) diff --git a/mypyc/analysis/selfleaks.py b/mypyc/analysis/selfleaks.py index 4f071a9e28bd2..cdc7ef3fb4c68 100644 --- a/mypyc/analysis/selfleaks.py +++ b/mypyc/analysis/selfleaks.py @@ -15,7 +15,6 @@ Branch, Call, CallC, - CallWrapper, Cast, ComparisonOp, DecRef, @@ -115,9 +114,6 @@ def visit_call(self, op: Call) -> GenAndKill: return CLEAN return self.check_register_op(op) - def visit_call_wrapper(self, op: CallWrapper) -> GenAndKill: - return self.check_register_op(op) - def visit_method_call(self, op: MethodCall) -> GenAndKill: return self.check_register_op(op) diff --git a/mypyc/codegen/emitfunc.py b/mypyc/codegen/emitfunc.py index a04aa6575fa09..354059508d3cf 100644 --- a/mypyc/codegen/emitfunc.py +++ b/mypyc/codegen/emitfunc.py @@ -12,7 +12,6 @@ TracebackAndGotoHandler, c_array_initializer, ) -from mypyc.codegen.emitwrapper import is_fastcall_supported from mypyc.common import ( NATIVE_PREFIX, REG_PREFIX, @@ -32,7 +31,6 @@ Branch, Call, CallC, - CallWrapper, Cast, ComparisonOp, ControlOp, @@ -737,22 +735,6 @@ def visit_call(self, op: Call) -> None: cname = op.fn.cname(self.names) self.emit_line(f"{dest}{lib}{NATIVE_PREFIX}{cname}({args});") - def visit_call_wrapper(self, op: CallWrapper) -> None: - """Call the wrapper function of a native function.""" - dest = self.get_dest_assign(op) - wrapper = self.emitter.wrapper_function_call(op.fn) - args = self.reg(op.args) - kwargs = self.reg(op.kwargs) if op.kwargs is not None else "NULL" - if is_fastcall_supported(op.fn, self.emitter.capi_version): - # The wrapper of a function or a static method doesn't take self. - takes_self = int(op.fn.class_name is not None and op.fn.kind != FUNC_STATICMETHOD) - self.emit_line( - f"{dest}CPy_CallFastcallWrapper({wrapper}, {takes_self}, {args}, {kwargs});" - ) - else: - # The wrappers of __init__ and __new__ take the first argument as self. - self.emit_line(f"{dest}CPy_CallLegacyWrapper({wrapper}, {args}, {kwargs});") - def visit_method_call(self, op: MethodCall) -> None: """Call native method.""" dest = self.get_dest_assign(op) @@ -1044,6 +1026,12 @@ def visit_load_address(self, op: LoadAddress) -> None: elif isinstance(op.src, LoadStatic): prefix = PREFIX_MAP[op.src.namespace] src = self.emitter.static_name(op.src.identifier, op.src.module_name, prefix) + elif isinstance(op.src, FuncDecl): + # Omit '&': a local wrapper's name evaluates to its function pointer, + # while a wrapper in another group is already a pointer in the exports table. + wrapper = self.emitter.wrapper_function_call(op.src) + self.emit_line(f"{dest} = ({typ._ctype}){wrapper};") + return else: src = op.src self.emit_line(f"{dest} = ({typ._ctype})&{src};") diff --git a/mypyc/codegen/emitmodule.py b/mypyc/codegen/emitmodule.py index f5bac9f68e3ac..0f5093f3a258c 100644 --- a/mypyc/codegen/emitmodule.py +++ b/mypyc/codegen/emitmodule.py @@ -40,7 +40,6 @@ from mypyc.codegen.emitwrapper import ( generate_legacy_wrapper_function, generate_wrapper_function, - is_fastcall_supported, legacy_wrapper_function_header, wrapper_function_header, ) @@ -73,7 +72,7 @@ HeaderDep, SourceDep, ) -from mypyc.ir.func_ir import FuncIR +from mypyc.ir.func_ir import FuncIR, is_fastcall_supported from mypyc.ir.module_ir import ModuleIR, ModuleIRs, deserialize_modules from mypyc.ir.ops import DeserMaps, LoadLiteral from mypyc.ir.rtypes import RType @@ -633,7 +632,7 @@ def generate_function_declaration(fn: FuncIR, emitter: Emitter) -> None: # needs_export=True so Python-wrapper (CPyPy_) symbols are reachable from # other groups via the export table — needed for cross-group inherited # __init__ / __new__ slot dispatch under `separate=True`. - if is_fastcall_supported(fn, emitter.capi_version): + if is_fastcall_supported(fn): emitter.context.declarations[PREFIX + fn.cname(emitter.names)] = HeaderDeclaration( f"{wrapper_function_header(fn, emitter.names)};", needs_export=True ) @@ -774,7 +773,7 @@ def generate_c_for_modules(self) -> list[tuple[str, str]]: ) if fn.name != TOP_LEVEL_NAME and not fn.internal: emitter.emit_line() - if is_fastcall_supported(fn, emitter.capi_version): + if is_fastcall_supported(fn): generate_wrapper_function( fn, emitter, self.source_paths[module_name], module_name ) @@ -1250,7 +1249,7 @@ def emit_module_methods( if fn.decl.is_coroutine: continue name = short_id_from_name(fn.name, fn.decl.shortname, fn.line) - if is_fastcall_supported(fn, emitter.capi_version): + if is_fastcall_supported(fn): flag = "METH_FASTCALL" else: flag = "METH_VARARGS" diff --git a/mypyc/codegen/emitwrapper.py b/mypyc/codegen/emitwrapper.py index 9f21c88aa3e80..e21d919265329 100644 --- a/mypyc/codegen/emitwrapper.py +++ b/mypyc/codegen/emitwrapper.py @@ -37,19 +37,6 @@ ) from mypyc.namegen import NameGenerator - -def is_fastcall_supported(fn: FuncIR | FuncDecl, capi_version: tuple[int, int]) -> bool: - if fn.class_name is not None: - if fn.name == "__call__": - # We can use vectorcalls (PEP 590) when supported - return True - # The wrappers of __init__ and __new__ are called from tp_init and tp_new, - # which get a tuple and a dict. A call to the type object can skip them, see - # can_use_constructor_for_type_call(). - return fn.name != "__init__" and fn.name != "__new__" - return True - - # Generic vectorcall wrapper functions (Python 3.7+) # # A wrapper function has a signature like this: diff --git a/mypyc/ir/func_ir.py b/mypyc/ir/func_ir.py index f2a80ea692a12..ba81f1cb08315 100644 --- a/mypyc/ir/func_ir.py +++ b/mypyc/ir/func_ir.py @@ -362,6 +362,23 @@ def deserialize(cls, data: JsonDict, ctx: DeserMaps) -> FuncIR: ) +def is_fastcall_supported(fn: FuncIR | FuncDecl) -> bool: + """Does the wrapper function of fn use the vectorcall convention? + + If not, the wrapper takes the positional arguments in a tuple and the keyword + arguments in a dict. + """ + if fn.class_name is not None: + if fn.name == "__call__": + # We can use vectorcalls (PEP 590) when supported + return True + # The wrappers of __init__ and __new__ are called from tp_init and tp_new, + # which get a tuple and a dict. A call to the type object can skip them, see + # can_use_constructor_for_type_call(). + return fn.name != "__init__" and fn.name != "__new__" + return True + + INVALID_FUNC_DEF: Final = FuncDef("", [], Block([])) diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index 9f4197cfe2cd1..9776aef98b205 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -689,43 +689,6 @@ def accept(self, visitor: OpVisitor[T]) -> T: return visitor.visit_method_call(self) -@final -class CallWrapper(RegisterOp): - """Call the wrapper function of a native function: result = fn(*args, **kwargs) - - The wrapper function checks and unboxes the arguments, as when the function - is called from Python code. This is much slower than Call, but it can unpack - *args and **kwargs into ordinary arguments. - - Attributes: - fn: The native function - args: Tuple with the positional arguments (for a method, self or cls first) - kwargs: Dict with the keyword arguments, or None if there are none - """ - - error_kind = ERR_MAGIC - - def __init__(self, fn: FuncDecl, args: Value, kwargs: Value | None, line: int) -> None: - super().__init__(line) - self.fn = fn - self.args = args - self.kwargs = kwargs - self.type = object_rprimitive - - def sources(self) -> list[Value]: - if self.kwargs is None: - return [self.args] - return [self.args, self.kwargs] - - def set_sources(self, new: list[Value]) -> None: - self.args = new[0] - if self.kwargs is not None: - self.kwargs = new[1] - - def accept(self, visitor: OpVisitor[T]) -> T: - return visitor.visit_call_wrapper(self) - - @final class PrimitiveDescription: """Description of a primitive op. @@ -1853,13 +1816,16 @@ class LoadAddress(RegisterOp): type: Type of the loaded address(e.g. ptr/object_ptr) src: Source value (str for globals like 'PyList_Type', Register for temporary values or locals, LoadStatic - for statics.) + for statics, FuncDecl for the wrapper function of a + native function.) """ error_kind = ERR_NEVER is_borrowed = True - def __init__(self, type: RType, src: str | Register | LoadStatic, line: int = -1) -> None: + def __init__( + self, type: RType, src: str | Register | LoadStatic | FuncDecl, line: int = -1 + ) -> None: super().__init__(line) self.type = type self.src = src @@ -2050,10 +2016,6 @@ def visit_call(self, op: Call) -> T: def visit_method_call(self, op: MethodCall) -> T: raise NotImplementedError - @abstractmethod - def visit_call_wrapper(self, op: CallWrapper) -> T: - raise NotImplementedError - @abstractmethod def visit_cast(self, op: Cast) -> T: raise NotImplementedError diff --git a/mypyc/ir/pprint.py b/mypyc/ir/pprint.py index 62b7aebae2c40..fd64106b331b2 100644 --- a/mypyc/ir/pprint.py +++ b/mypyc/ir/pprint.py @@ -7,7 +7,7 @@ from typing import Any, Final from mypyc.common import short_name -from mypyc.ir.func_ir import FuncIR, all_values_full +from mypyc.ir.func_ir import FuncDecl, FuncIR, all_values_full from mypyc.ir.module_ir import ModuleIRs from mypyc.ir.ops import ( ERR_NEVER, @@ -18,7 +18,6 @@ Branch, Call, CallC, - CallWrapper, Cast, ComparisonOp, ControlOp, @@ -190,11 +189,6 @@ def visit_call(self, op: Call) -> str: s = self.format("%r = ", op) + s return s - def visit_call_wrapper(self, op: CallWrapper) -> str: - if op.kwargs is None: - return self.format("%r = %s(*%r)", op, op.fn.shortname, op.args) - return self.format("%r = %s(*%r, **%r)", op, op.fn.shortname, op.args, op.kwargs) - def visit_method_call(self, op: MethodCall) -> str: args = ", ".join(self.format("%r", arg) for arg in op.args) s = self.format("%r.%s(%s)", op.obj, op.method, args) @@ -309,6 +303,8 @@ def visit_load_address(self, op: LoadAddress) -> str: if op.src.module_name is not None: name = f"{op.src.module_name}.{name}" return self.format("%r = load_address %s :: %s", op, name, op.src.namespace) + elif isinstance(op.src, FuncDecl): + return self.format("%r = load_address %s :: wrapper", op, op.src.shortname) else: return self.format("%r = load_address %s", op, op.src) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 21e6e26064bfd..a499dc4814d6b 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -27,7 +27,7 @@ ) from mypyc.errors import Errors from mypyc.ir.class_ir import ClassIR, all_concrete_classes -from mypyc.ir.func_ir import FuncDecl, FuncSignature +from mypyc.ir.func_ir import FUNC_STATICMETHOD, FuncDecl, FuncSignature, is_fastcall_supported from mypyc.ir.ops import ( ERR_FALSE, ERR_NEVER, @@ -41,7 +41,6 @@ Branch, Call, CallC, - CallWrapper, Cast, ComparisonOp, Extend, @@ -146,6 +145,8 @@ from mypyc.primitives.exc_ops import err_occurred_op, keep_propagating_op from mypyc.primitives.float_ops import copysign_op, int_to_float_op from mypyc.primitives.generic_ops import ( + call_fastcall_wrapper_op, + call_legacy_wrapper_op, generic_len_op, generic_ssize_t_len_op, py_call_op, @@ -1391,7 +1392,20 @@ def call_wrapper( list(zip(args, arg_kinds, arg_names)), line, has_star=True, has_star2=True ) assert pos_args is not None - result = self.add(CallWrapper(decl, pos_args, kw_args, line)) + wrapper = self.add(LoadAddress(c_pointer_rprimitive, decl, line)) + if kw_args is None: + kw_args = Integer(0, object_rprimitive) + if is_fastcall_supported(decl): + # The wrapper of a function or a static method doesn't take self. + takes_self = decl.class_name is not None and decl.kind != FUNC_STATICMETHOD + result = self.call_c( + call_fastcall_wrapper_op, + [wrapper, Integer(int(takes_self), c_int_rprimitive), pos_args, kw_args], + line, + ) + else: + # The wrappers of __init__ and __new__ take the first argument as self. + result = self.call_c(call_legacy_wrapper_op, [wrapper, pos_args, kw_args], line) return self.coerce(result, decl.sig.ret_type, line) def native_args_to_positional( diff --git a/mypyc/lib-rt/CPy.h b/mypyc/lib-rt/CPy.h index 4edac8732f198..dfb7e43ed769b 100644 --- a/mypyc/lib-rt/CPy.h +++ b/mypyc/lib-rt/CPy.h @@ -786,11 +786,9 @@ PyObject *CPyNumber_Power(PyObject *base, PyObject *index); PyObject *CPyNumber_InPlacePower(PyObject *base, PyObject *index); // An omitted slice bound is represented by CPY_INT_TAG. PyObject *CPyObject_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end); -typedef PyObject *(*CPyFastcallWrapper)(PyObject *, PyObject *const *, size_t, PyObject *); -PyObject *CPy_CallFastcallWrapper(CPyFastcallWrapper wrapper, int takes_self, - PyObject *args, PyObject *kwargs); -PyObject *CPy_CallLegacyWrapper(PyCFunctionWithKeywords wrapper, PyObject *args, - PyObject *kwargs); +PyObject *CPy_CallFastcallWrapper(void *wrapper, int takes_self, PyObject *args, + PyObject *kwargs); +PyObject *CPy_CallLegacyWrapper(void *wrapper, PyObject *args, PyObject *kwargs); // List operations diff --git a/mypyc/lib-rt/generic_ops.c b/mypyc/lib-rt/generic_ops.c index 91d4dc47a74c8..58312033f4db1 100644 --- a/mypyc/lib-rt/generic_ops.c +++ b/mypyc/lib-rt/generic_ops.c @@ -85,11 +85,14 @@ PyObject *CPy_SetupObject(PyObject *type) { return NULL; } +typedef PyObject *(*CPyFastcallWrapper)(PyObject *, PyObject *const *, size_t, PyObject *); + // Call the wrapper function of a native function that uses the vectorcall convention, // with the positional arguments in a tuple and the keyword arguments in a dict (or NULL). // If takes_self is set, the first positional argument is passed to the wrapper as self. -PyObject *CPy_CallFastcallWrapper(CPyFastcallWrapper wrapper, int takes_self, - PyObject *args, PyObject *kwargs) { +PyObject *CPy_CallFastcallWrapper(void *wrapper_ptr, int takes_self, PyObject *args, + PyObject *kwargs) { + CPyFastcallWrapper wrapper = (CPyFastcallWrapper)wrapper_ptr; PyObject *const *items = &PyTuple_GET_ITEM(args, 0); Py_ssize_t nargs = PyTuple_GET_SIZE(args); PyObject *self = NULL; @@ -143,8 +146,8 @@ PyObject *CPy_CallFastcallWrapper(CPyFastcallWrapper wrapper, int takes_self, // Call the wrapper function of __init__ or __new__ of a native class, with the positional // arguments in a tuple and the keyword arguments in a dict (or NULL). These wrappers take // the first argument (self or cls) separately. -PyObject *CPy_CallLegacyWrapper(PyCFunctionWithKeywords wrapper, PyObject *args, - PyObject *kwargs) { +PyObject *CPy_CallLegacyWrapper(void *wrapper_ptr, PyObject *args, PyObject *kwargs) { + PyCFunctionWithKeywords wrapper = (PyCFunctionWithKeywords)wrapper_ptr; Py_ssize_t nargs = PyTuple_GET_SIZE(args); if (nargs == 0) { PyErr_SetString(PyExc_TypeError, "missing the self argument"); diff --git a/mypyc/primitives/generic_ops.py b/mypyc/primitives/generic_ops.py index 738f7bf8808ac..1355619211f96 100644 --- a/mypyc/primitives/generic_ops.py +++ b/mypyc/primitives/generic_ops.py @@ -15,6 +15,7 @@ from mypyc.ir.rtypes import ( bool_rprimitive, c_int_rprimitive, + c_pointer_rprimitive, c_pyssize_t_rprimitive, c_size_t_rprimitive, int_rprimitive, @@ -325,6 +326,26 @@ error_kind=ERR_MAGIC, ) +# Call a native function's vectorcall wrapper: func(*args, **kwargs) +# Arguments are (wrapper, takes_self, *args tuple, **kwargs dict or NULL). +# If takes_self is nonzero, pass the first positional argument separately as self. +call_fastcall_wrapper_op = custom_op( + arg_types=[c_pointer_rprimitive, c_int_rprimitive, object_rprimitive, object_rprimitive], + return_type=object_rprimitive, + c_function_name="CPy_CallFastcallWrapper", + error_kind=ERR_MAGIC, +) + +# Call a native class's __init__ or __new__ wrapper: func(*args, **kwargs) +# Arguments are (wrapper, *args tuple, **kwargs dict or NULL). +# Pass the first positional argument separately as self (or cls). +call_legacy_wrapper_op = custom_op( + arg_types=[c_pointer_rprimitive, object_rprimitive, object_rprimitive], + return_type=object_rprimitive, + c_function_name="CPy_CallLegacyWrapper", + error_kind=ERR_MAGIC, +) + # Call method with positional arguments: obj.method(arg1, ...) # Arguments are (object, attribute name, arg1, ...). py_method_call_op = custom_op( diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index 2077fa603ec2e..ea339d32af2db 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -758,8 +758,9 @@ def B.call_f(self, args): r1 :: ptr r2 :: object r3 :: tuple - r4 :: object - r5 :: int + r4 :: c_ptr + r5 :: object + r6 :: int L0: r0 = PyList_New(1) r1 = list_items r0 @@ -767,25 +768,28 @@ L0: keep_alive r0 r2 = CPyList_Extend(r0, args) r3 = PyList_AsTuple(r0) - r4 = A.f(*r3) - r5 = unbox(int, r4) - return r5 + r4 = load_address A.f :: wrapper + r5 = CPy_CallFastcallWrapper(r4, 1, r3, 0) + r6 = unbox(int, r5) + return r6 def B.call_f_kwargs(self, kwargs): self :: __main__.B kwargs, r0 :: dict r1 :: i32 r2 :: bit r3 :: tuple - r4 :: object - r5 :: int + r4 :: c_ptr + r5 :: object + r6 :: int L0: r0 = PyDict_New() r1 = CPyDict_UpdateInDisplay(r0, kwargs) r2 = r1 >= 0 :: signed r3 = PyTuple_Pack(1, self) - r4 = A.f(*r3, **r0) - r5 = unbox(int, r4) - return r5 + r4 = load_address A.f :: wrapper + r5 = CPy_CallFastcallWrapper(r4, 1, r3, r0) + r6 = unbox(int, r5) + return r6 def B.call_g(self, args, kwargs): self :: __main__.B args :: tuple @@ -796,6 +800,46 @@ L0: r1 = A.g(self, args, r0) return r1 +[case testNativeInitCallWithUnpackedArgs] +from typing import Any + +class A: + def __init__(self, x: int) -> None: + self.x = x + +class B(A): + def __init__(self, *args: Any) -> None: + # The wrapper of __init__ takes a tuple and a dict + super().__init__(*args) +[out] +def A.__init__(self, x): + self :: __main__.A + x :: int +L0: + self.x = x + return 1 +def B.__init__(self, args): + self :: __main__.B + args :: tuple + r0 :: list + r1 :: ptr + r2 :: object + r3 :: tuple + r4 :: c_ptr + r5 :: object + r6 :: None +L0: + r0 = PyList_New(1) + r1 = list_items r0 + buf_init_item r1, 0, self + keep_alive r0 + r2 = CPyList_Extend(r0, args) + r3 = PyList_AsTuple(r0) + r4 = load_address A.__init__ :: wrapper + r5 = CPy_CallLegacyWrapper(r4, r3, 0) + r6 = unbox(None, r5) + return 1 + [case testCannotAssignToClsArgument] from typing import Any, cast diff --git a/mypyc/test/test_emitfunc.py b/mypyc/test/test_emitfunc.py index c2d5274d0eb7a..550531976a07d 100644 --- a/mypyc/test/test_emitfunc.py +++ b/mypyc/test/test_emitfunc.py @@ -63,6 +63,7 @@ RVec, bool_rprimitive, c_int_rprimitive, + c_pointer_rprimitive, cstring_rprimitive, dict_rprimitive, int32_rprimitive, @@ -925,6 +926,12 @@ def test_load_address(self) -> None: """cpy_r_r0 = (PyObject *)&PyDict_Type;""", ) + def test_load_address_of_wrapper(self) -> None: + decl = FuncDecl("myfn", None, "mod", FuncSignature([], int_rprimitive)) + self.assert_emit( + LoadAddress(c_pointer_rprimitive, decl), """cpy_r_r0 = (void *)CPyPy_myfn;""" + ) + def test_assign_multi(self) -> None: t = RArray(object_rprimitive, 2) a = Register(t, "a") diff --git a/mypyc/transform/ir_transform.py b/mypyc/transform/ir_transform.py index d1c3e933ee963..0ae48b7c80d7a 100644 --- a/mypyc/transform/ir_transform.py +++ b/mypyc/transform/ir_transform.py @@ -12,7 +12,6 @@ Branch, Call, CallC, - CallWrapper, Cast, ComparisonOp, DecRef, @@ -163,9 +162,6 @@ def visit_dec_ref(self, op: DecRef) -> Value | None: def visit_call(self, op: Call) -> Value | None: return self.add(op) - def visit_call_wrapper(self, op: CallWrapper) -> Value | None: - return self.add(op) - def visit_method_call(self, op: MethodCall) -> Value | None: return self.add(op) @@ -305,11 +301,6 @@ def visit_dec_ref(self, op: DecRef) -> None: def visit_call(self, op: Call) -> None: op.args = [self.fix_op(arg) for arg in op.args] - def visit_call_wrapper(self, op: CallWrapper) -> None: - op.args = self.fix_op(op.args) - if op.kwargs is not None: - op.kwargs = self.fix_op(op.kwargs) - def visit_method_call(self, op: MethodCall) -> None: op.obj = self.fix_op(op.obj) op.args = [self.fix_op(arg) for arg in op.args]