Author: Armin Rigo <[email protected]>
Branch: cpyext-gc-support-2
Changeset: r81853:567f4931007c
Date: 2016-01-19 15:01 +0100
http://bitbucket.org/pypy/pypy/changeset/567f4931007c/
Log: copy the basic GC support
diff --git a/pypy/doc/discussion/rawrefcount.rst
b/pypy/doc/discussion/rawrefcount.rst
new file mode 100644
--- /dev/null
+++ b/pypy/doc/discussion/rawrefcount.rst
@@ -0,0 +1,153 @@
+======================
+Rawrefcount and the GC
+======================
+
+
+GC Interface
+------------
+
+"PyObject" is a raw structure with at least two fields, ob_refcnt and
+ob_pypy_link. The ob_refcnt is the reference counter as used on
+CPython. If the PyObject structure is linked to a live PyPy object,
+its current address is stored in ob_pypy_link and ob_refcnt is bumped
+by either the constant REFCNT_FROM_PYPY, or the constant
+REFCNT_FROM_PYPY_LIGHT (== REFCNT_FROM_PYPY + SOME_HUGE_VALUE)
+(to mean "light finalizer").
+
+Most PyPy objects exist outside cpyext, and conversely in cpyext it is
+possible that a lot of PyObjects exist without being seen by the rest
+of PyPy. At the interface, however, we can "link" a PyPy object and a
+PyObject. There are two kinds of link:
+
+rawrefcount.create_link_pypy(p, ob)
+
+ Makes a link between an exising object gcref 'p' and a newly
+ allocated PyObject structure 'ob'. ob->ob_refcnt must be
+ initialized to either REFCNT_FROM_PYPY, or
+ REFCNT_FROM_PYPY_LIGHT. (The second case is an optimization:
+ when the GC finds the PyPy object and PyObject no longer
+ referenced, it can just free() the PyObject.)
+
+rawrefcount.create_link_pyobj(p, ob)
+
+ Makes a link from an existing PyObject structure 'ob' to a newly
+ allocated W_CPyExtPlaceHolderObject 'p'. You must also add
+ REFCNT_FROM_PYPY to ob->ob_refcnt. For cases where the PyObject
+ contains all the data, and the PyPy object is just a proxy. The
+ W_CPyExtPlaceHolderObject should have only a field that contains
+ the address of the PyObject, but that's outside the scope of the
+ GC.
+
+rawrefcount.from_obj(p)
+
+ If there is a link from object 'p' made with create_link_pypy(),
+ returns the corresponding 'ob'. Otherwise, returns NULL.
+
+rawrefcount.to_obj(Class, ob)
+
+ Returns ob->ob_pypy_link, cast to an instance of 'Class'.
+
+
+Collection logic
+----------------
+
+Objects existing purely on the C side have ob->ob_pypy_link == 0;
+these are purely reference counted. On the other hand, if
+ob->ob_pypy_link != 0, then ob->ob_refcnt is at least REFCNT_FROM_PYPY
+and the object is part of a "link".
+
+The idea is that links whose 'p' is not reachable from other PyPy
+objects *and* whose 'ob->ob_refcnt' is REFCNT_FROM_PYPY or
+REFCNT_FROM_PYPY_LIGHT are the ones who die. But it is more messy
+because PyObjects still (usually) need to have a tp_dealloc called,
+and this cannot occur immediately (and can do random things like
+accessing other references this object points to, or resurrecting the
+object).
+
+Let P = list of links created with rawrefcount.create_link_pypy()
+and O = list of links created with rawrefcount.create_link_pyobj().
+The PyPy objects in the list O are all W_CPyExtPlaceHolderObject: all
+the data is in the PyObjects, and all references (if any) are regular
+CPython-like reference counts.
+
+So, during the collection we do this about P links:
+
+ for (p, ob) in P:
+ if ob->ob_refcnt != REFCNT_FROM_PYPY
+ and ob->ob_refcnt != REFCNT_FROM_PYPY_LIGHT:
+ mark 'p' as surviving, as well as all its dependencies
+
+At the end of the collection, the P and O links are both handled like
+this:
+
+ for (p, ob) in P + O:
+ if p is not surviving:
+ unlink p and ob
+ if ob->ob_refcnt == REFCNT_FROM_PYPY_LIGHT:
+ free(ob)
+ elif ob->ob_refcnt > REFCNT_FROM_PYPY_LIGHT:
+ ob->ob_refcnt -= REFCNT_FROM_PYPY_LIGHT
+ else:
+ ob->ob_refcnt -= REFCNT_FROM_PYPY
+ if ob->ob_refcnt == 0:
+ invoke _Py_Dealloc(ob) later, outside the GC
+
+
+GC Implementation
+-----------------
+
+We need two copies of both the P list and O list, for young or old
+objects. All four lists can be regular AddressLists of 'ob' objects.
+
+We also need an AddressDict mapping 'p' to 'ob' for all links in the P
+list, and update it when PyPy objects move.
+
+
+Further notes
+-------------
+
+For objects that are opaque in CPython, like <dict>, we always create
+a PyPy object, and then when needed we make an empty PyObject and
+attach it with create_link_pypy()/REFCNT_FROM_PYPY_LIGHT.
+
+For <int> and <float> objects, the corresponding PyObjects contain a
+"long" or "double" field too. We link them with create_link_pypy()
+and we can use REFCNT_FROM_PYPY_LIGHT too: 'tp_dealloc' doesn't
+need to be called, and instead just calling free() is fine.
+
+For <type> objects, we need both a PyPy and a PyObject side. These
+are made with create_link_pypy()/REFCNT_FROM_PYPY.
+
+For custom PyXxxObjects allocated from the C extension module, we
+need create_link_pyobj().
+
+For <str> or <unicode> objects coming from PyPy, we use
+create_link_pypy()/REFCNT_FROM_PYPY_LIGHT with a PyObject
+preallocated with the size of the string. We copy the string
+lazily into that area if PyString_AS_STRING() is called.
+
+For <str>, <unicode>, <tuple> or <list> objects in the C extension
+module, we first allocate it as only a PyObject, which supports
+mutation of the data from C, like CPython. When it is exported to
+PyPy we could make a W_CPyExtPlaceHolderObject with
+create_link_pyobj().
+
+For <tuple> objects coming from PyPy, if they are not specialized,
+then the PyPy side holds a regular reference to the items. Then we
+can allocate a PyTupleObject and store in it borrowed PyObject
+pointers to the items. Such a case is created with
+create_link_pypy()/REFCNT_FROM_PYPY_LIGHT. If it is specialized,
+then it doesn't work because the items are created just-in-time on the
+PyPy side. In this case, the PyTupleObject needs to hold real
+references to the PyObject items, and we use create_link_pypy()/
+REFCNT_FROM_PYPY. In all cases, we have a C array of PyObjects
+that we can directly return from PySequence_Fast_ITEMS, PyTuple_ITEMS,
+PyTuple_GetItem, and so on.
+
+For <list> objects coming from PyPy, we can use a cpyext list
+strategy. The list turns into a PyListObject, as if it had been
+allocated from C in the first place. The special strategy can hold
+(only) a direct reference to the PyListObject, and we can use either
+create_link_pyobj() or create_link_pypy() (to be decided).
+PySequence_Fast_ITEMS then works for lists too, and PyList_GetItem
+can return a borrowed reference, and so on.
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
@@ -706,6 +706,7 @@
self.major_collection_step()
else:
self.minor_and_major_collection()
+ self.rrc_invoke_callback()
def collect_and_reserve(self, totalsize):
@@ -783,12 +784,15 @@
self.threshold_reached()): # ^^but only if
still
self.minor_collection() # the same
collection
self.major_collection_step()
- #
- # The nursery might not be empty now, because of
- # execute_finalizers(). If it is almost full again,
- # we need to fix it with another call to
minor_collection().
- if self.nursery_free + totalsize > self.nursery_top:
- self.minor_collection()
+ #
+ self.rrc_invoke_callback()
+ #
+ # The nursery might not be empty now, because of
+ # execute_finalizers() or rrc_invoke_callback().
+ # If it is almost full again,
+ # we need to fix it with another call to
minor_collection().
+ if self.nursery_free + totalsize > self.nursery_top:
+ self.minor_collection()
#
else:
ll_assert(minor_collection_count == 2,
@@ -861,6 +865,7 @@
if self.threshold_reached(raw_malloc_usage(totalsize) +
self.nursery_size // 2):
self.major_collection_step(raw_malloc_usage(totalsize))
+ self.rrc_invoke_callback()
# note that this loop should not be infinite: when the
# last step of a major collection is done but
# threshold_reached(totalsize) is still true, then
@@ -1080,35 +1085,19 @@
"odd-valued (i.e. tagged) pointer unexpected here")
return self.nursery <= addr < self.nursery + self.nursery_size
- def appears_to_be_young(self, addr):
- # "is a valid addr to a young object?"
- # but it's ok to occasionally return True accidentally.
- # Maybe the best implementation would be a bloom filter
- # of some kind instead of the dictionary lookup that is
- # sometimes done below. But the expected common answer
- # is "Yes" because addr points to the nursery, so it may
- # not be useful to optimize the other case too much.
- #
- # First, if 'addr' appears to be a pointer to some place within
- # the nursery, return True
- if not self.translated_to_c:
- # When non-translated, filter out tagged pointers explicitly.
- # When translated, it may occasionally give a wrong answer
- # of True if 'addr' is a tagged pointer with just the wrong value.
- if not self.is_valid_gc_object(addr):
- return False
-
+ def is_young_object(self, addr):
+ # Check if the object at 'addr' is young.
+ if not self.is_valid_gc_object(addr):
+ return False # filter out tagged pointers explicitly.
if self.nursery <= addr < self.nursery_top:
return True # addr is in the nursery
- #
# Else, it may be in the set 'young_rawmalloced_objects'
return (bool(self.young_rawmalloced_objects) and
self.young_rawmalloced_objects.contains(addr))
- appears_to_be_young._always_inline_ = True
def debug_is_old_object(self, addr):
return (self.is_valid_gc_object(addr)
- and not self.appears_to_be_young(addr))
+ and not self.is_young_object(addr))
def is_forwarded(self, obj):
"""Returns True if the nursery obj is marked as forwarded.
@@ -1618,6 +1607,10 @@
self._visit_old_objects_pointing_to_pinned, None)
current_old_objects_pointing_to_pinned.delete()
#
+ # visit the P list from rawrefcount, if enabled.
+ if self.rrc_enabled:
+ self.rrc_minor_collection_trace()
+ #
while True:
# If we are using card marking, do a partial trace of the arrays
# that are flagged with GCFLAG_CARDS_SET.
@@ -1666,6 +1659,10 @@
if self.young_rawmalloced_objects:
self.free_young_rawmalloced_objects()
#
+ # visit the P and O lists from rawrefcount, if enabled.
+ if self.rrc_enabled:
+ self.rrc_minor_collection_free()
+ #
# All live nursery objects are out of the nursery or pinned inside
# the nursery. Create nursery barriers to protect the pinned objects,
# fill the rest of the nursery with zeros and reset the current nursery
@@ -2178,9 +2175,13 @@
# finalizers/weak references are rare and short which means that
# they do not need a separate state and do not need to be
# made incremental.
+ # For now, the same applies to rawrefcount'ed objects.
if (not self.objects_to_trace.non_empty() and
not self.more_objects_to_trace.non_empty()):
#
+ if self.rrc_enabled:
+ self.rrc_major_collection_trace()
+ #
if self.objects_with_finalizers.non_empty():
self.deal_with_objects_with_finalizers()
elif self.old_objects_with_weakrefs.non_empty():
@@ -2215,6 +2216,10 @@
self.old_objects_pointing_to_pinned = \
new_old_objects_pointing_to_pinned
self.updated_old_objects_pointing_to_pinned = True
+ #
+ if self.rrc_enabled:
+ self.rrc_major_collection_free()
+ #
self.gc_state = STATE_SWEEPING
#END MARKING
elif self.gc_state == STATE_SWEEPING:
@@ -2745,3 +2750,234 @@
(obj + offset).address[0] = llmemory.NULL
self.old_objects_with_weakrefs.delete()
self.old_objects_with_weakrefs = new_with_weakref
+
+
+ # ----------
+ # RawRefCount
+
+ rrc_enabled = False
+
+ _ADDRARRAY = lltype.Array(llmemory.Address, hints={'nolength': True})
+ PYOBJ_HDR = lltype.Struct('GCHdr_PyObject',
+ ('ob_refcnt', lltype.Signed),
+ ('ob_pypy_link', lltype.Signed))
+ PYOBJ_HDR_PTR = lltype.Ptr(PYOBJ_HDR)
+ RAWREFCOUNT_DEALLOC_TRIGGER = lltype.Ptr(lltype.FuncType([], lltype.Void))
+
+ def _pyobj(self, pyobjaddr):
+ return llmemory.cast_adr_to_ptr(pyobjaddr, self.PYOBJ_HDR_PTR)
+
+ def rawrefcount_init(self, dealloc_trigger_callback):
+ # see pypy/doc/discussion/rawrefcount.rst
+ if not self.rrc_enabled:
+ self.rrc_p_list_young = self.AddressStack()
+ self.rrc_p_list_old = self.AddressStack()
+ self.rrc_o_list_young = self.AddressStack()
+ self.rrc_o_list_old = self.AddressStack()
+ self.rrc_p_dict = self.AddressDict() # non-nursery keys only
+ self.rrc_p_dict_nurs = self.AddressDict() # nursery keys only
+ p = lltype.malloc(self._ADDRARRAY, 1, flavor='raw',
+ track_allocation=False)
+ self.rrc_singleaddr = llmemory.cast_ptr_to_adr(p)
+ self.rrc_dealloc_trigger_callback = dealloc_trigger_callback
+ self.rrc_dealloc_pending = self.AddressStack()
+ self.rrc_enabled = True
+
+ def check_no_more_rawrefcount_state(self):
+ "NOT_RPYTHON: for tests"
+ assert self.rrc_p_list_young.length() == 0
+ assert self.rrc_p_list_old .length() == 0
+ assert self.rrc_o_list_young.length() == 0
+ assert self.rrc_o_list_old .length() == 0
+ def check_value_is_null(key, value, ignore):
+ assert value == llmemory.NULL
+ self.rrc_p_dict.foreach(check_value_is_null, None)
+ self.rrc_p_dict_nurs.foreach(check_value_is_null, None)
+
+ def rawrefcount_create_link_pypy(self, gcobj, pyobject):
+ ll_assert(self.rrc_enabled, "rawrefcount.init not called")
+ obj = llmemory.cast_ptr_to_adr(gcobj)
+ objint = llmemory.cast_adr_to_int(obj, "symbolic")
+ self._pyobj(pyobject).ob_pypy_link = objint
+ #
+ lst = self.rrc_p_list_young
+ if self.is_in_nursery(obj):
+ dct = self.rrc_p_dict_nurs
+ else:
+ dct = self.rrc_p_dict
+ if not self.is_young_object(obj):
+ lst = self.rrc_p_list_old
+ lst.append(pyobject)
+ dct.setitem(obj, pyobject)
+
+ def rawrefcount_create_link_pyobj(self, gcobj, pyobject):
+ ll_assert(self.rrc_enabled, "rawrefcount.init not called")
+ obj = llmemory.cast_ptr_to_adr(gcobj)
+ if self.is_young_object(obj):
+ self.rrc_o_list_young.append(pyobject)
+ else:
+ self.rrc_o_list_old.append(pyobject)
+ objint = llmemory.cast_adr_to_int(obj, "symbolic")
+ self._pyobj(pyobject).ob_pypy_link = objint
+ # there is no rrc_o_dict
+
+ def rawrefcount_from_obj(self, gcobj):
+ obj = llmemory.cast_ptr_to_adr(gcobj)
+ if self.is_in_nursery(obj):
+ dct = self.rrc_p_dict_nurs
+ else:
+ dct = self.rrc_p_dict
+ return dct.get(obj)
+
+ def rawrefcount_to_obj(self, pyobject):
+ obj = llmemory.cast_int_to_adr(self._pyobj(pyobject).ob_pypy_link)
+ return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
+
+ def rawrefcount_next_dead(self):
+ if self.rrc_dealloc_pending.non_empty():
+ return self.rrc_dealloc_pending.pop()
+ return llmemory.NULL
+
+
+ def rrc_invoke_callback(self):
+ if self.rrc_enabled and self.rrc_dealloc_pending.non_empty():
+ self.rrc_dealloc_trigger_callback()
+
+ def rrc_minor_collection_trace(self):
+ length_estimate = self.rrc_p_dict_nurs.length()
+ self.rrc_p_dict_nurs.delete()
+ self.rrc_p_dict_nurs = self.AddressDict(length_estimate)
+ self.rrc_p_list_young.foreach(self._rrc_minor_trace,
+ self.rrc_singleaddr)
+
+ def _rrc_minor_trace(self, pyobject, singleaddr):
+ from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY
+ from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY_LIGHT
+ #
+ rc = self._pyobj(pyobject).ob_refcnt
+ if rc == REFCNT_FROM_PYPY or rc == REFCNT_FROM_PYPY_LIGHT:
+ pass # the corresponding object may die
+ else:
+ # force the corresponding object to be alive
+ intobj = self._pyobj(pyobject).ob_pypy_link
+ singleaddr.address[0] = llmemory.cast_int_to_adr(intobj)
+ self._trace_drag_out(singleaddr, llmemory.NULL)
+
+ def rrc_minor_collection_free(self):
+ ll_assert(self.rrc_p_dict_nurs.length() == 0, "p_dict_nurs not empty
1")
+ lst = self.rrc_p_list_young
+ while lst.non_empty():
+ self._rrc_minor_free(lst.pop(), self.rrc_p_list_old,
+ self.rrc_p_dict)
+ lst = self.rrc_o_list_young
+ no_o_dict = self.null_address_dict()
+ while lst.non_empty():
+ self._rrc_minor_free(lst.pop(), self.rrc_o_list_old,
+ no_o_dict)
+
+ def _rrc_minor_free(self, pyobject, surviving_list, surviving_dict):
+ intobj = self._pyobj(pyobject).ob_pypy_link
+ obj = llmemory.cast_int_to_adr(intobj)
+ if self.is_in_nursery(obj):
+ if self.is_forwarded(obj):
+ # Common case: survives and moves
+ obj = self.get_forwarding_address(obj)
+ intobj = llmemory.cast_adr_to_int(obj, "symbolic")
+ self._pyobj(pyobject).ob_pypy_link = intobj
+ surviving = True
+ if surviving_dict:
+ # Surviving nursery object: was originally in
+ # rrc_p_dict_nurs and now must be put into rrc_p_dict
+ surviving_dict.setitem(obj, pyobject)
+ else:
+ surviving = False
+ elif (bool(self.young_rawmalloced_objects) and
+ self.young_rawmalloced_objects.contains(obj)):
+ # young weakref to a young raw-malloced object
+ if self.header(obj).tid & GCFLAG_VISITED_RMY:
+ surviving = True # survives, but does not move
+ else:
+ surviving = False
+ if surviving_dict:
+ # Dying young large object: was in rrc_p_dict,
+ # must be deleted
+ surviving_dict.setitem(obj, llmemory.NULL)
+ else:
+ ll_assert(False, "rrc_X_list_young contains non-young obj")
+ return
+ #
+ if surviving:
+ surviving_list.append(pyobject)
+ else:
+ self._rrc_free(pyobject)
+
+ def _rrc_free(self, pyobject):
+ from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY
+ from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY_LIGHT
+ #
+ rc = self._pyobj(pyobject).ob_refcnt
+ if rc >= REFCNT_FROM_PYPY_LIGHT:
+ rc -= REFCNT_FROM_PYPY_LIGHT
+ if rc == 0:
+ lltype.free(self._pyobj(pyobject), flavor='raw')
+ else:
+ # can only occur if LIGHT is used in create_link_pyobj()
+ self._pyobj(pyobject).ob_refcnt = rc
+ self._pyobj(pyobject).ob_pypy_link = 0
+ else:
+ ll_assert(rc >= REFCNT_FROM_PYPY, "refcount underflow?")
+ ll_assert(rc < int(REFCNT_FROM_PYPY_LIGHT * 0.99),
+ "refcount underflow from REFCNT_FROM_PYPY_LIGHT?")
+ rc -= REFCNT_FROM_PYPY
+ self._pyobj(pyobject).ob_refcnt = rc
+ self._pyobj(pyobject).ob_pypy_link = 0
+ if rc == 0:
+ self.rrc_dealloc_pending.append(pyobject)
+ _rrc_free._always_inline_ = True
+
+ def rrc_major_collection_trace(self):
+ self.rrc_p_list_old.foreach(self._rrc_major_trace, None)
+
+ def _rrc_major_trace(self, pyobject, ignore):
+ from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY
+ from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY_LIGHT
+ #
+ rc = self._pyobj(pyobject).ob_refcnt
+ if rc == REFCNT_FROM_PYPY or rc == REFCNT_FROM_PYPY_LIGHT:
+ pass # the corresponding object may die
+ else:
+ # force the corresponding object to be alive
+ intobj = self._pyobj(pyobject).ob_pypy_link
+ obj = llmemory.cast_int_to_adr(intobj)
+ self.objects_to_trace.append(obj)
+ self.visit_all_objects()
+
+ def rrc_major_collection_free(self):
+ ll_assert(self.rrc_p_dict_nurs.length() == 0, "p_dict_nurs not empty
2")
+ length_estimate = self.rrc_p_dict.length()
+ self.rrc_p_dict.delete()
+ self.rrc_p_dict = new_p_dict = self.AddressDict(length_estimate)
+ new_p_list = self.AddressStack()
+ while self.rrc_p_list_old.non_empty():
+ self._rrc_major_free(self.rrc_p_list_old.pop(), new_p_list,
+ new_p_dict)
+ self.rrc_p_list_old.delete()
+ self.rrc_p_list_old = new_p_list
+ #
+ new_o_list = self.AddressStack()
+ no_o_dict = self.null_address_dict()
+ while self.rrc_o_list_old.non_empty():
+ self._rrc_major_free(self.rrc_o_list_old.pop(), new_o_list,
+ no_o_dict)
+ self.rrc_o_list_old.delete()
+ self.rrc_o_list_old = new_o_list
+
+ def _rrc_major_free(self, pyobject, surviving_list, surviving_dict):
+ intobj = self._pyobj(pyobject).ob_pypy_link
+ obj = llmemory.cast_int_to_adr(intobj)
+ if self.header(obj).tid & GCFLAG_VISITED:
+ surviving_list.append(pyobject)
+ if surviving_dict:
+ surviving_dict.insertclean(obj, pyobject)
+ else:
+ self._rrc_free(pyobject)
diff --git a/rpython/memory/gc/test/test_rawrefcount.py
b/rpython/memory/gc/test/test_rawrefcount.py
new file mode 100644
--- /dev/null
+++ b/rpython/memory/gc/test/test_rawrefcount.py
@@ -0,0 +1,270 @@
+import py
+from rpython.rtyper.lltypesystem import lltype, llmemory
+from rpython.memory.gc.incminimark import IncrementalMiniMarkGC
+from rpython.memory.gc.test.test_direct import BaseDirectGCTest
+from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY
+from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY_LIGHT
+
+PYOBJ_HDR = IncrementalMiniMarkGC.PYOBJ_HDR
+PYOBJ_HDR_PTR = IncrementalMiniMarkGC.PYOBJ_HDR_PTR
+
+S = lltype.GcForwardReference()
+S.become(lltype.GcStruct('S',
+ ('x', lltype.Signed),
+ ('prev', lltype.Ptr(S)),
+ ('next', lltype.Ptr(S))))
+
+
+class TestRawRefCount(BaseDirectGCTest):
+ GCClass = IncrementalMiniMarkGC
+
+ def _collect(self, major, expected_trigger=0):
+ if major:
+ self.gc.collect()
+ else:
+ self.gc.minor_collection()
+ count1 = len(self.trigger)
+ self.gc.rrc_invoke_callback()
+ count2 = len(self.trigger)
+ assert count2 - count1 == expected_trigger
+
+ def _rawrefcount_pair(self, intval, is_light=False, is_pyobj=False,
+ create_old=False):
+ if is_light:
+ rc = REFCNT_FROM_PYPY_LIGHT
+ else:
+ rc = REFCNT_FROM_PYPY
+ self.trigger = []
+ self.gc.rawrefcount_init(lambda: self.trigger.append(1))
+ #
+ p1 = self.malloc(S)
+ p1.x = intval
+ if create_old:
+ self.stackroots.append(p1)
+ self._collect(major=False)
+ p1 = self.stackroots.pop()
+ p1ref = lltype.cast_opaque_ptr(llmemory.GCREF, p1)
+ r1 = lltype.malloc(PYOBJ_HDR, flavor='raw')
+ r1.ob_refcnt = rc
+ r1.ob_pypy_link = 0
+ r1addr = llmemory.cast_ptr_to_adr(r1)
+ if is_pyobj:
+ assert not is_light
+ self.gc.rawrefcount_create_link_pyobj(p1ref, r1addr)
+ else:
+ self.gc.rawrefcount_create_link_pypy(p1ref, r1addr)
+ assert r1.ob_refcnt == rc
+ assert r1.ob_pypy_link != 0
+
+ def check_alive(extra_refcount):
+ assert r1.ob_refcnt == rc + extra_refcount
+ assert r1.ob_pypy_link != 0
+ p1ref = self.gc.rawrefcount_to_obj(r1addr)
+ p1 = lltype.cast_opaque_ptr(lltype.Ptr(S), p1ref)
+ assert p1.x == intval
+ if not is_pyobj:
+ assert self.gc.rawrefcount_from_obj(p1ref) == r1addr
+ else:
+ assert self.gc.rawrefcount_from_obj(p1ref) == llmemory.NULL
+ return p1
+ return p1, p1ref, r1, r1addr, check_alive
+
+ def test_rawrefcount_objects_basic(self, old=False):
+ p1, p1ref, r1, r1addr, check_alive = (
+ self._rawrefcount_pair(42, is_light=True, create_old=old))
+ p2 = self.malloc(S)
+ p2.x = 84
+ p2ref = lltype.cast_opaque_ptr(llmemory.GCREF, p2)
+ r2 = lltype.malloc(PYOBJ_HDR, flavor='raw')
+ r2.ob_refcnt = 1
+ r2.ob_pypy_link = 0
+ r2addr = llmemory.cast_ptr_to_adr(r2)
+ # p2 and r2 are not linked
+ assert r1.ob_pypy_link != 0
+ assert r2.ob_pypy_link == 0
+ assert self.gc.rawrefcount_from_obj(p1ref) == r1addr
+ assert self.gc.rawrefcount_from_obj(p2ref) == llmemory.NULL
+ assert self.gc.rawrefcount_to_obj(r1addr) == p1ref
+ assert self.gc.rawrefcount_to_obj(r2addr) == lltype.nullptr(
+ llmemory.GCREF.TO)
+ lltype.free(r1, flavor='raw')
+ lltype.free(r2, flavor='raw')
+
+ def test_rawrefcount_objects_collection_survives_from_raw(self, old=False):
+ p1, p1ref, r1, r1addr, check_alive = (
+ self._rawrefcount_pair(42, is_light=True, create_old=old))
+ check_alive(0)
+ r1.ob_refcnt += 1
+ self._collect(major=False)
+ check_alive(+1)
+ self._collect(major=True)
+ check_alive(+1)
+ r1.ob_refcnt -= 1
+ self._collect(major=False)
+ p1 = check_alive(0)
+ self._collect(major=True)
+ py.test.raises(RuntimeError, "r1.ob_refcnt") # dead
+ py.test.raises(RuntimeError, "p1.x") # dead
+ self.gc.check_no_more_rawrefcount_state()
+ assert self.trigger == []
+ assert self.gc.rawrefcount_next_dead() == llmemory.NULL
+
+ def test_rawrefcount_dies_quickly(self, old=False):
+ p1, p1ref, r1, r1addr, check_alive = (
+ self._rawrefcount_pair(42, is_light=True, create_old=old))
+ check_alive(0)
+ self._collect(major=False)
+ if old:
+ check_alive(0)
+ self._collect(major=True)
+ py.test.raises(RuntimeError, "r1.ob_refcnt") # dead
+ py.test.raises(RuntimeError, "p1.x") # dead
+ self.gc.check_no_more_rawrefcount_state()
+
+ def test_rawrefcount_objects_collection_survives_from_obj(self, old=False):
+ p1, p1ref, r1, r1addr, check_alive = (
+ self._rawrefcount_pair(42, is_light=True, create_old=old))
+ check_alive(0)
+ self.stackroots.append(p1)
+ self._collect(major=False)
+ check_alive(0)
+ self._collect(major=True)
+ check_alive(0)
+ p1 = self.stackroots.pop()
+ self._collect(major=False)
+ check_alive(0)
+ assert p1.x == 42
+ self._collect(major=True)
+ py.test.raises(RuntimeError, "r1.ob_refcnt") # dead
+ py.test.raises(RuntimeError, "p1.x") # dead
+ self.gc.check_no_more_rawrefcount_state()
+
+ def test_rawrefcount_objects_basic_old(self):
+ self.test_rawrefcount_objects_basic(old=True)
+ def test_rawrefcount_objects_collection_survives_from_raw_old(self):
+ self.test_rawrefcount_objects_collection_survives_from_raw(old=True)
+ def test_rawrefcount_dies_quickly_old(self):
+ self.test_rawrefcount_dies_quickly(old=True)
+ def test_rawrefcount_objects_collection_survives_from_obj_old(self):
+ self.test_rawrefcount_objects_collection_survives_from_obj(old=True)
+
+ def test_pypy_nonlight_survives_from_raw(self, old=False):
+ p1, p1ref, r1, r1addr, check_alive = (
+ self._rawrefcount_pair(42, is_light=False, create_old=old))
+ check_alive(0)
+ r1.ob_refcnt += 1
+ self._collect(major=False)
+ check_alive(+1)
+ self._collect(major=True)
+ check_alive(+1)
+ r1.ob_refcnt -= 1
+ self._collect(major=False)
+ p1 = check_alive(0)
+ self._collect(major=True, expected_trigger=1)
+ py.test.raises(RuntimeError, "p1.x") # dead
+ assert r1.ob_refcnt == 0
+ assert r1.ob_pypy_link == 0
+ assert self.gc.rawrefcount_next_dead() == r1addr
+ assert self.gc.rawrefcount_next_dead() == llmemory.NULL
+ assert self.gc.rawrefcount_next_dead() == llmemory.NULL
+ self.gc.check_no_more_rawrefcount_state()
+ lltype.free(r1, flavor='raw')
+
+ def test_pypy_nonlight_survives_from_obj(self, old=False):
+ p1, p1ref, r1, r1addr, check_alive = (
+ self._rawrefcount_pair(42, is_light=False, create_old=old))
+ check_alive(0)
+ self.stackroots.append(p1)
+ self._collect(major=False)
+ check_alive(0)
+ self._collect(major=True)
+ check_alive(0)
+ p1 = self.stackroots.pop()
+ self._collect(major=False)
+ check_alive(0)
+ assert p1.x == 42
+ self._collect(major=True, expected_trigger=1)
+ py.test.raises(RuntimeError, "p1.x") # dead
+ assert r1.ob_refcnt == 0
+ assert r1.ob_pypy_link == 0
+ assert self.gc.rawrefcount_next_dead() == r1addr
+ self.gc.check_no_more_rawrefcount_state()
+ lltype.free(r1, flavor='raw')
+
+ def test_pypy_nonlight_dies_quickly(self, old=False):
+ p1, p1ref, r1, r1addr, check_alive = (
+ self._rawrefcount_pair(42, is_light=False, create_old=old))
+ check_alive(0)
+ if old:
+ self._collect(major=False)
+ check_alive(0)
+ self._collect(major=True, expected_trigger=1)
+ else:
+ self._collect(major=False, expected_trigger=1)
+ py.test.raises(RuntimeError, "p1.x") # dead
+ assert r1.ob_refcnt == 0
+ assert r1.ob_pypy_link == 0
+ assert self.gc.rawrefcount_next_dead() == r1addr
+ self.gc.check_no_more_rawrefcount_state()
+ lltype.free(r1, flavor='raw')
+
+ def test_pypy_nonlight_survives_from_raw_old(self):
+ self.test_pypy_nonlight_survives_from_raw(old=True)
+ def test_pypy_nonlight_survives_from_obj_old(self):
+ self.test_pypy_nonlight_survives_from_obj(old=True)
+ def test_pypy_nonlight_dies_quickly_old(self):
+ self.test_pypy_nonlight_dies_quickly(old=True)
+
+ def test_pyobject_pypy_link_dies_on_minor_collection(self):
+ p1, p1ref, r1, r1addr, check_alive = (
+ self._rawrefcount_pair(42, is_pyobj=True))
+ check_alive(0)
+ r1.ob_refcnt += 1 # the pyobject is kept alive
+ self._collect(major=False)
+ assert r1.ob_refcnt == 1 # refcnt dropped to 1
+ assert r1.ob_pypy_link == 0 # detached
+ self.gc.check_no_more_rawrefcount_state()
+ lltype.free(r1, flavor='raw')
+
+ def test_pyobject_dies(self, old=False):
+ p1, p1ref, r1, r1addr, check_alive = (
+ self._rawrefcount_pair(42, is_pyobj=True, create_old=old))
+ check_alive(0)
+ if old:
+ self._collect(major=False)
+ check_alive(0)
+ self._collect(major=True, expected_trigger=1)
+ else:
+ self._collect(major=False, expected_trigger=1)
+ assert r1.ob_refcnt == 0 # refcnt dropped to 0
+ assert r1.ob_pypy_link == 0 # detached
+ assert self.gc.rawrefcount_next_dead() == r1addr
+ self.gc.check_no_more_rawrefcount_state()
+ lltype.free(r1, flavor='raw')
+
+ def test_pyobject_survives_from_obj(self, old=False):
+ p1, p1ref, r1, r1addr, check_alive = (
+ self._rawrefcount_pair(42, is_pyobj=True, create_old=old))
+ check_alive(0)
+ self.stackroots.append(p1)
+ self._collect(major=False)
+ check_alive(0)
+ self._collect(major=True)
+ check_alive(0)
+ p1 = self.stackroots.pop()
+ self._collect(major=False)
+ check_alive(0)
+ assert p1.x == 42
+ assert self.trigger == []
+ self._collect(major=True, expected_trigger=1)
+ py.test.raises(RuntimeError, "p1.x") # dead
+ assert r1.ob_refcnt == 0
+ assert r1.ob_pypy_link == 0
+ assert self.gc.rawrefcount_next_dead() == r1addr
+ self.gc.check_no_more_rawrefcount_state()
+ lltype.free(r1, flavor='raw')
+
+ def test_pyobject_dies_old(self):
+ self.test_pyobject_dies(old=True)
+ def test_pyobject_survives_from_obj_old(self):
+ self.test_pyobject_survives_from_obj(old=True)
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
@@ -153,6 +153,7 @@
else:
# for regular translation: pick the GC from the config
GCClass, GC_PARAMS = choose_gc_from_config(translator.config)
+ self.GCClass = GCClass
if hasattr(translator, '_jit2gc'):
self.layoutbuilder = translator._jit2gc['layoutbuilder']
@@ -483,6 +484,29 @@
annmodel.SomeInteger(nonneg=True)],
annmodel.s_None)
+ if hasattr(GCClass, 'rawrefcount_init'):
+ self.rawrefcount_init_ptr = getfn(
+ GCClass.rawrefcount_init,
+ [s_gc, SomePtr(GCClass.RAWREFCOUNT_DEALLOC_TRIGGER)],
+ annmodel.s_None)
+ self.rawrefcount_create_link_pypy_ptr = getfn(
+ GCClass.rawrefcount_create_link_pypy,
+ [s_gc, s_gcref, SomeAddress()],
+ annmodel.s_None)
+ self.rawrefcount_create_link_pyobj_ptr = getfn(
+ GCClass.rawrefcount_create_link_pyobj,
+ [s_gc, s_gcref, SomeAddress()],
+ annmodel.s_None)
+ self.rawrefcount_from_obj_ptr = getfn(
+ GCClass.rawrefcount_from_obj, [s_gc, s_gcref], SomeAddress(),
+ inline = True)
+ self.rawrefcount_to_obj_ptr = getfn(
+ GCClass.rawrefcount_to_obj, [s_gc, SomeAddress()], s_gcref,
+ inline = True)
+ self.rawrefcount_next_dead_ptr = getfn(
+ GCClass.rawrefcount_next_dead, [s_gc], SomeAddress(),
+ inline = True)
+
if GCClass.can_usually_pin_objects:
self.pin_ptr = getfn(GCClass.pin,
[s_gc, SomeAddress()],
@@ -1228,6 +1252,50 @@
resultvar=hop.spaceop.result)
self.pop_roots(hop, livevars)
+ def gct_gc_rawrefcount_init(self, hop):
+ [v_fnptr] = hop.spaceop.args
+ assert v_fnptr.concretetype == self.GCClass.RAWREFCOUNT_DEALLOC_TRIGGER
+ hop.genop("direct_call",
+ [self.rawrefcount_init_ptr, self.c_const_gc, v_fnptr])
+
+ def gct_gc_rawrefcount_create_link_pypy(self, hop):
+ [v_gcobj, v_pyobject] = hop.spaceop.args
+ assert v_gcobj.concretetype == llmemory.GCREF
+ assert v_pyobject.concretetype == llmemory.Address
+ hop.genop("direct_call",
+ [self.rawrefcount_create_link_pypy_ptr, self.c_const_gc,
+ v_gcobj, v_pyobject])
+
+ def gct_gc_rawrefcount_create_link_pyobj(self, hop):
+ [v_gcobj, v_pyobject] = hop.spaceop.args
+ assert v_gcobj.concretetype == llmemory.GCREF
+ assert v_pyobject.concretetype == llmemory.Address
+ hop.genop("direct_call",
+ [self.rawrefcount_create_link_pyobj_ptr, self.c_const_gc,
+ v_gcobj, v_pyobject])
+
+ def gct_gc_rawrefcount_from_obj(self, hop):
+ [v_gcobj] = hop.spaceop.args
+ assert v_gcobj.concretetype == llmemory.GCREF
+ assert hop.spaceop.result.concretetype == llmemory.Address
+ hop.genop("direct_call",
+ [self.rawrefcount_from_obj_ptr, self.c_const_gc, v_gcobj],
+ resultvar=hop.spaceop.result)
+
+ def gct_gc_rawrefcount_to_obj(self, hop):
+ [v_pyobject] = hop.spaceop.args
+ assert v_pyobject.concretetype == llmemory.Address
+ assert hop.spaceop.result.concretetype == llmemory.GCREF
+ hop.genop("direct_call",
+ [self.rawrefcount_to_obj_ptr, self.c_const_gc, v_pyobject],
+ resultvar=hop.spaceop.result)
+
+ def gct_gc_rawrefcount_next_dead(self, hop):
+ assert hop.spaceop.result.concretetype == llmemory.Address
+ hop.genop("direct_call",
+ [self.rawrefcount_next_dead_ptr, self.c_const_gc],
+ resultvar=hop.spaceop.result)
+
def _set_into_gc_array_part(self, op):
if op.opname == 'setarrayitem':
return op.args[1]
diff --git a/rpython/rlib/exports.py b/rpython/rlib/exports.py
--- a/rpython/rlib/exports.py
+++ b/rpython/rlib/exports.py
@@ -1,5 +1,7 @@
from rpython.rtyper.lltypesystem.lltype import typeOf, ContainerType
+# XXX kill me
+
def export_struct(name, struct):
assert name not in EXPORTS_names, "Duplicate export " + name
assert isinstance(typeOf(struct), ContainerType)
diff --git a/rpython/rlib/rawrefcount.py b/rpython/rlib/rawrefcount.py
new file mode 100644
--- /dev/null
+++ b/rpython/rlib/rawrefcount.py
@@ -0,0 +1,262 @@
+#
+# See documentation in pypy/doc/discussion/rawrefcount.rst
+#
+import sys, weakref
+from rpython.rtyper.lltypesystem import lltype, llmemory
+from rpython.rlib.objectmodel import we_are_translated, specialize
+from rpython.rtyper.extregistry import ExtRegistryEntry
+from rpython.rlib import rgc
+
+
+REFCNT_FROM_PYPY = 80
+REFCNT_FROM_PYPY_LIGHT = REFCNT_FROM_PYPY + (sys.maxint//2+1)
+
+RAWREFCOUNT_DEALLOC_TRIGGER = lltype.Ptr(lltype.FuncType([], lltype.Void))
+
+
+def _build_pypy_link(p):
+ res = len(_adr2pypy)
+ _adr2pypy.append(p)
+ return res
+
+
+def init(dealloc_trigger_callback=None):
+ """NOT_RPYTHON: set up rawrefcount with the GC. This is only used
+ for tests; it should not be called at all during translation.
+ """
+ global _p_list, _o_list, _adr2pypy, _pypy2ob
+ global _d_list, _dealloc_trigger_callback
+ _p_list = []
+ _o_list = []
+ _adr2pypy = [None]
+ _pypy2ob = {}
+ _d_list = []
+ _dealloc_trigger_callback = dealloc_trigger_callback
+
+def create_link_pypy(p, ob):
+ "NOT_RPYTHON: a link where the PyPy object contains some or all the data"
+ #print 'create_link_pypy\n\t%s\n\t%s' % (p, ob)
+ assert p not in _pypy2ob
+ #assert not ob.c_ob_pypy_link
+ ob.c_ob_pypy_link = _build_pypy_link(p)
+ _pypy2ob[p] = ob
+ _p_list.append(ob)
+
+def create_link_pyobj(p, ob):
+ """NOT_RPYTHON: a link where the PyObject contains all the data.
+ from_obj() will not work on this 'p'."""
+ #print 'create_link_pyobj\n\t%s\n\t%s' % (p, ob)
+ assert p not in _pypy2ob
+ #assert not ob.c_ob_pypy_link
+ ob.c_ob_pypy_link = _build_pypy_link(p)
+ _o_list.append(ob)
+
+def from_obj(OB_PTR_TYPE, p):
+ "NOT_RPYTHON"
+ ob = _pypy2ob.get(p)
+ if ob is None:
+ return lltype.nullptr(OB_PTR_TYPE.TO)
+ assert lltype.typeOf(ob) == OB_PTR_TYPE
+ return ob
+
+def to_obj(Class, ob):
+ "NOT_RPYTHON"
+ link = ob.c_ob_pypy_link
+ if link == 0:
+ return None
+ p = _adr2pypy[link]
+ assert isinstance(p, Class)
+ return p
+
+def next_dead(OB_PTR_TYPE):
+ if len(_d_list) == 0:
+ return lltype.nullptr(OB_PTR_TYPE.TO)
+ ob = _d_list.pop()
+ assert lltype.typeOf(ob) == OB_PTR_TYPE
+ return ob
+
+def _collect(track_allocation=True):
+ """NOT_RPYTHON: for tests only. Emulates a GC collection.
+ Will invoke dealloc_trigger_callback() once if there are objects
+ whose _Py_Dealloc() should be called.
+ """
+ def detach(ob, wr_list):
+ assert ob.c_ob_refcnt >= REFCNT_FROM_PYPY
+ assert ob.c_ob_pypy_link
+ p = _adr2pypy[ob.c_ob_pypy_link]
+ assert p is not None
+ _adr2pypy[ob.c_ob_pypy_link] = None
+ wr_list.append((ob, weakref.ref(p)))
+ return p
+
+ global _p_list, _o_list
+ wr_p_list = []
+ new_p_list = []
+ for ob in reversed(_p_list):
+ if ob.c_ob_refcnt not in (REFCNT_FROM_PYPY, REFCNT_FROM_PYPY_LIGHT):
+ new_p_list.append(ob)
+ else:
+ p = detach(ob, wr_p_list)
+ del _pypy2ob[p]
+ del p
+ ob = None
+ _p_list = Ellipsis
+
+ wr_o_list = []
+ for ob in reversed(_o_list):
+ detach(ob, wr_o_list)
+ _o_list = Ellipsis
+
+ rgc.collect() # forces the cycles to be resolved and the weakrefs to die
+ rgc.collect()
+ rgc.collect()
+
+ def attach(ob, wr, final_list):
+ assert ob.c_ob_refcnt >= REFCNT_FROM_PYPY
+ p = wr()
+ if p is not None:
+ assert ob.c_ob_pypy_link
+ _adr2pypy[ob.c_ob_pypy_link] = p
+ final_list.append(ob)
+ return p
+ else:
+ ob.c_ob_pypy_link = 0
+ if ob.c_ob_refcnt >= REFCNT_FROM_PYPY_LIGHT:
+ ob.c_ob_refcnt -= REFCNT_FROM_PYPY_LIGHT
+ ob.c_ob_pypy_link = 0
+ if ob.c_ob_refcnt == 0:
+ lltype.free(ob, flavor='raw',
+ track_allocation=track_allocation)
+ else:
+ assert ob.c_ob_refcnt >= REFCNT_FROM_PYPY
+ assert ob.c_ob_refcnt < int(REFCNT_FROM_PYPY_LIGHT * 0.99)
+ ob.c_ob_refcnt -= REFCNT_FROM_PYPY
+ ob.c_ob_pypy_link = 0
+ if ob.c_ob_refcnt == 0:
+ _d_list.append(ob)
+ return None
+
+ _p_list = new_p_list
+ for ob, wr in wr_p_list:
+ p = attach(ob, wr, _p_list)
+ if p is not None:
+ _pypy2ob[p] = ob
+ _o_list = []
+ for ob, wr in wr_o_list:
+ attach(ob, wr, _o_list)
+
+ if _d_list:
+ res = _dealloc_trigger_callback()
+ if res == "RETRY":
+ _collect(track_allocation=track_allocation)
+
+_keepalive_forever = set()
+def _dont_free_any_more():
+ "Make sure that any object still referenced won't be freed any more."
+ for ob in _p_list + _o_list:
+ _keepalive_forever.add(to_obj(object, ob))
+ del _d_list[:]
+
+# ____________________________________________________________
+
+
+def _unspec_p(hop, v_p):
+ assert isinstance(v_p.concretetype, lltype.Ptr)
+ assert v_p.concretetype.TO._gckind == 'gc'
+ return hop.genop('cast_opaque_ptr', [v_p], resulttype=llmemory.GCREF)
+
+def _unspec_ob(hop, v_ob):
+ assert isinstance(v_ob.concretetype, lltype.Ptr)
+ assert v_ob.concretetype.TO._gckind == 'raw'
+ return hop.genop('cast_ptr_to_adr', [v_ob], resulttype=llmemory.Address)
+
+def _spec_p(hop, v_p):
+ assert v_p.concretetype == llmemory.GCREF
+ return hop.genop('cast_opaque_ptr', [v_p],
+ resulttype=hop.r_result.lowleveltype)
+
+def _spec_ob(hop, v_ob):
+ assert v_ob.concretetype == llmemory.Address
+ return hop.genop('cast_adr_to_ptr', [v_ob],
+ resulttype=hop.r_result.lowleveltype)
+
+
+class Entry(ExtRegistryEntry):
+ _about_ = init
+
+ def compute_result_annotation(self, s_dealloc_callback):
+ from rpython.rtyper.llannotation import SomePtr
+ assert isinstance(s_dealloc_callback, SomePtr) # ll-ptr-to-function
+
+ def specialize_call(self, hop):
+ hop.exception_cannot_occur()
+ [v_dealloc_callback] = hop.inputargs(hop.args_r[0])
+ hop.genop('gc_rawrefcount_init', [v_dealloc_callback])
+
+
+class Entry(ExtRegistryEntry):
+ _about_ = (create_link_pypy, create_link_pyobj)
+
+ def compute_result_annotation(self, s_p, s_ob):
+ pass
+
+ def specialize_call(self, hop):
+ if self.instance is create_link_pypy:
+ name = 'gc_rawrefcount_create_link_pypy'
+ elif self.instance is create_link_pyobj:
+ name = 'gc_rawrefcount_create_link_pyobj'
+ v_p, v_ob = hop.inputargs(*hop.args_r)
+ hop.exception_cannot_occur()
+ hop.genop(name, [_unspec_p(hop, v_p), _unspec_ob(hop, v_ob)])
+
+
+class Entry(ExtRegistryEntry):
+ _about_ = from_obj
+
+ def compute_result_annotation(self, s_OB_PTR_TYPE, s_p):
+ from rpython.annotator import model as annmodel
+ from rpython.rtyper.llannotation import lltype_to_annotation
+ assert (isinstance(s_p, annmodel.SomeInstance) or
+ annmodel.s_None.contains(s_p))
+ assert s_OB_PTR_TYPE.is_constant()
+ return lltype_to_annotation(s_OB_PTR_TYPE.const)
+
+ def specialize_call(self, hop):
+ hop.exception_cannot_occur()
+ v_p = hop.inputarg(hop.args_r[1], arg=1)
+ v_ob = hop.genop('gc_rawrefcount_from_obj', [_unspec_p(hop, v_p)],
+ resulttype = llmemory.Address)
+ return _spec_ob(hop, v_ob)
+
+class Entry(ExtRegistryEntry):
+ _about_ = to_obj
+
+ def compute_result_annotation(self, s_Class, s_ob):
+ from rpython.annotator import model as annmodel
+ from rpython.rtyper.llannotation import SomePtr
+ assert isinstance(s_ob, SomePtr)
+ assert s_Class.is_constant()
+ classdef = self.bookkeeper.getuniqueclassdef(s_Class.const)
+ return annmodel.SomeInstance(classdef, can_be_None=True)
+
+ def specialize_call(self, hop):
+ hop.exception_cannot_occur()
+ v_ob = hop.inputarg(hop.args_r[1], arg=1)
+ v_p = hop.genop('gc_rawrefcount_to_obj', [_unspec_ob(hop, v_ob)],
+ resulttype = llmemory.GCREF)
+ return _spec_p(hop, v_p)
+
+class Entry(ExtRegistryEntry):
+ _about_ = next_dead
+
+ def compute_result_annotation(self, s_OB_PTR_TYPE):
+ from rpython.annotator import model as annmodel
+ from rpython.rtyper.llannotation import lltype_to_annotation
+ assert s_OB_PTR_TYPE.is_constant()
+ return lltype_to_annotation(s_OB_PTR_TYPE.const)
+
+ def specialize_call(self, hop):
+ hop.exception_cannot_occur()
+ v_ob = hop.genop('gc_rawrefcount_next_dead', [],
+ resulttype = llmemory.Address)
+ return _spec_ob(hop, v_ob)
diff --git a/rpython/rlib/rgc.py b/rpython/rlib/rgc.py
--- a/rpython/rlib/rgc.py
+++ b/rpython/rlib/rgc.py
@@ -487,6 +487,7 @@
class _GcRef(object):
# implementation-specific: there should not be any after translation
__slots__ = ['_x', '_handle']
+ _TYPE = llmemory.GCREF
def __init__(self, x):
self._x = x
def __hash__(self):
diff --git a/rpython/rlib/test/test_rawrefcount.py
b/rpython/rlib/test/test_rawrefcount.py
new file mode 100644
--- /dev/null
+++ b/rpython/rlib/test/test_rawrefcount.py
@@ -0,0 +1,268 @@
+import weakref
+from rpython.rlib import rawrefcount, objectmodel, rgc
+from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY, REFCNT_FROM_PYPY_LIGHT
+from rpython.rtyper.lltypesystem import lltype, llmemory
+from rpython.rtyper.annlowlevel import llhelper
+from rpython.translator.c.test.test_standalone import StandaloneTests
+from rpython.config.translationoption import get_combined_translation_config
+
+
+class W_Root(object):
+ def __init__(self, intval=0):
+ self.intval = intval
+ def __nonzero__(self):
+ raise Exception("you cannot do that, you must use space.is_true()")
+
+PyObjectS = lltype.Struct('PyObjectS',
+ ('c_ob_refcnt', lltype.Signed),
+ ('c_ob_pypy_link', lltype.Signed))
+PyObject = lltype.Ptr(PyObjectS)
+
+
+class TestRawRefCount:
+
+ def setup_method(self, meth):
+ rawrefcount.init()
+
+ def test_create_link_pypy(self):
+ p = W_Root(42)
+ ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+ assert rawrefcount.from_obj(PyObject, p) == lltype.nullptr(PyObjectS)
+ assert rawrefcount.to_obj(W_Root, ob) == None
+ rawrefcount.create_link_pypy(p, ob)
+ assert ob.c_ob_refcnt == 0
+ ob.c_ob_refcnt += REFCNT_FROM_PYPY_LIGHT
+ assert rawrefcount.from_obj(PyObject, p) == ob
+ assert rawrefcount.to_obj(W_Root, ob) == p
+ lltype.free(ob, flavor='raw')
+
+ def test_create_link_pyobj(self):
+ p = W_Root(42)
+ ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+ assert rawrefcount.from_obj(PyObject, p) == lltype.nullptr(PyObjectS)
+ assert rawrefcount.to_obj(W_Root, ob) == None
+ rawrefcount.create_link_pyobj(p, ob)
+ assert ob.c_ob_refcnt == 0
+ ob.c_ob_refcnt += REFCNT_FROM_PYPY
+ assert rawrefcount.from_obj(PyObject, p) == lltype.nullptr(PyObjectS)
+ assert rawrefcount.to_obj(W_Root, ob) == p
+ lltype.free(ob, flavor='raw')
+
+ def test_collect_p_dies(self):
+ p = W_Root(42)
+ ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+ rawrefcount.create_link_pypy(p, ob)
+ ob.c_ob_refcnt += REFCNT_FROM_PYPY_LIGHT
+ assert rawrefcount._p_list == [ob]
+ wr_ob = weakref.ref(ob)
+ wr_p = weakref.ref(p)
+ del ob, p
+ rawrefcount._collect()
+ assert rawrefcount._p_list == []
+ assert wr_ob() is None
+ assert wr_p() is None
+
+ def test_collect_p_keepalive_pyobject(self):
+ p = W_Root(42)
+ ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+ rawrefcount.create_link_pypy(p, ob)
+ ob.c_ob_refcnt += REFCNT_FROM_PYPY_LIGHT
+ assert rawrefcount._p_list == [ob]
+ wr_ob = weakref.ref(ob)
+ wr_p = weakref.ref(p)
+ ob.c_ob_refcnt += 1 # <=
+ del ob, p
+ rawrefcount._collect()
+ ob = wr_ob()
+ p = wr_p()
+ assert ob is not None and p is not None
+ assert rawrefcount._p_list == [ob]
+ assert rawrefcount.to_obj(W_Root, ob) == p
+ assert rawrefcount.from_obj(PyObject, p) == ob
+ lltype.free(ob, flavor='raw')
+
+ def test_collect_p_keepalive_w_root(self):
+ p = W_Root(42)
+ ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+ rawrefcount.create_link_pypy(p, ob)
+ ob.c_ob_refcnt += REFCNT_FROM_PYPY_LIGHT
+ assert rawrefcount._p_list == [ob]
+ wr_ob = weakref.ref(ob)
+ del ob # p remains
+ rawrefcount._collect()
+ ob = wr_ob()
+ assert ob is not None
+ assert rawrefcount._p_list == [ob]
+ assert rawrefcount.to_obj(W_Root, ob) == p
+ assert rawrefcount.from_obj(PyObject, p) == ob
+ lltype.free(ob, flavor='raw')
+
+ def test_collect_o_dies(self):
+ trigger = []; rawrefcount.init(lambda: trigger.append(1))
+ p = W_Root(42)
+ ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+ rawrefcount.create_link_pyobj(p, ob)
+ ob.c_ob_refcnt += REFCNT_FROM_PYPY
+ assert rawrefcount._o_list == [ob]
+ wr_ob = weakref.ref(ob)
+ wr_p = weakref.ref(p)
+ del ob, p
+ rawrefcount._collect()
+ ob = wr_ob()
+ assert ob is not None
+ assert trigger == [1]
+ assert rawrefcount.next_dead(PyObject) == ob
+ assert rawrefcount.next_dead(PyObject) == lltype.nullptr(PyObjectS)
+ assert rawrefcount.next_dead(PyObject) == lltype.nullptr(PyObjectS)
+ assert rawrefcount._o_list == []
+ assert wr_p() is None
+ assert ob.c_ob_refcnt == 0
+ assert ob.c_ob_pypy_link == 0
+ lltype.free(ob, flavor='raw')
+
+ def test_collect_o_keepalive_pyobject(self):
+ p = W_Root(42)
+ ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+ p.pyobj = ob
+ rawrefcount.create_link_pyobj(p, ob)
+ ob.c_ob_refcnt += REFCNT_FROM_PYPY
+ assert rawrefcount._o_list == [ob]
+ wr_ob = weakref.ref(ob)
+ wr_p = weakref.ref(p)
+ ob.c_ob_refcnt += 1 # <=
+ del p
+ rawrefcount._collect()
+ p = wr_p()
+ assert p is None # was unlinked
+ assert ob.c_ob_refcnt == 1 # != REFCNT_FROM_PYPY_OBJECT + 1
+ assert rawrefcount._o_list == []
+ assert rawrefcount.to_obj(W_Root, ob) == None
+ lltype.free(ob, flavor='raw')
+
+ def test_collect_o_keepalive_w_root(self):
+ p = W_Root(42)
+ ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+ p.pyobj = ob
+ rawrefcount.create_link_pyobj(p, ob)
+ ob.c_ob_refcnt += REFCNT_FROM_PYPY
+ assert rawrefcount._o_list == [ob]
+ wr_ob = weakref.ref(ob)
+ del ob # p remains
+ rawrefcount._collect()
+ ob = wr_ob()
+ assert ob is not None
+ assert rawrefcount._o_list == [ob]
+ assert rawrefcount.to_obj(W_Root, ob) == p
+ assert p.pyobj == ob
+ lltype.free(ob, flavor='raw')
+
+ def test_collect_s_dies(self):
+ trigger = []; rawrefcount.init(lambda: trigger.append(1))
+ p = W_Root(42)
+ ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+ rawrefcount.create_link_pypy(p, ob)
+ ob.c_ob_refcnt += REFCNT_FROM_PYPY
+ assert rawrefcount._p_list == [ob]
+ wr_ob = weakref.ref(ob)
+ wr_p = weakref.ref(p)
+ del ob, p
+ rawrefcount._collect()
+ ob = wr_ob()
+ assert ob is not None
+ assert trigger == [1]
+ assert rawrefcount._d_list == [ob]
+ assert rawrefcount._p_list == []
+ assert wr_p() is None
+ assert ob.c_ob_refcnt == 0
+ assert ob.c_ob_pypy_link == 0
+ lltype.free(ob, flavor='raw')
+
+ def test_collect_s_keepalive_pyobject(self):
+ p = W_Root(42)
+ ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+ p.pyobj = ob
+ rawrefcount.create_link_pypy(p, ob)
+ ob.c_ob_refcnt += REFCNT_FROM_PYPY
+ assert rawrefcount._p_list == [ob]
+ wr_ob = weakref.ref(ob)
+ wr_p = weakref.ref(p)
+ ob.c_ob_refcnt += 1 # <=
+ del ob, p
+ rawrefcount._collect()
+ ob = wr_ob()
+ p = wr_p()
+ assert ob is not None and p is not None
+ assert rawrefcount._p_list == [ob]
+ assert rawrefcount.to_obj(W_Root, ob) == p
+ lltype.free(ob, flavor='raw')
+
+ def test_collect_s_keepalive_w_root(self):
+ p = W_Root(42)
+ ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+ p.pyobj = ob
+ rawrefcount.create_link_pypy(p, ob)
+ ob.c_ob_refcnt += REFCNT_FROM_PYPY
+ assert rawrefcount._p_list == [ob]
+ wr_ob = weakref.ref(ob)
+ del ob # p remains
+ rawrefcount._collect()
+ ob = wr_ob()
+ assert ob is not None
+ assert rawrefcount._p_list == [ob]
+ assert rawrefcount.to_obj(W_Root, ob) == p
+ lltype.free(ob, flavor='raw')
+
+
+class TestTranslated(StandaloneTests):
+
+ def test_full_translation(self):
+ class State:
+ pass
+ state = State()
+ state.seen = []
+ def dealloc_trigger():
+ state.seen.append(1)
+
+ def make_p():
+ p = W_Root(42)
+ ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+ rawrefcount.create_link_pypy(p, ob)
+ ob.c_ob_refcnt += REFCNT_FROM_PYPY
+ assert rawrefcount.from_obj(PyObject, p) == ob
+ assert rawrefcount.to_obj(W_Root, ob) == p
+ return ob, p
+
+ FTYPE = rawrefcount.RAWREFCOUNT_DEALLOC_TRIGGER
+
+ def entry_point(argv):
+ ll_dealloc_trigger_callback = llhelper(FTYPE, dealloc_trigger)
+ rawrefcount.init(ll_dealloc_trigger_callback)
+ ob, p = make_p()
+ if state.seen != []:
+ print "OB COLLECTED REALLY TOO SOON"
+ return 1
+ rgc.collect()
+ if state.seen != []:
+ print "OB COLLECTED TOO SOON"
+ return 1
+ objectmodel.keepalive_until_here(p)
+ p = None
+ rgc.collect()
+ if state.seen != [1]:
+ print "OB NOT COLLECTED"
+ return 1
+ if rawrefcount.next_dead(PyObject) != ob:
+ print "NEXT_DEAD != OB"
+ return 1
+ if rawrefcount.next_dead(PyObject) != lltype.nullptr(PyObjectS):
+ print "NEXT_DEAD second time != NULL"
+ return 1
+ print "OK!"
+ lltype.free(ob, flavor='raw')
+ return 0
+
+ self.config = get_combined_translation_config(translating=True)
+ self.config.translation.gc = "incminimark"
+ t, cbuilder = self.compile(entry_point)
+ data = cbuilder.cmdexec('hi there')
+ assert data.startswith('OK!\n')
diff --git a/rpython/rtyper/lltypesystem/ll2ctypes.py
b/rpython/rtyper/lltypesystem/ll2ctypes.py
--- a/rpython/rtyper/lltypesystem/ll2ctypes.py
+++ b/rpython/rtyper/lltypesystem/ll2ctypes.py
@@ -515,8 +515,10 @@
struct_use_ctypes_storage(struct_container, struct_storage)
struct_container._setparentstructure(container, field_name)
elif isinstance(FIELDTYPE, lltype.Array):
- assert FIELDTYPE._hints.get('nolength', False) == False
- arraycontainer = _array_of_known_length(FIELDTYPE)
+ if FIELDTYPE._hints.get('nolength', False):
+ arraycontainer = _array_of_unknown_length(FIELDTYPE)
+ else:
+ arraycontainer = _array_of_known_length(FIELDTYPE)
arraycontainer._storage = ctypes.pointer(
getattr(ctypes_storage.contents, field_name))
arraycontainer._setparentstructure(container, field_name)
@@ -567,6 +569,7 @@
raise Exception("internal ll2ctypes error - "
"double conversion from lltype to ctypes?")
# XXX don't store here immortal structures
+ print "LL2CTYPES:", addr
ALLOCATED[addr] = self
def _addressof_storage(self):
@@ -579,6 +582,7 @@
self._check() # no double-frees
# allow the ctypes object to go away now
addr = ctypes.cast(self._storage, ctypes.c_void_p).value
+ print "LL2C FREE:", addr
try:
del ALLOCATED[addr]
except KeyError:
@@ -613,11 +617,14 @@
return object.__hash__(self)
def __repr__(self):
+ if '__str__' in self._TYPE._adtmeths:
+ r = self._TYPE._adtmeths['__str__'](self)
+ else:
+ r = 'C object %s' % (self._TYPE,)
if self._storage is None:
- return '<freed C object %s>' % (self._TYPE,)
+ return '<freed %s>' % (r,)
else:
- return '<C object %s at 0x%x>' % (self._TYPE,
- fixid(self._addressof_storage()))
+ return '<%s at 0x%x>' % (r, fixid(self._addressof_storage()))
def __str__(self):
return repr(self)
@@ -942,7 +949,8 @@
REAL_TYPE = T.TO
if T.TO._arrayfld is not None:
carray = getattr(cobj.contents, T.TO._arrayfld)
- container = lltype._struct(T.TO, carray.length)
+ length = getattr(carray, 'length', 9999) # XXX
+ container = lltype._struct(T.TO, length)
else:
# special treatment of 'OBJECT' subclasses
if get_rtyper() and lltype._castdepth(REAL_TYPE, OBJECT)
>= 0:
diff --git a/rpython/rtyper/lltypesystem/lloperation.py
b/rpython/rtyper/lltypesystem/lloperation.py
--- a/rpython/rtyper/lltypesystem/lloperation.py
+++ b/rpython/rtyper/lltypesystem/lloperation.py
@@ -503,6 +503,12 @@
'gc_gcflag_extra' : LLOp(),
'gc_add_memory_pressure': LLOp(),
+ 'gc_rawrefcount_init': LLOp(),
+ 'gc_rawrefcount_create_link_pypy': LLOp(),
+ 'gc_rawrefcount_create_link_pyobj': LLOp(),
+ 'gc_rawrefcount_from_obj': LLOp(sideeffects=False),
+ 'gc_rawrefcount_to_obj': LLOp(sideeffects=False),
+
# ------- JIT & GC interaction, only for some GCs ----------
'gc_adr_of_nursery_free' : LLOp(),
diff --git a/rpython/rtyper/lltypesystem/rffi.py
b/rpython/rtyper/lltypesystem/rffi.py
--- a/rpython/rtyper/lltypesystem/rffi.py
+++ b/rpython/rtyper/lltypesystem/rffi.py
@@ -631,7 +631,8 @@
def CExternVariable(TYPE, name, eci, _CConstantClass=CConstant,
sandboxsafe=False, _nowrapper=False,
- c_type=None, getter_only=False):
+ c_type=None, getter_only=False,
+ declare_as_extern=(sys.platform != 'win32')):
"""Return a pair of functions - a getter and a setter - to access
the given global C variable.
"""
@@ -661,7 +662,7 @@
c_setter = "void %(setter_name)s (%(c_type)s v) { %(name)s = v; }" %
locals()
lines = ["#include <%s>" % i for i in eci.includes]
- if sys.platform != 'win32':
+ if declare_as_extern:
lines.append('extern %s %s;' % (c_type, name))
lines.append(c_getter)
if not getter_only:
@@ -790,6 +791,12 @@
return length
str2chararray._annenforceargs_ = [strtype, None, int]
+ # s[start:start+length] -> already-existing char[],
+ # all characters including zeros
+ def str2rawmem(s, array, start, length):
+ ll_s = llstrtype(s)
+ copy_string_to_raw(ll_s, array, start, length)
+
# char* -> str
# doesn't free char*
def charp2str(cp):
@@ -940,19 +947,19 @@
return (str2charp, free_charp, charp2str,
get_nonmovingbuffer, free_nonmovingbuffer,
alloc_buffer, str_from_buffer, keep_buffer_alive_until_here,
- charp2strn, charpsize2str, str2chararray,
+ charp2strn, charpsize2str, str2chararray, str2rawmem,
)
(str2charp, free_charp, charp2str,
get_nonmovingbuffer, free_nonmovingbuffer,
alloc_buffer, str_from_buffer, keep_buffer_alive_until_here,
- charp2strn, charpsize2str, str2chararray,
+ charp2strn, charpsize2str, str2chararray, str2rawmem,
) = make_string_mappings(str)
(unicode2wcharp, free_wcharp, wcharp2unicode,
get_nonmoving_unicodebuffer, free_nonmoving_unicodebuffer,
alloc_unicodebuffer, unicode_from_buffer, keep_unicodebuffer_alive_until_here,
- wcharp2unicoden, wcharpsize2unicode, unicode2wchararray,
+ wcharp2unicoden, wcharpsize2unicode, unicode2wchararray, unicode2rawmem,
) = make_string_mappings(unicode)
# char**
diff --git a/rpython/rtyper/tool/rffi_platform.py
b/rpython/rtyper/tool/rffi_platform.py
--- a/rpython/rtyper/tool/rffi_platform.py
+++ b/rpython/rtyper/tool/rffi_platform.py
@@ -263,10 +263,11 @@
"""An entry in a CConfig class that stands for an externally
defined structure.
"""
- def __init__(self, name, interesting_fields, ifdef=None):
+ def __init__(self, name, interesting_fields, ifdef=None, adtmeths={}):
self.name = name
self.interesting_fields = interesting_fields
self.ifdef = ifdef
+ self.adtmeths = adtmeths
def prepare_code(self):
if self.ifdef is not None:
@@ -313,7 +314,9 @@
offset = info['fldofs ' + fieldname]
size = info['fldsize ' + fieldname]
sign = info.get('fldunsigned ' + fieldname, False)
- if (size, sign) != rffi.size_and_sign(fieldtype):
+ if is_array_nolength(fieldtype):
+ pass # ignore size and sign
+ elif (size, sign) != rffi.size_and_sign(fieldtype):
fieldtype = fixup_ctype(fieldtype, fieldname, (size, sign))
layout_addfield(layout, offset, fieldtype, fieldname)
@@ -353,7 +356,7 @@
name = name[7:]
else:
hints['typedef'] = True
- kwds = {'hints': hints}
+ kwds = {'hints': hints, 'adtmeths': self.adtmeths}
return rffi.CStruct(name, *fields, **kwds)
class SimpleType(CConfigEntry):
@@ -682,8 +685,14 @@
def __repr__(self):
return '<field %s: %s>' % (self.name, self.ctype)
+def is_array_nolength(TYPE):
+ return isinstance(TYPE, lltype.Array) and TYPE._hints.get('nolength',
False)
+
def layout_addfield(layout, offset, ctype, prefix):
- size = _sizeof(ctype)
+ if is_array_nolength(ctype):
+ size = len(layout) - offset # all the rest of the struct
+ else:
+ size = _sizeof(ctype)
name = prefix
i = 0
while name in layout:
diff --git a/rpython/rtyper/tool/test/test_rffi_platform.py
b/rpython/rtyper/tool/test/test_rffi_platform.py
--- a/rpython/rtyper/tool/test/test_rffi_platform.py
+++ b/rpython/rtyper/tool/test/test_rffi_platform.py
@@ -270,6 +270,19 @@
[("d_name",
lltype.FixedSizeArray(rffi.CHAR, 1))])
assert dirent.c_d_name.length == 32
+def test_array_varsized_struct():
+ dirent = rffi_platform.getstruct("struct dirent",
+ """
+ struct dirent /* for this example only, not the exact dirent */
+ {
+ int d_off;
+ char d_name[1];
+ };
+ """,
+ [("d_name", rffi.CArray(rffi.CHAR))])
+ assert rffi.offsetof(dirent, 'c_d_name') == 4
+ assert dirent.c_d_name == rffi.CArray(rffi.CHAR)
+
def test_has_0001():
assert rffi_platform.has("x", "int x = 3;")
assert not rffi_platform.has("x", "")
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit