aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Friedrich Bolz-Tereick <cfbolz@gmx.de>2021-02-01 14:10:13 +0100
committerCarl Friedrich Bolz-Tereick <cfbolz@gmx.de>2021-02-01 14:10:13 +0100
commitdd7373fc460cd9a304c3bef2a3c150c3c2a4dd02 (patch)
treee85853ee6e56601e541842d8bf803621efbd1d3f
parentmerge default (diff)
parentmerge heads (diff)
downloadpypy-dd7373fc460cd9a304c3bef2a3c150c3c2a4dd02.tar.gz
pypy-dd7373fc460cd9a304c3bef2a3c150c3c2a4dd02.tar.bz2
pypy-dd7373fc460cd9a304c3bef2a3c150c3c2a4dd02.zip
merge default
-rw-r--r--pypy/doc/whatsnew-head.rst5
-rw-r--r--rpython/memory/gctransform/transform.py16
-rw-r--r--rpython/rlib/objectmodel.py10
-rw-r--r--rpython/rlib/test/test_objectmodel.py37
-rw-r--r--rpython/rtyper/lltypesystem/ll2ctypes.py6
-rw-r--r--rpython/rtyper/lltypesystem/lltype.py2
-rw-r--r--rpython/rtyper/rclass.py3
-rw-r--r--rpython/translator/c/database.py14
-rw-r--r--rpython/translator/c/node.py2
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,))