[pypy-commit] pypy default: hg merge cffi-handle-lifetime
arigo
noreply at buildbot.pypy.org
Mon Oct 12 22:04:16 CEST 2015
Author: Armin Rigo <arigo at tunes.org>
Branch:
Changeset: r80149:3ef464913dfc
Date: 2015-10-12 22:02 +0200
http://bitbucket.org/pypy/pypy/changeset/3ef464913dfc/
Log: hg merge cffi-handle-lifetime
Change the way the cffi handles are done: this version is better
because handles are valid as long as the object exists, as opposed
to the previous version where handles become invalid before the
object's __del__ is called. This is done using a new way to ask the
GC to make nonmovable objects.
diff --git a/pypy/module/_cffi_backend/ccallback.py b/pypy/module/_cffi_backend/ccallback.py
--- a/pypy/module/_cffi_backend/ccallback.py
+++ b/pypy/module/_cffi_backend/ccallback.py
@@ -1,14 +1,14 @@
"""
Callbacks.
"""
-import sys, os
+import sys, os, py
-from rpython.rlib import clibffi, jit, jit_libffi
+from rpython.rlib import clibffi, jit, jit_libffi, rgc, objectmodel
from rpython.rlib.objectmodel import keepalive_until_here
-from rpython.rtyper.lltypesystem import lltype, rffi
+from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
from pypy.interpreter.error import OperationError, oefmt
-from pypy.module._cffi_backend import cerrno, misc, handle
+from pypy.module._cffi_backend import cerrno, misc
from pypy.module._cffi_backend.cdataobj import W_CData
from pypy.module._cffi_backend.ctypefunc import SIZE_OF_FFI_ARG, W_CTypeFunc
from pypy.module._cffi_backend.ctypeprim import W_CTypePrimitiveSigned
@@ -19,6 +19,23 @@
# ____________________________________________________________
+ at jit.dont_look_inside
+def make_callback(space, ctype, w_callable, w_error, w_onerror):
+ # Allocate a callback as a nonmovable W_CDataCallback instance, which
+ # we can cast to a plain VOIDP. As long as the object is not freed,
+ # we can cast the VOIDP back to a W_CDataCallback in reveal_callback().
+ cdata = objectmodel.instantiate(W_CDataCallback, nonmovable=True)
+ gcref = rgc.cast_instance_to_gcref(cdata)
+ raw_cdata = rgc.hide_nonmovable_gcref(gcref)
+ cdata.__init__(space, ctype, w_callable, w_error, w_onerror, raw_cdata)
+ return cdata
+
+def reveal_callback(raw_ptr):
+ addr = rffi.cast(llmemory.Address, raw_ptr)
+ gcref = rgc.reveal_gcref(addr)
+ return rgc.try_cast_gcref_to_instance(W_CDataCallback, gcref)
+
+
class Closure(object):
"""This small class is here to have a __del__ outside any cycle."""
@@ -37,7 +54,8 @@
_immutable_fields_ = ['key_pycode']
w_onerror = None
- def __init__(self, space, ctype, w_callable, w_error, w_onerror):
+ def __init__(self, space, ctype, w_callable, w_error, w_onerror,
+ raw_cdata):
raw_closure = rffi.cast(rffi.CCHARP, clibffi.closureHeap.alloc())
self._closure = Closure(raw_closure)
W_CData.__init__(self, space, raw_closure, ctype)
@@ -72,8 +90,6 @@
from pypy.module.thread.os_thread import setup_threads
setup_threads(space)
#
- handle_index = handle.get_handles(space).reserve_next_handle_index()
- #
cif_descr = self.getfunctype().cif_descr
if not cif_descr:
raise oefmt(space.w_NotImplementedError,
@@ -81,16 +97,13 @@
"return type or with '...'", self.getfunctype().name)
with self as ptr:
closure_ptr = rffi.cast(clibffi.FFI_CLOSUREP, ptr)
- unique_id = rffi.cast(rffi.VOIDP, handle_index)
+ unique_id = rffi.cast(rffi.VOIDP, raw_cdata)
res = clibffi.c_ffi_prep_closure(closure_ptr, cif_descr.cif,
invoke_callback,
unique_id)
if rffi.cast(lltype.Signed, res) != clibffi.FFI_OK:
raise OperationError(space.w_SystemError,
space.wrap("libffi failed to build this callback"))
- #
- _current_space.space = space
- handle.get_handles(space).store_handle(handle_index, self)
def _repr_extra(self):
space = self.space
@@ -221,12 +234,6 @@
except OperationError, e:
_handle_applevel_exception(callback, e, ll_res, extra_line)
-class CurrentSpace:
- def _cleanup_(self):
- if hasattr(self, 'space'):
- del self.space
-_current_space = CurrentSpace()
-
def _invoke_callback(ffi_cif, ll_res, ll_args, ll_userdata):
""" Callback specification.
ffi_cif - something ffi specific, don't care
@@ -236,10 +243,8 @@
(what the real callback is for example), casted to VOIDP
"""
ll_res = rffi.cast(rffi.CCHARP, ll_res)
- unique_id = rffi.cast(lltype.Signed, ll_userdata)
- space = _current_space.space
- callback = handle.get_handles(space).fetch_handle(unique_id)
- if callback is None or not isinstance(callback, W_CDataCallback):
+ callback = reveal_callback(ll_userdata)
+ if callback is None:
# oups!
try:
os.write(STDERR, "SystemError: invoking a callback "
@@ -251,6 +256,7 @@
misc._raw_memclear(ll_res, SIZE_OF_FFI_ARG)
return
#
+ space = callback.space
must_leave = False
try:
must_leave = space.threadlocals.try_enter_thread(space)
diff --git a/pypy/module/_cffi_backend/ffi_obj.py b/pypy/module/_cffi_backend/ffi_obj.py
--- a/pypy/module/_cffi_backend/ffi_obj.py
+++ b/pypy/module/_cffi_backend/ffi_obj.py
@@ -294,9 +294,9 @@
CONSIDER_FN_AS_FNPTR)
space = self.space
if not space.is_none(w_python_callable):
- return ccallback.W_CDataCallback(space, w_ctype,
- w_python_callable, w_error,
- w_onerror)
+ return ccallback.make_callback(space, w_ctype,
+ w_python_callable, w_error,
+ w_onerror)
else:
# decorator mode: returns a single-argument function
return space.appexec([w_ctype, w_error, w_onerror],
diff --git a/pypy/module/_cffi_backend/func.py b/pypy/module/_cffi_backend/func.py
--- a/pypy/module/_cffi_backend/func.py
+++ b/pypy/module/_cffi_backend/func.py
@@ -24,8 +24,8 @@
@unwrap_spec(w_ctype=ctypeobj.W_CType)
def callback(space, w_ctype, w_callable, w_error=None, w_onerror=None):
- from pypy.module._cffi_backend.ccallback import W_CDataCallback
- return W_CDataCallback(space, w_ctype, w_callable, w_error, w_onerror)
+ from pypy.module._cffi_backend.ccallback import make_callback
+ return make_callback(space, w_ctype, w_callable, w_error, w_onerror)
# ____________________________________________________________
diff --git a/pypy/module/_cffi_backend/handle.py b/pypy/module/_cffi_backend/handle.py
--- a/pypy/module/_cffi_backend/handle.py
+++ b/pypy/module/_cffi_backend/handle.py
@@ -1,24 +1,24 @@
+import py
from pypy.interpreter.error import OperationError, oefmt
from pypy.interpreter.gateway import unwrap_spec
+from pypy.interpreter.baseobjspace import W_Root
from pypy.module._cffi_backend import ctypeobj, ctypeptr, cdataobj
-from rpython.rtyper.lltypesystem import lltype, rffi
-from rpython.rlib import rweaklist
-
-
-class CffiHandles(rweaklist.RWeakListMixin):
- def __init__(self, space):
- self.initialize()
-
-def get_handles(space):
- return space.fromcache(CffiHandles)
+from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
+from rpython.rlib import rgc, objectmodel, jit
# ____________________________________________________________
+ at jit.dont_look_inside
def _newp_handle(space, w_ctype, w_x):
- index = get_handles(space).reserve_next_handle_index()
- _cdata = rffi.cast(rffi.CCHARP, index + 1)
- new_cdataobj = cdataobj.W_CDataHandle(space, _cdata, w_ctype, w_x)
- get_handles(space).store_handle(index, new_cdataobj)
+ # Allocate a handle as a nonmovable W_CDataHandle instance, which
+ # we can cast to a plain CCHARP. As long as the object is not freed,
+ # we can cast the CCHARP back to a W_CDataHandle with reveal_gcref().
+ new_cdataobj = objectmodel.instantiate(cdataobj.W_CDataHandle,
+ nonmovable=True)
+ gcref = rgc.cast_instance_to_gcref(new_cdataobj)
+ _cdata = rgc.hide_nonmovable_gcref(gcref)
+ _cdata = rffi.cast(rffi.CCHARP, _cdata)
+ cdataobj.W_CDataHandle.__init__(new_cdataobj, space, _cdata, w_ctype, w_x)
return new_cdataobj
@unwrap_spec(w_ctype=ctypeobj.W_CType)
@@ -38,14 +38,17 @@
"expected a 'cdata' object with a 'void *' out of "
"new_handle(), got '%s'", ctype.name)
with w_cdata as ptr:
- index = rffi.cast(lltype.Signed, ptr)
- original_cdataobj = get_handles(space).fetch_handle(index - 1)
- #
- if isinstance(original_cdataobj, cdataobj.W_CDataHandle):
- return original_cdataobj.w_keepalive
- else:
- if index == 0:
- msg = "cannot use from_handle() on NULL pointer"
- else:
- msg = "'void *' value does not correspond to any object"
- raise OperationError(space.w_RuntimeError, space.wrap(msg))
+ return _reveal(space, ptr)
+
+ at jit.dont_look_inside
+def _reveal(space, ptr):
+ addr = rffi.cast(llmemory.Address, ptr)
+ gcref = rgc.reveal_gcref(addr)
+ if not gcref:
+ raise oefmt(space.w_RuntimeError,
+ "cannot use from_handle() on NULL pointer")
+ cd = rgc.try_cast_gcref_to_instance(cdataobj.W_CDataHandle, gcref)
+ if cd is None:
+ raise oefmt(space.w_SystemError,
+ "ffi.from_handle(): dead or bogus object handle")
+ return cd.w_keepalive
diff --git a/pypy/module/_cffi_backend/test/test_handle.py b/pypy/module/_cffi_backend/test/test_handle.py
deleted file mode 100644
--- a/pypy/module/_cffi_backend/test/test_handle.py
+++ /dev/null
@@ -1,44 +0,0 @@
-import random
-from pypy.module._cffi_backend.handle import CffiHandles
-
-
-class PseudoWeakRef(object):
- _content = 42
-
- def __call__(self):
- return self._content
-
-
-def test_cffi_handles_1():
- ch = CffiHandles(None)
- expected_content = {}
- for i in range(10000):
- index = ch.reserve_next_handle_index()
- assert 0 <= index < len(ch.handles)
- assert ch.handles[index]() is None
- pwr = PseudoWeakRef()
- expected_content[index] = pwr
- ch.handles[index] = pwr
- assert len(ch.handles) <= 16384
- for index, pwr in expected_content.items():
- assert ch.handles[index] is pwr
-
-def test_cffi_handles_2():
- ch = CffiHandles(None)
- expected_content = {}
- for i in range(10000):
- index = ch.reserve_next_handle_index()
- assert 0 <= index < len(ch.handles)
- assert ch.handles[index]() is None
- pwr = PseudoWeakRef()
- expected_content[index] = pwr
- ch.handles[index] = pwr
- #
- if len(expected_content) > 20:
- r = random.choice(list(expected_content))
- pwr = expected_content.pop(r)
- pwr._content = None
- #
- assert len(ch.handles) < 100
- for index, pwr in expected_content.items():
- assert ch.handles[index] is pwr
diff --git a/rpython/annotator/builtin.py b/rpython/annotator/builtin.py
--- a/rpython/annotator/builtin.py
+++ b/rpython/annotator/builtin.py
@@ -290,7 +290,7 @@
return SomeInteger(knowntype=rpython.rlib.rarithmetic.r_longlong)
@analyzer_for(rpython.rlib.objectmodel.instantiate)
-def robjmodel_instantiate(s_clspbc):
+def robjmodel_instantiate(s_clspbc, s_nonmovable=None):
assert isinstance(s_clspbc, SomePBC)
clsdef = None
more_than_one = len(s_clspbc.descriptions) > 1
diff --git a/rpython/jit/codewriter/jtransform.py b/rpython/jit/codewriter/jtransform.py
--- a/rpython/jit/codewriter/jtransform.py
+++ b/rpython/jit/codewriter/jtransform.py
@@ -915,10 +915,13 @@
return [op0, op1]
def rewrite_op_malloc(self, op):
- if op.args[1].value['flavor'] == 'raw':
+ d = op.args[1].value
+ if d.get('nonmovable', False):
+ raise UnsupportedMallocFlags(d)
+ if d['flavor'] == 'raw':
return self._rewrite_raw_malloc(op, 'raw_malloc_fixedsize', [])
#
- if op.args[1].value.get('zero', False):
+ if d.get('zero', False):
zero = True
else:
zero = False
diff --git a/rpython/memory/gc/base.py b/rpython/memory/gc/base.py
--- a/rpython/memory/gc/base.py
+++ b/rpython/memory/gc/base.py
@@ -172,6 +172,9 @@
def can_move(self, addr):
return False
+ def malloc_fixedsize_nonmovable(self, typeid):
+ raise MemoryError
+
def pin(self, addr):
return False
diff --git a/rpython/memory/gc/incminimark.py b/rpython/memory/gc/incminimark.py
--- a/rpython/memory/gc/incminimark.py
+++ b/rpython/memory/gc/incminimark.py
@@ -597,7 +597,7 @@
if needs_finalizer and not is_finalizer_light:
ll_assert(not contains_weakptr,
"'needs_finalizer' and 'contains_weakptr' both specified")
- obj = self.external_malloc(typeid, 0, can_make_young=False)
+ obj = self.external_malloc(typeid, 0, alloc_young=False)
self.objects_with_finalizers.append(obj)
#
# If totalsize is greater than nonlarge_max (which should never be
@@ -606,7 +606,7 @@
elif rawtotalsize > self.nonlarge_max:
ll_assert(not contains_weakptr,
"'contains_weakptr' specified for a large object")
- obj = self.external_malloc(typeid, 0)
+ obj = self.external_malloc(typeid, 0, alloc_young=True)
#
else:
# If totalsize is smaller than minimal_size_in_nursery, round it
@@ -659,7 +659,7 @@
# If the total size of the object would be larger than
# 'nonlarge_max', then allocate it externally. We also
# go there if 'length' is actually negative.
- obj = self.external_malloc(typeid, length)
+ obj = self.external_malloc(typeid, length, alloc_young=True)
#
else:
# With the above checks we know now that totalsize cannot be more
@@ -692,6 +692,11 @@
return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
+ def malloc_fixedsize_nonmovable(self, typeid):
+ obj = self.external_malloc(typeid, 0, alloc_young=True)
+ return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
+
+
def collect(self, gen=2):
"""Do a minor (gen=0), start a major (gen=1), or do a full
major (gen>=2) collection."""
@@ -808,7 +813,7 @@
collect_and_reserve._dont_inline_ = True
- def external_malloc(self, typeid, length, can_make_young=True):
+ def external_malloc(self, typeid, length, alloc_young):
"""Allocate a large object using the ArenaCollection or
raw_malloc(), possibly as an object with card marking enabled,
if it has gc pointers in its var-sized part. 'length' should be
@@ -862,7 +867,9 @@
# we should get a MemoryError from major_collection_step().
#
# Check if the object would fit in the ArenaCollection.
- if raw_malloc_usage(totalsize) <= self.small_request_threshold:
+ # Also, an object allocated from ArenaCollection must be old.
+ if (raw_malloc_usage(totalsize) <= self.small_request_threshold
+ and not alloc_young):
#
# Yes. Round up 'totalsize' (it cannot overflow and it
# must remain <= self.small_request_threshold.)
@@ -874,10 +881,6 @@
# Allocate from the ArenaCollection. Don't clear it.
result = self.ac.malloc(totalsize)
#
- # An object allocated from ArenaCollection is always old, even
- # if 'can_make_young'. The interesting case of 'can_make_young'
- # is for large objects, bigger than the 'large_objects' threshold,
- # which are raw-malloced but still young.
extra_flags = GCFLAG_TRACK_YOUNG_PTRS
#
else:
@@ -897,11 +900,11 @@
extra_words = self.card_marking_words_for_length(length)
cardheadersize = WORD * extra_words
extra_flags = GCFLAG_HAS_CARDS | GCFLAG_TRACK_YOUNG_PTRS
- # if 'can_make_young', then we also immediately set
+ # if 'alloc_young', then we also immediately set
# GCFLAG_CARDS_SET, but without adding the object to
# 'old_objects_with_cards_set'. In this way it should
# never be added to that list as long as it is young.
- if can_make_young:
+ if alloc_young:
extra_flags |= GCFLAG_CARDS_SET
#
# Detect very rare cases of overflows
@@ -939,7 +942,7 @@
# Record the newly allocated object and its full malloced size.
# The object is young or old depending on the argument.
self.rawmalloced_total_size += r_uint(allocsize)
- if can_make_young:
+ if alloc_young:
if not self.young_rawmalloced_objects:
self.young_rawmalloced_objects = self.AddressDict()
self.young_rawmalloced_objects.add(result + size_gc_header)
diff --git a/rpython/memory/gc/minimark.py b/rpython/memory/gc/minimark.py
--- a/rpython/memory/gc/minimark.py
+++ b/rpython/memory/gc/minimark.py
@@ -519,7 +519,7 @@
if needs_finalizer and not is_finalizer_light:
ll_assert(not contains_weakptr,
"'needs_finalizer' and 'contains_weakptr' both specified")
- obj = self.external_malloc(typeid, 0, can_make_young=False)
+ obj = self.external_malloc(typeid, 0, alloc_young=False)
self.objects_with_finalizers.append(obj)
#
# If totalsize is greater than nonlarge_max (which should never be
@@ -528,7 +528,7 @@
elif rawtotalsize > self.nonlarge_max:
ll_assert(not contains_weakptr,
"'contains_weakptr' specified for a large object")
- obj = self.external_malloc(typeid, 0)
+ obj = self.external_malloc(typeid, 0, alloc_young=True)
#
else:
# If totalsize is smaller than minimal_size_in_nursery, round it
@@ -581,7 +581,7 @@
# If the total size of the object would be larger than
# 'nonlarge_max', then allocate it externally. We also
# go there if 'length' is actually negative.
- obj = self.external_malloc(typeid, length)
+ obj = self.external_malloc(typeid, length, alloc_young=True)
#
else:
# With the above checks we know now that totalsize cannot be more
@@ -614,6 +614,11 @@
return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
+ def malloc_fixedsize_nonmovable(self, typeid):
+ obj = self.external_malloc(typeid, 0, alloc_young=True)
+ return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
+
+
def collect(self, gen=1):
"""Do a minor (gen=0) or major (gen>0) collection."""
self.minor_collection()
@@ -671,7 +676,7 @@
collect_and_reserve._dont_inline_ = True
- def external_malloc(self, typeid, length, can_make_young=True):
+ def external_malloc(self, typeid, length, alloc_young):
"""Allocate a large object using the ArenaCollection or
raw_malloc(), possibly as an object with card marking enabled,
if it has gc pointers in its var-sized part. 'length' should be
@@ -711,7 +716,9 @@
self.major_collection(raw_malloc_usage(totalsize))
#
# Check if the object would fit in the ArenaCollection.
- if raw_malloc_usage(totalsize) <= self.small_request_threshold:
+ # Also, an object allocated from ArenaCollection must be old.
+ if (raw_malloc_usage(totalsize) <= self.small_request_threshold
+ and not alloc_young):
#
# Yes. Round up 'totalsize' (it cannot overflow and it
# must remain <= self.small_request_threshold.)
@@ -724,10 +731,6 @@
result = self.ac.malloc(totalsize)
llmemory.raw_memclear(result, totalsize)
#
- # An object allocated from ArenaCollection is always old, even
- # if 'can_make_young'. The interesting case of 'can_make_young'
- # is for large objects, bigger than the 'large_objects' threshold,
- # which are raw-malloced but still young.
extra_flags = GCFLAG_TRACK_YOUNG_PTRS
#
else:
@@ -747,11 +750,11 @@
extra_words = self.card_marking_words_for_length(length)
cardheadersize = WORD * extra_words
extra_flags = GCFLAG_HAS_CARDS | GCFLAG_TRACK_YOUNG_PTRS
- # if 'can_make_young', then we also immediately set
+ # if 'alloc_young', then we also immediately set
# GCFLAG_CARDS_SET, but without adding the object to
# 'old_objects_with_cards_set'. In this way it should
# never be added to that list as long as it is young.
- if can_make_young:
+ if alloc_young:
extra_flags |= GCFLAG_CARDS_SET
#
# Detect very rare cases of overflows
@@ -787,7 +790,7 @@
# Record the newly allocated object and its full malloced size.
# The object is young or old depending on the argument.
self.rawmalloced_total_size += r_uint(allocsize)
- if can_make_young:
+ if alloc_young:
if not self.young_rawmalloced_objects:
self.young_rawmalloced_objects = self.AddressDict()
self.young_rawmalloced_objects.add(result + size_gc_header)
diff --git a/rpython/memory/gc/test/test_direct.py b/rpython/memory/gc/test/test_direct.py
--- a/rpython/memory/gc/test/test_direct.py
+++ b/rpython/memory/gc/test/test_direct.py
@@ -565,8 +565,8 @@
tid = self.get_type_id(VAR)
largeobj_size = self.gc.nonlarge_max + 1
self.gc.next_major_collection_threshold = 99999.0
- addr_src = self.gc.external_malloc(tid, largeobj_size)
- addr_dst = self.gc.external_malloc(tid, largeobj_size)
+ addr_src = self.gc.external_malloc(tid, largeobj_size, alloc_young=True)
+ addr_dst = self.gc.external_malloc(tid, largeobj_size, alloc_young=True)
hdr_src = self.gc.header(addr_src)
hdr_dst = self.gc.header(addr_dst)
#
diff --git a/rpython/memory/gctransform/framework.py b/rpython/memory/gctransform/framework.py
--- a/rpython/memory/gctransform/framework.py
+++ b/rpython/memory/gctransform/framework.py
@@ -531,6 +531,9 @@
getfn(func,
[SomeAddress()],
annmodel.s_None)
+ self.malloc_nonmovable_ptr = getfn(GCClass.malloc_fixedsize_nonmovable,
+ [s_gc, s_typeid16],
+ s_gcref)
def create_custom_trace_funcs(self, gc, rtyper):
custom_trace_funcs = tuple(rtyper.custom_trace_funcs)
@@ -757,7 +760,12 @@
c_has_light_finalizer = rmodel.inputconst(lltype.Bool,
has_light_finalizer)
- if not op.opname.endswith('_varsize') and not flags.get('varsize'):
+ if flags.get('nonmovable'):
+ assert op.opname == 'malloc'
+ assert not flags.get('varsize')
+ malloc_ptr = self.malloc_nonmovable_ptr
+ args = [self.c_const_gc, c_type_id]
+ elif not op.opname.endswith('_varsize') and not flags.get('varsize'):
zero = flags.get('zero', False)
if (self.malloc_fast_ptr is not None and
not c_has_finalizer.value and
diff --git a/rpython/memory/test/test_transformed_gc.py b/rpython/memory/test/test_transformed_gc.py
--- a/rpython/memory/test/test_transformed_gc.py
+++ b/rpython/memory/test/test_transformed_gc.py
@@ -1247,6 +1247,26 @@
res = self.runner('nursery_hash_base')
assert res([]) >= 195
+ def define_instantiate_nonmovable(cls):
+ from rpython.rlib import objectmodel
+ from rpython.rtyper import annlowlevel
+ class A:
+ pass
+ def fn():
+ a1 = A()
+ a = objectmodel.instantiate(A, nonmovable=True)
+ a.next = a1 # 'a' is known young here, so no write barrier emitted
+ res = rgc.can_move(annlowlevel.cast_instance_to_base_ptr(a))
+ rgc.collect()
+ objectmodel.keepalive_until_here(a)
+ return res
+ return fn
+
+ def test_instantiate_nonmovable(self):
+ res = self.runner('instantiate_nonmovable')
+ assert res([]) == 0
+
+
class TestIncrementalMiniMarkGC(TestMiniMarkGC):
gcname = "incminimark"
diff --git a/rpython/rlib/objectmodel.py b/rpython/rlib/objectmodel.py
--- a/rpython/rlib/objectmodel.py
+++ b/rpython/rlib/objectmodel.py
@@ -276,7 +276,7 @@
# ____________________________________________________________
-def instantiate(cls):
+def instantiate(cls, nonmovable=False):
"Create an empty instance of 'cls'."
if isinstance(cls, type):
return cls.__new__(cls)
diff --git a/rpython/rlib/rgc.py b/rpython/rlib/rgc.py
--- a/rpython/rlib/rgc.py
+++ b/rpython/rlib/rgc.py
@@ -480,7 +480,7 @@
class _GcRef(object):
# implementation-specific: there should not be any after translation
- __slots__ = ['_x']
+ __slots__ = ['_x', '_handle']
def __init__(self, x):
self._x = x
def __hash__(self):
@@ -529,6 +529,48 @@
return None
try_cast_gcref_to_instance._annspecialcase_ = 'specialize:arg(0)'
+_ffi_cache = None
+def _fetch_ffi():
+ global _ffi_cache
+ if _ffi_cache is None:
+ try:
+ import _cffi_backend
+ _ffi_cache = _cffi_backend.FFI()
+ except (ImportError, AttributeError):
+ import py
+ py.test.skip("need CFFI >= 1.0")
+ return _ffi_cache
+
+ at jit.dont_look_inside
+def hide_nonmovable_gcref(gcref):
+ from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
+ if we_are_translated():
+ assert lltype.typeOf(gcref) == llmemory.GCREF
+ assert not can_move(gcref)
+ return rffi.cast(llmemory.Address, gcref)
+ else:
+ assert isinstance(gcref, _GcRef)
+ x = gcref._x
+ ffi = _fetch_ffi()
+ if not hasattr(x, '__handle'):
+ x.__handle = ffi.new_handle(x)
+ addr = int(ffi.cast("intptr_t", x.__handle))
+ return rffi.cast(llmemory.Address, addr)
+
+ at jit.dont_look_inside
+def reveal_gcref(addr):
+ from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
+ assert lltype.typeOf(addr) == llmemory.Address
+ if we_are_translated():
+ return rffi.cast(llmemory.GCREF, addr)
+ else:
+ addr = rffi.cast(lltype.Signed, addr)
+ if addr == 0:
+ return lltype.nullptr(llmemory.GCREF.TO)
+ ffi = _fetch_ffi()
+ x = ffi.from_handle(ffi.cast("void *", addr))
+ return _GcRef(x)
+
# ------------------- implementation -------------------
_cache_s_list_of_gcrefs = None
diff --git a/rpython/rtyper/lltypesystem/rtagged.py b/rpython/rtyper/lltypesystem/rtagged.py
--- a/rpython/rtyper/lltypesystem/rtagged.py
+++ b/rpython/rtyper/lltypesystem/rtagged.py
@@ -27,7 +27,8 @@
self.classdef, flds))
self.specialfieldname = flds[0]
- def new_instance(self, llops, classcallhop=None):
+ def new_instance(self, llops, classcallhop=None, nonmovable=False):
+ assert not nonmovable
if self.is_parent:
raise TyperError("don't instantiate %r, it is a parent of an "
"UnboxedValue class" % (self.classdef,))
diff --git a/rpython/rtyper/rbuiltin.py b/rpython/rtyper/rbuiltin.py
--- a/rpython/rtyper/rbuiltin.py
+++ b/rpython/rtyper/rbuiltin.py
@@ -693,18 +693,24 @@
return hop.args_r[0].rtype_isinstance(hop)
@typer_for(objectmodel.instantiate)
-def rtype_instantiate(hop):
+def rtype_instantiate(hop, i_nonmovable=None):
hop.exception_cannot_occur()
s_class = hop.args_s[0]
assert isinstance(s_class, annmodel.SomePBC)
+ v_nonmovable, = parse_kwds(hop, (i_nonmovable, None))
+ nonmovable = (i_nonmovable is not None and v_nonmovable.value)
if len(s_class.descriptions) != 1:
# instantiate() on a variable class
+ if nonmovable:
+ raise TyperError("instantiate(x, nonmovable=True) cannot be used "
+ "if x is not a constant class")
vtypeptr, = hop.inputargs(rclass.get_type_repr(hop.rtyper))
r_class = hop.args_r[0]
return r_class._instantiate_runtime_class(hop, vtypeptr,
hop.r_result.lowleveltype)
classdef = s_class.any_description().getuniqueclassdef()
- return rclass.rtype_new_instance(hop.rtyper, classdef, hop.llops)
+ return rclass.rtype_new_instance(hop.rtyper, classdef, hop.llops,
+ nonmovable=nonmovable)
@typer_for(hasattr)
diff --git a/rpython/rtyper/rclass.py b/rpython/rtyper/rclass.py
--- a/rpython/rtyper/rclass.py
+++ b/rpython/rtyper/rclass.py
@@ -684,10 +684,12 @@
rbase = rbase.rbase
return False
- def new_instance(self, llops, classcallhop=None):
+ def new_instance(self, llops, classcallhop=None, nonmovable=False):
"""Build a new instance, without calling __init__."""
flavor = self.gcflavor
flags = {'flavor': flavor}
+ if nonmovable:
+ flags['nonmovable'] = True
ctype = inputconst(Void, self.object_type)
cflags = inputconst(Void, flags)
vlist = [ctype, cflags]
@@ -1031,9 +1033,10 @@
# ____________________________________________________________
-def rtype_new_instance(rtyper, classdef, llops, classcallhop=None):
+def rtype_new_instance(rtyper, classdef, llops, classcallhop=None,
+ nonmovable=False):
rinstance = getinstancerepr(rtyper, classdef)
- return rinstance.new_instance(llops, classcallhop)
+ return rinstance.new_instance(llops, classcallhop, nonmovable=nonmovable)
def ll_inst_hash(ins):
if not ins:
diff --git a/rpython/rtyper/test/test_rbuiltin.py b/rpython/rtyper/test/test_rbuiltin.py
--- a/rpython/rtyper/test/test_rbuiltin.py
+++ b/rpython/rtyper/test/test_rbuiltin.py
@@ -432,6 +432,14 @@
res = self.interpret(f, [2])
assert self.class_name(res) == 'B'
+ def test_instantiate_nonmovable(self):
+ class A:
+ pass
+ def f():
+ return instantiate(A, nonmovable=True) # no effect before GC
+ res = self.interpret(f, [])
+ assert self.class_name(res) == 'A'
+
def test_os_path_join(self):
def fn(a, b):
return os.path.join(a, b)
More information about the pypy-commit
mailing list