Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions mypyc/analysis/dataflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
Branch,
Call,
CallC,
CallWrapper,
Cast,
ComparisonOp,
ControlOp,
Expand Down Expand Up @@ -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)

Expand Down
4 changes: 0 additions & 4 deletions mypyc/analysis/ircheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
Branch,
Call,
CallC,
CallWrapper,
Cast,
ComparisonOp,
ControlOp,
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 0 additions & 4 deletions mypyc/analysis/selfleaks.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
Branch,
Call,
CallC,
CallWrapper,
Cast,
ComparisonOp,
DecRef,
Expand Down Expand Up @@ -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)

Expand Down
24 changes: 6 additions & 18 deletions mypyc/codegen/emitfunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
TracebackAndGotoHandler,
c_array_initializer,
)
from mypyc.codegen.emitwrapper import is_fastcall_supported
from mypyc.common import (
NATIVE_PREFIX,
REG_PREFIX,
Expand All @@ -32,7 +31,6 @@
Branch,
Call,
CallC,
CallWrapper,
Cast,
ComparisonOp,
ControlOp,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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};")
Expand Down
9 changes: 4 additions & 5 deletions mypyc/codegen/emitmodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
)
Expand Down Expand Up @@ -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
)
Expand Down Expand Up @@ -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"
Expand Down
13 changes: 0 additions & 13 deletions mypyc/codegen/emitwrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
17 changes: 17 additions & 0 deletions mypyc/ir/func_ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -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("<INVALID_FUNC_DEF>", [], Block([]))


Expand Down
48 changes: 5 additions & 43 deletions mypyc/ir/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
10 changes: 3 additions & 7 deletions mypyc/ir/pprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -18,7 +18,6 @@
Branch,
Call,
CallC,
CallWrapper,
Cast,
ComparisonOp,
ControlOp,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down
20 changes: 17 additions & 3 deletions mypyc/irbuild/ll_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -41,7 +41,6 @@
Branch,
Call,
CallC,
CallWrapper,
Cast,
ComparisonOp,
Extend,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
8 changes: 3 additions & 5 deletions mypyc/lib-rt/CPy.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 7 additions & 4 deletions mypyc/lib-rt/generic_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down
Loading
Loading