Author: Stefan Beyer <[email protected]>
Branch: cpyext-gc-trialdeletion
Changeset: r91867:f8d7bb5f29b6
Date: 2017-07-14 13:47 +0200
http://bitbucket.org/pypy/pypy/changeset/f8d7bb5f29b6/

Log:    Implemented flags for concurrent cycle deletion (Bacon and Rajan
        2001) with overflow handling for refcount and removed unnecessary
        code

diff --git a/pypy/module/cpyext/include/object.h 
b/pypy/module/cpyext/include/object.h
--- a/pypy/module/cpyext/include/object.h
+++ b/pypy/module/cpyext/include/object.h
@@ -2,6 +2,7 @@
 #define Py_OBJECT_H
 
 #include <stdio.h>
+#include <math.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -9,10 +10,17 @@
 
 #include "cpyext_object.h"
 
+int* foo;
 #define PY_SSIZE_T_MAX ((Py_ssize_t)(((size_t)-1)>>1))
 #define PY_SSIZE_T_MIN (-PY_SSIZE_T_MAX-1)
 
-#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
+#define PY_REFCNT_FROM_PYPY (4L << ((long)(log(PY_SSIZE_T_MAX) / log(2) - 2)))
+#define PY_REFCNT_GREEN (4L << ((long)(log(PY_SSIZE_T_MAX) / log(2) - 6)))
+#define PY_REFCNT_OVERFLOW (1L << ((long)(log(PY_SSIZE_T_MAX) / log(2) - 6) / 
2L - 1L))
+#define PY_REFCNT_MASK ((PY_REFCNT_OVERFLOW << 1L) - 1L)
+
+#define Py_RETURN_NONE return (((((PyObject *)(Py_None))->ob_refcnt & 
PY_REFCNT_OVERFLOW) == 0) ? \
+                              ((PyObject *)(Py_None))->ob_refcnt++ : 
Py_IncRef((PyObject *)(Py_None))), Py_None
 
 /*
 CPython has this for backwards compatibility with really old extensions, and 
now
@@ -26,26 +34,34 @@
 #define PyVarObject_HEAD_INIT(type, size)      \
        PyObject_HEAD_INIT(type) size,
 
-// #ifdef PYPY_DEBUG_REFCOUNT
-// /* Slow version, but useful for debugging */
+#ifdef PYPY_DEBUG_REFCOUNT
+/* Slow version, but useful for debugging */
 #define Py_INCREF(ob)   (Py_IncRef((PyObject *)(ob)))
 #define Py_DECREF(ob)   (Py_DecRef((PyObject *)(ob)))
 #define Py_XINCREF(ob)  (Py_IncRef((PyObject *)(ob)))
 #define Py_XDECREF(ob)  (Py_DecRef((PyObject *)(ob)))
-// #else
-// /* Fast version */
-// #define Py_INCREF(ob)   (((PyObject *)(ob))->ob_refcnt++)
-// #define Py_DECREF(op)                                   \
-//     do {                                                \
-//         if (--((PyObject *)(op))->ob_refcnt != 0)       \
-//             ;                                           \
-//         else                                            \
-//             _Py_Dealloc((PyObject *)(op));              \
-//     } while (0)
-
-// #define Py_XINCREF(op) do { if ((op) == NULL) ; else Py_INCREF(op); } while 
(0)
-// #define Py_XDECREF(op) do { if ((op) == NULL) ; else Py_DECREF(op); } while 
(0)
-// #endif
+#else
+/* Fast version */
+#define Py_INCREF(ob)                                                          
 \
+        do {                                                                   
 \
+                if (!(((PyObject *)(ob))->ob_refcnt & PY_REFCNT_OVERFLOW))     
 \
+                        ((PyObject *)(ob))->ob_refcnt++;                       
 \
+                else                                                           
 \
+                        Py_IncRef((PyObject *)(ob));                           
 \
+        } while (0)
+#define Py_DECREF(ob)                                                          
 \
+        do {                                                                   
 \
+                if (!(((PyObject *)(ob))->ob_refcnt & PY_REFCNT_GREEN) ||      
 \
+                    (((PyObject *)(ob))->ob_refcnt & PY_REFCNT_OVERFLOW))      
 \
+                        Py_DecRef((PyObject *)(ob));                           
 \
+                else if (--((PyObject *)(ob))->ob_refcnt & PY_REFCNT_MASK)     
 \
+                        ;                                                      
 \
+                else if (!((PyObject *)(ob))->ob_refcnt & PY_REFCNT_FROM_PYPY) 
 \
+                        _Py_Dealloc((PyObject *)(ob));                         
 \
+        } while (0)
+#define Py_XINCREF(op) do { if ((op) == NULL) ; else Py_INCREF(op); } while (0)
+#define Py_XDECREF(op) do { if ((op) == NULL) ; else Py_DECREF(op); } while (0)
+#endif
 
 #define Py_CLEAR(op)                           \
         do {                                   \
@@ -56,7 +72,8 @@
                 }                              \
         } while (0)
 
-#define Py_REFCNT(ob)          (((PyObject*)(ob))->ob_refcnt)
+#define Py_REFCNT(ob) ((((PyObject *)(ob))->ob_refcnt & PY_REFCNT_OVERFLOW == 
0) ?  \
+                      (((PyObject*)(ob))->ob_refcnt & PY_REFCNT_MASK) : 
_Py_RefCnt_Overflow(ob))
 #define Py_TYPE(ob)            (((PyObject*)(ob))->ob_type)
 #define Py_SIZE(ob)            (((PyVarObject*)(ob))->ob_size)
 
diff --git a/pypy/module/cpyext/object.py b/pypy/module/cpyext/object.py
--- a/pypy/module/cpyext/object.py
+++ b/pypy/module/cpyext/object.py
@@ -11,6 +11,7 @@
 from pypy.module.cpyext.pyerrors import PyErr_NoMemory, PyErr_BadInternalCall
 from pypy.objspace.std.typeobject import W_TypeObject
 from pypy.interpreter.error import OperationError, oefmt
+from rpython.rlib.rawrefcount import REFCNT_MASK
 import pypy.module.__builtin__.operation as operation
 
 
@@ -61,7 +62,7 @@
 def _dealloc(space, obj):
     # This frees an object after its refcount dropped to zero, so we
     # assert that it is really zero here.
-    assert obj.c_ob_refcnt == 0
+    assert obj.c_ob_refcnt & REFCNT_MASK == 0
     pto = obj.c_ob_type
     obj_voidp = rffi.cast(rffi.VOIDP, obj)
     generic_cpy_call(space, pto.c_tp_free, obj_voidp)
diff --git a/pypy/module/cpyext/pyobject.py b/pypy/module/cpyext/pyobject.py
--- a/pypy/module/cpyext/pyobject.py
+++ b/pypy/module/cpyext/pyobject.py
@@ -270,6 +270,27 @@
         hop.exception_cannot_occur()
         return hop.inputconst(lltype.Bool, hop.s_result.const)
 
+def _decref(pyobj):
+    if pyobj.c_ob_refcnt & rawrefcount.REFCNT_OVERFLOW == 0:
+        pyobj.c_ob_refcnt -= 1
+    else:
+        if pyobj.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+           == rawrefcount.REFCNT_OVERFLOW:
+            pyobj.c_ob_refcnt -= 1
+        elif rawrefcount.overflow_sub(pyobj):
+            pyobj.c_ob_refcnt -= 1
+
+def _incref(pyobj):
+    if pyobj.c_ob_refcnt & rawrefcount.REFCNT_OVERFLOW == 0:
+        pyobj.c_ob_refcnt += 1
+    else:
+        if pyobj.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+           == rawrefcount.REFCNT_OVERFLOW:
+            pyobj.c_ob_refcnt += 1
+            rawrefcount.overflow_new(pyobj)
+        else:
+            rawrefcount.overflow_add(pyobj)
+
 @specialize.ll()
 def make_ref(space, obj, w_userdata=None):
     """Increment the reference counter of the PyObject and return it.
@@ -280,8 +301,7 @@
     else:
         pyobj = as_pyobj(space, obj, w_userdata)
     if pyobj:
-        assert pyobj.c_ob_refcnt > 0
-        pyobj.c_ob_refcnt += 1
+        _incref(pyobj)
         if not is_pyobj(obj):
             keepalive_until_here(obj)
     return pyobj
@@ -301,12 +321,10 @@
         w_obj = obj
         pyobj = as_pyobj(space, w_obj)
     if pyobj:
-        pyobj.c_ob_refcnt -= 1
-        assert pyobj.c_ob_refcnt >= rawrefcount.REFCNT_FROM_PYPY
+        _decref(pyobj)
         keepalive_until_here(w_obj)
     return w_obj
 
-
 @specialize.ll()
 def incref(space, obj):
     make_ref(space, obj)
@@ -316,20 +334,33 @@
     if is_pyobj(obj):
         obj = rffi.cast(PyObject, obj)
         if obj:
-            assert obj.c_ob_refcnt > 0
-            obj.c_ob_refcnt -= 1
-            if obj.c_ob_refcnt == 0 and \
+            _decref(obj)
+
+            if obj.c_ob_refcnt & rawrefcount.REFCNT_MASK == 0 and \
                rawrefcount.get_trialdeletion_phase() != 1:
-                debug_print("dealloc", obj)
-                _Py_Dealloc(space, obj)
-            elif obj.c_ob_refcnt == rawrefcount.REFCNT_FROM_PYPY:
-                debug_print("dead", obj)
-            else:
+                if obj.c_ob_refcnt & rawrefcount.REFCNT_FROM_PYPY == 0:
+                    _Py_Dealloc(space, obj)
+            elif obj.c_ob_refcnt & rawrefcount.REFCNT_CLR_GREEN == 0:
                 if rawrefcount.get_trialdeletion_phase() == 0:
                     trial_delete(space, obj)
     else:
         get_w_obj_and_decref(space, obj)
 
[email protected]()
+def refcnt_overflow(space, obj):
+    if is_pyobj(obj):
+        pyobj = rffi.cast(PyObject, obj)
+    else:
+        pyobj = as_pyobj(space, obj, None)
+    if pyobj:
+        if (pyobj.c_ob_refcnt & rawrefcount.REFCNT_MASK ==
+           rawrefcount.REFCNT_OVERFLOW):
+            return rawrefcount.REFCNT_OVERFLOW
+        else:
+            return (pyobj.c_ob_refcnt & rawrefcount.REFCNT_MASK) \
+                + rawrefcount.overflow_get(pyobj)
+    return 0
+
 def traverse(space, obj, visit):
     from pypy.module.cpyext.api import generic_cpy_call
     if obj.c_ob_type and obj.c_ob_type.c_tp_traverse:
@@ -343,7 +374,7 @@
 
 @slot_function([PyObject, rffi.VOIDP], rffi.INT_real, error=-1)
 def visit_decref(space, obj, args):
-    obj.c_ob_refcnt = obj.c_ob_refcnt - 1
+    _decref(obj)
     debug_print("visited dec", obj, "new refcnt", obj.c_ob_refcnt)
     if (obj not in rawrefcount.get_visited()):
         rawrefcount.add_visited(obj)
@@ -353,7 +384,7 @@
 
 @slot_function([PyObject, rffi.VOIDP], rffi.INT_real, error=-1)
 def visit_incref(space, obj, args):
-    obj.c_ob_refcnt = obj.c_ob_refcnt + 1
+    _incref(obj)
     debug_print("visited inc", obj, "new refcnt", obj.c_ob_refcnt)
     if (obj not in rawrefcount.get_visited()):
         rawrefcount.add_visited(obj)
@@ -364,6 +395,7 @@
 @specialize.ll()
 def trial_delete(space, obj):
     if not obj.c_ob_type or not obj.c_ob_type.c_tp_traverse:
+        obj.c_ob_refcnt = obj.c_ob_refcnt | rawrefcount.REFCNT_CLR_GREEN
         return
 
     from pypy.module.cpyext.slotdefs import llslot
@@ -427,6 +459,10 @@
 def Py_DecRef(space, obj):
     decref(space, obj)
 
+@cpython_api([PyObject], lltype.SignedLongLong, error=CANNOT_FAIL)
+def _Py_RefCnt_Overflow(space, obj):
+    return refcnt_overflow(space, obj)
+
 @cpython_api([PyObject], lltype.Void)
 def _Py_NewReference(space, obj):
     obj.c_ob_refcnt = 1
@@ -444,10 +480,6 @@
     rawrefcount.mark_deallocating(w_marker_deallocating, obj)
     generic_cpy_call(space, pto.c_tp_dealloc, obj)
 
-@cpython_api([PyObject], lltype.Void)
-def _Py_Mark(space, obj):
-    rawrefcount.add_marked(obj)
-
 @cpython_api([rffi.VOIDP], lltype.Signed, error=CANNOT_FAIL)
 def _Py_HashPointer(space, ptr):
     return rffi.cast(lltype.Signed, ptr)
diff --git a/pypy/module/cpyext/test/test_bytesobject.py 
b/pypy/module/cpyext/test/test_bytesobject.py
--- a/pypy/module/cpyext/test/test_bytesobject.py
+++ b/pypy/module/cpyext/test/test_bytesobject.py
@@ -1,5 +1,6 @@
 # encoding: utf-8
 import pytest
+from rpython.rlib import rawrefcount
 from rpython.rtyper.lltypesystem import rffi, lltype
 from pypy.interpreter.error import OperationError
 from pypy.module.cpyext.test.test_api import BaseApiTest, raises_w
@@ -9,8 +10,10 @@
     PyString_ConcatAndDel, PyString_Format, PyString_InternFromString,
     PyString_AsEncodedObject, PyString_AsDecodedObject, _PyString_Eq,
     _PyString_Join)
-from pypy.module.cpyext.api import PyObjectP, PyObject, Py_ssize_tP, 
generic_cpy_call
-from pypy.module.cpyext.pyobject import Py_DecRef, from_ref, make_ref
+from pypy.module.cpyext.api import (
+    PyObjectP, PyObject, Py_ssize_tP, generic_cpy_call)
+from pypy.module.cpyext.pyobject import (
+    Py_DecRef, Py_IncRef, _Py_RefCnt_Overflow, from_ref, make_ref)
 from pypy.module.cpyext.object import PyObject_AsCharBuffer
 from pypy.module.cpyext.api import PyTypeObjectPtr
 
@@ -498,9 +501,9 @@
         ref = make_ref(space, space.wrap('abc'))
         ptr = lltype.malloc(PyObjectP.TO, 1, flavor='raw')
         ptr[0] = ref
-        prev_refcnt = ref.c_ob_refcnt
+        prev_refcnt = ref.c_ob_refcnt & rawrefcount.REFCNT_MASK
         PyString_Concat(space, ptr, space.wrap('def'))
-        assert ref.c_ob_refcnt == prev_refcnt - 1
+        assert ref.c_ob_refcnt & rawrefcount.REFCNT_MASK == prev_refcnt - 1
         assert space.str_w(from_ref(space, ptr[0])) == 'abcdef'
         with pytest.raises(OperationError):
             PyString_Concat(space, ptr, space.w_None)
@@ -536,9 +539,9 @@
 
         w_text = space.wrap("text")
         ref = make_ref(space, w_text)
-        prev_refcnt = ref.c_ob_refcnt
+        prev_refcnt = ref.c_ob_refcnt & rawrefcount.REFCNT_MASK
         assert PyObject_AsCharBuffer(space, ref, bufp, lenp) == 0
-        assert ref.c_ob_refcnt == prev_refcnt
+        assert ref.c_ob_refcnt & rawrefcount.REFCNT_MASK == prev_refcnt
         assert lenp[0] == 4
         assert rffi.charp2str(bufp[0]) == 'text'
         lltype.free(bufp, flavor='raw')
@@ -597,3 +600,53 @@
         w_seq = space.wrap(['a', 'b'])
         w_joined = _PyString_Join(space, w_sep, w_seq)
         assert space.unwrap(w_joined) == 'a<sep>b'
+
+    def test_refcnt_overflow(self, space):
+        ref1 = make_ref(space, space.wrap('foo'))
+        ref1.c_ob_refcnt = rawrefcount.REFCNT_OVERFLOW - 1
+
+        Py_IncRef(space, ref1)
+        assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+            == rawrefcount.REFCNT_OVERFLOW
+        assert _Py_RefCnt_Overflow(space, ref1) \
+            == rawrefcount.REFCNT_OVERFLOW
+
+        Py_IncRef(space, ref1)
+        assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+            == rawrefcount.REFCNT_OVERFLOW + 1
+        assert _Py_RefCnt_Overflow(space, ref1) \
+            == rawrefcount.REFCNT_OVERFLOW + 1
+
+        Py_IncRef(space, ref1)
+        assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+            == rawrefcount.REFCNT_OVERFLOW + 1
+        assert _Py_RefCnt_Overflow(space, ref1) \
+            == rawrefcount.REFCNT_OVERFLOW + 2
+
+        Py_IncRef(space, ref1)
+        assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+            == rawrefcount.REFCNT_OVERFLOW + 1
+        assert _Py_RefCnt_Overflow(space, ref1) \
+            == rawrefcount.REFCNT_OVERFLOW + 3
+
+        Py_DecRef(space, ref1)
+        assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+            == rawrefcount.REFCNT_OVERFLOW + 1
+        assert _Py_RefCnt_Overflow(space, ref1) \
+            == rawrefcount.REFCNT_OVERFLOW + 2
+
+        Py_DecRef(space, ref1)
+        assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+            == rawrefcount.REFCNT_OVERFLOW + 1
+        assert _Py_RefCnt_Overflow(space, ref1) \
+            == rawrefcount.REFCNT_OVERFLOW + 1
+
+        Py_DecRef(space, ref1)
+        assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+            == rawrefcount.REFCNT_OVERFLOW
+        assert _Py_RefCnt_Overflow(space, ref1)  \
+            == rawrefcount.REFCNT_OVERFLOW
+
+        Py_DecRef(space, ref1)
+        assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+            == rawrefcount.REFCNT_OVERFLOW - 1
diff --git a/rpython/rlib/rawrefcount.py b/rpython/rlib/rawrefcount.py
--- a/rpython/rlib/rawrefcount.py
+++ b/rpython/rlib/rawrefcount.py
@@ -4,7 +4,7 @@
 #  This is meant for pypy's cpyext module, but is a generally
 #  useful interface over our GC.  XXX "pypy" should be removed here
 #
-import sys, weakref, py
+import sys, weakref, py, math
 from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
 from rpython.rlib.objectmodel import we_are_translated, specialize, not_rpython
 from rpython.rtyper.extregistry import ExtRegistryEntry
@@ -12,8 +12,37 @@
 from rpython.rlib import rgc
 
 
-REFCNT_FROM_PYPY       = sys.maxint // 4 + 1
-REFCNT_FROM_PYPY_LIGHT = REFCNT_FROM_PYPY + (sys.maxint // 2 + 1)
+MAX_BIT = int(math.log(sys.maxint, 2))
+
+REFCNT_FROM_PYPY = 1 << MAX_BIT - 2
+REFCNT_FROM_PYPY_LIGHT = (1 << MAX_BIT - 1) + REFCNT_FROM_PYPY
+
+# Object either in Roots or Cycle Buffer (= Link-object exists)
+REFCNT_CYCLE_BUFFERED = 1 << MAX_BIT - 3
+
+# Offsets and sizes
+REFCNT_CLR_OFFS = MAX_BIT - 6
+REFCNT_CRC_OFFS = REFCNT_CLR_OFFS / 2
+REFCNT_BITS = REFCNT_CRC_OFFS - 1
+
+# Concurrent cycle collection colors
+REFCNT_CLR_BLACK = 0 << REFCNT_CLR_OFFS   # In use or free (Default)
+REFCNT_CLR_GRAY = 1 << REFCNT_CLR_OFFS    # Possible member of cycle
+REFCNT_CLR_WHITE = 2 << REFCNT_CLR_OFFS   # Member of garbage cycle
+REFCNT_CLR_PURPLE = 3 << REFCNT_CLR_OFFS  # Possible root of cycle
+REFCNT_CLR_GREEN = 4 << REFCNT_CLR_OFFS   # Acyclic
+REFCNT_CLR_RED = 5 << REFCNT_CLR_OFFS     # Cand cycle undergoing SIGMA-comp.
+REFCNT_CLR_ORANGE = 6 << REFCNT_CLR_OFFS  # Cand cycle awaiting epoch boundary
+
+# Cyclic reference count with overflow bit
+REFCNT_CRC_OVERFLOW = 1 << REFCNT_CRC_OFFS + REFCNT_BITS
+REFCNT_CRC_MASK = (1 << REFCNT_CRC_OFFS + REFCNT_BITS + 1) - 1
+REFCNT_CRC = 1 < REFCNT_CRC_OFFS
+
+# True reference count with overflow bit
+REFCNT_OVERFLOW = 1 << REFCNT_BITS
+REFCNT_MASK = (1 << REFCNT_BITS + 1) - 1
+
 
 RAWREFCOUNT_DEALLOC_TRIGGER = lltype.Ptr(lltype.FuncType([], lltype.Void))
 
@@ -24,25 +53,40 @@
     return res
 
 _trial_deletion_phase = 0
-_visited = []
-_marked = []
 
 def set_trialdeletion_phase(value):
     _trial_deletion_phase = value
 def get_trialdeletion_phase():
     return _trial_deletion_phase
+
+_visited = []
+
 def add_visited(obj):
     _visited.append(obj)
 def get_visited():
     return _visited
 def clear_visited():
     del _visited[:]
-def add_marked(obj):
-    _marked.append(obj)
-def get_marked():
-    return marked
-def clear_marked():
-    del _marked[:]
+
+_refcount_overflow = dict()
+
+# TODO: if object moves, address changes!
+def overflow_new(obj):
+    _refcount_overflow[id(obj)] = 0
+def overflow_add(obj):
+    _refcount_overflow[id(obj)] += 1
+def overflow_sub(obj):
+    c = _refcount_overflow[id(obj)]
+    if c > 0:
+        _refcount_overflow[id(obj)] = c - 1
+        return False
+    else:
+        _refcount_overflow.pop(id(obj))
+        return True
+def overflow_get(obj):
+    return _refcount_overflow[id(obj)]
+
+# TODO: _cyclic_refcount_overflow = dict()
 
 @not_rpython
 def init(dealloc_trigger_callback=None):
@@ -142,7 +186,8 @@
     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):
+        if ob.c_ob_refcnt & REFCNT_MASK > 0 \
+           or ob.c_ob_refcnt & REFCNT_FROM_PYPY == 0:
             new_p_list.append(ob)
         else:
             p = detach(ob, wr_p_list)
@@ -175,7 +220,8 @@
             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:
+                if ob.c_ob_refcnt & REFCNT_MASK == 0 \
+                   and ob.c_ob_refcnt < REFCNT_FROM_PYPY:
                     lltype.free(ob, flavor='raw',
                                 track_allocation=track_allocation)
             else:
@@ -183,8 +229,9 @@
                 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:
-                    ob.c_ob_refcnt = 1
+                if ob.c_ob_refcnt & REFCNT_MASK == 0 \
+                   and ob.c_ob_refcnt < REFCNT_FROM_PYPY:
+                    ob.c_ob_refcnt += 1
                     _d_list.append(ob)
             return None
 
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to