From: Stefan Behnel Date: Sat, 31 Oct 2009 13:01:34 +0000 (+0100) Subject: optimise MyType.__new__(MyType) into a tp_new() slot call X-Git-Tag: 0.12.alpha0~8^2~8 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=80359387be15c26d59831076a45a58c171d65ead;p=cython.git optimise MyType.__new__(MyType) into a tp_new() slot call --- diff --git a/Cython/Compiler/Optimize.py b/Cython/Compiler/Optimize.py index 0ac80d12..c26a32a5 100644 --- a/Cython/Compiler/Optimize.py +++ b/Cython/Compiler/Optimize.py @@ -7,6 +7,7 @@ import UtilNodes import TypeSlots import Symtab import Options +import Naming from Code import UtilityCode from StringEncoding import EncodedString, BytesLiteral @@ -851,7 +852,10 @@ class OptimizeBuiltinCalls(Visitor.EnvTransform): method_handler = self._find_handler( "method_%s_%s" % (type_name, function.attribute), kwargs) if method_handler is None: - return node + method_handler = self._find_handler( + "methodany_%s" % function.attribute, kwargs) + if method_handler is None: + return node if self_arg is not None: arg_list = [self_arg] + list(arg_list) if kwargs: @@ -1030,6 +1034,55 @@ class OptimizeBuiltinCalls(Visitor.EnvTransform): ) return node + ### special methods + + def _handle_simple_methodany___new__(self, node, args, is_unbound_method): + """Replace 'exttype.__new__(exttype)' by a call to exttype->tp_new() + """ + obj = node.function.obj + if not is_unbound_method or len(args) != 1: + return node + type_arg = args[0] + if not obj.is_name or not type_arg.is_name: + # play safe + return node + if obj.type != Builtin.type_type or type_arg.type != Builtin.type_type: + # not a known type, play safe + return node + if not type_arg.type_entry or not obj.type_entry: + if obj.name != type_arg.name: + return node + # otherwise, we know it's a type and we know it's the same + # type for both - that should do + elif type_arg.type_entry != obj.type_entry: + # different types - do what CPython does at runtime + error(type_arg.pos, "%s.__new__(%s) is not safe, use %s.__new__()" % + (obj.type_entry.name, type_arg.type_entry.name, + type_arg.type_entry.name)) + return node + + return_type = None + if obj.type_entry: + return_type = obj.type_entry.type + if return_type is None and type_arg.type_entry: + return_type = type_arg.type_entry.type + if return_type is None: + return_type = PyrexTypes.py_object_type + + # FIXME: we could potentially look up the actual tp_new C method + # of the extension type and call that instead of the generic slot + func_type = PyrexTypes.CFuncType( + return_type, [ + PyrexTypes.CFuncTypeArg("type", PyrexTypes.py_object_type, None) + ]) + + return ExprNodes.PythonCapiCallNode( + node.pos, "__Pyx_tp_new", func_type, + args = args, + utility_code = tpnew_utility_code, + is_temp = node.is_temp + ) + ### methods of builtin types PyObject_Append_func_type = PyrexTypes.CFuncType( @@ -1317,6 +1370,16 @@ static INLINE PyObject* __Pyx_Type(PyObject* o) { ) +tpnew_utility_code = UtilityCode( +proto = """ +static INLINE PyObject* __Pyx_tp_new(PyObject* type_obj) { + return (PyObject*) (((PyTypeObject*)(type_obj))->tp_new( + (PyTypeObject*)(type_obj), %(TUPLE)s, NULL)); +} +""" % {'TUPLE' : Naming.empty_tuple} +) + + class ConstantFolding(Visitor.VisitorTransform, SkipDeclarations): """Calculate the result of constant expressions to store it in ``expr_node.constant_result``, and replace trivial cases by their diff --git a/tests/errors/tp_new_errors.pyx b/tests/errors/tp_new_errors.pyx new file mode 100644 index 00000000..05b56159 --- /dev/null +++ b/tests/errors/tp_new_errors.pyx @@ -0,0 +1,29 @@ + +cdef class MyType: + def __init__(self): + print "INIT" + +cdef class MySubType(MyType): + def __init__(self): + print "INIT" + +cdef class MyOtherType: + def __init__(self): + print "INIT" + +def make_new(): + m = MyType.__new__(MyType) + m = MyOtherType.__new__(MyOtherType) + return m + +def make_new_error(): + m = MySubType.__new__(MyType) + m = MyOtherType.__new__(MyType) + m = MyOtherType.__new__(MySubType) + return m + +_ERRORS = """ +20:32: MySubType.__new__(MyType) is not safe, use MyType.__new__() +21:34: MyOtherType.__new__(MyType) is not safe, use MyType.__new__() +22:37: MyOtherType.__new__(MySubType) is not safe, use MySubType.__new__() +""" diff --git a/tests/run/tp_new.pyx b/tests/run/tp_new.pyx new file mode 100644 index 00000000..87ad202e --- /dev/null +++ b/tests/run/tp_new.pyx @@ -0,0 +1,113 @@ + +cimport cython + +cdef class MyType: + def __init__(self): + print "INIT" + +cdef class MySubType(MyType): + def __init__(self): + print "INIT" + +class MyClass(object): + def __init__(self): + print "INIT" + +class MyTypeSubClass(MyType): + def __init__(self): + print "INIT" + +# only this can be safely optimised: + +@cython.test_assert_path_exists('//PythonCapiCallNode') +@cython.test_fail_if_path_exists('//SimpleCallNode/AttributeNode') +def make_new(): + """ + >>> isinstance(make_new(), MyType) + True + """ + m = MyType.__new__(MyType) + return m + +@cython.test_assert_path_exists('//PythonCapiCallNode') +@cython.test_fail_if_path_exists('//SimpleCallNode/AttributeNode') +def make_new_builtin(): + """ + >>> isinstance(make_new_builtin(), tuple) + True + """ + m = dict.__new__(dict) + m = list.__new__(list) + m = tuple.__new__(tuple) + return m + +# these cannot: + +@cython.test_assert_path_exists('//SimpleCallNode/AttributeNode') +@cython.test_fail_if_path_exists('//PythonCapiCallNode') +def make_new_pyclass(): + """ + >>> isinstance(make_new_pyclass(), MyTypeSubClass) + True + """ + m = MyClass.__new__(MyClass) + m = MyTypeSubClass.__new__(MyTypeSubClass) + return m + +@cython.test_assert_path_exists('//SimpleCallNode/AttributeNode') +@cython.test_fail_if_path_exists('//PythonCapiCallNode') +def make_new_args(type t1=None, type t2=None): + """ + >>> isinstance(make_new_args(), MyType) + True + >>> isinstance(make_new_args(MyType), MyType) + True + >>> isinstance(make_new_args(MyType, MyType), MyType) + True + + >>> isinstance(make_new_args(MyType, MySubType), MySubType) + Traceback (most recent call last): + TypeError: tp_new.MyType.__new__(tp_new.MySubType) is not safe, use tp_new.MySubType.__new__() + >>> isinstance(make_new_args(MySubType, MyType), MyType) + Traceback (most recent call last): + TypeError: tp_new.MySubType.__new__(tp_new.MyType): tp_new.MyType is not a subtype of tp_new.MySubType + """ + if t1 is None: + t1 = MyType + if t2 is None: + t2 = MyType + m = t1.__new__(t2) + return m + +@cython.test_assert_path_exists('//SimpleCallNode/AttributeNode') +@cython.test_fail_if_path_exists('//PythonCapiCallNode') +def make_new_none(type t1=None, type t2=None): + """ + >>> isinstance(make_new_none(), MyType) + Traceback (most recent call last): + TypeError: object.__new__(X): X is not a type object (NoneType) + """ + m = t1.__new__(t2) + return m + +@cython.test_assert_path_exists('//SimpleCallNode/AttributeNode') +@cython.test_fail_if_path_exists('//PythonCapiCallNode') +def make_new_none_typed(tuple t1=None, tuple t2=None): + """ + >>> isinstance(make_new_none(), MyType) + Traceback (most recent call last): + TypeError: object.__new__(X): X is not a type object (NoneType) + """ + m = t1.__new__(t2) + return m + +@cython.test_assert_path_exists('//SimpleCallNode/AttributeNode') +@cython.test_fail_if_path_exists('//PythonCapiCallNode') +def make_new_untyped(t): + """ + >>> make_new_untyped(None) + Traceback (most recent call last): + TypeError: object.__new__(X): X is not a type object (NoneType) + """ + m = t.__new__(t) + return m