diff options
Diffstat (limited to 'Cython/Compiler/TypeSlots.py')
-rw-r--r-- | Cython/Compiler/TypeSlots.py | 742 |
1 files changed, 490 insertions, 252 deletions
diff --git a/Cython/Compiler/TypeSlots.py b/Cython/Compiler/TypeSlots.py index 0ef17ede6..2c891f648 100644 --- a/Cython/Compiler/TypeSlots.py +++ b/Cython/Compiler/TypeSlots.py @@ -9,6 +9,8 @@ from . import Naming from . import PyrexTypes from .Errors import error +import copy + invisible = ['__cinit__', '__dealloc__', '__richcmp__', '__nonzero__', '__bool__'] @@ -23,6 +25,7 @@ class Signature(object): # fixed_arg_format string # ret_format string # error_value string + # use_fastcall boolean # # The formats are strings made up of the following # characters: @@ -49,6 +52,7 @@ class Signature(object): # '*' rest of args passed as generic Python # arg tuple and kw dict (must be last # char in format string) + # '?' optional object arg (currently for pow only) format_map = { 'O': PyrexTypes.py_object_type, @@ -68,6 +72,7 @@ class Signature(object): 'S': PyrexTypes.c_char_ptr_ptr_type, 'r': PyrexTypes.c_returncode_type, 'B': PyrexTypes.c_py_buffer_ptr_type, + '?': PyrexTypes.py_object_type # 'T', '-' and '*' are handled otherwise # and are not looked up in here } @@ -86,20 +91,27 @@ class Signature(object): 'z': "-1", } - def __init__(self, arg_format, ret_format): - self.has_dummy_arg = 0 - self.has_generic_args = 0 + # Use METH_FASTCALL instead of METH_VARARGS + use_fastcall = False + + def __init__(self, arg_format, ret_format, nogil=False): + self.has_dummy_arg = False + self.has_generic_args = False + self.optional_object_arg_count = 0 if arg_format[:1] == '-': - self.has_dummy_arg = 1 + self.has_dummy_arg = True arg_format = arg_format[1:] if arg_format[-1:] == '*': - self.has_generic_args = 1 + self.has_generic_args = True arg_format = arg_format[:-1] + if arg_format[-1:] == '?': + self.optional_object_arg_count += 1 self.fixed_arg_format = arg_format self.ret_format = ret_format self.error_value = self.error_value_map.get(ret_format, None) self.exception_check = ret_format != 'r' and self.error_value is not None self.is_staticmethod = False + self.nogil = nogil def __repr__(self): return '<Signature[%s(%s%s)]>' % ( @@ -107,7 +119,10 @@ class Signature(object): ', '.join(self.fixed_arg_format), '*' if self.has_generic_args else '') - def num_fixed_args(self): + def min_num_fixed_args(self): + return self.max_num_fixed_args() - self.optional_object_arg_count + + def max_num_fixed_args(self): return len(self.fixed_arg_format) def is_self_arg(self, i): @@ -135,7 +150,7 @@ class Signature(object): def function_type(self, self_arg_override=None): # Construct a C function type descriptor for this signature args = [] - for i in range(self.num_fixed_args()): + for i in range(self.max_num_fixed_args()): if self_arg_override is not None and self.is_self_arg(i): assert isinstance(self_arg_override, PyrexTypes.CFuncTypeArg) args.append(self_arg_override) @@ -149,7 +164,8 @@ class Signature(object): exc_value = self.exception_value() return PyrexTypes.CFuncType( ret_type, args, exception_value=exc_value, - exception_check=self.exception_check) + exception_check=self.exception_check, + nogil=self.nogil) def method_flags(self): if self.ret_format == "O": @@ -157,17 +173,50 @@ class Signature(object): if self.has_dummy_arg: full_args = "O" + full_args if full_args in ["O", "T"]: - if self.has_generic_args: - return [method_varargs, method_keywords] - else: + if not self.has_generic_args: return [method_noargs] + elif self.use_fastcall: + return [method_fastcall, method_keywords] + else: + return [method_varargs, method_keywords] elif full_args in ["OO", "TO"] and not self.has_generic_args: return [method_onearg] if self.is_staticmethod: - return [method_varargs, method_keywords] + if self.use_fastcall: + return [method_fastcall, method_keywords] + else: + return [method_varargs, method_keywords] return None + def method_function_type(self): + # Return the C function type + mflags = self.method_flags() + kw = "WithKeywords" if (method_keywords in mflags) else "" + for m in mflags: + if m == method_noargs or m == method_onearg: + return "PyCFunction" + if m == method_varargs: + return "PyCFunction" + kw + if m == method_fastcall: + return "__Pyx_PyCFunction_FastCall" + kw + return None + + def with_fastcall(self): + # Return a copy of this Signature with use_fastcall=True + sig = copy.copy(self) + sig.use_fastcall = True + return sig + + @property + def fastvar(self): + # Used to select variants of functions, one dealing with METH_VARARGS + # and one dealing with __Pyx_METH_FASTCALL + if self.use_fastcall: + return "FASTCALL" + else: + return "VARARGS" + class SlotDescriptor(object): # Abstract base class for type slot descriptors. @@ -178,15 +227,26 @@ class SlotDescriptor(object): # py3 Indicates presence of slot in Python 3 # py2 Indicates presence of slot in Python 2 # ifdef Full #ifdef string that slot is wrapped in. Using this causes py3, py2 and flags to be ignored.) + # used_ifdef Full #ifdef string that the slot value is wrapped in (otherwise it is assigned NULL) + # Unlike "ifdef" the slot is defined and this just controls if it receives a value def __init__(self, slot_name, dynamic=False, inherited=False, - py3=True, py2=True, ifdef=None): + py3=True, py2=True, ifdef=None, is_binop=False, + used_ifdef=None): self.slot_name = slot_name self.is_initialised_dynamically = dynamic self.is_inherited = inherited self.ifdef = ifdef + self.used_ifdef = used_ifdef self.py3 = py3 self.py2 = py2 + self.is_binop = is_binop + + def slot_code(self, scope): + raise NotImplementedError() + + def spec_value(self, scope): + return self.slot_code(scope) def preprocessor_guard_code(self): ifdef = self.ifdef @@ -194,13 +254,30 @@ class SlotDescriptor(object): py3 = self.py3 guard = None if ifdef: - guard = ("#if %s" % ifdef) + guard = "#if %s" % ifdef elif not py3 or py3 == '<RESERVED>': - guard = ("#if PY_MAJOR_VERSION < 3") + guard = "#if PY_MAJOR_VERSION < 3" elif not py2: - guard = ("#if PY_MAJOR_VERSION >= 3") + guard = "#if PY_MAJOR_VERSION >= 3" return guard + def generate_spec(self, scope, code): + if self.is_initialised_dynamically: + return + value = self.spec_value(scope) + if value == "0": + return + preprocessor_guard = self.preprocessor_guard_code() + if not preprocessor_guard: + if self.py3 and self.slot_name.startswith('bf_'): + # The buffer protocol requires Limited API 3.11, so check if the spec slots are available. + preprocessor_guard = "#if defined(Py_%s)" % self.slot_name + if preprocessor_guard: + code.putln(preprocessor_guard) + code.putln("{Py_%s, (void *)%s}," % (self.slot_name, value)) + if preprocessor_guard: + code.putln("#endif") + def generate(self, scope, code): preprocessor_guard = self.preprocessor_guard_code() if preprocessor_guard: @@ -215,7 +292,7 @@ class SlotDescriptor(object): # PyPy currently has a broken PyType_Ready() that fails to # inherit some slots. To work around this, we explicitly # set inherited slots here, but only in PyPy since CPython - # handles this better than we do. + # handles this better than we do (except for buffer slots in type specs). inherited_value = value current_scope = scope while (inherited_value == "0" @@ -225,12 +302,20 @@ class SlotDescriptor(object): current_scope = current_scope.parent_type.base_type.scope inherited_value = self.slot_code(current_scope) if inherited_value != "0": - code.putln("#if CYTHON_COMPILING_IN_PYPY") + # we always need inherited buffer slots for the type spec + is_buffer_slot = int(self.slot_name in ("bf_getbuffer", "bf_releasebuffer")) + code.putln("#if CYTHON_COMPILING_IN_PYPY || %d" % is_buffer_slot) code.putln("%s, /*%s*/" % (inherited_value, self.slot_name)) code.putln("#else") end_pypy_guard = True + if self.used_ifdef: + code.putln("#if %s" % self.used_ifdef) code.putln("%s, /*%s*/" % (value, self.slot_name)) + if self.used_ifdef: + code.putln("#else") + code.putln("NULL, /*%s*/" % self.slot_name) + code.putln("#endif") if end_pypy_guard: code.putln("#endif") @@ -248,14 +333,20 @@ class SlotDescriptor(object): def generate_dynamic_init_code(self, scope, code): if self.is_initialised_dynamically: - value = self.slot_code(scope) - if value != "0": - code.putln("%s.%s = %s;" % ( - scope.parent_type.typeobj_cname, - self.slot_name, - value - ) - ) + self.generate_set_slot_code( + self.slot_code(scope), scope, code) + + def generate_set_slot_code(self, value, scope, code): + if value == "0": + return + + if scope.parent_type.typeptr_cname: + target = "%s->%s" % (scope.parent_type.typeptr_cname, self.slot_name) + else: + assert scope.parent_type.typeobj_cname + target = "%s.%s" % (scope.parent_type.typeobj_cname, self.slot_name) + + code.putln("%s = %s;" % (target, value)) class FixedSlot(SlotDescriptor): @@ -285,8 +376,8 @@ class MethodSlot(SlotDescriptor): # method_name string The __xxx__ name of the method # alternatives [string] Alternative list of __xxx__ names for the method - def __init__(self, signature, slot_name, method_name, fallback=None, - py3=True, py2=True, ifdef=None, inherited=True): + def __init__(self, signature, slot_name, method_name, method_name_to_slot, + fallback=None, py3=True, py2=True, ifdef=None, inherited=True): SlotDescriptor.__init__(self, slot_name, py3=py3, py2=py2, ifdef=ifdef, inherited=inherited) self.signature = signature @@ -360,30 +451,61 @@ class GCClearReferencesSlot(GCDependentSlot): class ConstructorSlot(InternalMethodSlot): # Descriptor for tp_new and tp_dealloc. - def __init__(self, slot_name, method, **kargs): + def __init__(self, slot_name, method=None, **kargs): InternalMethodSlot.__init__(self, slot_name, **kargs) self.method = method - def slot_code(self, scope): - entry = scope.lookup_here(self.method) - if (self.slot_name != 'tp_new' - and scope.parent_type.base_type + def _needs_own(self, scope): + if (scope.parent_type.base_type and not scope.has_pyobject_attrs and not scope.has_memoryview_attrs - and not scope.has_cpp_class_attrs - and not (entry and entry.is_special)): + and not scope.has_cpp_constructable_attrs + and not (self.slot_name == 'tp_new' and scope.parent_type.vtabslot_cname)): + entry = scope.lookup_here(self.method) if self.method else None + if not (entry and entry.is_special): + return False + # Unless we can safely delegate to the parent, all types need a tp_new(). + return True + + def _parent_slot_function(self, scope): + parent_type_scope = scope.parent_type.base_type.scope + if scope.parent_scope is parent_type_scope.parent_scope: + entry = scope.parent_scope.lookup_here(scope.parent_type.base_type.name) + if entry.visibility != 'extern': + return self.slot_code(parent_type_scope) + return None + + def slot_code(self, scope): + if not self._needs_own(scope): # if the type does not have object attributes, it can # delegate GC methods to its parent - iff the parent # functions are defined in the same module - parent_type_scope = scope.parent_type.base_type.scope - if scope.parent_scope is parent_type_scope.parent_scope: - entry = scope.parent_scope.lookup_here(scope.parent_type.base_type.name) - if entry.visibility != 'extern': - return self.slot_code(parent_type_scope) - if entry and not entry.is_special: - return "0" + slot_code = self._parent_slot_function(scope) + return slot_code or '0' return InternalMethodSlot.slot_code(self, scope) + def spec_value(self, scope): + slot_function = self.slot_code(scope) + if self.slot_name == "tp_dealloc" and slot_function != scope.mangle_internal("tp_dealloc"): + # Not used => inherit from base type. + return "0" + return slot_function + + def generate_dynamic_init_code(self, scope, code): + if self.slot_code(scope) != '0': + return + # If we don't have our own slot function and don't know the + # parent function statically, copy it dynamically. + base_type = scope.parent_type.base_type + if base_type.typeptr_cname: + src = '%s->%s' % (base_type.typeptr_cname, self.slot_name) + elif base_type.is_extension_type and base_type.typeobj_cname: + src = '%s.%s' % (base_type.typeobj_cname, self.slot_name) + else: + return + + self.generate_set_slot_code(src, scope, code) + class SyntheticSlot(InternalMethodSlot): # Type slot descriptor for a synthesized method which @@ -404,6 +526,20 @@ class SyntheticSlot(InternalMethodSlot): else: return self.default_value + def spec_value(self, scope): + return self.slot_code(scope) + + +class BinopSlot(SyntheticSlot): + def __init__(self, signature, slot_name, left_method, method_name_to_slot, **kargs): + assert left_method.startswith('__') + right_method = '__r' + left_method[2:] + SyntheticSlot.__init__( + self, slot_name, [left_method, right_method], "0", is_binop=True, **kargs) + # MethodSlot causes special method registration. + self.left_slot = MethodSlot(signature, "", left_method, method_name_to_slot, **kargs) + self.right_slot = MethodSlot(signature, "", right_method, method_name_to_slot, **kargs) + class RichcmpSlot(MethodSlot): def slot_code(self, scope): @@ -432,8 +568,16 @@ class TypeFlagsSlot(SlotDescriptor): value += "|Py_TPFLAGS_BASETYPE" if scope.needs_gc(): value += "|Py_TPFLAGS_HAVE_GC" + if scope.may_have_finalize(): + value += "|Py_TPFLAGS_HAVE_FINALIZE" + if scope.parent_type.has_sequence_flag: + value += "|Py_TPFLAGS_SEQUENCE" return value + def generate_spec(self, scope, code): + # Flags are stored in the PyType_Spec, not in a PyType_Slot. + return + class DocStringSlot(SlotDescriptor): # Descriptor for the docstring slot. @@ -444,7 +588,7 @@ class DocStringSlot(SlotDescriptor): return "0" if doc.is_unicode: doc = doc.as_utf8_string() - return doc.as_c_string_literal() + return "PyDoc_STR(%s)" % doc.as_c_string_literal() class SuiteSlot(SlotDescriptor): @@ -452,7 +596,7 @@ class SuiteSlot(SlotDescriptor): # # sub_slots [SlotDescriptor] - def __init__(self, sub_slots, slot_type, slot_name, ifdef=None): + def __init__(self, sub_slots, slot_type, slot_name, substructures, ifdef=None): SlotDescriptor.__init__(self, slot_name, ifdef=ifdef) self.sub_slots = sub_slots self.slot_type = slot_type @@ -487,7 +631,9 @@ class SuiteSlot(SlotDescriptor): if self.ifdef: code.putln("#endif") -substructures = [] # List of all SuiteSlot instances + def generate_spec(self, scope, code): + for slot in self.sub_slots: + slot.generate_spec(scope, code) class MethodTableSlot(SlotDescriptor): # Slot descriptor for the method table. @@ -503,8 +649,42 @@ class MemberTableSlot(SlotDescriptor): # Slot descriptor for the table of Python-accessible attributes. def slot_code(self, scope): + # Only used in specs. return "0" + def get_member_specs(self, scope): + return [ + get_slot_by_name("tp_dictoffset", scope.directives).members_slot_value(scope), + #get_slot_by_name("tp_weaklistoffset").spec_value(scope), + ] + + def is_empty(self, scope): + for member_entry in self.get_member_specs(scope): + if member_entry: + return False + return True + + def substructure_cname(self, scope): + return "%s%s_%s" % (Naming.pyrex_prefix, self.slot_name, scope.class_name) + + def generate_substructure_spec(self, scope, code): + if self.is_empty(scope): + return + from .Code import UtilityCode + code.globalstate.use_utility_code(UtilityCode.load_cached("IncludeStructmemberH", "ModuleSetupCode.c")) + + code.putln("static struct PyMemberDef %s[] = {" % self.substructure_cname(scope)) + for member_entry in self.get_member_specs(scope): + if member_entry: + code.putln(member_entry) + code.putln("{NULL, 0, 0, 0, NULL}") + code.putln("};") + + def spec_value(self, scope): + if self.is_empty(scope): + return "0" + return self.substructure_cname(scope) + class GetSetSlot(SlotDescriptor): # Slot descriptor for the table of attribute get & set methods. @@ -520,13 +700,13 @@ class BaseClassSlot(SlotDescriptor): # Slot descriptor for the base class slot. def __init__(self, name): - SlotDescriptor.__init__(self, name, dynamic = 1) + SlotDescriptor.__init__(self, name, dynamic=True) def generate_dynamic_init_code(self, scope, code): base_type = scope.parent_type.base_type if base_type: - code.putln("%s.%s = %s;" % ( - scope.parent_type.typeobj_cname, + code.putln("%s->%s = %s;" % ( + scope.parent_type.typeptr_cname, self.slot_name, base_type.typeptr_cname)) @@ -551,10 +731,11 @@ class DictOffsetSlot(SlotDescriptor): else: return "0" - -# The following dictionary maps __xxx__ method names to slot descriptors. - -method_name_to_slot = {} + def members_slot_value(self, scope): + dict_offset = self.slot_code(scope) + if dict_offset == "0": + return None + return '{"__dictoffset__", T_PYSSIZET, %s, READONLY, NULL},' % dict_offset ## The following slots are (or could be) initialised with an ## extern function pointer. @@ -569,17 +750,6 @@ method_name_to_slot = {} # #------------------------------------------------------------------------------------------ -def get_special_method_signature(name): - # Given a method name, if it is a special method, - # return its signature, else return None. - slot = method_name_to_slot.get(name) - if slot: - return slot.signature - elif name in richcmp_special_methods: - return ibinaryfunc - else: - return None - def get_property_accessor_signature(name): # Return signature of accessor for an extension type @@ -592,7 +762,7 @@ def get_base_slot_function(scope, slot): # This is useful for enabling the compiler to optimize calls # that recursively climb the class hierarchy. base_type = scope.parent_type.base_type - if scope.parent_scope is base_type.scope.parent_scope: + if base_type and scope.parent_scope is base_type.scope.parent_scope: parent_slot = slot.slot_code(base_type.scope) if parent_slot != '0': entry = scope.parent_scope.lookup_here(scope.parent_type.base_type.name) @@ -613,16 +783,16 @@ def get_slot_function(scope, slot): return None -def get_slot_by_name(slot_name): +def get_slot_by_name(slot_name, compiler_directives): # For now, only search the type struct, no referenced sub-structs. - for slot in slot_table: + for slot in get_slot_table(compiler_directives).slot_table: if slot.slot_name == slot_name: return slot assert False, "Slot not found: %s" % slot_name def get_slot_code_by_name(scope, slot_name): - slot = get_slot_by_name(slot_name) + slot = get_slot_by_name(slot_name, scope.directives) return slot.slot_code(scope) def is_reverse_number_slot(name): @@ -634,8 +804,8 @@ def is_reverse_number_slot(name): """ if name.startswith("__r") and name.endswith("__"): forward_name = name.replace("r", "", 1) - for meth in PyNumberMethods: - if getattr(meth, "method_name", None) == forward_name: + for meth in get_slot_table(None).PyNumberMethods: + if hasattr(meth, "right_slot"): return True return False @@ -668,8 +838,8 @@ pyfunction_onearg = Signature("-O", "O") unaryfunc = Signature("T", "O") # typedef PyObject * (*unaryfunc)(PyObject *); binaryfunc = Signature("OO", "O") # typedef PyObject * (*binaryfunc)(PyObject *, PyObject *); ibinaryfunc = Signature("TO", "O") # typedef PyObject * (*binaryfunc)(PyObject *, PyObject *); -ternaryfunc = Signature("OOO", "O") # typedef PyObject * (*ternaryfunc)(PyObject *, PyObject *, PyObject *); -iternaryfunc = Signature("TOO", "O") # typedef PyObject * (*ternaryfunc)(PyObject *, PyObject *, PyObject *); +powternaryfunc = Signature("OO?", "O") # typedef PyObject * (*ternaryfunc)(PyObject *, PyObject *, PyObject *); +ipowternaryfunc = Signature("TO?", "O") # typedef PyObject * (*ternaryfunc)(PyObject *, PyObject *, PyObject *); callfunc = Signature("T*", "O") # typedef PyObject * (*ternaryfunc)(PyObject *, PyObject *, PyObject *); inquiry = Signature("T", "i") # typedef int (*inquiry)(PyObject *); lenfunc = Signature("T", "z") # typedef Py_ssize_t (*lenfunc)(PyObject *); @@ -682,7 +852,7 @@ ssizessizeargfunc = Signature("Tzz", "O") # typedef PyObject *(*ssizessizeargfu intobjargproc = Signature("TiO", 'r') # typedef int(*intobjargproc)(PyObject *, int, PyObject *); ssizeobjargproc = Signature("TzO", 'r') # typedef int(*ssizeobjargproc)(PyObject *, Py_ssize_t, PyObject *); intintobjargproc = Signature("TiiO", 'r') # typedef int(*intintobjargproc)(PyObject *, int, int, PyObject *); -ssizessizeobjargproc = Signature("TzzO", 'r') # typedef int(*ssizessizeobjargproc)(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *); +ssizessizeobjargproc = Signature("TzzO", 'r') # typedef int(*ssizessizeobjargproc)(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *); intintargproc = Signature("Tii", 'r') ssizessizeargproc = Signature("Tzz", 'r') @@ -732,103 +902,8 @@ property_accessor_signatures = { '__del__': Signature("T", 'r') } -#------------------------------------------------------------------------------------------ -# -# Descriptor tables for the slots of the various type object -# substructures, in the order they appear in the structure. -# -#------------------------------------------------------------------------------------------ -PyNumberMethods_Py3_GUARD = "PY_MAJOR_VERSION < 3 || (CYTHON_COMPILING_IN_PYPY && PY_VERSION_HEX < 0x03050000)" - -PyNumberMethods = ( - MethodSlot(binaryfunc, "nb_add", "__add__"), - MethodSlot(binaryfunc, "nb_subtract", "__sub__"), - MethodSlot(binaryfunc, "nb_multiply", "__mul__"), - MethodSlot(binaryfunc, "nb_divide", "__div__", ifdef = PyNumberMethods_Py3_GUARD), - MethodSlot(binaryfunc, "nb_remainder", "__mod__"), - MethodSlot(binaryfunc, "nb_divmod", "__divmod__"), - MethodSlot(ternaryfunc, "nb_power", "__pow__"), - MethodSlot(unaryfunc, "nb_negative", "__neg__"), - MethodSlot(unaryfunc, "nb_positive", "__pos__"), - MethodSlot(unaryfunc, "nb_absolute", "__abs__"), - MethodSlot(inquiry, "nb_nonzero", "__nonzero__", py3 = ("nb_bool", "__bool__")), - MethodSlot(unaryfunc, "nb_invert", "__invert__"), - MethodSlot(binaryfunc, "nb_lshift", "__lshift__"), - MethodSlot(binaryfunc, "nb_rshift", "__rshift__"), - MethodSlot(binaryfunc, "nb_and", "__and__"), - MethodSlot(binaryfunc, "nb_xor", "__xor__"), - MethodSlot(binaryfunc, "nb_or", "__or__"), - EmptySlot("nb_coerce", ifdef = PyNumberMethods_Py3_GUARD), - MethodSlot(unaryfunc, "nb_int", "__int__", fallback="__long__"), - MethodSlot(unaryfunc, "nb_long", "__long__", fallback="__int__", py3 = "<RESERVED>"), - MethodSlot(unaryfunc, "nb_float", "__float__"), - MethodSlot(unaryfunc, "nb_oct", "__oct__", ifdef = PyNumberMethods_Py3_GUARD), - MethodSlot(unaryfunc, "nb_hex", "__hex__", ifdef = PyNumberMethods_Py3_GUARD), - - # Added in release 2.0 - MethodSlot(ibinaryfunc, "nb_inplace_add", "__iadd__"), - MethodSlot(ibinaryfunc, "nb_inplace_subtract", "__isub__"), - MethodSlot(ibinaryfunc, "nb_inplace_multiply", "__imul__"), - MethodSlot(ibinaryfunc, "nb_inplace_divide", "__idiv__", ifdef = PyNumberMethods_Py3_GUARD), - MethodSlot(ibinaryfunc, "nb_inplace_remainder", "__imod__"), - MethodSlot(ibinaryfunc, "nb_inplace_power", "__ipow__"), # actually ternaryfunc!!! - MethodSlot(ibinaryfunc, "nb_inplace_lshift", "__ilshift__"), - MethodSlot(ibinaryfunc, "nb_inplace_rshift", "__irshift__"), - MethodSlot(ibinaryfunc, "nb_inplace_and", "__iand__"), - MethodSlot(ibinaryfunc, "nb_inplace_xor", "__ixor__"), - MethodSlot(ibinaryfunc, "nb_inplace_or", "__ior__"), - - # Added in release 2.2 - # The following require the Py_TPFLAGS_HAVE_CLASS flag - MethodSlot(binaryfunc, "nb_floor_divide", "__floordiv__"), - MethodSlot(binaryfunc, "nb_true_divide", "__truediv__"), - MethodSlot(ibinaryfunc, "nb_inplace_floor_divide", "__ifloordiv__"), - MethodSlot(ibinaryfunc, "nb_inplace_true_divide", "__itruediv__"), - - # Added in release 2.5 - MethodSlot(unaryfunc, "nb_index", "__index__"), - - # Added in release 3.5 - MethodSlot(binaryfunc, "nb_matrix_multiply", "__matmul__", ifdef="PY_VERSION_HEX >= 0x03050000"), - MethodSlot(ibinaryfunc, "nb_inplace_matrix_multiply", "__imatmul__", ifdef="PY_VERSION_HEX >= 0x03050000"), -) - -PySequenceMethods = ( - MethodSlot(lenfunc, "sq_length", "__len__"), - EmptySlot("sq_concat"), # nb_add used instead - EmptySlot("sq_repeat"), # nb_multiply used instead - SyntheticSlot("sq_item", ["__getitem__"], "0"), #EmptySlot("sq_item"), # mp_subscript used instead - MethodSlot(ssizessizeargfunc, "sq_slice", "__getslice__"), - EmptySlot("sq_ass_item"), # mp_ass_subscript used instead - SyntheticSlot("sq_ass_slice", ["__setslice__", "__delslice__"], "0"), - MethodSlot(cmpfunc, "sq_contains", "__contains__"), - EmptySlot("sq_inplace_concat"), # nb_inplace_add used instead - EmptySlot("sq_inplace_repeat"), # nb_inplace_multiply used instead -) - -PyMappingMethods = ( - MethodSlot(lenfunc, "mp_length", "__len__"), - MethodSlot(objargfunc, "mp_subscript", "__getitem__"), - SyntheticSlot("mp_ass_subscript", ["__setitem__", "__delitem__"], "0"), -) - -PyBufferProcs = ( - MethodSlot(readbufferproc, "bf_getreadbuffer", "__getreadbuffer__", py3 = False), - MethodSlot(writebufferproc, "bf_getwritebuffer", "__getwritebuffer__", py3 = False), - MethodSlot(segcountproc, "bf_getsegcount", "__getsegcount__", py3 = False), - MethodSlot(charbufferproc, "bf_getcharbuffer", "__getcharbuffer__", py3 = False), - - MethodSlot(getbufferproc, "bf_getbuffer", "__getbuffer__"), - MethodSlot(releasebufferproc, "bf_releasebuffer", "__releasebuffer__") -) - -PyAsyncMethods = ( - MethodSlot(unaryfunc, "am_await", "__await__"), - MethodSlot(unaryfunc, "am_aiter", "__aiter__"), - MethodSlot(unaryfunc, "am_anext", "__anext__"), - EmptySlot("am_send", ifdef="PY_VERSION_HEX >= 0x030A00A3"), -) +PyNumberMethods_Py2only_GUARD = "PY_MAJOR_VERSION < 3 || (CYTHON_COMPILING_IN_PYPY && PY_VERSION_HEX < 0x03050000)" #------------------------------------------------------------------------------------------ # @@ -836,100 +911,262 @@ PyAsyncMethods = ( # top-level type slots, beginning with tp_dealloc, in the order they # appear in the type object. # +# It depends on some compiler directives (currently c_api_binop_methods), so the +# slot tables for each set of compiler directives are generated lazily and put in +# the _slot_table_dict +# #------------------------------------------------------------------------------------------ -slot_table = ( - ConstructorSlot("tp_dealloc", '__dealloc__'), - EmptySlot("tp_print", ifdef="PY_VERSION_HEX < 0x030800b4"), - EmptySlot("tp_vectorcall_offset", ifdef="PY_VERSION_HEX >= 0x030800b4"), - EmptySlot("tp_getattr"), - EmptySlot("tp_setattr"), - - # tp_compare (Py2) / tp_reserved (Py3<3.5) / tp_as_async (Py3.5+) is always used as tp_as_async in Py3 - MethodSlot(cmpfunc, "tp_compare", "__cmp__", ifdef="PY_MAJOR_VERSION < 3"), - SuiteSlot(PyAsyncMethods, "__Pyx_PyAsyncMethodsStruct", "tp_as_async", ifdef="PY_MAJOR_VERSION >= 3"), - - MethodSlot(reprfunc, "tp_repr", "__repr__"), - - SuiteSlot(PyNumberMethods, "PyNumberMethods", "tp_as_number"), - SuiteSlot(PySequenceMethods, "PySequenceMethods", "tp_as_sequence"), - SuiteSlot(PyMappingMethods, "PyMappingMethods", "tp_as_mapping"), - - MethodSlot(hashfunc, "tp_hash", "__hash__", inherited=False), # Py3 checks for __richcmp__ - MethodSlot(callfunc, "tp_call", "__call__"), - MethodSlot(reprfunc, "tp_str", "__str__"), - - SyntheticSlot("tp_getattro", ["__getattr__","__getattribute__"], "0"), #"PyObject_GenericGetAttr"), - SyntheticSlot("tp_setattro", ["__setattr__", "__delattr__"], "0"), #"PyObject_GenericSetAttr"), - - SuiteSlot(PyBufferProcs, "PyBufferProcs", "tp_as_buffer"), - - TypeFlagsSlot("tp_flags"), - DocStringSlot("tp_doc"), - - GCDependentSlot("tp_traverse"), - GCClearReferencesSlot("tp_clear"), - - RichcmpSlot(richcmpfunc, "tp_richcompare", "__richcmp__", inherited=False), # Py3 checks for __hash__ - - EmptySlot("tp_weaklistoffset"), - - MethodSlot(getiterfunc, "tp_iter", "__iter__"), - MethodSlot(iternextfunc, "tp_iternext", "__next__"), +class SlotTable(object): + def __init__(self, old_binops): + # The following dictionary maps __xxx__ method names to slot descriptors. + method_name_to_slot = {} + self._get_slot_by_method_name = method_name_to_slot.get + self.substructures = [] # List of all SuiteSlot instances + + bf = binaryfunc if old_binops else ibinaryfunc + ptf = powternaryfunc if old_binops else ipowternaryfunc + + # Descriptor tables for the slots of the various type object + # substructures, in the order they appear in the structure. + self.PyNumberMethods = ( + BinopSlot(bf, "nb_add", "__add__", method_name_to_slot), + BinopSlot(bf, "nb_subtract", "__sub__", method_name_to_slot), + BinopSlot(bf, "nb_multiply", "__mul__", method_name_to_slot), + BinopSlot(bf, "nb_divide", "__div__", method_name_to_slot, + ifdef = PyNumberMethods_Py2only_GUARD), + BinopSlot(bf, "nb_remainder", "__mod__", method_name_to_slot), + BinopSlot(bf, "nb_divmod", "__divmod__", method_name_to_slot), + BinopSlot(ptf, "nb_power", "__pow__", method_name_to_slot), + MethodSlot(unaryfunc, "nb_negative", "__neg__", method_name_to_slot), + MethodSlot(unaryfunc, "nb_positive", "__pos__", method_name_to_slot), + MethodSlot(unaryfunc, "nb_absolute", "__abs__", method_name_to_slot), + MethodSlot(inquiry, "nb_bool", "__bool__", method_name_to_slot, + py2 = ("nb_nonzero", "__nonzero__")), + MethodSlot(unaryfunc, "nb_invert", "__invert__", method_name_to_slot), + BinopSlot(bf, "nb_lshift", "__lshift__", method_name_to_slot), + BinopSlot(bf, "nb_rshift", "__rshift__", method_name_to_slot), + BinopSlot(bf, "nb_and", "__and__", method_name_to_slot), + BinopSlot(bf, "nb_xor", "__xor__", method_name_to_slot), + BinopSlot(bf, "nb_or", "__or__", method_name_to_slot), + EmptySlot("nb_coerce", ifdef = PyNumberMethods_Py2only_GUARD), + MethodSlot(unaryfunc, "nb_int", "__int__", method_name_to_slot, fallback="__long__"), + MethodSlot(unaryfunc, "nb_long", "__long__", method_name_to_slot, + fallback="__int__", py3 = "<RESERVED>"), + MethodSlot(unaryfunc, "nb_float", "__float__", method_name_to_slot), + MethodSlot(unaryfunc, "nb_oct", "__oct__", method_name_to_slot, + ifdef = PyNumberMethods_Py2only_GUARD), + MethodSlot(unaryfunc, "nb_hex", "__hex__", method_name_to_slot, + ifdef = PyNumberMethods_Py2only_GUARD), + + # Added in release 2.0 + MethodSlot(ibinaryfunc, "nb_inplace_add", "__iadd__", method_name_to_slot), + MethodSlot(ibinaryfunc, "nb_inplace_subtract", "__isub__", method_name_to_slot), + MethodSlot(ibinaryfunc, "nb_inplace_multiply", "__imul__", method_name_to_slot), + MethodSlot(ibinaryfunc, "nb_inplace_divide", "__idiv__", method_name_to_slot, + ifdef = PyNumberMethods_Py2only_GUARD), + MethodSlot(ibinaryfunc, "nb_inplace_remainder", "__imod__", method_name_to_slot), + MethodSlot(ptf, "nb_inplace_power", "__ipow__", method_name_to_slot), + MethodSlot(ibinaryfunc, "nb_inplace_lshift", "__ilshift__", method_name_to_slot), + MethodSlot(ibinaryfunc, "nb_inplace_rshift", "__irshift__", method_name_to_slot), + MethodSlot(ibinaryfunc, "nb_inplace_and", "__iand__", method_name_to_slot), + MethodSlot(ibinaryfunc, "nb_inplace_xor", "__ixor__", method_name_to_slot), + MethodSlot(ibinaryfunc, "nb_inplace_or", "__ior__", method_name_to_slot), + + # Added in release 2.2 + # The following require the Py_TPFLAGS_HAVE_CLASS flag + BinopSlot(bf, "nb_floor_divide", "__floordiv__", method_name_to_slot), + BinopSlot(bf, "nb_true_divide", "__truediv__", method_name_to_slot), + MethodSlot(ibinaryfunc, "nb_inplace_floor_divide", "__ifloordiv__", method_name_to_slot), + MethodSlot(ibinaryfunc, "nb_inplace_true_divide", "__itruediv__", method_name_to_slot), + + # Added in release 2.5 + MethodSlot(unaryfunc, "nb_index", "__index__", method_name_to_slot), + + # Added in release 3.5 + BinopSlot(bf, "nb_matrix_multiply", "__matmul__", method_name_to_slot, + ifdef="PY_VERSION_HEX >= 0x03050000"), + MethodSlot(ibinaryfunc, "nb_inplace_matrix_multiply", "__imatmul__", method_name_to_slot, + ifdef="PY_VERSION_HEX >= 0x03050000"), + ) + + self.PySequenceMethods = ( + MethodSlot(lenfunc, "sq_length", "__len__", method_name_to_slot), + EmptySlot("sq_concat"), # nb_add used instead + EmptySlot("sq_repeat"), # nb_multiply used instead + SyntheticSlot("sq_item", ["__getitem__"], "0"), #EmptySlot("sq_item"), # mp_subscript used instead + MethodSlot(ssizessizeargfunc, "sq_slice", "__getslice__", method_name_to_slot), + EmptySlot("sq_ass_item"), # mp_ass_subscript used instead + SyntheticSlot("sq_ass_slice", ["__setslice__", "__delslice__"], "0"), + MethodSlot(cmpfunc, "sq_contains", "__contains__", method_name_to_slot), + EmptySlot("sq_inplace_concat"), # nb_inplace_add used instead + EmptySlot("sq_inplace_repeat"), # nb_inplace_multiply used instead + ) + + self.PyMappingMethods = ( + MethodSlot(lenfunc, "mp_length", "__len__", method_name_to_slot), + MethodSlot(objargfunc, "mp_subscript", "__getitem__", method_name_to_slot), + SyntheticSlot("mp_ass_subscript", ["__setitem__", "__delitem__"], "0"), + ) + + self.PyBufferProcs = ( + MethodSlot(readbufferproc, "bf_getreadbuffer", "__getreadbuffer__", method_name_to_slot, + py3 = False), + MethodSlot(writebufferproc, "bf_getwritebuffer", "__getwritebuffer__", method_name_to_slot, + py3 = False), + MethodSlot(segcountproc, "bf_getsegcount", "__getsegcount__", method_name_to_slot, + py3 = False), + MethodSlot(charbufferproc, "bf_getcharbuffer", "__getcharbuffer__", method_name_to_slot, + py3 = False), + + MethodSlot(getbufferproc, "bf_getbuffer", "__getbuffer__", method_name_to_slot), + MethodSlot(releasebufferproc, "bf_releasebuffer", "__releasebuffer__", method_name_to_slot) + ) + + self.PyAsyncMethods = ( + MethodSlot(unaryfunc, "am_await", "__await__", method_name_to_slot), + MethodSlot(unaryfunc, "am_aiter", "__aiter__", method_name_to_slot), + MethodSlot(unaryfunc, "am_anext", "__anext__", method_name_to_slot), + EmptySlot("am_send", ifdef="PY_VERSION_HEX >= 0x030A00A3"), + ) + + self.slot_table = ( + ConstructorSlot("tp_dealloc", '__dealloc__'), + EmptySlot("tp_print", ifdef="PY_VERSION_HEX < 0x030800b4"), + EmptySlot("tp_vectorcall_offset", ifdef="PY_VERSION_HEX >= 0x030800b4"), + EmptySlot("tp_getattr"), + EmptySlot("tp_setattr"), + + # tp_compare (Py2) / tp_reserved (Py3<3.5) / tp_as_async (Py3.5+) is always used as tp_as_async in Py3 + MethodSlot(cmpfunc, "tp_compare", "__cmp__", method_name_to_slot, ifdef="PY_MAJOR_VERSION < 3"), + SuiteSlot(self. PyAsyncMethods, "__Pyx_PyAsyncMethodsStruct", "tp_as_async", + self.substructures, ifdef="PY_MAJOR_VERSION >= 3"), + + MethodSlot(reprfunc, "tp_repr", "__repr__", method_name_to_slot), + + SuiteSlot(self.PyNumberMethods, "PyNumberMethods", "tp_as_number", self.substructures), + SuiteSlot(self.PySequenceMethods, "PySequenceMethods", "tp_as_sequence", self.substructures), + SuiteSlot(self.PyMappingMethods, "PyMappingMethods", "tp_as_mapping", self.substructures), + + MethodSlot(hashfunc, "tp_hash", "__hash__", method_name_to_slot, + inherited=False), # Py3 checks for __richcmp__ + MethodSlot(callfunc, "tp_call", "__call__", method_name_to_slot), + MethodSlot(reprfunc, "tp_str", "__str__", method_name_to_slot), + + SyntheticSlot("tp_getattro", ["__getattr__","__getattribute__"], "0"), #"PyObject_GenericGetAttr"), + SyntheticSlot("tp_setattro", ["__setattr__", "__delattr__"], "0"), #"PyObject_GenericSetAttr"), + + SuiteSlot(self.PyBufferProcs, "PyBufferProcs", "tp_as_buffer", self.substructures), + + TypeFlagsSlot("tp_flags"), + DocStringSlot("tp_doc"), + + GCDependentSlot("tp_traverse"), + GCClearReferencesSlot("tp_clear"), + + RichcmpSlot(richcmpfunc, "tp_richcompare", "__richcmp__", method_name_to_slot, + inherited=False), # Py3 checks for __hash__ + + EmptySlot("tp_weaklistoffset"), + + MethodSlot(getiterfunc, "tp_iter", "__iter__", method_name_to_slot), + MethodSlot(iternextfunc, "tp_iternext", "__next__", method_name_to_slot), + + MethodTableSlot("tp_methods"), + MemberTableSlot("tp_members"), + GetSetSlot("tp_getset"), + + BaseClassSlot("tp_base"), #EmptySlot("tp_base"), + EmptySlot("tp_dict"), + + SyntheticSlot("tp_descr_get", ["__get__"], "0"), + SyntheticSlot("tp_descr_set", ["__set__", "__delete__"], "0"), + + DictOffsetSlot("tp_dictoffset", ifdef="!CYTHON_USE_TYPE_SPECS"), # otherwise set via "__dictoffset__" member + + MethodSlot(initproc, "tp_init", "__init__", method_name_to_slot), + EmptySlot("tp_alloc"), #FixedSlot("tp_alloc", "PyType_GenericAlloc"), + ConstructorSlot("tp_new", "__cinit__"), + EmptySlot("tp_free"), + + EmptySlot("tp_is_gc"), + EmptySlot("tp_bases"), + EmptySlot("tp_mro"), + EmptySlot("tp_cache"), + EmptySlot("tp_subclasses"), + EmptySlot("tp_weaklist"), + EmptySlot("tp_del"), + EmptySlot("tp_version_tag"), + SyntheticSlot("tp_finalize", ["__del__"], "0", ifdef="PY_VERSION_HEX >= 0x030400a1", + used_ifdef="CYTHON_USE_TP_FINALIZE"), + EmptySlot("tp_vectorcall", ifdef="PY_VERSION_HEX >= 0x030800b1 && (!CYTHON_COMPILING_IN_PYPY || PYPY_VERSION_NUM >= 0x07030800)"), + EmptySlot("tp_print", ifdef="__PYX_NEED_TP_PRINT_SLOT == 1"), + EmptySlot("tp_watched", ifdef="PY_VERSION_HEX >= 0x030C0000"), + # PyPy specific extension - only here to avoid C compiler warnings. + EmptySlot("tp_pypy_flags", ifdef="CYTHON_COMPILING_IN_PYPY && PY_VERSION_HEX >= 0x03090000 && PY_VERSION_HEX < 0x030a0000"), + ) + + #------------------------------------------------------------------------------------------ + # + # Descriptors for special methods which don't appear directly + # in the type object or its substructures. These methods are + # called from slot functions synthesized by Cython. + # + #------------------------------------------------------------------------------------------ + + MethodSlot(initproc, "", "__cinit__", method_name_to_slot) + MethodSlot(destructor, "", "__dealloc__", method_name_to_slot) + MethodSlot(destructor, "", "__del__", method_name_to_slot) + MethodSlot(objobjargproc, "", "__setitem__", method_name_to_slot) + MethodSlot(objargproc, "", "__delitem__", method_name_to_slot) + MethodSlot(ssizessizeobjargproc, "", "__setslice__", method_name_to_slot) + MethodSlot(ssizessizeargproc, "", "__delslice__", method_name_to_slot) + MethodSlot(getattrofunc, "", "__getattr__", method_name_to_slot) + MethodSlot(getattrofunc, "", "__getattribute__", method_name_to_slot) + MethodSlot(setattrofunc, "", "__setattr__", method_name_to_slot) + MethodSlot(delattrofunc, "", "__delattr__", method_name_to_slot) + MethodSlot(descrgetfunc, "", "__get__", method_name_to_slot) + MethodSlot(descrsetfunc, "", "__set__", method_name_to_slot) + MethodSlot(descrdelfunc, "", "__delete__", method_name_to_slot) + + def get_special_method_signature(self, name): + # Given a method name, if it is a special method, + # return its signature, else return None. + slot = self._get_slot_by_method_name(name) + if slot: + return slot.signature + elif name in richcmp_special_methods: + return ibinaryfunc + else: + return None - MethodTableSlot("tp_methods"), - MemberTableSlot("tp_members"), - GetSetSlot("tp_getset"), + def get_slot_by_method_name(self, method_name): + # For now, only search the type struct, no referenced sub-structs. + return self._get_slot_by_method_name(method_name) - BaseClassSlot("tp_base"), #EmptySlot("tp_base"), - EmptySlot("tp_dict"), + def __iter__(self): + # make it easier to iterate over all the slots + return iter(self.slot_table) - SyntheticSlot("tp_descr_get", ["__get__"], "0"), - SyntheticSlot("tp_descr_set", ["__set__", "__delete__"], "0"), - DictOffsetSlot("tp_dictoffset"), +_slot_table_dict = {} - MethodSlot(initproc, "tp_init", "__init__"), - EmptySlot("tp_alloc"), #FixedSlot("tp_alloc", "PyType_GenericAlloc"), - InternalMethodSlot("tp_new"), - EmptySlot("tp_free"), +def get_slot_table(compiler_directives): + if not compiler_directives: + # fetch default directives here since the builtin type classes don't have + # directives set + from .Options import get_directive_defaults + compiler_directives = get_directive_defaults() - EmptySlot("tp_is_gc"), - EmptySlot("tp_bases"), - EmptySlot("tp_mro"), - EmptySlot("tp_cache"), - EmptySlot("tp_subclasses"), - EmptySlot("tp_weaklist"), - EmptySlot("tp_del"), - EmptySlot("tp_version_tag"), - EmptySlot("tp_finalize", ifdef="PY_VERSION_HEX >= 0x030400a1"), - EmptySlot("tp_vectorcall", ifdef="PY_VERSION_HEX >= 0x030800b1 && (!CYTHON_COMPILING_IN_PYPY || PYPY_VERSION_NUM >= 0x07030800)"), - EmptySlot("tp_print", ifdef="PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000"), - # PyPy specific extension - only here to avoid C compiler warnings. - EmptySlot("tp_pypy_flags", ifdef="CYTHON_COMPILING_IN_PYPY && PY_VERSION_HEX >= 0x03090000 && PY_VERSION_HEX < 0x030a0000"), -) + old_binops = compiler_directives['c_api_binop_methods'] + key = (old_binops,) + if key not in _slot_table_dict: + _slot_table_dict[key] = SlotTable(old_binops=old_binops) + return _slot_table_dict[key] -#------------------------------------------------------------------------------------------ -# -# Descriptors for special methods which don't appear directly -# in the type object or its substructures. These methods are -# called from slot functions synthesized by Cython. -# -#------------------------------------------------------------------------------------------ -MethodSlot(initproc, "", "__cinit__") -MethodSlot(destructor, "", "__dealloc__") -MethodSlot(objobjargproc, "", "__setitem__") -MethodSlot(objargproc, "", "__delitem__") -MethodSlot(ssizessizeobjargproc, "", "__setslice__") -MethodSlot(ssizessizeargproc, "", "__delslice__") -MethodSlot(getattrofunc, "", "__getattr__") -MethodSlot(getattrofunc, "", "__getattribute__") -MethodSlot(setattrofunc, "", "__setattr__") -MethodSlot(delattrofunc, "", "__delattr__") -MethodSlot(descrgetfunc, "", "__get__") -MethodSlot(descrsetfunc, "", "__set__") -MethodSlot(descrdelfunc, "", "__delete__") +# Populate "special_method_names" based on the default directives (so it can always be accessed quickly). +special_method_names = set(get_slot_table(compiler_directives=None)) # Method flags for python-exposed methods. @@ -937,5 +1174,6 @@ MethodSlot(descrdelfunc, "", "__delete__") method_noargs = "METH_NOARGS" method_onearg = "METH_O" method_varargs = "METH_VARARGS" +method_fastcall = "__Pyx_METH_FASTCALL" # Actually VARARGS on versions < 3.7 method_keywords = "METH_KEYWORDS" method_coexist = "METH_COEXIST" |