[pypy-commit] pypy default: hg merge

mjacob noreply at buildbot.pypy.org
Fri Oct 24 16:17:32 CEST 2014


Author: Manuel Jacob <me at manueljacob.de>
Branch: 
Changeset: r74178:3f5db34f528d
Date: 2014-10-24 16:19 +0200
http://bitbucket.org/pypy/pypy/changeset/3f5db34f528d/

Log:	hg merge

diff too long, truncating to 2000 out of 3530 lines

diff --git a/pypy/doc/release-2.3.1.rst b/pypy/doc/release-2.3.1.rst
--- a/pypy/doc/release-2.3.1.rst
+++ b/pypy/doc/release-2.3.1.rst
@@ -18,14 +18,11 @@
 Please consider donating more, or even better convince your employer to donate,
 so we can finish those projects!  The three sub-projects are:
 
-* `Py3k`_ (supporting Python 3.x): the release PyPy3 2.3 is imminent.
-
 * `STM`_ (software transactional memory): a preview will be released very soon,
   once we fix a few bugs
 
 * `NumPy`_ which requires installation of our fork of upstream numpy, available `on bitbucket`_
 
-.. _`Py3k`: http://pypy.org/py3donate.html
 .. _`STM`: http://pypy.org/tmdonate2.html
 .. _`NumPy`: http://pypy.org/numpydonate.html
 .. _`on bitbucket`: https://www.bitbucket.org/pypy/numpy   
diff --git a/pypy/module/_multibytecodec/c_codecs.py b/pypy/module/_multibytecodec/c_codecs.py
--- a/pypy/module/_multibytecodec/c_codecs.py
+++ b/pypy/module/_multibytecodec/c_codecs.py
@@ -115,8 +115,7 @@
 def decodeex(decodebuf, stringdata, errors="strict", errorcb=None, namecb=None,
              ignore_error=0):
     inleft = len(stringdata)
-    inbuf = rffi.get_nonmovingbuffer(stringdata)
-    try:
+    with rffi.scoped_nonmovingbuffer(stringdata) as inbuf:
         if pypy_cjk_dec_init(decodebuf, inbuf, inleft) < 0:
             raise MemoryError
         while True:
@@ -128,9 +127,6 @@
         src = pypy_cjk_dec_outbuf(decodebuf)
         length = pypy_cjk_dec_outlen(decodebuf)
         return rffi.wcharpsize2unicode(src, length)
-    #
-    finally:
-        rffi.free_nonmovingbuffer(stringdata, inbuf)
 
 def multibytecodec_decerror(decodebuf, e, errors,
                             errorcb, namecb, stringdata):
@@ -159,11 +155,8 @@
         assert errorcb
         replace, end = errorcb(errors, namecb, reason,
                                stringdata, start, end)
-    inbuf = rffi.get_nonmoving_unicodebuffer(replace)
-    try:
+    with rffi.scoped_nonmoving_unicodebuffer(replace) as inbuf:
         r = pypy_cjk_dec_replace_on_error(decodebuf, inbuf, len(replace), end)
-    finally:
-        rffi.free_nonmoving_unicodebuffer(replace, inbuf)
     if r == MBERR_NOMEMORY:
         raise MemoryError
 
@@ -210,8 +203,7 @@
 def encodeex(encodebuf, unicodedata, errors="strict", errorcb=None,
              namecb=None, ignore_error=0):
     inleft = len(unicodedata)
-    inbuf = rffi.get_nonmoving_unicodebuffer(unicodedata)
-    try:
+    with rffi.scoped_nonmoving_unicodebuffer(unicodedata) as inbuf:
         if pypy_cjk_enc_init(encodebuf, inbuf, inleft) < 0:
             raise MemoryError
         if ignore_error == 0:
@@ -233,9 +225,6 @@
         src = pypy_cjk_enc_outbuf(encodebuf)
         length = pypy_cjk_enc_outlen(encodebuf)
         return rffi.charpsize2str(src, length)
-    #
-    finally:
-        rffi.free_nonmoving_unicodebuffer(unicodedata, inbuf)
 
 def multibytecodec_encerror(encodebuf, e, errors,
                             errorcb, namecb, unicodedata):
@@ -275,10 +264,7 @@
             assert retu is not None
             codec = pypy_cjk_enc_getcodec(encodebuf)
             replace = encode(codec, retu, "strict", errorcb, namecb)
-    inbuf = rffi.get_nonmovingbuffer(replace)
-    try:
+    with rffi.scoped_nonmovingbuffer(replace) as inbuf:
         r = pypy_cjk_enc_replace_on_error(encodebuf, inbuf, len(replace), end)
-    finally:
-        rffi.free_nonmovingbuffer(replace, inbuf)
     if r == MBERR_NOMEMORY:
         raise MemoryError
diff --git a/pypy/module/bz2/interp_bz2.py b/pypy/module/bz2/interp_bz2.py
--- a/pypy/module/bz2/interp_bz2.py
+++ b/pypy/module/bz2/interp_bz2.py
@@ -195,7 +195,7 @@
         self._allocate_chunk(initial_size)
 
     def _allocate_chunk(self, size):
-        self.raw_buf, self.gc_buf = rffi.alloc_buffer(size)
+        self.raw_buf, self.gc_buf, self.case_num = rffi.alloc_buffer(size)
         self.current_size = size
         self.bzs.c_next_out = self.raw_buf
         rffi.setintfield(self.bzs, 'c_avail_out', size)
@@ -204,8 +204,10 @@
         assert 0 <= chunksize <= self.current_size
         raw_buf = self.raw_buf
         gc_buf = self.gc_buf
-        s = rffi.str_from_buffer(raw_buf, gc_buf, self.current_size, chunksize)
-        rffi.keep_buffer_alive_until_here(raw_buf, gc_buf)
+        case_num = self.case_num
+        s = rffi.str_from_buffer(raw_buf, gc_buf, case_num,
+                                 self.current_size, chunksize)
+        rffi.keep_buffer_alive_until_here(raw_buf, gc_buf, case_num)
         self.current_size = 0
         return s
 
@@ -225,7 +227,8 @@
 
     def free(self):
         if self.current_size > 0:
-            rffi.keep_buffer_alive_until_here(self.raw_buf, self.gc_buf)
+            rffi.keep_buffer_alive_until_here(self.raw_buf, self.gc_buf,
+                                              self.case_num)
 
     def __enter__(self):
         return self
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
@@ -446,11 +446,8 @@
 
     count = space.len_w(w_str)
     data = space.str_w(w_str)
-    buf = rffi.get_nonmovingbuffer(data)
-    try:
+    with rffi.scoped_nonmovingbuffer(data) as buf:
         fwrite(buf, 1, count, fp)
-    finally:
-        rffi.free_nonmovingbuffer(data, buf)
     return 0
 
 
diff --git a/pypy/module/cpyext/test/test_unicodeobject.py b/pypy/module/cpyext/test/test_unicodeobject.py
--- a/pypy/module/cpyext/test/test_unicodeobject.py
+++ b/pypy/module/cpyext/test/test_unicodeobject.py
@@ -326,13 +326,6 @@
         self.raises(space, api, TypeError, api.PyUnicode_FromEncodedObject, space.wrap(u_text), null_charp, None)
         rffi.free_charp(b_text)
 
-    def test_leak(self):
-        size = 50
-        raw_buf, gc_buf = rffi.alloc_buffer(size)
-        for i in range(size): raw_buf[i] = 'a'
-        str = rffi.str_from_buffer(raw_buf, gc_buf, size, size)
-        rffi.keep_buffer_alive_until_here(raw_buf, gc_buf)
-
     def test_mbcs(self, space, api):
         if sys.platform != 'win32':
             py.test.skip("mcbs encoding only exists on Windows")
diff --git a/pypy/tool/release/package.py b/pypy/tool/release/package.py
--- a/pypy/tool/release/package.py
+++ b/pypy/tool/release/package.py
@@ -160,6 +160,9 @@
     if sys.platform == 'win32' and not rename_pypy_c.lower().endswith('.exe'):
         rename_pypy_c += '.exe'
     binaries = [(pypy_c, rename_pypy_c)]
+    libpypy_c = basedir.join('pypy', 'goal', 'libpypy-c.so')
+    if libpypy_c.check():
+        binaries.append('libpypy-c.so')
     #
     builddir = options.builddir
     pypydir = builddir.ensure(name, dir=True)
@@ -212,6 +215,7 @@
 directory next to the dlls, as per build instructions."""
                 import traceback;traceback.print_exc()
                 raise MissingDependenciesError('Tk runtime')
+        
 
     # Careful: to copy lib_pypy, copying just the hg-tracked files
     # would not be enough: there are also ctypes_config_cache/_*_cache.py.
diff --git a/rpython/jit/backend/llsupport/gc.py b/rpython/jit/backend/llsupport/gc.py
--- a/rpython/jit/backend/llsupport/gc.py
+++ b/rpython/jit/backend/llsupport/gc.py
@@ -9,8 +9,8 @@
 from rpython.rtyper.annlowlevel import llhelper, cast_instance_to_gcref
 from rpython.translator.tool.cbuild import ExternalCompilationInfo
 from rpython.jit.codewriter import heaptracker
-from rpython.jit.metainterp.history import ConstPtr, AbstractDescr
-from rpython.jit.metainterp.resoperation import rop
+from rpython.jit.metainterp.history import ConstPtr, AbstractDescr, BoxPtr, ConstInt
+from rpython.jit.metainterp.resoperation import rop, ResOperation
 from rpython.jit.backend.llsupport import symbolic, jitframe
 from rpython.jit.backend.llsupport.symbolic import WORD
 from rpython.jit.backend.llsupport.descr import SizeDescr, ArrayDescr
@@ -21,6 +21,37 @@
 from rpython.memory.gctransform import asmgcroot
 from rpython.jit.codewriter.effectinfo import EffectInfo
 
+class MovableObjectTracker(object):
+
+    ptr_array_type = lltype.GcArray(llmemory.GCREF)
+
+    def __init__(self, cpu, const_pointers):
+        size = len(const_pointers)
+        # check that there are any moving object (i.e. chaning pointers).
+        # Otherwise there is no reason for an instance of this class.
+        assert size > 0
+        #
+        # prepare GC array to hold the pointers that may change
+        self.ptr_array = lltype.malloc(MovableObjectTracker.ptr_array_type, size)
+        self.ptr_array_descr = cpu.arraydescrof(MovableObjectTracker.ptr_array_type)
+        self.ptr_array_gcref = lltype.cast_opaque_ptr(llmemory.GCREF, self.ptr_array)
+        # use always the same ConstPtr to access the array
+        # (easer to read JIT trace)
+        self.const_ptr_gcref_array = ConstPtr(self.ptr_array_gcref)
+        #
+        # assign each pointer an index and put the pointer into the GC array.
+        # as pointers and addresses are not a good key to use before translation
+        # ConstPtrs are used as the key for the dict.
+        self._indexes = {}
+        for index in range(size):
+            ptr = const_pointers[index]
+            self._indexes[ptr] = index
+            self.ptr_array[index] = ptr.value
+
+    def get_array_index(self, const_ptr):
+        index = self._indexes[const_ptr]
+        assert const_ptr.value == self.ptr_array[index]
+        return index
 # ____________________________________________________________
 
 class GcLLDescription(GcCache):
@@ -97,25 +128,91 @@
     def gc_malloc_unicode(self, num_elem):
         return self._bh_malloc_array(num_elem, self.unicode_descr)
 
-    def _record_constptrs(self, op, gcrefs_output_list):
+    def _record_constptrs(self, op, gcrefs_output_list, ops_with_movable_const_ptr,
+            changeable_const_pointers):
+        ops_with_movable_const_ptr[op] = []
         for i in range(op.numargs()):
             v = op.getarg(i)
             if isinstance(v, ConstPtr) and bool(v.value):
                 p = v.value
-                rgc._make_sure_does_not_move(p)
-                gcrefs_output_list.append(p)
+                if rgc._make_sure_does_not_move(p):
+                    gcrefs_output_list.append(p)
+                else:
+                    ops_with_movable_const_ptr[op].append(i)
+                    if v not in changeable_const_pointers:
+                        changeable_const_pointers.append(v)
+        #
         if op.is_guard() or op.getopnum() == rop.FINISH:
             llref = cast_instance_to_gcref(op.getdescr())
-            rgc._make_sure_does_not_move(llref)
+            assert rgc._make_sure_does_not_move(llref)
             gcrefs_output_list.append(llref)
+        #
+        if len(ops_with_movable_const_ptr[op]) == 0:
+            del ops_with_movable_const_ptr[op]
+
+    def _rewrite_changeable_constptrs(self, op, ops_with_movable_const_ptr, moving_obj_tracker):
+        newops = []
+        for arg_i in ops_with_movable_const_ptr[op]:
+            v = op.getarg(arg_i)
+            # assert to make sure we got what we expected
+            assert isinstance(v, ConstPtr)
+            result_ptr = BoxPtr()
+            array_index = moving_obj_tracker.get_array_index(v)
+            load_op = ResOperation(rop.GETARRAYITEM_GC,
+                    [moving_obj_tracker.const_ptr_gcref_array,
+                        ConstInt(array_index)],
+                    result_ptr,
+                    descr=moving_obj_tracker.ptr_array_descr)
+            newops.append(load_op)
+            op.setarg(arg_i, result_ptr)
+        #
+        newops.append(op)
+        return newops
 
     def rewrite_assembler(self, cpu, operations, gcrefs_output_list):
         rewriter = GcRewriterAssembler(self, cpu)
         newops = rewriter.rewrite(operations)
-        # record all GCREFs, because the GC (or Boehm) cannot see them and
-        # keep them alive if they end up as constants in the assembler
+
+        # the key is an operation that contains a ConstPtr as an argument and
+        # this ConstPtrs pointer might change as it points to an object that
+        # can't be made non-moving (e.g. the object is pinned).
+        ops_with_movable_const_ptr = {}
+        #
+        # a list of such not really constant ConstPtrs.
+        changeable_const_pointers = []
         for op in newops:
-            self._record_constptrs(op, gcrefs_output_list)
+            # record all GCREFs, because the GC (or Boehm) cannot see them and
+            # keep them alive if they end up as constants in the assembler.
+            # If such a GCREF can change and we can't make the object it points
+            # to non-movable, we have to handle it seperatly. Such GCREF's are
+            # returned as ConstPtrs in 'changeable_const_pointers' and the
+            # affected operation is returned in 'op_with_movable_const_ptr'.
+            # For this special case see 'rewrite_changeable_constptrs'.
+            self._record_constptrs(op, gcrefs_output_list,
+                    ops_with_movable_const_ptr, changeable_const_pointers)
+        #
+        # handle pointers that are not guaranteed to stay the same
+        if len(ops_with_movable_const_ptr) > 0:
+            moving_obj_tracker = MovableObjectTracker(cpu, changeable_const_pointers)
+            #
+            if not we_are_translated():
+                # used for testing
+                self.last_moving_obj_tracker = moving_obj_tracker
+            # make sure the array containing the pointers is not collected by
+            # the GC (or Boehm)
+            gcrefs_output_list.append(moving_obj_tracker.ptr_array_gcref)
+            rgc._make_sure_does_not_move(moving_obj_tracker.ptr_array_gcref)
+
+            ops = newops
+            newops = []
+            for op in ops:
+                if op in ops_with_movable_const_ptr:
+                    rewritten_ops = self._rewrite_changeable_constptrs(op,
+                            ops_with_movable_const_ptr, moving_obj_tracker)
+                    newops.extend(rewritten_ops)
+                else:
+                    newops.append(op)
+        #
         return newops
 
     @specialize.memo()
diff --git a/rpython/jit/backend/llsupport/llmodel.py b/rpython/jit/backend/llsupport/llmodel.py
--- a/rpython/jit/backend/llsupport/llmodel.py
+++ b/rpython/jit/backend/llsupport/llmodel.py
@@ -301,6 +301,7 @@
         return ofs, size, sign
     unpack_fielddescr_size._always_inline_ = True
 
+    @specialize.memo()
     def arraydescrof(self, A):
         return get_array_descr(self.gc_ll_descr, A)
 
diff --git a/rpython/jit/backend/llsupport/test/test_pinned_object_rewrite.py b/rpython/jit/backend/llsupport/test/test_pinned_object_rewrite.py
new file mode 100644
--- /dev/null
+++ b/rpython/jit/backend/llsupport/test/test_pinned_object_rewrite.py
@@ -0,0 +1,149 @@
+from test_rewrite import get_size_descr, get_array_descr, get_description, BaseFakeCPU
+from rpython.jit.backend.llsupport.descr import get_size_descr,\
+     get_field_descr, get_array_descr, ArrayDescr, FieldDescr,\
+     SizeDescrWithVTable, get_interiorfield_descr
+from rpython.jit.backend.llsupport.gc import GcLLDescr_boehm,\
+     GcLLDescr_framework, MovableObjectTracker
+from rpython.jit.backend.llsupport import jitframe, gc
+from rpython.jit.metainterp.gc import get_description
+from rpython.jit.tool.oparser import parse
+from rpython.jit.metainterp.optimizeopt.util import equaloplists
+from rpython.jit.codewriter.heaptracker import register_known_gctype
+from rpython.jit.metainterp.history import JitCellToken, FLOAT
+from rpython.rtyper.lltypesystem import lltype, rclass, rffi, lltype, llmemory
+from rpython.jit.backend.x86.arch import WORD
+from rpython.rlib import rgc
+
+class Evaluator(object):
+    def __init__(self, scope):
+        self.scope = scope
+    def __getitem__(self, key):
+        return eval(key, self.scope)
+
+
+class FakeLoopToken(object):
+    pass
+
+# The following class is based on rpython.jit.backend.llsupport.test.test_rewrite.RewriteTests.
+# It's modified to be able to test the object pinning specific features.
+class RewriteTests(object):
+    def check_rewrite(self, frm_operations, to_operations, **namespace):
+        # objects to use inside the test
+        A = lltype.GcArray(lltype.Signed)
+        adescr = get_array_descr(self.gc_ll_descr, A)
+        adescr.tid = 4321
+        alendescr = adescr.lendescr
+        #
+        pinned_obj_type = lltype.GcStruct('PINNED_STRUCT', ('my_int', lltype.Signed))
+        pinned_obj_my_int_descr = get_field_descr(self.gc_ll_descr, pinned_obj_type, 'my_int')
+        pinned_obj_ptr = lltype.malloc(pinned_obj_type)
+        pinned_obj_gcref = lltype.cast_opaque_ptr(llmemory.GCREF, pinned_obj_ptr)
+        assert rgc.pin(pinned_obj_gcref)
+        #
+        notpinned_obj_type = lltype.GcStruct('NOT_PINNED_STRUCT', ('my_int', lltype.Signed))
+        notpinned_obj_my_int_descr = get_field_descr(self.gc_ll_descr, notpinned_obj_type, 'my_int')
+        notpinned_obj_ptr = lltype.malloc(notpinned_obj_type)
+        notpinned_obj_gcref = lltype.cast_opaque_ptr(llmemory.GCREF, notpinned_obj_ptr)
+        #
+        ptr_array_descr = self.cpu.arraydescrof(MovableObjectTracker.ptr_array_type)
+        #
+        vtable_descr = self.gc_ll_descr.fielddescr_vtable
+        O = lltype.GcStruct('O', ('parent', rclass.OBJECT),
+                                 ('x', lltype.Signed))
+        o_vtable = lltype.malloc(rclass.OBJECT_VTABLE, immortal=True)
+        register_known_gctype(self.cpu, o_vtable, O)
+        #
+        tiddescr = self.gc_ll_descr.fielddescr_tid
+        wbdescr = self.gc_ll_descr.write_barrier_descr
+        WORD = globals()['WORD']
+        #
+        strdescr     = self.gc_ll_descr.str_descr
+        unicodedescr = self.gc_ll_descr.unicode_descr
+        strlendescr     = strdescr.lendescr
+        unicodelendescr = unicodedescr.lendescr
+
+        casmdescr = JitCellToken()
+        clt = FakeLoopToken()
+        clt._ll_initial_locs = [0, 8]
+        frame_info = lltype.malloc(jitframe.JITFRAMEINFO, flavor='raw')
+        clt.frame_info = frame_info
+        frame_info.jfi_frame_depth = 13
+        frame_info.jfi_frame_size = 255
+        framedescrs = self.gc_ll_descr.getframedescrs(self.cpu)
+        framelendescr = framedescrs.arraydescr.lendescr
+        jfi_frame_depth = framedescrs.jfi_frame_depth
+        jfi_frame_size = framedescrs.jfi_frame_size
+        jf_frame_info = framedescrs.jf_frame_info
+        signedframedescr = self.cpu.signedframedescr
+        floatframedescr = self.cpu.floatframedescr
+        casmdescr.compiled_loop_token = clt
+        tzdescr = None # noone cares
+        #
+        namespace.update(locals())
+        #
+        for funcname in self.gc_ll_descr._generated_functions:
+            namespace[funcname] = self.gc_ll_descr.get_malloc_fn(funcname)
+            namespace[funcname + '_descr'] = getattr(self.gc_ll_descr,
+                                                     '%s_descr' % funcname)
+        #
+        ops = parse(frm_operations, namespace=namespace)
+        operations = self.gc_ll_descr.rewrite_assembler(self.cpu,
+                                                        ops.operations,
+                                                        [])
+        # make the array containing the GCREF's accessible inside the tests.
+        # This must be done after we call 'rewrite_assembler'. Before that
+        # call 'last_moving_obj_tracker' is None or filled with some old
+        # value.
+        namespace['ptr_array_gcref'] = self.gc_ll_descr.last_moving_obj_tracker.ptr_array_gcref
+        expected = parse(to_operations % Evaluator(namespace),
+                         namespace=namespace)
+        equaloplists(operations, expected.operations)
+        lltype.free(frame_info, flavor='raw')
+
+class TestFramework(RewriteTests):
+    def setup_method(self, meth):
+        class config_(object):
+            class translation(object):
+                gc = 'minimark'
+                gcrootfinder = 'asmgcc'
+                gctransformer = 'framework'
+                gcremovetypeptr = False
+        gcdescr = get_description(config_)
+        self.gc_ll_descr = GcLLDescr_framework(gcdescr, None, None, None,
+                                               really_not_translated=True)
+        self.gc_ll_descr.write_barrier_descr.has_write_barrier_from_array = (
+            lambda cpu: True)
+        #
+        class FakeCPU(BaseFakeCPU):
+            def sizeof(self, STRUCT):
+                descr = SizeDescrWithVTable(104)
+                descr.tid = 9315
+                return descr
+        self.cpu = FakeCPU()
+
+    def test_simple_getfield(self):
+        self.check_rewrite("""
+            []
+            i0 = getfield_gc(ConstPtr(pinned_obj_gcref), descr=pinned_obj_my_int_descr)
+            """, """
+            []
+            p1 = getarrayitem_gc(ConstPtr(ptr_array_gcref), 0, descr=ptr_array_descr)
+            i0 = getfield_gc(p1, descr=pinned_obj_my_int_descr)
+            """)
+        assert len(self.gc_ll_descr.last_moving_obj_tracker._indexes) == 1
+
+    def test_simple_getfield_twice(self):
+        self.check_rewrite("""
+            []
+            i0 = getfield_gc(ConstPtr(pinned_obj_gcref), descr=pinned_obj_my_int_descr)
+            i1 = getfield_gc(ConstPtr(notpinned_obj_gcref), descr=notpinned_obj_my_int_descr)
+            i2 = getfield_gc(ConstPtr(pinned_obj_gcref), descr=pinned_obj_my_int_descr)
+            """, """
+            []
+            p1 = getarrayitem_gc(ConstPtr(ptr_array_gcref), 0, descr=ptr_array_descr)
+            i0 = getfield_gc(p1, descr=pinned_obj_my_int_descr)
+            i1 = getfield_gc(ConstPtr(notpinned_obj_gcref), descr=notpinned_obj_my_int_descr)
+            p2 = getarrayitem_gc(ConstPtr(ptr_array_gcref), 1, descr=ptr_array_descr)
+            i2 = getfield_gc(p2, descr=pinned_obj_my_int_descr)
+            """)
+        assert len(self.gc_ll_descr.last_moving_obj_tracker._indexes) == 2
diff --git a/rpython/jit/backend/llsupport/test/zrpy_gc_test.py b/rpython/jit/backend/llsupport/test/zrpy_gc_test.py
--- a/rpython/jit/backend/llsupport/test/zrpy_gc_test.py
+++ b/rpython/jit/backend/llsupport/test/zrpy_gc_test.py
@@ -10,6 +10,7 @@
 from rpython.rtyper.lltypesystem import lltype
 from rpython.rlib.jit import JitDriver, dont_look_inside
 from rpython.rlib.jit import elidable, unroll_safe
+from rpython.rlib.jit import promote
 from rpython.jit.backend.llsupport.gc import GcLLDescr_framework
 from rpython.tool.udir import udir
 from rpython.config.translationoption import DEFL_GC
@@ -22,6 +23,12 @@
 
     next = None
 
+class Y(object):
+    # for pinning tests we need an object without references to other
+    # objects
+    def __init__(self, x=0):
+        self.x = x
+
 class CheckError(Exception):
     pass
 
@@ -778,3 +785,121 @@
 
     def test_compile_framework_call_assembler(self):
         self.run('compile_framework_call_assembler')
+
+    def define_pinned_simple(cls):
+        class H:
+            inst = None
+        helper = H()
+
+        @dont_look_inside
+        def get_y():
+            if not helper.inst:
+                helper.inst = Y()
+                helper.inst.x = 101
+                check(rgc.pin(helper.inst))
+            else:
+                check(rgc._is_pinned(helper.inst))
+            return helper.inst
+
+        def fn(n, x, *args):
+            t = get_y()
+            promote(t)
+            t.x += 11
+            n -= 1
+            return (n, x) + args
+
+        return None, fn, None
+
+    def test_pinned_simple(self):
+        self.run('pinned_simple')
+
+    def define_pinned_unpin(cls):
+        class H:
+            inst = None
+            pinned = False
+            count_pinned = 0
+            count_unpinned = 0
+        helper = H()
+
+        @dont_look_inside
+        def get_y(n):
+            if not helper.inst:
+                helper.inst = Y()
+                helper.inst.x = 101
+                helper.pinned = True
+                check(rgc.pin(helper.inst))
+            elif n < 100 and helper.pinned:
+                rgc.unpin(helper.inst)
+                helper.pinned = False
+            #
+            if helper.pinned:
+                check(rgc._is_pinned(helper.inst))
+                helper.count_pinned += 1
+            else:
+                check(not rgc._is_pinned(helper.inst))
+                helper.count_unpinned += 1
+            return helper.inst
+
+        def fn(n, x, *args):
+            t = get_y(n)
+            promote(t)
+            check(t.x == 101)
+            n -= 1
+            return (n, x) + args
+
+        def after(n, x, *args):
+            check(helper.count_pinned > 0)
+            check(helper.count_unpinned > 0)
+            check(not helper.pinned)
+
+        return None, fn, after
+
+    def test_pinned_unpin(self):
+        self.run('pinned_unpin')
+
+    def define_multiple_pinned(cls):
+        class H:
+            inst1 = None
+            inst2 = None
+            inst3 = None
+            initialised = False
+        helper = H()
+
+        @dont_look_inside
+        def get_instances():
+            if not helper.initialised:
+                helper.inst1 = Y()
+                helper.inst1.x = 101
+                check(rgc.pin(helper.inst1))
+                #
+                helper.inst2 = Y()
+                helper.inst2.x = 102
+                #
+                helper.inst3 = Y()
+                helper.inst3.x = 103
+                check(rgc.pin(helper.inst3))
+                #
+                helper.initialised = True
+            #
+            check(rgc._is_pinned(helper.inst1))
+            check(not rgc._is_pinned(helper.inst2))
+            check(rgc._is_pinned(helper.inst3))
+            return (helper.inst1, helper.inst2, helper.inst3)
+
+        def fn(n, x, *args):
+            inst1, inst2, inst3 = get_instances()
+            promote(inst1)
+            promote(inst2)
+            promote(inst3)
+            #
+            check(inst1.x == 101)
+            check(inst2.x == 102)
+            check(inst3.x == 103)
+            #
+            n -= 1
+            return (n, x) + args
+        
+        return None, fn, None
+
+    def test_multiple_pinned(self):
+        self.run('multiple_pinned')
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
@@ -506,30 +506,32 @@
 
     # XXX some of the following functions should not become residual calls
     # but be really compiled
-    rewrite_op_int_floordiv_ovf_zer = _do_builtin_call
-    rewrite_op_int_floordiv_ovf     = _do_builtin_call
-    rewrite_op_int_floordiv_zer     = _do_builtin_call
-    rewrite_op_int_mod_ovf_zer = _do_builtin_call
-    rewrite_op_int_mod_ovf     = _do_builtin_call
-    rewrite_op_int_mod_zer     = _do_builtin_call
-    rewrite_op_int_lshift_ovf  = _do_builtin_call
-    rewrite_op_int_abs         = _do_builtin_call
-    rewrite_op_llong_abs          = _do_builtin_call
-    rewrite_op_llong_floordiv     = _do_builtin_call
-    rewrite_op_llong_floordiv_zer = _do_builtin_call
-    rewrite_op_llong_mod          = _do_builtin_call
-    rewrite_op_llong_mod_zer      = _do_builtin_call
-    rewrite_op_ullong_floordiv     = _do_builtin_call
-    rewrite_op_ullong_floordiv_zer = _do_builtin_call
-    rewrite_op_ullong_mod          = _do_builtin_call
-    rewrite_op_ullong_mod_zer      = _do_builtin_call
-    rewrite_op_gc_identityhash = _do_builtin_call
-    rewrite_op_gc_id           = _do_builtin_call
-    rewrite_op_uint_mod        = _do_builtin_call
-    rewrite_op_cast_float_to_uint = _do_builtin_call
-    rewrite_op_cast_uint_to_float = _do_builtin_call
-    rewrite_op_weakref_create = _do_builtin_call
-    rewrite_op_weakref_deref = _do_builtin_call
+    rewrite_op_int_floordiv_ovf_zer   = _do_builtin_call
+    rewrite_op_int_floordiv_ovf       = _do_builtin_call
+    rewrite_op_int_floordiv_zer       = _do_builtin_call
+    rewrite_op_int_mod_ovf_zer        = _do_builtin_call
+    rewrite_op_int_mod_ovf            = _do_builtin_call
+    rewrite_op_int_mod_zer            = _do_builtin_call
+    rewrite_op_int_lshift_ovf         = _do_builtin_call
+    rewrite_op_int_abs                = _do_builtin_call
+    rewrite_op_llong_abs              = _do_builtin_call
+    rewrite_op_llong_floordiv         = _do_builtin_call
+    rewrite_op_llong_floordiv_zer     = _do_builtin_call
+    rewrite_op_llong_mod              = _do_builtin_call
+    rewrite_op_llong_mod_zer          = _do_builtin_call
+    rewrite_op_ullong_floordiv        = _do_builtin_call
+    rewrite_op_ullong_floordiv_zer    = _do_builtin_call
+    rewrite_op_ullong_mod             = _do_builtin_call
+    rewrite_op_ullong_mod_zer         = _do_builtin_call
+    rewrite_op_gc_identityhash        = _do_builtin_call
+    rewrite_op_gc_id                  = _do_builtin_call
+    rewrite_op_gc_pin                 = _do_builtin_call
+    rewrite_op_gc_unpin               = _do_builtin_call
+    rewrite_op_uint_mod               = _do_builtin_call
+    rewrite_op_cast_float_to_uint     = _do_builtin_call
+    rewrite_op_cast_uint_to_float     = _do_builtin_call
+    rewrite_op_weakref_create         = _do_builtin_call
+    rewrite_op_weakref_deref          = _do_builtin_call
     rewrite_op_gc_add_memory_pressure = _do_builtin_call
 
     # ----------
diff --git a/rpython/jit/codewriter/support.py b/rpython/jit/codewriter/support.py
--- a/rpython/jit/codewriter/support.py
+++ b/rpython/jit/codewriter/support.py
@@ -230,6 +230,13 @@
     return llop.gc_id(lltype.Signed, ptr)
 
 
+def _ll_1_gc_pin(ptr):
+    return llop.gc_pin(lltype.Bool, ptr)
+
+def _ll_1_gc_unpin(ptr):
+    llop.gc_unpin(lltype.Void, ptr)
+
+
 @oopspec("jit.force_virtual(inst)")
 def _ll_1_jit_force_virtual(inst):
     return llop.jit_force_virtual(lltype.typeOf(inst), inst)
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
@@ -18,6 +18,7 @@
     needs_write_barrier = False
     malloc_zero_filled = False
     prebuilt_gc_objects_are_static_roots = True
+    can_usually_pin_objects = False
     object_minimal_size = 0
     gcflag_extra = 0   # or a real GC flag that is always 0 when not collecting
 
@@ -72,7 +73,8 @@
                             has_custom_trace,
                             get_custom_trace,
                             fast_path_tracing,
-                            has_gcptr):
+                            has_gcptr,
+                            cannot_pin):
         self.getfinalizer = getfinalizer
         self.getlightfinalizer = getlightfinalizer
         self.is_varsize = is_varsize
@@ -91,6 +93,7 @@
         self.get_custom_trace = get_custom_trace
         self.fast_path_tracing = fast_path_tracing
         self.has_gcptr = has_gcptr
+        self.cannot_pin = cannot_pin
 
     def get_member_index(self, type_id):
         return self.member_index(type_id)
@@ -168,6 +171,15 @@
     def can_move(self, addr):
         return False
 
+    def pin(self, addr):
+        return False
+
+    def unpin(self, addr):
+        pass
+
+    def _is_pinned(self, addr):
+        return False
+
     def set_max_heap_size(self, size):
         raise NotImplementedError
 
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
@@ -64,16 +64,16 @@
 from rpython.rlib.debug import ll_assert, debug_print, debug_start, debug_stop
 from rpython.rlib.objectmodel import specialize
 
-
 #
 # Handles the objects in 2 generations:
 #
 #  * young objects: allocated in the nursery if they are not too large, or
 #    raw-malloced otherwise.  The nursery is a fixed-size memory buffer of
 #    4MB by default.  When full, we do a minor collection;
-#    the surviving objects from the nursery are moved outside, and the
-#    non-surviving raw-malloced objects are freed.  All surviving objects
-#    become old.
+#    - surviving objects from the nursery are moved outside and become old,
+#    - non-surviving raw-malloced objects are freed,
+#    - and pinned objects are kept at their place inside the nursery and stay
+#      young.
 #
 #  * old objects: never move again.  These objects are either allocated by
 #    minimarkpage.py (if they are small), or raw-malloced (if they are not
@@ -81,7 +81,6 @@
 #
 
 WORD = LONG_BIT // 8
-NULL = llmemory.NULL
 
 first_gcflag = 1 << (LONG_BIT//2)
 
@@ -132,7 +131,21 @@
 # a minor collection.
 GCFLAG_VISITED_RMY   = first_gcflag << 8
 
-_GCFLAG_FIRST_UNUSED = first_gcflag << 9    # the first unused bit
+# The following flag is set on nursery objects to keep them in the nursery.
+# This means that a young object with this flag is not moved out
+# of the nursery during a minor collection. See pin()/unpin() for further
+# details.
+GCFLAG_PINNED        = first_gcflag << 9
+
+# The following flag is set only on objects outside the nursery
+# (i.e. old objects).  Therefore we can reuse GCFLAG_PINNED as it is used for
+# the same feature (object pinning) and GCFLAG_PINNED is only used on nursery
+# objects.
+# If this flag is set, the flagged object is already an element of
+# 'old_objects_pointing_to_pinned' and doesn't have to be added again.
+GCFLAG_PINNED_OBJECT_PARENT_KNOWN = GCFLAG_PINNED
+
+_GCFLAG_FIRST_UNUSED = first_gcflag << 10    # the first unused bit
 
 
 # States for the incremental GC
@@ -168,7 +181,8 @@
     inline_simple_malloc_varsize = True
     needs_write_barrier = True
     prebuilt_gc_objects_are_static_roots = False
-    malloc_zero_filled = False    # xxx experiment with False
+    can_usually_pin_objects = True
+    malloc_zero_filled = False
     gcflag_extra = GCFLAG_EXTRA
 
     # All objects start with a HDR, i.e. with a field 'tid' which contains
@@ -270,6 +284,7 @@
         self.max_heap_size = 0.0
         self.max_heap_size_already_raised = False
         self.max_delta = float(r_uint(-1))
+        self.max_number_of_pinned_objects = 0      # computed later
         #
         self.card_page_indices = card_page_indices
         if self.card_page_indices > 0:
@@ -281,9 +296,9 @@
         # it gives a lower bound on the allowed size of the nursery.
         self.nonlarge_max = large_object - 1
         #
-        self.nursery      = NULL
-        self.nursery_free = NULL
-        self.nursery_top  = NULL
+        self.nursery      = llmemory.NULL
+        self.nursery_free = llmemory.NULL
+        self.nursery_top  = llmemory.NULL
         self.debug_tiny_nursery = -1
         self.debug_rotating_nurseries = lltype.nullptr(NURSARRAY)
         self.extra_threshold = 0
@@ -350,6 +365,22 @@
         # minor collection.
         self.nursery_objects_shadows = self.AddressDict()
         #
+        # A sorted deque containing addresses of pinned objects.
+        # This collection is used to make sure we don't overwrite pinned objects.
+        # Each minor collection creates a new deque containing the active pinned
+        # objects. The addresses are used to set the next 'nursery_top'.
+        self.nursery_barriers = self.AddressDeque()
+        #
+        # Counter tracking how many pinned objects currently reside inside
+        # the nursery.
+        self.pinned_objects_in_nursery = 0
+        #
+        # Keeps track of old objects pointing to pinned objects. These objects
+        # must be traced every minor collection. Without tracing them the
+        # referenced pinned object wouldn't be visited and therefore collected.
+        self.old_objects_pointing_to_pinned = self.AddressStack()
+        self.updated_old_objects_pointing_to_pinned = False
+        #
         # Allocate a nursery.  In case of auto_nursery_size, start by
         # allocating a very small nursery, enough to do things like look
         # up the env var, which requires the GC; and then really
@@ -423,6 +454,10 @@
             llarena.arena_free(self.nursery)
             self.nursery_size = newsize
             self.allocate_nursery()
+        #
+        # Estimate this number conservatively
+        bigobj = self.nonlarge_max + 1
+        self.max_number_of_pinned_objects = self.nursery_size / (bigobj * 2)
 
     def _nursery_memory_size(self):
         extra = self.nonlarge_max + 1
@@ -558,10 +593,7 @@
             #
             # Get the memory from the nursery.  If there is not enough space
             # there, do a collect first.
-            result = self.nursery_free
-            self.nursery_free = result + totalsize
-            if self.nursery_free > self.nursery_top:
-                result = self.collect_and_reserve(result, totalsize)
+            result = self.collect_and_reserve(rawtotalsize)
             #
             # Build the object.
             llarena.arena_reserve(result, totalsize)
@@ -617,10 +649,7 @@
             #
             # Get the memory from the nursery.  If there is not enough space
             # there, do a collect first.
-            result = self.nursery_free
-            self.nursery_free = result + totalsize
-            if self.nursery_free > self.nursery_top:
-                result = self.collect_and_reserve(result, totalsize)
+            result = self.collect_and_reserve(raw_malloc_usage(totalsize))
             #
             # Build the object.
             llarena.arena_reserve(result, totalsize)
@@ -644,7 +673,7 @@
             self.minor_and_major_collection()
 
 
-    def collect_and_reserve(self, prev_result, totalsize):
+    def collect_and_reserve(self, totalsize):
         """To call when nursery_free overflows nursery_top.
         First check if the nursery_top is the real top, otherwise we
         can just move the top of one cleanup and continue
@@ -653,26 +682,51 @@
         and finally reserve 'totalsize' bytes at the start of the
         now-empty nursery.
         """
-        self.minor_collection()
-        #
-        # If the gc_state is not STATE_SCANNING, we're in the middle of
-        # an incremental major collection.  In this case, always progress
-        # one step.  If the gc_state is STATE_SCANNING, wait until there
-        # is too much garbage before starting the next major collection.
-        if (self.gc_state != STATE_SCANNING or
-                    self.get_total_memory_used() >
-                    self.next_major_collection_threshold):
-            self.major_collection_step()
+        if self.nursery_free + totalsize <= self.nursery_top:
+            result = self.nursery_free
+            self.nursery_free = result + totalsize
+            return result
+
+        minor_collection_count = 0
+        while True:
+            if self.nursery_barriers.non_empty():
+                size_gc_header = self.gcheaderbuilder.size_gc_header
+                pinned_obj_size = size_gc_header + self.get_size(
+                        self.nursery_top + size_gc_header)
+
+                self.nursery_free = self.nursery_top + pinned_obj_size
+                self.nursery_top = self.nursery_barriers.popleft()
+            else:
+                minor_collection_count += 1
+                self.minor_collection()
+                if minor_collection_count == 1:
+                    #
+                    # If the gc_state is not STATE_SCANNING, we're in the middle of
+                    # an incremental major collection.  In this case, always progress
+                    # one step.  If the gc_state is STATE_SCANNING, wait until there
+                    # is too much garbage before starting the next major collection.
+                    if (self.gc_state != STATE_SCANNING or
+                                self.get_total_memory_used() >
+                                self.next_major_collection_threshold):
+                        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()
+                    #
+                else:
+                    ll_assert(minor_collection_count == 2,
+                            "Seeing minor_collection() at least twice."
+                            "Too many pinned objects?")
             #
-            # 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()
-        #
-        result = self.nursery_free
-        self.nursery_free = result + totalsize
-        ll_assert(self.nursery_free <= self.nursery_top, "nursery overflow")
+            result = self.nursery_free
+            if self.nursery_free + totalsize <= self.nursery_top:
+                self.nursery_free = result + totalsize
+                ll_assert(self.nursery_free <= self.nursery_top, "nursery overflow")
+                break
+            #
         #
         if self.debug_tiny_nursery >= 0:   # for debugging
             if self.nursery_top - self.nursery_free > self.debug_tiny_nursery:
@@ -845,6 +899,44 @@
         """Overrides the parent can_move()."""
         return self.is_in_nursery(obj)
 
+    def pin(self, obj):
+        if self.pinned_objects_in_nursery >= self.max_number_of_pinned_objects:
+            return False
+        if not self.is_in_nursery(obj):
+            # old objects are already non-moving, therefore pinning
+            # makes no sense. If you run into this case, you may forgot
+            # to check can_move(obj).
+            return False
+        if self._is_pinned(obj):
+            # already pinned, we do not allow to pin it again.
+            # Reason: It would be possible that the first caller unpins
+            # while the second caller thinks it's still pinned.
+            return False
+        #
+        obj_type_id = self.get_type_id(obj)
+        if self.cannot_pin(obj_type_id):
+            # objects containing GC pointers can't be pinned. If we would add
+            # it, we would have to track all pinned objects and trace them
+            # every minor collection to make sure the referenced object are
+            # kept alive. Right now this is not a use case that's needed.
+            # The check above also tests for being a less common kind of
+            # object: a weakref, or one with any kind of finalizer.
+            return False
+        #
+        self.header(obj).tid |= GCFLAG_PINNED
+        self.pinned_objects_in_nursery += 1
+        return True
+
+
+    def unpin(self, obj):
+        ll_assert(self._is_pinned(obj),
+            "unpin: object is already not pinned")
+        #
+        self.header(obj).tid &= ~GCFLAG_PINNED
+        self.pinned_objects_in_nursery -= 1
+
+    def _is_pinned(self, obj):
+        return (self.header(obj).tid & GCFLAG_PINNED) != 0
 
     def shrink_array(self, obj, smallerlength):
         #
@@ -897,7 +989,7 @@
     def is_in_nursery(self, addr):
         ll_assert(llmemory.cast_adr_to_int(addr) & 1 == 0,
                   "odd-valued (i.e. tagged) pointer unexpected here")
-        return self.nursery <= addr < self.nursery_top
+        return self.nursery <= addr < self.nursery + self.nursery_size
 
     def appears_to_be_young(self, addr):
         # "is a valid addr to a young object?"
@@ -1000,11 +1092,18 @@
 
     def debug_check_object(self, obj):
         # We are after a minor collection, and possibly after a major
-        # collection step.  No object should be in the nursery
-        ll_assert(not self.is_in_nursery(obj),
-                  "object in nursery after collection")
-        ll_assert(self.header(obj).tid & GCFLAG_VISITED_RMY == 0,
-                  "GCFLAG_VISITED_RMY after collection")
+        # collection step.  No object should be in the nursery (except
+        # pinned ones)
+        if not self._is_pinned(obj):
+            ll_assert(not self.is_in_nursery(obj),
+                      "object in nursery after collection")
+            ll_assert(self.header(obj).tid & GCFLAG_VISITED_RMY == 0,
+                      "GCFLAG_VISITED_RMY after collection")
+            ll_assert(self.header(obj).tid & GCFLAG_PINNED == 0,
+                      "GCFLAG_PINNED outside the nursery after collection")
+        else:
+            ll_assert(self.is_in_nursery(obj),
+                      "pinned object not in nursery")
 
         if self.gc_state == STATE_SCANNING:
             self._debug_check_object_scanning(obj)
@@ -1040,6 +1139,12 @@
             pass    # black -> gray
         elif self.header(obj).tid & GCFLAG_NO_HEAP_PTRS != 0:
             pass    # black -> white-but-prebuilt-so-dont-care
+        elif self._is_pinned(obj):
+            # black -> pinned: the pinned object is a white one as
+            # every minor collection visits them and takes care of
+            # visiting pinned objects.
+            # XXX (groggi) double check with fijal/armin
+            pass    # black -> pinned
         else:
             ll_assert(False, "black -> white pointer found")
 
@@ -1048,9 +1153,9 @@
         # but this flag is progressively removed in the sweeping phase.
 
         # All objects should have this flag, except if they
-        # don't have any GC pointer
+        # don't have any GC pointer or are pinned objects
         typeid = self.get_type_id(obj)
-        if self.has_gcptr(typeid):
+        if self.has_gcptr(typeid) and not self._is_pinned(obj):
             ll_assert(self.header(obj).tid & GCFLAG_TRACK_YOUNG_PTRS != 0,
                       "missing GCFLAG_TRACK_YOUNG_PTRS")
         # the GCFLAG_FINALIZATION_ORDERING should not be set between coll.
@@ -1264,6 +1369,17 @@
         one of the following flags a bit too eagerly, which means we'll have
         a bit more objects to track, but being on the safe side.
         """
+        # obscuuuure.  The flag 'updated_old_objects_pointing_to_pinned'
+        # is set to True when 'old_objects_pointing_to_pinned' is modified.
+        # Here, when it was modified, then we do a write_barrier() on
+        # all items in that list (there should only be a small number,
+        # so we don't care).  The goal is that the logic that follows below
+        # works as expected...
+        if self.updated_old_objects_pointing_to_pinned:
+            self.old_objects_pointing_to_pinned.foreach(
+                self._wb_old_object_pointing_to_pinned, None)
+            self.updated_old_objects_pointing_to_pinned = False
+        #
         source_hdr = self.header(source_addr)
         dest_hdr = self.header(dest_addr)
         if dest_hdr.tid & GCFLAG_TRACK_YOUNG_PTRS == 0:
@@ -1324,6 +1440,21 @@
                 self.old_objects_with_cards_set.append(dest_addr)
                 dest_hdr.tid |= GCFLAG_CARDS_SET
 
+    def _wb_old_object_pointing_to_pinned(self, obj, ignore):
+        self.write_barrier(obj)
+
+    def record_pinned_object_with_shadow(self, obj, new_shadow_object_dict):
+        # checks if the pinned object has a shadow and if so add it to the
+        # dict of shadows.
+        obj = obj + self.gcheaderbuilder.size_gc_header
+        shadow = self.nursery_objects_shadows.get(obj)
+        if shadow != llmemory.NULL:
+            # visit shadow to keep it alive
+            # XXX seems like it is save to set GCFLAG_VISITED, however
+            # should be double checked
+            self.header(shadow).tid |= GCFLAG_VISITED
+            new_shadow_object_dict.setitem(obj, shadow)
+
     # ----------
     # Nursery collection
 
@@ -1333,6 +1464,19 @@
         #
         debug_start("gc-minor")
         #
+        # All nursery barriers are invalid from this point on.  They
+        # are evaluated anew as part of the minor collection.
+        self.nursery_barriers.delete()
+        #
+        # Keeps track of surviving pinned objects. See also '_trace_drag_out()'
+        # where this stack is filled.  Pinning an object only prevents it from
+        # being moved, not from being collected if it is not reachable anymore.
+        self.surviving_pinned_objects = self.AddressStack()
+        # The following counter keeps track of alive and pinned young objects
+        # inside the nursery. We reset it here and increace it in
+        # '_trace_drag_out()'.
+        self.pinned_objects_in_nursery = 0
+        #
         # Before everything else, remove from 'old_objects_pointing_to_young'
         # the young arrays.
         if self.young_rawmalloced_objects:
@@ -1357,6 +1501,21 @@
         self.nursery_surviving_size = 0
         self.collect_roots_in_nursery()
         #
+        # visit all objects that are known for pointing to pinned
+        # objects. This way we populate 'surviving_pinned_objects'
+        # with pinned object that are (only) visible from an old
+        # object.
+        # Additionally we create a new list as it may be that an old object
+        # no longer points to a pinned one. Such old objects won't be added
+        # again to 'old_objects_pointing_to_pinned'.
+        if self.old_objects_pointing_to_pinned.non_empty():
+            current_old_objects_pointing_to_pinned = \
+                    self.old_objects_pointing_to_pinned
+            self.old_objects_pointing_to_pinned = self.AddressStack()
+            current_old_objects_pointing_to_pinned.foreach(
+                self._visit_old_objects_pointing_to_pinned, None)
+            current_old_objects_pointing_to_pinned.delete()
+        #
         while True:
             # If we are using card marking, do a partial trace of the arrays
             # that are flagged with GCFLAG_CARDS_SET.
@@ -1385,27 +1544,83 @@
         if self.young_objects_with_light_finalizers.non_empty():
             self.deal_with_young_objects_with_finalizers()
         #
-        # Clear this mapping.
+        # Clear this mapping.  Without pinned objects we just clear the dict
+        # as all objects in the nursery are dragged out of the nursery and, if
+        # needed, into their shadow.  However, if we have pinned objects we have
+        # to check if those pinned object have a shadow and keep a dictionary
+        # filled with shadow information for them as they stay in the nursery.
         if self.nursery_objects_shadows.length() > 0:
-            self.nursery_objects_shadows.clear()
+            if self.surviving_pinned_objects.non_empty():
+                new_shadows = self.AddressDict()
+                self.surviving_pinned_objects.foreach(
+                    self.record_pinned_object_with_shadow, new_shadows)
+                self.nursery_objects_shadows.delete()
+                self.nursery_objects_shadows = new_shadows
+            else:
+                self.nursery_objects_shadows.clear()
         #
         # Walk the list of young raw-malloced objects, and either free
         # them or make them old.
         if self.young_rawmalloced_objects:
             self.free_young_rawmalloced_objects()
         #
-        # All live nursery objects are out, and the rest dies.  Fill
-        # the nursery up to the cleanup point with zeros
+        # 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
+        # pointer.
+        size_gc_header = self.gcheaderbuilder.size_gc_header
+        nursery_barriers = self.AddressDeque()
+        prev = self.nursery
+        self.surviving_pinned_objects.sort()
+        assert self.pinned_objects_in_nursery == \
+            self.surviving_pinned_objects.length()
+        while self.surviving_pinned_objects.non_empty():
+            #
+            cur = self.surviving_pinned_objects.pop()
+            assert cur >= prev
+            #
+            # clear the arena between the last pinned object (or arena start)
+            # and the pinned object
+            pinned_obj_size = llarena.getfakearenaaddress(cur) - prev
+            if self.gc_nursery_debug:
+                llarena.arena_reset(prev, pinned_obj_size, 3)
+            else:
+                llarena.arena_reset(prev, pinned_obj_size, 0)
+            # XXX: debug_rotate_nursery missing here
+            #
+            # clean up object's flags
+            obj = cur + size_gc_header
+            self.header(obj).tid &= ~GCFLAG_VISITED
+            #
+            # create a new nursery barrier for the pinned object
+            nursery_barriers.append(cur)
+            #
+            # update 'prev' to the end of the 'cur' object
+            prev = prev + pinned_obj_size + \
+                (size_gc_header + self.get_size(obj))
+        #
+        # reset everything after the last pinned object till the end of the arena
         if self.gc_nursery_debug:
-            llarena.arena_reset(self.nursery, self.nursery_size, 3)
+            llarena.arena_reset(prev, self.nursery + self.nursery_size - prev, 3)
         else:
-            llarena.arena_reset(self.nursery, self.nursery_size, 0)
-        self.debug_rotate_nursery()
+            llarena.arena_reset(prev, self.nursery + self.nursery_size - prev, 0)
+        #
+        nursery_barriers.append(self.nursery + self.nursery_size)
+        self.nursery_barriers = nursery_barriers
+        self.surviving_pinned_objects.delete()
+        #
+        # XXX gc-minimark-pinning does a debug_rotate_nursery() here (groggi)
         self.nursery_free = self.nursery
-        self.nursery_top = self.nursery + self.nursery_size
+        self.nursery_top = self.nursery_barriers.popleft()
+        #
+        # clear GCFLAG_PINNED_OBJECT_PARENT_KNOWN from all parents in the list.
+        self.old_objects_pointing_to_pinned.foreach(
+                self._reset_flag_old_objects_pointing_to_pinned, None)
         #
         debug_print("minor collect, total memory used:",
                     self.get_total_memory_used())
+        debug_print("number of pinned objects:",
+                    self.pinned_objects_in_nursery)
         if self.DEBUG >= 2:
             self.debug_check_consistency()     # expensive!
         #
@@ -1413,6 +1628,12 @@
         #
         debug_stop("gc-minor")
 
+    def _reset_flag_old_objects_pointing_to_pinned(self, obj, ignore):
+        assert self.header(obj).tid & GCFLAG_PINNED_OBJECT_PARENT_KNOWN
+        self.header(obj).tid &= ~GCFLAG_PINNED_OBJECT_PARENT_KNOWN
+
+    def _visit_old_objects_pointing_to_pinned(self, obj, ignore):
+        self.trace(obj, self._trace_drag_out, obj)
 
     def collect_roots_in_nursery(self):
         # we don't need to trace prebuilt GcStructs during a minor collect:
@@ -1520,7 +1741,7 @@
         """obj must not be in the nursery.  This copies all the
         young objects it references out of the nursery.
         """
-        self.trace(obj, self._trace_drag_out, None)
+        self.trace(obj, self._trace_drag_out, obj)
 
     def trace_and_drag_out_of_nursery_partial(self, obj, start, stop):
         """Like trace_and_drag_out_of_nursery(), but limited to the array
@@ -1529,14 +1750,14 @@
         ll_assert(start < stop, "empty or negative range "
                                 "in trace_and_drag_out_of_nursery_partial()")
         #print 'trace_partial:', start, stop, '\t', obj
-        self.trace_partial(obj, start, stop, self._trace_drag_out, None)
+        self.trace_partial(obj, start, stop, self._trace_drag_out, obj)
 
 
     def _trace_drag_out1(self, root):
-        self._trace_drag_out(root, None)
+        self._trace_drag_out(root, llmemory.NULL)
 
     def _trace_drag_out1_marking_phase(self, root):
-        self._trace_drag_out(root, None)
+        self._trace_drag_out(root, llmemory.NULL)
         #
         # We are in the MARKING state: we must also record this object
         # if it was young.  Don't bother with old objects in general,
@@ -1549,7 +1770,7 @@
         if not self.header(obj).tid & GCFLAG_VISITED:
             self.more_objects_to_trace.append(obj)
 
-    def _trace_drag_out(self, root, ignored):
+    def _trace_drag_out(self, root, parent):
         obj = root.address[0]
         #print '_trace_drag_out(%x: %r)' % (hash(obj.ptr._obj), obj)
         #
@@ -1567,7 +1788,7 @@
             return
         #
         size_gc_header = self.gcheaderbuilder.size_gc_header
-        if self.header(obj).tid & GCFLAG_HAS_SHADOW == 0:
+        if self.header(obj).tid & (GCFLAG_HAS_SHADOW | GCFLAG_PINNED) == 0:
             #
             # Common case: 'obj' was not already forwarded (otherwise
             # tid == -42, containing all flags), and it doesn't have the
@@ -1584,10 +1805,36 @@
             root.address[0] = self.get_forwarding_address(obj)
             return
             #
+        elif self._is_pinned(obj):
+            hdr = self.header(obj)
+            #
+            # track parent of pinned object specially. This mus be done before
+            # checking for GCFLAG_VISITED: it may be that the same pinned object
+            # is reachable from multiple sources (e.g. two old objects pointing
+            # to the same pinned object). In such a case we need all parents
+            # of the pinned object in the list. Otherwise he pinned object could
+            # become dead and be removed just because the first parent of it
+            # is dead and collected.
+            if parent != llmemory.NULL and \
+                not self.header(parent).tid & GCFLAG_PINNED_OBJECT_PARENT_KNOWN:
+                #
+                self.old_objects_pointing_to_pinned.append(parent)
+                self.updated_old_objects_pointing_to_pinned = True
+                self.header(parent).tid |= GCFLAG_PINNED
+            #
+            if hdr.tid & GCFLAG_VISITED:
+                return
+            #
+            hdr.tid |= GCFLAG_VISITED
+            #
+            self.surviving_pinned_objects.append(
+                llarena.getfakearenaaddress(obj - size_gc_header))
+            self.pinned_objects_in_nursery += 1
+            return
         else:
             # First visit to an object that has already a shadow.
             newobj = self.nursery_objects_shadows.get(obj)
-            ll_assert(newobj != NULL, "GCFLAG_HAS_SHADOW but no shadow found")
+            ll_assert(newobj != llmemory.NULL, "GCFLAG_HAS_SHADOW but no shadow found")
             newhdr = newobj - size_gc_header
             #
             # Remove the flag GCFLAG_HAS_SHADOW, so that it doesn't get
@@ -1737,8 +1984,13 @@
         debug_start("gc-collect-step")
         debug_print("starting gc state: ", GC_STATES[self.gc_state])
         # Debugging checks
-        ll_assert(self.nursery_free == self.nursery,
-                  "nursery not empty in major_collection_step()")
+        if self.pinned_objects_in_nursery == 0:
+            ll_assert(self.nursery_free == self.nursery,
+                      "nursery not empty in major_collection_step()")
+        else:
+            # XXX try to add some similar check to the above one for the case
+            # that the nursery still contains some pinned objects (groggi)
+            pass
         self.debug_check_consistency()
 
 
@@ -1800,9 +2052,21 @@
                 # Light finalizers
                 if self.old_objects_with_light_finalizers.non_empty():
                     self.deal_with_old_objects_with_finalizers()
-                #objects_to_trace processed fully, can move on to sweeping
+                # objects_to_trace processed fully, can move on to sweeping
                 self.ac.mass_free_prepare()
                 self.start_free_rawmalloc_objects()
+                #
+                # get rid of objects pointing to pinned objects that were not
+                # visited
+                if self.old_objects_pointing_to_pinned.non_empty():
+                    new_old_objects_pointing_to_pinned = self.AddressStack()
+                    self.old_objects_pointing_to_pinned.foreach(
+                            self._sweep_old_objects_pointing_to_pinned,
+                            new_old_objects_pointing_to_pinned)
+                    self.old_objects_pointing_to_pinned.delete()
+                    self.old_objects_pointing_to_pinned = \
+                            new_old_objects_pointing_to_pinned
+                    self.updated_old_objects_pointing_to_pinned = True
                 self.gc_state = STATE_SWEEPING
             #END MARKING
         elif self.gc_state == STATE_SWEEPING:
@@ -1881,6 +2145,10 @@
         debug_print("stopping, now in gc state: ", GC_STATES[self.gc_state])
         debug_stop("gc-collect-step")
 
+    def _sweep_old_objects_pointing_to_pinned(self, obj, new_list):
+        if self.header(obj).tid & GCFLAG_VISITED:
+            new_list.append(obj)
+
     def _free_if_unvisited(self, hdr):
         size_gc_header = self.gcheaderbuilder.size_gc_header
         obj = hdr + size_gc_header
@@ -1965,7 +2233,13 @@
     def _collect_ref_stk(self, root):
         obj = root.address[0]
         llop.debug_nonnull_pointer(lltype.Void, obj)
-        self.objects_to_trace.append(obj)
+        if not self._is_pinned(obj):
+            # XXX: check if this is the right way (groggi).
+            # A pinned object can be on the stack. Such an object is handled
+            # by minor collections and shouldn't be specially handled by
+            # major collections. Therefore we only add not pinned objects to the
+            # list below.
+            self.objects_to_trace.append(obj)
 
     def _collect_ref_rec(self, root, ignored):
         self.objects_to_trace.append(root.address[0])
@@ -1995,8 +2269,10 @@
         # flag set, then the object should be in 'prebuilt_root_objects',
         # and the GCFLAG_VISITED will be reset at the end of the
         # collection.
+        # Objects with GCFLAG_PINNED can't have gcptrs (see pin()), they can be
+        # ignored.
         hdr = self.header(obj)
-        if hdr.tid & (GCFLAG_VISITED | GCFLAG_NO_HEAP_PTRS):
+        if hdr.tid & (GCFLAG_VISITED | GCFLAG_NO_HEAP_PTRS | GCFLAG_PINNED):
             return 0
         #
         # It's the first time.  We set the flag VISITED.  The trick is
@@ -2048,7 +2324,7 @@
         # collection
         if self.header(obj).tid & GCFLAG_HAS_SHADOW:
             shadow = self.nursery_objects_shadows.get(obj)
-            ll_assert(shadow != NULL,
+            ll_assert(shadow != llmemory.NULL,
                       "GCFLAG_HAS_SHADOW but no shadow found")
         else:
             shadow = self._allocate_shadow(obj)
@@ -2233,6 +2509,9 @@
     # ----------
     # Weakrefs
 
+    # XXX (groggi): weakref pointing to pinned object not supported.
+    # XXX (groggi): missing asserts/checks for the missing feature.
+
     # The code relies on the fact that no weakref can be an old object
     # weakly pointing to a young object.  Indeed, weakrefs are immutable
     # so they cannot point to an object that was created after it.
@@ -2255,6 +2534,11 @@
                     (obj + offset).address[0] = self.get_forwarding_address(
                         pointing_to)
                 else:
+                    # If the target is pinned, then we reach this point too.
+                    # It means that a hypothetical RPython interpreter that
+                    # would let you take a weakref to a pinned object (strange
+                    # thing not possible at all in PyPy) might see these
+                    # weakrefs marked as dead too early.
                     (obj + offset).address[0] = llmemory.NULL
                     continue    # no need to remember this weakref any longer
             #
diff --git a/rpython/memory/gc/test/test_object_pinning.py b/rpython/memory/gc/test/test_object_pinning.py
new file mode 100644
--- /dev/null
+++ b/rpython/memory/gc/test/test_object_pinning.py
@@ -0,0 +1,919 @@
+import py
+from rpython.rtyper.lltypesystem import lltype, llmemory, llarena
+from rpython.memory.gc.incminimark import IncrementalMiniMarkGC, WORD
+from test_direct import BaseDirectGCTest
+
+T = lltype.GcForwardReference()
+T.become(lltype.GcStruct('pinning_test_struct2',
+                         ('someInt', lltype.Signed)))
+
+S = lltype.GcForwardReference()
+S.become(lltype.GcStruct('pinning_test_struct1',
+                         ('someInt', lltype.Signed),
+                         ('next', lltype.Ptr(T)),
+                         ('data', lltype.Ptr(T))))
+
+class PinningGCTest(BaseDirectGCTest):
+
+    def setup_method(self, meth):
+        BaseDirectGCTest.setup_method(self, meth)
+        max = getattr(meth, 'max_number_of_pinned_objects', 20)
+        self.gc.max_number_of_pinned_objects = max
+
+    def test_pin_can_move(self):
+        # even a pinned object is considered to be movable. Only the caller
+        # of pin() knows if it is currently movable or not.
+        ptr = self.malloc(T)
+        adr = llmemory.cast_ptr_to_adr(ptr)
+        assert self.gc.can_move(adr)
+        assert self.gc.pin(adr)
+        assert self.gc.can_move(adr)
+
+    def test_pin_twice(self):
+        ptr = self.malloc(T)
+        adr = llmemory.cast_ptr_to_adr(ptr)
+        assert self.gc.pin(adr)
+        assert not self.gc.pin(adr)
+
+    def test_unpin_not_pinned(self):
+        # this test checks a requirement of the unpin() interface
+        ptr = self.malloc(S)
+        py.test.raises(Exception,
+            self.gc.unpin, llmemory.cast_ptr_to_adr(ptr))
+
+    def test__is_pinned(self):
+        ptr = self.malloc(T)
+        adr = llmemory.cast_ptr_to_adr(ptr)
+        assert not self.gc._is_pinned(adr)
+        assert self.gc.pin(adr)
+        assert self.gc._is_pinned(adr)
+        self.gc.unpin(adr)
+        assert not self.gc._is_pinned(adr)
+
+    def test_prebuilt_not_pinnable(self):
+        ptr = lltype.malloc(T, immortal=True)
+        self.consider_constant(ptr)
+        assert not self.gc.pin(llmemory.cast_ptr_to_adr(ptr))
+        self.gc.collect()
+        assert not self.gc.pin(llmemory.cast_ptr_to_adr(ptr))
+
+    # XXX test with multiple mallocs, and only part of them is pinned
+
+    def test_random(self):
+        # scenario: create bunch of objects. randomly pin, unpin, add to
+        # stackroots and remove from stackroots.
+        import random
+
+        for i in xrange(10**3):
+            obj = self.malloc(T)
+            obj.someInt = 100
+            #
+            if random.random() < 0.5:
+                self.stackroots.append(obj)
+                print("+stack")
+            if random.random() < 0.5:
+                self.gc.pin(llmemory.cast_ptr_to_adr(obj))
+                print("+pin")
+            self.gc.debug_gc_step(random.randint(1, 4))
+            for o in self.stackroots[:]:
+                assert o.someInt == 100
+                o_adr = llmemory.cast_ptr_to_adr(o)
+                if random.random() < 0.1 and self.gc._is_pinned(o_adr):
+                    print("-pin")
+                    self.gc.unpin(o_adr)
+                if random.random() < 0.1:
+                    print("-stack")
+                    self.stackroots.remove(o)
+
+
+class TestIncminimark(PinningGCTest):
+    from rpython.memory.gc.incminimark import IncrementalMiniMarkGC as GCClass
+    from rpython.memory.gc.incminimark import STATE_SCANNING
+
+    def test_try_pin_gcref_containing_type(self):
+        # scenario: incminimark's object pinning can't pin objects that may
+        # contain GC pointers
+        obj = self.malloc(S)
+        assert not self.gc.pin(llmemory.cast_ptr_to_adr(obj))
+
+
+    def test_pin_old(self):
+        # scenario: try pinning an old object. This should be not possible and
+        # we want to make sure everything stays as it is.
+        old_ptr = self.malloc(S)
+        old_ptr.someInt = 900
+        self.stackroots.append(old_ptr)
+        assert self.stackroots[0] == old_ptr # test assumption
+        self.gc.collect()
+        old_ptr = self.stackroots[0]
+        # now we try to pin it
+        old_adr = llmemory.cast_ptr_to_adr(old_ptr)
+        assert not self.gc.is_in_nursery(old_adr)
+        assert not self.gc.pin(old_adr)
+        assert self.gc.pinned_objects_in_nursery == 0
+
+    
+    def pin_pin_pinned_object_count(self, collect_func):
+        # scenario: pin two objects that are referenced from stackroots. Check
+        # if the pinned objects count is correct, even after an other collection
+        pinned1_ptr = self.malloc(T)
+        pinned1_ptr.someInt = 100
+        self.stackroots.append(pinned1_ptr)
+        #
+        pinned2_ptr = self.malloc(T)
+        pinned2_ptr.someInt = 200
+        self.stackroots.append(pinned2_ptr)
+        #
+        assert self.gc.pin(llmemory.cast_ptr_to_adr(pinned1_ptr))
+        assert self.gc.pinned_objects_in_nursery == 1
+        assert self.gc.pin(llmemory.cast_ptr_to_adr(pinned2_ptr))
+        assert self.gc.pinned_objects_in_nursery == 2
+        #
+        collect_func()
+        #
+        assert self.gc.pinned_objects_in_nursery == 2
+
+    def test_pin_pin_pinned_object_count_minor_collection(self):
+        self.pin_pin_pinned_object_count(self.gc.minor_collection)
+
+    def test_pin_pin_pinned_object_count_major_collection(self):
+        self.pin_pin_pinned_object_count(self.gc.collect)
+
+
+    def pin_unpin_pinned_object_count(self, collect_func):
+        # scenario: pin an object and check the pinned object count. Unpin it
+        # and check the count again.
+        pinned_ptr = self.malloc(T)
+        pinned_ptr.someInt = 100
+        self.stackroots.append(pinned_ptr)
+        pinned_adr = llmemory.cast_ptr_to_adr(pinned_ptr)
+        #
+        assert self.gc.pinned_objects_in_nursery == 0
+        assert self.gc.pin(pinned_adr)
+        assert self.gc.pinned_objects_in_nursery == 1
+        collect_func()
+        assert self.gc.pinned_objects_in_nursery == 1
+        self.gc.unpin(pinned_adr)
+        assert self.gc.pinned_objects_in_nursery == 0
+        collect_func()
+        assert self.gc.pinned_objects_in_nursery == 0
+
+    def test_pin_unpin_pinned_object_count_minor_collection(self):
+        self.pin_unpin_pinned_object_count(self.gc.minor_collection)
+
+    def test_pin_unpin_pinned_object_count_major_collection(self):
+        self.pin_unpin_pinned_object_count(self.gc.collect)
+
+
+    def pinned_obj_in_stackroot(self, collect_func):
+        # scenario: a pinned object that is part of the stack roots. Check if
+        # it is not moved
+        #
+        ptr = self.malloc(T)
+        ptr.someInt = 100
+        self.stackroots.append(ptr)
+        assert self.stackroots[0] == ptr # validate our assumption
+        
+        adr = llmemory.cast_ptr_to_adr(ptr)
+        assert self.gc.is_in_nursery(adr) # to be sure
+        assert self.gc.pin(adr)
+        #
+        # the object shouldn't move from now on
+        collect_func()
+        #
+        # check if it is still at the same location as expected
+        adr_after_collect = llmemory.cast_ptr_to_adr(self.stackroots[0])
+        assert self.gc.is_in_nursery(adr_after_collect)
+        assert adr == adr_after_collect
+        assert self.gc._is_pinned(adr)
+        assert ptr.someInt == 100
+        assert self.gc.pinned_objects_in_nursery == 1
+
+    def test_pinned_obj_in_stackroot_minor_collection(self):
+        self.pinned_obj_in_stackroot(self.gc.minor_collection)
+
+    def test_pinned_obj_in_stackroot_full_major_collection(self):
+        self.pinned_obj_in_stackroot(self.gc.collect)
+
+    def test_pinned_obj_in_stackroots_stepwise_major_collection(self):
+        # scenario: same as for 'pinned_obj_in_stackroot' with minor change
+        # that we do stepwise major collection and check in each step for
+        # a correct state
+        #
+        ptr = self.malloc(T)
+        ptr.someInt = 100
+        self.stackroots.append(ptr)
+        assert self.stackroots[0] == ptr # validate our assumption
+
+        adr = llmemory.cast_ptr_to_adr(ptr)
+        assert self.gc.is_in_nursery(adr)
+        assert self.gc.pin(adr)
+        #
+        # the object shouldn't move from now on. Do a full round of major
+        # steps and check each time for correct state
+        #
+        # check that we start at the expected point
+        assert self.gc.gc_state == self.STATE_SCANNING
+        done = False
+        while not done:
+            self.gc.debug_gc_step()
+            # check that the pinned object didn't move
+            ptr_after_collection = self.stackroots[0]
+            adr_after_collection = llmemory.cast_ptr_to_adr(ptr_after_collection)
+            assert self.gc.is_in_nursery(adr_after_collection)
+            assert adr == adr_after_collection
+            assert self.gc._is_pinned(adr)
+            assert ptr.someInt == 100
+            assert self.gc.pinned_objects_in_nursery == 1
+            # as the object is referenced from the stackroots, the gc internal
+            # 'old_objects_pointing_to_pinned' should be empty
+            assert not self.gc.old_objects_pointing_to_pinned.non_empty()
+            #
+            # break condition
+            done = self.gc.gc_state == self.STATE_SCANNING
+
+    
+    def pin_unpin_moved_stackroot(self, collect_func):
+        # scenario: test if the pinned object is moved after being unpinned.
+        # the second part of the scenario is the tested one. The first part
+        # is already tests by other tests.
+        ptr = self.malloc(T)
+        ptr.someInt = 100
+        self.stackroots.append(ptr)
+        assert self.stackroots[0] == ptr # validate our assumption
+
+        adr = llmemory.cast_ptr_to_adr(ptr)
+        assert self.gc.pin(adr)
+
+        collect_func()
+        #
+        # from here on the test really starts. previouse logic is already tested
+        #
+        self.gc.unpin(adr)
+        assert not self.gc._is_pinned(adr)
+        assert self.gc.is_in_nursery(adr)
+        #
+        # now we do another collection and the object should be moved out of
+        # the nursery.
+        collect_func()
+        new_adr = llmemory.cast_ptr_to_adr(self.stackroots[0])
+        assert not self.gc.is_in_nursery(new_adr)
+        assert self.stackroots[0].someInt == 100
+        with py.test.raises(RuntimeError) as exinfo:
+            ptr.someInt = 200
+        assert "freed" in str(exinfo.value)
+
+    def test_pin_unpin_moved_stackroot_minor_collection(self):
+        self.pin_unpin_moved_stackroot(self.gc.minor_collection)
+
+    def test_pin_unpin_moved_stackroot_major_collection(self):
+        self.pin_unpin_moved_stackroot(self.gc.collect)
+
+    
+    def pin_referenced_from_old(self, collect_func):
+        # scenario: an old object points to a pinned one. Check if the pinned
+        # object is correctly kept in the nursery and not moved.
+        #
+        # create old object
+        old_ptr = self.malloc(S)
+        old_ptr.someInt = 900
+        self.stackroots.append(old_ptr)
+        assert self.stackroots[0] == old_ptr # validate our assumption
+        collect_func() # make it old: move it out of the nursery
+        old_ptr = self.stackroots[0]
+        assert not self.gc.is_in_nursery(llmemory.cast_ptr_to_adr(old_ptr))
+        #
+        # create young pinned one and let the old one reference the young one
+        pinned_ptr = self.malloc(T)
+        pinned_ptr.someInt = 100
+        self.write(old_ptr, 'next', pinned_ptr)
+        pinned_adr = llmemory.cast_ptr_to_adr(pinned_ptr)
+        assert self.gc.pin(pinned_adr)
+        assert self.gc.is_in_nursery(pinned_adr)
+        assert old_ptr.next.someInt == 100
+        assert self.gc.pinned_objects_in_nursery == 1
+        #
+        # do a collection run and make sure the pinned one didn't move
+        collect_func()
+        assert old_ptr.next.someInt == pinned_ptr.someInt == 100
+        assert llmemory.cast_ptr_to_adr(old_ptr.next) == pinned_adr
+        assert self.gc.is_in_nursery(pinned_adr)
+        
+    def test_pin_referenced_from_old_minor_collection(self):
+        self.pin_referenced_from_old(self.gc.minor_collection)
+
+    def test_pin_referenced_from_old_major_collection(self):
+        self.pin_referenced_from_old(self.gc.collect)
+
+    def test_pin_referenced_from_old_stepwise_major_collection(self):
+        # scenario: same as in 'pin_referenced_from_old'. However,
+        # this time we do a major collection step by step and check
+        # between steps that the states are as expected.
+        #
+        # create old object
+        old_ptr = self.malloc(S)
+        old_ptr.someInt = 900
+        self.stackroots.append(old_ptr)
+        assert self.stackroots[0] == old_ptr # validate our assumption
+        self.gc.minor_collection() # make it old: move it out of the nursery
+        old_ptr = self.stackroots[0]
+        old_adr = llmemory.cast_ptr_to_adr(old_ptr)
+        assert not self.gc.is_in_nursery(old_adr)
+        #
+        # create young pinned one and let the old one reference the young one
+        pinned_ptr = self.malloc(T)
+        pinned_ptr.someInt = 100
+        self.write(old_ptr, 'next', pinned_ptr)
+        pinned_adr = llmemory.cast_ptr_to_adr(pinned_ptr)
+        assert self.gc.pin(pinned_adr)
+        assert self.gc.is_in_nursery(pinned_adr)
+        assert old_ptr.next.someInt == 100
+        assert self.gc.pinned_objects_in_nursery == 1
+        #
+        # stepwise major collection with validation between steps
+        # check that we start at the expected point
+        assert self.gc.gc_state == self.STATE_SCANNING
+        done = False
+        while not done:
+            self.gc.debug_gc_step()
+            #
+            # make sure pinned object didn't move
+            assert old_ptr.next.someInt == pinned_ptr.someInt == 100
+            assert llmemory.cast_ptr_to_adr(old_ptr.next) == pinned_adr
+            assert self.gc.is_in_nursery(pinned_adr)
+            assert self.gc.pinned_objects_in_nursery == 1
+            #
+            # validate that the old object is part of the internal list
+            # 'old_objects_pointing_to_pinned' as expected.
+            should_be_old_adr = self.gc.old_objects_pointing_to_pinned.pop()
+            assert should_be_old_adr == old_adr
+            self.gc.old_objects_pointing_to_pinned.append(should_be_old_adr)
+            #
+            # break condition
+            done = self.gc.gc_state == self.STATE_SCANNING
+
+    
+    def pin_referenced_from_old_remove_ref(self, collect_func):
+        # scenario: an old object points to a pinned one. We remove the
+        # reference from the old one. So nothing points to the pinned object.
+        # After this the pinned object should be collected (it's dead).
+        #
+        # Create the objects and get them to our initial state (this is not
+        # tested here, should be already tested by other tests)
+        old_ptr = self.malloc(S)
+        old_ptr.someInt = 900
+        self.stackroots.append(old_ptr)
+        assert self.stackroots[0] == old_ptr # check assumption
+        collect_func() # make it old
+        old_ptr = self.stackroots[0]
+        #
+        pinned_ptr = self.malloc(T)
+        pinned_ptr.someInt = 100
+        self.write(old_ptr, 'next', pinned_ptr)
+        pinned_adr = llmemory.cast_ptr_to_adr(pinned_ptr)
+        assert self.gc.pin(pinned_adr)
+        #
+        collect_func()
+        # from here on we have our initial state for this test.
+        #
+        # first check some basic assumptions.
+        assert self.gc.is_in_nursery(pinned_adr)
+        assert self.gc._is_pinned(pinned_adr)
+        # remove the reference
+        self.write(old_ptr, 'next', lltype.nullptr(T))
+        # from now on the pinned object is dead. Do a collection and make sure
+        # old object still there and the pinned one is gone.
+        collect_func()
+        assert self.stackroots[0].someInt == 900
+        assert not self.gc.old_objects_pointing_to_pinned.non_empty()
+        with py.test.raises(RuntimeError) as exinfo:
+            pinned_ptr.someInt = 200
+        assert "freed" in str(exinfo.value)
+
+    def test_pin_referenced_from_old_remove_ref_minor_collection(self):
+        self.pin_referenced_from_old_remove_ref(self.gc.minor_collection)
+
+    def test_pin_referenced_from_old_remove_ref_major_collection(self):
+        self.pin_referenced_from_old_remove_ref(self.gc.collect)
+
+
+    def pin_referenced_from_old_remove_old(self, collect_func):
+        # scenario: an old object referenced a pinned object. After removing
+        # the stackroot reference to the old object, bot objects (old and pinned)
+        # must be collected.
+        # This test is important as we expect not reachable pinned objects to
+        # be collected. At the same time we have an internal list of objects
+        # pointing to pinned ones and we must make sure that because of it the
+        # old/pinned object survive.
+        #
+        # create the objects and get them to the initial state for this test.
+        # Everything on the way to the initial state should be covered by
+        # other tests.
+        old_ptr = self.malloc(S)
+        old_ptr.someInt = 900
+        self.stackroots.append(old_ptr)
+        collect_func()
+        old_ptr = self.stackroots[0]
+        #
+        pinned_ptr = self.malloc(T)
+        pinned_ptr.someInt = 100
+        self.write(old_ptr, 'next', pinned_ptr)
+        assert self.gc.pin(llmemory.cast_ptr_to_adr(pinned_ptr))
+        #
+        collect_func()
+        #
+        # now we have our initial state: old object referenced from stackroots.
+        # Old object referencing a young pinned one. Next step is to make some
+        # basic checks that we got the expected state.
+        assert not self.gc.is_in_nursery(llmemory.cast_ptr_to_adr(old_ptr))
+        assert self.gc.is_in_nursery(llmemory.cast_ptr_to_adr(pinned_ptr))
+        assert pinned_ptr == old_ptr.next
+        #
+        # now we remove the old object from the stackroots...
+        self.stackroots.remove(old_ptr)
+        # ... and do a major collection (otherwise the old object wouldn't be
+        # gone).
+        self.gc.collect()
+        # check that both objects are gone
+        assert not self.gc.old_objects_pointing_to_pinned.non_empty()
+        with py.test.raises(RuntimeError) as exinfo_old:
+            old_ptr.someInt = 800
+        assert "freed" in str(exinfo_old.value)
+        #
+        with py.test.raises(RuntimeError) as exinfo_pinned:
+            pinned_ptr.someInt = 200
+        assert "freed" in str(exinfo_pinned.value)
+
+    def test_pin_referenced_from_old_remove_old_minor_collection(self):
+        self.pin_referenced_from_old_remove_old(self.gc.minor_collection)
+
+    def test_pin_referenced_from_old_remove_old_major_collection(self):
+        self.pin_referenced_from_old_remove_old(self.gc.collect)
+
+
+    def pin_referenced_from_young_in_stackroots(self, collect_func):
+        # scenario: a young object is referenced from the stackroots. This
+        # young object points to a young pinned object. We check if everything
+        # behaves as expected after a collection: the young object is moved out
+        # of the nursery while the pinned one stays where it is.
+        #
+        root_ptr = self.malloc(S)
+        root_ptr.someInt = 900
+        self.stackroots.append(root_ptr)
+        assert self.stackroots[0] == root_ptr # validate assumption
+        #
+        pinned_ptr = self.malloc(T)
+        pinned_ptr.someInt = 100
+        self.write(root_ptr, 'next', pinned_ptr)
+        pinned_adr = llmemory.cast_ptr_to_adr(pinned_ptr)
+        assert self.gc.pin(pinned_adr)
+        # check both are in nursery
+        assert self.gc.is_in_nursery(llmemory.cast_ptr_to_adr(root_ptr))
+        assert self.gc.is_in_nursery(pinned_adr)
+        #
+        # no old object yet pointing to a pinned one
+        assert not self.gc.old_objects_pointing_to_pinned.non_empty()
+        #
+        # now we do a collection and check if the result is as expected
+        collect_func()
+        #
+        # check if objects are where we expect them
+        root_ptr = self.stackroots[0]
+        assert not self.gc.is_in_nursery(llmemory.cast_ptr_to_adr(root_ptr))
+        assert self.gc.is_in_nursery(pinned_adr)
+        # and as 'root_ptr' object is now old, it should be tracked specially
+        should_be_root_adr = self.gc.old_objects_pointing_to_pinned.pop()
+        assert should_be_root_adr == llmemory.cast_ptr_to_adr(root_ptr)
+        self.gc.old_objects_pointing_to_pinned.append(should_be_root_adr)
+        # check that old object still points to the pinned one as expected
+        assert root_ptr.next == pinned_ptr
+
+    def test_pin_referenced_from_young_in_stackroots_minor_collection(self):
+        self.pin_referenced_from_young_in_stackroots(self.gc.minor_collection)
+
+    def test_pin_referenced_from_young_in_stackroots_major_collection(self):
+        self.pin_referenced_from_young_in_stackroots(self.gc.collect)
+
+
+    def pin_referenced_from_prebuilt(self, collect_func):
+        # scenario: a prebuilt object points to a pinned object. Check if the
+        # pinned object doesn't move and is still accessible.
+        #
+        prebuilt_ptr = lltype.malloc(S, immortal=True)
+        prebuilt_ptr.someInt = 900
+        self.consider_constant(prebuilt_ptr)
+        prebuilt_adr = llmemory.cast_ptr_to_adr(prebuilt_ptr)
+        collect_func()
+        #        
+        pinned_ptr = self.malloc(T)
+        pinned_ptr.someInt = 100
+        self.write(prebuilt_ptr, 'next', pinned_ptr)
+        pinned_adr = llmemory.cast_ptr_to_adr(pinned_ptr)
+        assert self.gc.pin(pinned_adr)
+        #
+        # check if everything is as expected
+        assert not self.gc.is_in_nursery(prebuilt_adr)
+        assert self.gc.is_in_nursery(pinned_adr)
+        assert pinned_ptr == prebuilt_ptr.next
+        assert pinned_ptr.someInt == 100
+        #
+        # do a collection and check again
+        collect_func()
+        assert self.gc.is_in_nursery(pinned_adr)
+        assert pinned_ptr == prebuilt_ptr.next
+        assert pinned_ptr.someInt == 100
+
+    def test_pin_referenced_from_prebuilt_minor_collection(self):
+        self.pin_referenced_from_prebuilt(self.gc.minor_collection)
+
+    def test_pin_referenced_from_prebuilt_major_collection(self):
+        self.pin_referenced_from_prebuilt(self.gc.collect)
+


More information about the pypy-commit mailing list