diff options
-rw-r--r-- | pypy/doc/whatsnew-head.rst | 5 | ||||
-rw-r--r-- | rpython/memory/gctransform/transform.py | 16 | ||||
-rw-r--r-- | rpython/rlib/objectmodel.py | 10 | ||||
-rw-r--r-- | rpython/rlib/test/test_objectmodel.py | 37 | ||||
-rw-r--r-- | rpython/rtyper/lltypesystem/ll2ctypes.py | 6 | ||||
-rw-r--r-- | rpython/rtyper/lltypesystem/lltype.py | 2 | ||||
-rw-r--r-- | rpython/rtyper/rclass.py | 3 | ||||
-rw-r--r-- | rpython/translator/c/database.py | 14 | ||||
-rw-r--r-- | rpython/translator/c/node.py | 2 |
9 files changed, 80 insertions, 15 deletions
diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst index f932c3b2d8..d195d41f61 100644 --- a/pypy/doc/whatsnew-head.rst +++ b/pypy/doc/whatsnew-head.rst @@ -55,6 +55,11 @@ Backport fixes to winreg adding reflection and fix for passing None (bpo Change parameter type of ``PyModule_New`` to ``const char*``, add ``PyModule_Check`` and ``PyModule_CheckExact`` +.. branch: rpython-never-allocate + +Introduce a ``@never_allocate`` class decorator, which ensure that a certain +RPython class is never actually instantiated at runtime. Useful to ensure that +e.g. it's always constant-folded away .. branch: map-improvements diff --git a/rpython/memory/gctransform/transform.py b/rpython/memory/gctransform/transform.py index d793f91ef2..d3dd64702f 100644 --- a/rpython/memory/gctransform/transform.py +++ b/rpython/memory/gctransform/transform.py @@ -19,6 +19,8 @@ from rpython.rtyper.lltypesystem.lloperation import llop from rpython.translator.simplify import cleanup_graph from rpython.memory.gctransform.log import log +class GCTransformError(Exception): + pass class GcHighLevelOp(object): def __init__(self, gct, op, index, llops): @@ -236,8 +238,12 @@ class BaseGCTransformer(object): inserted_empty_startblock = True is_borrowed = self.compute_borrowed_vars(graph) - for block in graph.iterblocks(): - self.transform_block(block, is_borrowed) + try: + for block in graph.iterblocks(): + self.transform_block(block, is_borrowed) + except GCTransformError as e: + e.args = ('[function %s]: %s' % (graph.name, e.message),) + raise for link, livecounts in self.links_to_split.iteritems(): llops = LowLevelOpList() @@ -519,6 +525,12 @@ class GCTransformer(BaseGCTransformer): def gct_malloc(self, hop, add_flags=None): TYPE = hop.spaceop.result.concretetype.TO + if TYPE._hints.get('never_allocate'): + raise GCTransformError( + "struct %s was marked as @never_allocate but a call to malloc() " + "was found. This probably means that the corresponding class is " + "supposed to be constant-folded away, but for some reason it was not." + % TYPE._name) assert not TYPE._is_varsize() flags = hop.spaceop.args[1].value flavor = flags['flavor'] diff --git a/rpython/rlib/objectmodel.py b/rpython/rlib/objectmodel.py index 5faa6850fd..37ccdf9cb1 100644 --- a/rpython/rlib/objectmodel.py +++ b/rpython/rlib/objectmodel.py @@ -1067,3 +1067,13 @@ def import_from_mixin(M, special_methods=['__init__', '__del__']): target[key] = value if immutable_fields: target['_immutable_fields_'] = target.get('_immutable_fields_', []) + immutable_fields + +def never_allocate(cls): + """ + Class decorator to ensure that a class is NEVER instantiated at runtime. + + Useful e.g for context manager which are expected to be constant-folded + away. + """ + cls._rpython_never_allocate_ = True + return cls diff --git a/rpython/rlib/test/test_objectmodel.py b/rpython/rlib/test/test_objectmodel.py index 6bcb9d60c6..5f14528556 100644 --- a/rpython/rlib/test/test_objectmodel.py +++ b/rpython/rlib/test/test_objectmodel.py @@ -7,7 +7,8 @@ from rpython.rlib.objectmodel import ( resizelist_hint, is_annotation_constant, always_inline, NOT_CONSTANT, iterkeys_with_hash, iteritems_with_hash, contains_with_hash, setitem_with_hash, getitem_with_hash, delitem_with_hash, import_from_mixin, - fetch_translated_config, try_inline, delitem_if_value_is, move_to_end) + fetch_translated_config, try_inline, delitem_if_value_is, move_to_end, + never_allocate, dont_inline) from rpython.translator.translator import TranslationContext, graphof from rpython.rtyper.test.tool import BaseRtypingTest from rpython.rtyper.test.test_llinterp import interpret @@ -851,3 +852,37 @@ def test_import_from_mixin_immutable_fields(): import_from_mixin(C) assert BA._immutable_fields_ == ['c', 'a'] + + +def test_never_allocate(): + from rpython.translator.c.test.test_genc import compile as c_compile + from rpython.memory.gctransform.transform import GCTransformError + + @never_allocate + class MyClass(object): + def __init__(self, x): + self.x = x + 1 + + @dont_inline + def allocate_MyClass(x): + return MyClass(x) + + def f(x): + # this fails because the allocation of MyClass can't be + # constant-folded (because it's inside a @dont_inline function) + return allocate_MyClass(x).x + + def g(x): + # this works because MyClass is constant folded, so the GC transformer + # never sees a malloc(MyClass) + return MyClass(x).x + + # test what happens if MyClass escapes + with py.test.raises(GCTransformError) as exc: + c_compile(f, [int]) + assert '[function allocate_MyClass]' in str(exc) + assert 'was marked as @never_allocate' in str(exc) + + # test that it works in the "normal" case + compiled_g = c_compile(g, [int]) + assert compiled_g(41) == 42 diff --git a/rpython/rtyper/lltypesystem/ll2ctypes.py b/rpython/rtyper/lltypesystem/ll2ctypes.py index d9fdd322d7..efa6f11a2f 100644 --- a/rpython/rtyper/lltypesystem/ll2ctypes.py +++ b/rpython/rtyper/lltypesystem/ll2ctypes.py @@ -374,9 +374,9 @@ def build_new_ctypes_type(T, delayed_builders): elif isinstance(T, lltype.OpaqueType): if T is lltype.RuntimeTypeInfo: return ctypes.c_char * 2 - if T.hints.get('external', None) != 'C': + if T._hints.get('external', None) != 'C': raise TypeError("%s is not external" % T) - return ctypes.c_char * T.hints['getsize']() + return ctypes.c_char * T._hints['getsize']() else: _setup_ctypes_cache() if T in _ctypes_cache: @@ -934,7 +934,7 @@ def lltype2ctypes(llobj, normalize=True): convert_array(container) elif isinstance(T.TO, lltype.OpaqueType): if T.TO != lltype.RuntimeTypeInfo: - cbuf = ctypes.create_string_buffer(T.TO.hints['getsize']()) + cbuf = ctypes.create_string_buffer(T.TO._hints['getsize']()) else: cbuf = ctypes.create_string_buffer("\x00") cbuf = ctypes.cast(cbuf, ctypes.c_void_p) diff --git a/rpython/rtyper/lltypesystem/lltype.py b/rpython/rtyper/lltypesystem/lltype.py index eaa51f1edc..c75fd90e7a 100644 --- a/rpython/rtyper/lltypesystem/lltype.py +++ b/rpython/rtyper/lltypesystem/lltype.py @@ -584,7 +584,7 @@ class OpaqueType(ContainerType): """ self.tag = tag self.__name__ = tag - self.hints = frozendict(hints) + self._hints = frozendict(hints) def __str__(self): return "%s (opaque)" % self.tag diff --git a/rpython/rtyper/rclass.py b/rpython/rtyper/rclass.py index 0d09123617..a98a04649b 100644 --- a/rpython/rtyper/rclass.py +++ b/rpython/rtyper/rclass.py @@ -527,6 +527,9 @@ class InstanceRepr(Repr): if hints is None: hints = {} hints = self._check_for_immutable_hints(hints) + if self.classdef.classdesc.get_param('_rpython_never_allocate_'): + hints['never_allocate'] = True + kwds = {} if self.gcflavor == 'gc': kwds['rtti'] = True diff --git a/rpython/translator/c/database.py b/rpython/translator/c/database.py index f21f661ba3..bf0ced6418 100644 --- a/rpython/translator/c/database.py +++ b/rpython/translator/c/database.py @@ -84,7 +84,7 @@ class LowLevelDatabase(object): node = BareBoneArrayDefNode(self, T, varlength) else: node = ArrayDefNode(self, T, varlength) - elif isinstance(T, OpaqueType) and T.hints.get("render_structure", False): + elif isinstance(T, OpaqueType) and T._hints.get("render_structure", False): node = ExtTypeOpaqueDefNode(self, T) elif T == WeakRef: REALT = self.gcpolicy.get_real_weakref_type() @@ -102,8 +102,8 @@ class LowLevelDatabase(object): return '%s @' % T.c_name elif isinstance(T, Ptr): if (isinstance(T.TO, OpaqueType) and - T.TO.hints.get('c_pointer_typedef') is not None): - return '%s @' % T.TO.hints['c_pointer_typedef'] + T.TO._hints.get('c_pointer_typedef') is not None): + return '%s @' % T.TO._hints['c_pointer_typedef'] try: node = self.gettypedefnode(T.TO) except NoCorrespondingNode: @@ -134,13 +134,13 @@ class LowLevelDatabase(object): elif isinstance(T, OpaqueType): if T == RuntimeTypeInfo: return self.gcpolicy.rtti_type() - elif T.hints.get("render_structure", False): + elif T._hints.get("render_structure", False): node = self.gettypedefnode(T, varlength=varlength) if who_asks is not None: who_asks.dependencies.add(node) return 'struct %s @' % node.name - elif T.hints.get('external', None) == 'C': - return '%s @' % T.hints['c_name'] + elif T._hints.get('external', None) == 'C': + return '%s @' % T._hints['c_name'] else: #raise Exception("don't know about opaque type %r" % (T,)) return 'struct %s @' % ( @@ -182,7 +182,7 @@ class LowLevelDatabase(object): return PrimitiveName[T](obj, self) elif isinstance(T, Ptr): if (isinstance(T.TO, OpaqueType) and - T.TO.hints.get('c_pointer_typedef') is not None): + T.TO._hints.get('c_pointer_typedef') is not None): if obj._obj is not None: value = rffi.cast(rffi.SSIZE_T, obj) return '((%s) %s)' % (cdecl(self.gettype(T), ''), diff --git a/rpython/translator/c/node.py b/rpython/translator/c/node.py index 91458b643c..7898be2bdb 100644 --- a/rpython/translator/c/node.py +++ b/rpython/translator/c/node.py @@ -947,7 +947,7 @@ class ExtType_OpaqueNode(ContainerNode): def opaquenode_factory(db, T, obj): if T == RuntimeTypeInfo: return db.gcpolicy.rtti_node_factory()(db, T, obj) - if T.hints.get("render_structure", False): + if T._hints.get("render_structure", False): return ExtType_OpaqueNode(db, T, obj) raise Exception("don't know about %r" % (T,)) |