11 changes: 5 additions & 6 deletions pypy/module/_cffi_backend/ctypefunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def __init__(self, space, fargs, fresult, ellipsis,
# is computed here.
builder = CifDescrBuilder(fargs, fresult, abi)
try:
builder.rawallocate(self)
self.cif_descr = builder.rawallocate(self.space)
except OperationError as e:
if not e.match(space, space.w_NotImplementedError):
raise
Expand Down Expand Up @@ -91,7 +91,7 @@ def new_ctypefunc_completing_argtypes(self, args_w):
ctypefunc.fargs = fvarargs
ctypefunc.ctitem = self.ctitem
#ctypefunc.cif_descr = NULL --- already provided as the default
CifDescrBuilder(fvarargs, self.ctitem, self.abi).rawallocate(ctypefunc)
ctypefunc.cif_descr = CifDescrBuilder(fvarargs, self.ctitem, self.abi).rawallocate(space)
return ctypefunc

@rgc.must_be_light_finalizer
Expand Down Expand Up @@ -503,8 +503,7 @@ def fb_extra_fields(self, cif_descr):
cif_descr.atypes = self.atypes

@jit.dont_look_inside
def rawallocate(self, ctypefunc):
space = ctypefunc.space
def rawallocate(self, space):
self.space = space

# compute the total size needed in the CIF_DESCRIPTION buffer
Expand All @@ -522,8 +521,6 @@ def rawallocate(self, ctypefunc):
rawmem = lltype.malloc(CIF_DESCRIPTION_P.TO, self.nb_bytes,
flavor='raw')

# the buffer is automatically managed from the W_CTypeFunc instance
ctypefunc.cif_descr = rawmem

# call again fb_build() to really build the libffi data structures
self.bufferp = rffi.cast(rffi.CCHARP, rawmem)
Expand All @@ -542,3 +539,5 @@ def rawallocate(self, ctypefunc):
if res != clibffi.FFI_OK:
raise oefmt(space.w_SystemError,
"libffi failed to build this function type")
# the buffer is automatically managed from the W_CTypeFunc instance
return rawmem
2 changes: 1 addition & 1 deletion pypy/module/cpyext/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def ferror(fp):
constant_names = """
Py_TPFLAGS_READY Py_TPFLAGS_READYING
METH_COEXIST METH_STATIC METH_CLASS Py_TPFLAGS_BASETYPE
METH_NOARGS METH_VARARGS METH_KEYWORDS METH_FASTCALL METH_O
METH_NOARGS METH_VARARGS METH_KEYWORDS METH_FASTCALL METH_O METH_TYPED
Py_TPFLAGS_HEAPTYPE METH_METHOD
Py_LT Py_LE Py_EQ Py_NE Py_GT Py_GE Py_MAX_NDIMS
Py_CLEANUP_SUPPORTED PyBUF_READ
Expand Down
4 changes: 4 additions & 0 deletions pypy/module/cpyext/include/methodobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ extern "C" {
#define METH_FASTCALL 0x0080
#endif

/* Specifies that a signature is attached to the PyMethodDef behind the ml_name
* field. */
#define METH_TYPED 0x1000

/* METH_METHOD means the function stores an
* additional reference to the class that defines it;
* both self and class are passed to it.
Expand Down
208 changes: 202 additions & 6 deletions pypy/module/cpyext/methodobject.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from rpython.rtyper.lltypesystem import lltype, rffi
from rpython.rlib import jit
import sys
from rpython.rtyper.lltypesystem import llmemory, lltype, rffi
from rpython.rlib import jit, clibffi, rgc
from rpython.rlib.rarithmetic import intmask
from rpython.rlib.jit_libffi import CIF_DESCRIPTION, jit_ffi_call
from rpython.rlib.objectmodel import keepalive_until_here

from pypy.interpreter.baseobjspace import W_Root
from pypy.interpreter.error import OperationError, oefmt
Expand All @@ -11,14 +15,15 @@
from pypy.objspace.std.typeobject import W_TypeObject
from pypy.module.cpyext.api import (
CONST_STRING, METH_CLASS, METH_COEXIST, METH_KEYWORDS, METH_FASTCALL,
METH_NOARGS, METH_O, METH_STATIC, METH_VARARGS, METH_METHOD,
METH_NOARGS, METH_O, METH_STATIC, METH_TYPED, METH_VARARGS, METH_METHOD,
PyObject, bootstrap_function, cpython_api, generic_cpy_call,
CANNOT_FAIL, slot_function, cts, build_type_checkers,
PyObjectP, Py_ssize_t)
PyObjectP, Py_ssize_t, include_dirs)
from pypy.module.cpyext.pyobject import (
decref, from_ref, make_ref, as_pyobj, make_typedescr)
decref, from_ref, make_ref, make_typedescr, get_w_obj_and_decref)
from pypy.module.cpyext.state import State
from pypy.module.cpyext.tupleobject import tuple_from_args_w, PyTupleObject
from rpython.translator.tool.cbuild import ExternalCompilationInfo

PyMethodDef = cts.gettype('PyMethodDef')
PyCFunction = cts.gettype('PyCFunction')
Expand All @@ -28,6 +33,37 @@
PyCMethod = cts.gettype('PyCMethod')
PyCFunctionObject = cts.gettype('PyCFunctionObject*')
PyCMethodObject = cts.gettype('PyCMethodObject*')
TypedMethodMetadata = cts.gettype('PyPyTypedMethodMetadata')

eci = ExternalCompilationInfo(
includes = ['Python.h', 'assert.h'],
include_dirs = include_dirs,
post_include_bits = [
"RPY_EXTERN\n"
"PyPyTypedMethodMetadata* PyPyGetTypedSignature(PyMethodDef*);"
],
# TODO(max): Do this in RPython to avoid C call
separate_module_sources = ['''
PyPyTypedMethodMetadata*
PyPyGetTypedSignature(PyMethodDef* def)
{
assert(def->ml_flags & METH_TYPED);
return (PyPyTypedMethodMetadata*)(def->ml_name - offsetof(PyPyTypedMethodMetadata, ml_name));
}
'''],
)

pypy_get_typed_signature = rffi.llexternal(
"PyPyGetTypedSignature",
[lltype.Ptr(PyMethodDef)],
lltype.Ptr(TypedMethodMetadata),
compilation_info=eci,
releasegil=False,
# TODO(max): Is this true? What if the def is mutable?
# elidable_function=True,
# sandboxsafe=True,
# _nowrapper=True,
)

@bootstrap_function
def init_functionobject(space):
Expand Down Expand Up @@ -137,8 +173,86 @@ def extract_txtsig(raw_doc, name):
return raw_doc[len(name): end_sig + 1]
return None


# TODO(max): Load these from the C code in the header
T_C_LONG = 1
T_C_DOUBLE = 2
T_PY_OBJECT = 3


class CSig(object):
_immutable_fields_ = ["arg_types[*]", "ret_type", "underlying_func", "can_raise", "cif_descr"]

cif_descr = lltype.nullptr(CIF_DESCRIPTION)

def __init__(self, space, arg_types, ret_type, underlying_func, can_raise):
self.space = space
self.arg_types = arg_types
self.ret_type = ret_type
self.underlying_func = underlying_func
self.can_raise = can_raise
self._build_cif_descr()

def _build_cif_descr(self):
from pypy.module._cffi_backend.ctypefunc import CifDescrBuilder
fargs = [self._cffi_typ(typ) for typ in self.arg_types]
fresult = self._cffi_typ(self.ret_type)
abi = clibffi.FFI_DEFAULT_ABI
builder = CifDescrBuilder(fargs, fresult, abi)
self.cif_descr = builder.rawallocate(self.space)

def _cffi_typ(self, typ):
# it's a bit of a hack to use cffi types in order to be able to use
# CifDescrBuilder. should probably be eventually be replaced
from pypy.module._cffi_backend import ctypeobj

space = self.space
cffibackend = space.getbuiltinmodule('_cffi_backend')
if typ == T_C_LONG:
w_res = space.call_method(cffibackend, 'new_primitive_type', space.newtext('long'))
elif typ == T_C_DOUBLE:
w_res = space.call_method(cffibackend, 'new_primitive_type', space.newtext('double'))
elif typ == T_PY_OBJECT:
# void* always means PyObject* in this context
w_res = space.call_method(cffibackend, 'new_pointer_type',
space.call_method(cffibackend, 'new_void_type'))
else:
raise ValueError
assert isinstance(w_res, ctypeobj.W_CType)
return w_res

@rgc.must_be_light_finalizer
def __del__(self):
if self.cif_descr:
lltype.free(self.cif_descr, flavor='raw')

@staticmethod
def from_ml(space, ml):
sig = pypy_get_typed_signature(ml)
arg_types = []
idx = 0
while True:
arg_type = intmask(sig.c_arg_types[idx])
assert arg_type != 0
if arg_type == -1:
break
arg_types.append(arg_type)
idx += 1
ret_type = intmask(sig.c_ret_type)
assert ret_type != 0
can_raise = False
if ret_type < 0:
ret_type = -ret_type
can_raise = True
try:
return CSig(space, arg_types[:], ret_type, sig.c_underlying_func, can_raise)
except ValueError:
return None # TODO: right now, invalid signatures just lead to the typed path not being used


class W_PyCFunctionObject(W_Root):
_immutable_fields_ = ["flags", "ml"]
# TODO(max): Maybe add w_self as immutable field
_immutable_fields_ = ["flags", "ml", "csig"]

def __init__(self, space, ml, w_self, w_module=None, type_name=None):
self.ml = ml
Expand All @@ -151,6 +265,11 @@ def __init__(self, space, ml, w_self, w_module=None, type_name=None):
self.flags = rffi.cast(lltype.Signed, self.ml.c_ml_flags)
self.w_self = w_self
self.w_module = w_module
flags = self.flags & ~(METH_CLASS | METH_STATIC | METH_COEXIST)
if flags & METH_TYPED:
self.csig = CSig.from_ml(space, ml)
else:
self.csig = None

def descr_call(self, space, __args__):
return self.call(space, self.w_self, __args__)
Expand All @@ -166,6 +285,11 @@ def call(self, space, w_self, __args__):
if flags & METH_METHOD:
return self.call_keywords_fastcall_method(space, w_self, __args__)
return self.call_keywords_fastcall(space, w_self, __args__)
if self.csig is not None:
w_res = self._call_typed(space, __args__.arguments_w)
if w_res is not None:
return w_res
# otherwise use slow variant
return self.call_varargs_fastcall(space, w_self, __args__)
elif flags & METH_KEYWORDS:
return self.call_keywords(space, w_self, __args__)
Expand All @@ -179,6 +303,11 @@ def call(self, space, w_self, __args__):
raise oefmt(space.w_TypeError,
"%s() takes exactly one argument (%d given)",
self.name, length)
if self.csig is not None:
w_res = self._call_typed(space, __args__.arguments_w)
if w_res is not None:
return w_res
# otherwise use slow variant
return self.call_o(space, w_self, __args__)
elif flags & METH_VARARGS:
return self.call_varargs(space, w_self, __args__)
Expand Down Expand Up @@ -212,6 +341,73 @@ def call_varargs_fastcall(self, space, w_self, __args__):
finally:
decref(space, py_args)

@jit.unroll_safe
def _call_typed(self, space, args_w):
assert sys.maxint == 2 ** 63 - 1
sig = self.csig
assert sig is not None
if len(sig.arg_types) != len(args_w):
return None # returns None if the generic call path must be used

cif_descr = sig.cif_descr
size = cif_descr.exchange_size
mustfree_max_plus_1 = 0
buffer = lltype.malloc(rffi.CCHARP.TO, size, flavor='raw')
try:
for i in range(len(args_w)):
data = rffi.ptradd(buffer, cif_descr.exchange_args[i])
w_obj = args_w[i]
typ = sig.arg_types[i]
if typ == T_C_LONG:
value = space.int_w(w_obj)
rffi.cast(rffi.LONGP, data)[0] = value
elif typ == T_C_DOUBLE:
if not space.isinstance_w(w_obj, space.w_float):
return None
value = space.float_w(w_obj)
rffi.cast(rffi.DOUBLEP, data)[0] = value
elif typ == T_PY_OBJECT:
value = make_ref(space, w_obj)
mustfree_max_plus_1 = i + 1
rffi.cast(PyObjectP, data)[0] = value

jit_ffi_call(cif_descr,
rffi.cast(rffi.VOIDP, sig.underlying_func),
buffer, releasegil=False)

resultdata = rffi.ptradd(buffer, cif_descr.exchange_result)
typ = sig.ret_type
if typ == T_C_LONG:
value = rffi.cast(rffi.LONGP, resultdata)[0]
if sig.can_raise and value == -1:
state = space.fromcache(State)
state.check_and_raise_exception(always=True)
w_res = space.newint(value)
elif typ == T_C_DOUBLE:
value = rffi.cast(rffi.DOUBLEP, resultdata)[0]
if sig.can_raise and value == -0.0: # TODO fix comparison
state = space.fromcache(State)
state.check_and_raise_exception(always=True)
w_res = space.newfloat(value)
elif typ == T_PY_OBJECT:
ptrdata = rffi.cast(PyObjectP, resultdata)[0]
if sig.can_raise and not ptrdata: # if == NULL
state = space.fromcache(State)
state.check_and_raise_exception(always=True)
w_res = get_w_obj_and_decref(space, ptrdata)
else:
assert 0, "should be unreachable"

finally:
for i in range(mustfree_max_plus_1):
typ = sig.arg_types[i]
if typ == T_PY_OBJECT:
data = rffi.ptradd(buffer, cif_descr.exchange_args[i])
decref(space, rffi.cast(PyObjectP, data)[0])
lltype.free(buffer, flavor='raw')
keepalive_until_here(args_w)
return w_res

def call_keywords(self, space, w_self, __args__):
func = rffi.cast(PyCFunctionKwArgs, self.ml.c_ml_meth)
py_args = tuple_from_args_w(space, __args__.arguments_w)
Expand Down
21 changes: 21 additions & 0 deletions pypy/module/cpyext/parse/cpyext_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,27 @@ struct PyMethodDef {
};
typedef struct PyMethodDef PyMethodDef;

// If a Python exception has been raised, return -1.
#define T_C_LONG 1
// If a Python exception has been raised, return -0.0.
#define T_C_DOUBLE 2
// If a Python exception has been raised, return NULL.
#define T_PY_OBJECT 3

// TODO(max): Define METH_TYPED_VERSION

// TODO(max): Add a None type to avoid boxing and unboxing on the return path

struct PyPyTypedMethodMetadata {
int* arg_types;
// Negative if underlying function can raise Python exception. The
// error-signaling value is different per type; see above.
int ret_type;
void* underlying_func;
const char ml_name[100];
};
typedef struct PyPyTypedMethodMetadata PyPyTypedMethodMetadata;

typedef struct {
PyObject_HEAD
PyMethodDef *m_ml; /* Description of the C function to call */
Expand Down
Loading