[pypy-commit] pypy py3.3: hg merge py3k

mjacob pypy.commits at gmail.com
Mon Feb 22 17:36:56 EST 2016


Author: Manuel Jacob <me at manueljacob.de>
Branch: py3.3
Changeset: r82413:6107f05ecc93
Date: 2016-02-22 23:37 +0100
http://bitbucket.org/pypy/pypy/changeset/6107f05ecc93/

Log:	hg merge py3k

diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst
--- a/pypy/doc/whatsnew-head.rst
+++ b/pypy/doc/whatsnew-head.rst
@@ -128,6 +128,7 @@
 
 Fix SSL tests by importing cpython's patch
 
+
 .. branch: remove-getfield-pure
 
 Remove pure variants of ``getfield_gc_*`` operations from the JIT. Relevant
@@ -163,3 +164,10 @@
 .. branch: windows-vmprof-support
 
 vmprof should work on Windows.
+
+
+.. branch: reorder-map-attributes
+
+When creating instances and adding attributes in several different orders
+depending on some condition, the JIT would create too much code. This is now
+fixed.
\ No newline at end of file
diff --git a/pypy/module/imp/test/test_import.py b/pypy/module/imp/test/test_import.py
--- a/pypy/module/imp/test/test_import.py
+++ b/pypy/module/imp/test/test_import.py
@@ -992,12 +992,12 @@
         py.test.skip("unresolved issues with win32 shell quoting rules")
     from pypy.interpreter.test.test_zpy import pypypath 
     extrapath = udir.ensure("pythonpath", dir=1) 
-    extrapath.join("urllib.py").write("print(42)\n")
+    extrapath.join("sched.py").write("print(42)\n")
     old = os.environ.get('PYTHONPATH', None)
     oldlang = os.environ.pop('LANG', None)
     try:
         os.environ['PYTHONPATH'] = str(extrapath)
-        output = py.process.cmdexec('''"%s" "%s" -c "import urllib"''' %
+        output = py.process.cmdexec('''"%s" "%s" -c "import sched"''' %
                                  (sys.executable, pypypath))
         assert output.strip() == '42'
     finally:
diff --git a/pypy/module/pypyjit/test_pypy_c/test_string.py b/pypy/module/pypyjit/test_pypy_c/test_string.py
--- a/pypy/module/pypyjit/test_pypy_c/test_string.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_string.py
@@ -28,7 +28,6 @@
             guard_true(i14, descr=...)
             guard_not_invalidated(descr=...)
             i16 = int_eq(i6, %d)
-            guard_false(i16, descr=...)
             i15 = int_mod(i6, i10)
             i17 = int_rshift(i15, %d)
             i18 = int_and(i10, i17)
@@ -68,7 +67,6 @@
             guard_true(i11, descr=...)
             guard_not_invalidated(descr=...)
             i13 = int_eq(i6, %d)         # value provided below
-            guard_false(i13, descr=...)
             i15 = int_mod(i6, 10)
             i17 = int_rshift(i15, %d)    # value provided below
             i18 = int_and(10, i17)
@@ -144,43 +142,6 @@
             jump(..., descr=...)
         """)
 
-    def test_getattr_promote(self):
-        def main(n):
-            class A(object):
-                def meth_a(self):
-                    return 1
-                def meth_b(self):
-                    return 2
-            a = A()
-
-            l = ['a', 'b']
-            s = 0
-            for i in range(n):
-                name = 'meth_' + l[i & 1]
-                meth = getattr(a, name) # ID: getattr
-                s += meth()
-            return s
-
-        log = self.run(main, [1000])
-        assert log.result == main(1000)
-        loops = log.loops_by_filename(self.filepath)
-        assert len(loops) == 1
-        for loop in loops:
-            assert loop.match_by_id('getattr','''
-            guard_not_invalidated?
-            i32 = strlen(p31)
-            i34 = int_add(5, i32)
-            p35 = newstr(i34)
-            strsetitem(p35, 0, 109)
-            strsetitem(p35, 1, 101)
-            strsetitem(p35, 2, 116)
-            strsetitem(p35, 3, 104)
-            strsetitem(p35, 4, 95)
-            copystrcontent(p31, p35, 0, 5, i32)
-            i49 = call_i(ConstClass(_ll_2_str_eq_nonnull__rpy_stringPtr_rpy_stringPtr), p35, ConstPtr(ptr48), descr=<Calli [48] rr EF=0 OS=28>)
-            guard_value(i49, 1, descr=...)
-            ''')
-
     def test_remove_duplicate_method_calls(self):
         def main(n):
             lst = []
diff --git a/pypy/objspace/std/mapdict.py b/pypy/objspace/std/mapdict.py
--- a/pypy/objspace/std/mapdict.py
+++ b/pypy/objspace/std/mapdict.py
@@ -1,4 +1,4 @@
-import weakref
+import weakref, sys
 
 from rpython.rlib import jit, objectmodel, debug, rerased
 from rpython.rlib.rarithmetic import intmask, r_uint
@@ -12,6 +12,11 @@
 from pypy.objspace.std.typeobject import MutableCell
 
 
+erase_item, unerase_item = rerased.new_erasing_pair("mapdict storage item")
+erase_map,  unerase_map = rerased.new_erasing_pair("map")
+erase_list, unerase_list = rerased.new_erasing_pair("mapdict storage list")
+
+
 # ____________________________________________________________
 # attribute shapes
 
@@ -20,6 +25,7 @@
 # note: we use "x * NUM_DIGITS_POW2" instead of "x << NUM_DIGITS" because
 # we want to propagate knowledge that the result cannot be negative
 
+
 class AbstractAttribute(object):
     _immutable_fields_ = ['terminator']
     cache_attrs = None
@@ -151,29 +157,124 @@
             cache[name, index] = attr
         return attr
 
+    @jit.elidable
+    def _get_cache_attr(self, name, index):
+        key = name, index
+        # this method is not actually elidable, but it's fine anyway
+        if self.cache_attrs is not None:
+            return self.cache_attrs.get(key, None)
+        return None
+
+    def add_attr(self, obj, name, index, w_value):
+        self._reorder_and_add(obj, name, index, w_value)
+        if not jit.we_are_jitted():
+            oldattr = self
+            attr = obj._get_mapdict_map()
+            size_est = (oldattr._size_estimate + attr.size_estimate()
+                                               - oldattr.size_estimate())
+            assert size_est >= (oldattr.length() * NUM_DIGITS_POW2)
+            oldattr._size_estimate = size_est
+
+    def _add_attr_without_reordering(self, obj, name, index, w_value):
+        attr = self._get_new_attr(name, index)
+        attr._switch_map_and_write_storage(obj, w_value)
+
+    @jit.unroll_safe
+    def _switch_map_and_write_storage(self, obj, w_value):
+        if self.length() > obj._mapdict_storage_length():
+            # note that self.size_estimate() is always at least self.length()
+            new_storage = [None] * self.size_estimate()
+            for i in range(obj._mapdict_storage_length()):
+                new_storage[i] = obj._mapdict_read_storage(i)
+            obj._set_mapdict_storage_and_map(new_storage, self)
+
+        # the order is important here: first change the map, then the storage,
+        # for the benefit of the special subclasses
+        obj._set_mapdict_map(self)
+        obj._mapdict_write_storage(self.storageindex, w_value)
+
+
+    @jit.elidable
+    def _find_branch_to_move_into(self, name, index):
+        # walk up the map chain to find an ancestor with lower order that
+        # already has the current name as a child inserted
+        current_order = sys.maxint
+        number_to_readd = 0
+        current = self
+        key = (name, index)
+        while True:
+            attr = None
+            if current.cache_attrs is not None:
+                attr = current.cache_attrs.get(key, None)
+            if attr is None or attr.order > current_order:
+                # we reached the top, so we didn't find it anywhere,
+                # just add it to the top attribute
+                if not isinstance(current, PlainAttribute):
+                    return 0, self._get_new_attr(name, index)
+
+            else:
+                return number_to_readd, attr
+            # if not found try parent
+            number_to_readd += 1
+            current_order = current.order
+            current = current.back
+
     @jit.look_inside_iff(lambda self, obj, name, index, w_value:
             jit.isconstant(self) and
             jit.isconstant(name) and
             jit.isconstant(index))
-    def add_attr(self, obj, name, index, w_value):
-        attr = self._get_new_attr(name, index)
-        oldattr = obj._get_mapdict_map()
-        if not jit.we_are_jitted():
-            size_est = (oldattr._size_estimate + attr.size_estimate()
-                                               - oldattr.size_estimate())
-            assert size_est >= (oldattr.length() * NUM_DIGITS_POW2)
-            oldattr._size_estimate = size_est
-        if attr.length() > obj._mapdict_storage_length():
-            # note that attr.size_estimate() is always at least attr.length()
-            new_storage = [None] * attr.size_estimate()
-            for i in range(obj._mapdict_storage_length()):
-                new_storage[i] = obj._mapdict_read_storage(i)
-            obj._set_mapdict_storage_and_map(new_storage, attr)
+    def _reorder_and_add(self, obj, name, index, w_value):
+        # the idea is as follows: the subtrees of any map are ordered by
+        # insertion.  the invariant is that subtrees that are inserted later
+        # must not contain the name of the attribute of any earlier inserted
+        # attribute anywhere
+        #                              m______
+        #         inserted first      / \ ... \   further attributes
+        #           attrname a      0/  1\    n\
+        #                           m  a must not appear here anywhere
+        #
+        # when inserting a new attribute in an object we check whether any
+        # parent of lower order has seen that attribute yet. if yes, we follow
+        # that branch. if not, we normally append that attribute. When we
+        # follow a prior branch, we necessarily remove some attributes to be
+        # able to do that. They need to be re-added, which has to follow the
+        # reordering procedure recusively.
 
-        # the order is important here: first change the map, then the storage,
-        # for the benefit of the special subclasses
-        obj._set_mapdict_map(attr)
-        obj._mapdict_write_storage(attr.storageindex, w_value)
+        # we store the to-be-readded attribute in the stack, with the map and
+        # the value paired up those are lazily initialized to a list large
+        # enough to store all current attributes
+        stack = None
+        stack_index = 0
+        while True:
+            current = self
+            number_to_readd = 0
+            number_to_readd, attr = self._find_branch_to_move_into(name, index)
+            # we found the attributes further up, need to save the
+            # previous values of the attributes we passed
+            if number_to_readd:
+                if stack is None:
+                    stack = [erase_map(None)] * (self.length() * 2)
+                current = self
+                for i in range(number_to_readd):
+                    assert isinstance(current, PlainAttribute)
+                    w_self_value = obj._mapdict_read_storage(
+                            current.storageindex)
+                    stack[stack_index] = erase_map(current)
+                    stack[stack_index + 1] = erase_item(w_self_value)
+                    stack_index += 2
+                    current = current.back
+            attr._switch_map_and_write_storage(obj, w_value)
+
+            if not stack_index:
+                return
+
+            # readd the current top of the stack
+            stack_index -= 2
+            next_map = unerase_map(stack[stack_index])
+            w_value = unerase_item(stack[stack_index + 1])
+            name = next_map.name
+            index = next_map.index
+            self = obj._get_mapdict_map()
 
     def materialize_r_dict(self, space, obj, dict_w):
         raise NotImplementedError("abstract base class")
@@ -279,7 +380,7 @@
         return Terminator.set_terminator(self, obj, terminator)
 
 class PlainAttribute(AbstractAttribute):
-    _immutable_fields_ = ['name', 'index', 'storageindex', 'back', 'ever_mutated?']
+    _immutable_fields_ = ['name', 'index', 'storageindex', 'back', 'ever_mutated?', 'order']
 
     def __init__(self, name, index, back):
         AbstractAttribute.__init__(self, back.space, back.terminator)
@@ -289,6 +390,7 @@
         self.back = back
         self._size_estimate = self.length() * NUM_DIGITS_POW2
         self.ever_mutated = False
+        self.order = len(back.cache_attrs) if back.cache_attrs else 0
 
     def _copy_attr(self, obj, new_obj):
         w_value = self.read(obj, self.name, self.index)
@@ -540,9 +642,6 @@
 memo_get_subclass_of_correct_size._annspecialcase_ = "specialize:memo"
 _subclass_cache = {}
 
-erase_item, unerase_item = rerased.new_erasing_pair("mapdict storage item")
-erase_list, unerase_list = rerased.new_erasing_pair("mapdict storage list")
-
 def _make_subclass_size_n(supercls, n):
     from rpython.rlib import unroll
     rangen = unroll.unrolling_iterable(range(n))
diff --git a/pypy/objspace/std/test/test_mapdict.py b/pypy/objspace/std/test/test_mapdict.py
--- a/pypy/objspace/std/test/test_mapdict.py
+++ b/pypy/objspace/std/test/test_mapdict.py
@@ -107,6 +107,153 @@
     assert obj2.getdictvalue(space, "b") == 60
     assert obj2.map is obj.map
 
+def test_insert_different_orders():
+    cls = Class()
+    obj = cls.instantiate()
+    obj.setdictvalue(space, "a", 10)
+    obj.setdictvalue(space, "b", 20)
+
+    obj2 = cls.instantiate()
+    obj2.setdictvalue(space, "b", 30)
+    obj2.setdictvalue(space, "a", 40)
+
+    assert obj.map is obj2.map
+
+def test_insert_different_orders_2():
+    cls = Class()
+    obj = cls.instantiate()
+    obj2 = cls.instantiate()
+
+    obj.setdictvalue(space, "a", 10)
+
+    obj2.setdictvalue(space, "b", 20)
+    obj2.setdictvalue(space, "a", 30)
+
+    obj.setdictvalue(space, "b", 40)
+    assert obj.map is obj2.map
+
+def test_insert_different_orders_3():
+    cls = Class()
+    obj = cls.instantiate()
+    obj2 = cls.instantiate()
+    obj3 = cls.instantiate()
+    obj4 = cls.instantiate()
+    obj5 = cls.instantiate()
+    obj6 = cls.instantiate()
+
+    obj.setdictvalue(space, "a", 10)
+    obj.setdictvalue(space, "b", 20)
+    obj.setdictvalue(space, "c", 30)
+
+    obj2.setdictvalue(space, "a", 30)
+    obj2.setdictvalue(space, "c", 40)
+    obj2.setdictvalue(space, "b", 50)
+
+    obj3.setdictvalue(space, "c", 30)
+    obj3.setdictvalue(space, "a", 40)
+    obj3.setdictvalue(space, "b", 50)
+
+    obj4.setdictvalue(space, "c", 30)
+    obj4.setdictvalue(space, "b", 40)
+    obj4.setdictvalue(space, "a", 50)
+
+    obj5.setdictvalue(space, "b", 30)
+    obj5.setdictvalue(space, "a", 40)
+    obj5.setdictvalue(space, "c", 50)
+
+    obj6.setdictvalue(space, "b", 30)
+    obj6.setdictvalue(space, "c", 40)
+    obj6.setdictvalue(space, "a", 50)
+
+    assert obj.map is obj2.map
+    assert obj.map is obj3.map
+    assert obj.map is obj4.map
+    assert obj.map is obj5.map
+    assert obj.map is obj6.map
+
+
+def test_insert_different_orders_4():
+    cls = Class()
+    obj = cls.instantiate()
+    obj2 = cls.instantiate()
+
+    obj.setdictvalue(space, "a", 10)
+    obj.setdictvalue(space, "b", 20)
+    obj.setdictvalue(space, "c", 30)
+    obj.setdictvalue(space, "d", 40)
+
+    obj2.setdictvalue(space, "d", 50)
+    obj2.setdictvalue(space, "c", 50)
+    obj2.setdictvalue(space, "b", 50)
+    obj2.setdictvalue(space, "a", 50)
+
+    assert obj.map is obj2.map
+
+def test_insert_different_orders_5():
+    cls = Class()
+    obj = cls.instantiate()
+    obj2 = cls.instantiate()
+
+    obj.setdictvalue(space, "a", 10)
+    obj.setdictvalue(space, "b", 20)
+    obj.setdictvalue(space, "c", 30)
+    obj.setdictvalue(space, "d", 40)
+
+    obj2.setdictvalue(space, "d", 50)
+    obj2.setdictvalue(space, "c", 50)
+    obj2.setdictvalue(space, "b", 50)
+    obj2.setdictvalue(space, "a", 50)
+
+    obj3 = cls.instantiate()
+    obj3.setdictvalue(space, "d", 50)
+    obj3.setdictvalue(space, "c", 50)
+    obj3.setdictvalue(space, "b", 50)
+    obj3.setdictvalue(space, "a", 50)
+
+    assert obj.map is obj3.map
+
+
+def test_bug_stack_overflow_insert_attributes():
+    cls = Class()
+    obj = cls.instantiate()
+
+    for i in range(1000):
+        obj.setdictvalue(space, str(i), i)
+
+
+def test_insert_different_orders_perm():
+    from itertools import permutations
+    cls = Class()
+    seen_maps = {}
+    for preexisting in ['', 'x', 'xy']:
+        for i, attributes in enumerate(permutations("abcdef")):
+            obj = cls.instantiate()
+            for i, attr in enumerate(preexisting):
+                obj.setdictvalue(space, attr, i*1000)
+            key = preexisting
+            for j, attr in enumerate(attributes):
+                obj.setdictvalue(space, attr, i*10+j)
+                key = "".join(sorted(key+attr))
+                if key in seen_maps:
+                    assert obj.map is seen_maps[key]
+                else:
+                    seen_maps[key] = obj.map
+
+    print len(seen_maps)
+
+
+def test_bug_infinite_loop():
+    cls = Class()
+    obj = cls.instantiate()
+    obj.setdictvalue(space, "e", 1)
+    obj2 = cls.instantiate()
+    obj2.setdictvalue(space, "f", 2)
+    obj3 = cls.instantiate()
+    obj3.setdictvalue(space, "a", 3)
+    obj3.setdictvalue(space, "e", 4)
+    obj3.setdictvalue(space, "f", 5)
+
+
 def test_attr_immutability(monkeypatch):
     cls = Class()
     obj = cls.instantiate()
@@ -359,9 +506,15 @@
 class TestMapDictImplementation(BaseTestRDictImplementation):
     StrategyClass = MapDictStrategy
     get_impl = get_impl
+    def test_setdefault_fast(self):
+        # mapdict can't pass this, which is fine
+        pass
 class TestDevolvedMapDictImplementation(BaseTestDevolvedDictImplementation):
     get_impl = get_impl
     StrategyClass = MapDictStrategy
+    def test_setdefault_fast(self):
+        # mapdict can't pass this, which is fine
+        pass
 
 # ___________________________________________________________
 # tests that check the obj interface after the dict has devolved
@@ -1143,3 +1296,7 @@
 class TestMapDictImplementationUsingnewdict(BaseTestRDictImplementation):
     StrategyClass = MapDictStrategy
     # NB: the get_impl method is not overwritten here, as opposed to above
+
+    def test_setdefault_fast(self):
+        # mapdict can't pass this, which is fine
+        pass
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+# hypothesis is used for test generation on untranslated jit tests
+hypothesis
+
diff --git a/rpython/jit/metainterp/resume.py b/rpython/jit/metainterp/resume.py
--- a/rpython/jit/metainterp/resume.py
+++ b/rpython/jit/metainterp/resume.py
@@ -27,6 +27,13 @@
         self.prev = prev
         self.boxes = boxes
 
+class TopSnapshot(Snapshot):
+    __slots__ = ('vable_boxes',)
+
+    def __init__(self, prev, boxes, vable_boxes):
+        Snapshot.__init__(self, prev, boxes)
+        self.vable_boxes = vable_boxes
+
 def combine_uint(index1, index2):
     assert 0 <= index1 < 65536
     assert 0 <= index2 < 65536
@@ -127,9 +134,11 @@
                        snapshot_storage):
     n = len(framestack) - 1
     if virtualizable_boxes is not None:
-        boxes = virtualref_boxes + virtualizable_boxes
+        virtualizable_boxes = ([virtualizable_boxes[-1]] +
+                                virtualizable_boxes[:-1])
     else:
-        boxes = virtualref_boxes[:]
+        virtualizable_boxes = []
+    virtualref_boxes = virtualref_boxes[:]
     if n >= 0:
         top = framestack[n]
         _ensure_parent_resumedata(framestack, n)
@@ -138,11 +147,12 @@
         snapshot_storage.rd_frame_info_list = frame_info_list
         snapshot = Snapshot(top.parent_resumedata_snapshot,
                             top.get_list_of_active_boxes(False))
-        snapshot = Snapshot(snapshot, boxes)
+        snapshot = TopSnapshot(snapshot, virtualref_boxes, virtualizable_boxes)
         snapshot_storage.rd_snapshot = snapshot
     else:
         snapshot_storage.rd_frame_info_list = None
-        snapshot_storage.rd_snapshot = Snapshot(None, boxes)
+        snapshot_storage.rd_snapshot = TopSnapshot(None, virtualref_boxes,
+                                                   virtualizable_boxes)
 
 PENDINGFIELDSTRUCT = lltype.Struct('PendingField',
                                    ('lldescr', OBJECTPTR),
@@ -200,10 +210,12 @@
         self.v = 0
 
     def count_boxes(self, lst):
-        c = 0
+        snapshot = lst[0]
+        assert isinstance(snapshot, TopSnapshot)
+        c = len(snapshot.vable_boxes)
         for snapshot in lst:
             c += len(snapshot.boxes)
-        c += 2 * (len(lst) - 1)
+        c += 2 * (len(lst) - 1) + 1 + 1
         return c
 
     def append(self, item):
@@ -294,13 +306,11 @@
             state.append(tagged)
         state.n = n
         state.v = v
-        state.position -= length + 2
 
-    def number(self, optimizer, snapshot, frameinfo):
+    def number(self, optimizer, topsnapshot, frameinfo):
         # flatten the list
-        vref_snapshot = snapshot
-        cur = snapshot.prev
-        snapshot_list = [vref_snapshot]
+        cur = topsnapshot.prev
+        snapshot_list = [topsnapshot]
         framestack_list = []
         while cur:
             framestack_list.append(frameinfo)
@@ -311,19 +321,30 @@
 
         # we want to number snapshots starting from the back, but ending
         # with a forward list
-        for i in range(len(snapshot_list) - 1, -1, -1):
-            state.position -= len(snapshot_list[i].boxes)
-            if i != 0:
-                frameinfo = framestack_list[i - 1]
-                jitcode_pos, pc = unpack_uint(frameinfo.packed_jitcode_pc)
-                state.position -= 2
-                state.append(rffi.cast(rffi.SHORT, jitcode_pos))
-                state.append(rffi.cast(rffi.SHORT, pc))
+        for i in range(len(snapshot_list) - 1, 0, -1):
+            state.position -= len(snapshot_list[i].boxes) + 2
+            frameinfo = framestack_list[i - 1]
+            jitcode_pos, pc = unpack_uint(frameinfo.packed_jitcode_pc)
+            state.append(rffi.cast(rffi.SHORT, jitcode_pos))
+            state.append(rffi.cast(rffi.SHORT, pc))
             self._number_boxes(snapshot_list[i].boxes, optimizer, state)
+            state.position -= len(snapshot_list[i].boxes) + 2
 
-        numb = resumecode.create_numbering(state.current,
-                                           len(vref_snapshot.boxes))
+        assert isinstance(topsnapshot, TopSnapshot)
+        special_boxes_size = (1 + len(topsnapshot.vable_boxes) +
+                              1 + len(topsnapshot.boxes))
+        assert state.position == special_boxes_size
 
+        state.position = 0
+        state.append(rffi.cast(rffi.SHORT, len(topsnapshot.vable_boxes)))
+        self._number_boxes(topsnapshot.vable_boxes, optimizer, state)
+        n = len(topsnapshot.boxes)
+        assert not (n & 1)
+        state.append(rffi.cast(rffi.SHORT, n >> 1))
+        self._number_boxes(topsnapshot.boxes, optimizer, state)
+        assert state.position == special_boxes_size
+
+        numb = resumecode.create_numbering(state.current)
         return numb, state.liveboxes, state.v
         
     def forget_numberings(self):
@@ -1113,48 +1134,42 @@
         self.boxes_f = boxes_f
         self._prepare_next_section(info)
 
-    def consume_virtualizable_boxes(self, vinfo):
+    def consume_virtualizable_boxes(self, vinfo, index):
         # we have to ignore the initial part of 'nums' (containing vrefs),
         # find the virtualizable from nums[-1], and use it to know how many
         # boxes of which type we have to return.  This does not write
         # anything into the virtualizable.
         numb = self.numb
-        first_snapshot_size = rffi.cast(lltype.Signed, numb.first_snapshot_size)
-        item, _ = resumecode.numb_next_item(numb, first_snapshot_size - 1)
+        item, index = resumecode.numb_next_item(numb, index)
         virtualizablebox = self.decode_ref(item)
-        index = first_snapshot_size - vinfo.get_total_size(virtualizablebox.getref_base()) - 1
         virtualizable = vinfo.unwrap_virtualizable_box(virtualizablebox)
         return vinfo.load_list_of_boxes(virtualizable, self, virtualizablebox,
             numb, index)
 
-    def consume_virtualref_boxes(self, end):
+    def consume_virtualref_boxes(self, index):
         # Returns a list of boxes, assumed to be all BoxPtrs.
         # We leave up to the caller to call vrefinfo.continue_tracing().
-        assert (end & 1) == 0
+        size, index = resumecode.numb_next_item(self.numb, index)
+        if size == 0:
+            return [], index
         lst = []
-        self.cur_index = 0
-        for i in range(end):
-            item, self.cur_index = resumecode.numb_next_item(self.numb,
-                self.cur_index)
+        for i in range(size * 2):
+            item, index = resumecode.numb_next_item(self.numb, index)
             lst.append(self.decode_ref(item))
-        return lst
+        return lst, index
 
     def consume_vref_and_vable_boxes(self, vinfo, ginfo):
-        first_snapshot_size = rffi.cast(lltype.Signed,
-                                        self.numb.first_snapshot_size)
+        vable_size, index = resumecode.numb_next_item(self.numb, 0)
         if vinfo is not None:
-            virtualizable_boxes = self.consume_virtualizable_boxes(vinfo)
-            end = first_snapshot_size - len(virtualizable_boxes)
+            virtualizable_boxes, index = self.consume_virtualizable_boxes(vinfo,
+                                                                          index)
         elif ginfo is not None:
-            item, self.cur_index = resumecode.numb_next_item(self.numb,
-                first_snapshot_size - 1)
+            item, index = resumecode.numb_next_item(self.numb, index)
             virtualizable_boxes = [self.decode_ref(item)]
-            end = first_snapshot_size - 1
         else:
-            end = first_snapshot_size
             virtualizable_boxes = None
-        virtualref_boxes = self.consume_virtualref_boxes(end)
-        self.cur_index = rffi.cast(lltype.Signed, self.numb.first_snapshot_size)
+        virtualref_boxes, index = self.consume_virtualref_boxes(index)
+        self.cur_index = index
         return virtualizable_boxes, virtualref_boxes
 
     def allocate_with_vtable(self, descr=None):
@@ -1429,39 +1444,36 @@
         info = blackholeinterp.get_current_position_info()
         self._prepare_next_section(info)
 
-    def consume_virtualref_info(self, vrefinfo, end):
+    def consume_virtualref_info(self, vrefinfo, index):
         # we have to decode a list of references containing pairs
-        # [..., virtual, vref, ...]  stopping at 'end'
-        if vrefinfo is None:
-            assert end == 0
-            return
-        assert (end & 1) == 0
-        self.cur_index = 0
-        for i in range(0, end, 2):
-            virtual_item, self.cur_index = resumecode.numb_next_item(
-                self.numb, self.cur_index)
-            vref_item, self.cur_index = resumecode.numb_next_item(
-                self.numb, self.cur_index)
+        # [..., virtual, vref, ...] and returns the index at the end
+        size, index = resumecode.numb_next_item(self.numb, index)
+        if vrefinfo is None or size == 0:
+            assert size == 0
+            return index
+        for i in range(size):
+            virtual_item, index = resumecode.numb_next_item(
+                self.numb, index)
+            vref_item, index = resumecode.numb_next_item(
+                self.numb, index)
             virtual = self.decode_ref(virtual_item)
             vref = self.decode_ref(vref_item)
             # For each pair, we store the virtual inside the vref.
             vrefinfo.continue_tracing(vref, virtual)
+        return index
 
-    def consume_vable_info(self, vinfo):
+    def consume_vable_info(self, vinfo, index):
         # we have to ignore the initial part of 'nums' (containing vrefs),
         # find the virtualizable from nums[-1], load all other values
         # from the CPU stack, and copy them into the virtualizable
         numb = self.numb
-        first_snapshot_size = rffi.cast(lltype.Signed, numb.first_snapshot_size)
-        item, _ = resumecode.numb_next_item(self.numb,
-            first_snapshot_size - 1)
+        item, index = resumecode.numb_next_item(self.numb, index)
         virtualizable = self.decode_ref(item)
-        start_index = first_snapshot_size - 1 - vinfo.get_total_size(virtualizable)
         # just reset the token, we'll force it later
         vinfo.reset_token_gcref(virtualizable)
-        vinfo.write_from_resume_data_partial(virtualizable, self, start_index,
-            numb)
-        return start_index
+        index = vinfo.write_from_resume_data_partial(virtualizable, self,
+            index, numb)
+        return index
 
     def load_value_of_type(self, TYPE, tagged):
         from rpython.jit.metainterp.warmstate import specialize_value
@@ -1478,14 +1490,18 @@
     load_value_of_type._annspecialcase_ = 'specialize:arg(1)'
 
     def consume_vref_and_vable(self, vrefinfo, vinfo, ginfo):
+        vable_size, index = resumecode.numb_next_item(self.numb, 0)
         if self.resume_after_guard_not_forced != 2:
-            end_vref = rffi.cast(lltype.Signed, self.numb.first_snapshot_size)
             if vinfo is not None:
-                end_vref = self.consume_vable_info(vinfo)
+                index = self.consume_vable_info(vinfo, index)
             if ginfo is not None:
-                end_vref -= 1
-            self.consume_virtualref_info(vrefinfo, end_vref) 
-        self.cur_index = rffi.cast(lltype.Signed, self.numb.first_snapshot_size)
+                _, index = resumecode.numb_next_item(self.numb, index)
+            index = self.consume_virtualref_info(vrefinfo, index)
+        else:
+            index = resumecode.numb_next_n_items(self.numb, vable_size, index)
+            vref_size, index = resumecode.numb_next_item(self.numb, index)
+            index = resumecode.numb_next_n_items(self.numb, vref_size * 2, index)
+        self.cur_index = index 
 
     def allocate_with_vtable(self, descr=None):
         from rpython.jit.metainterp.executor import exec_new_with_vtable
diff --git a/rpython/jit/metainterp/resumecode.py b/rpython/jit/metainterp/resumecode.py
--- a/rpython/jit/metainterp/resumecode.py
+++ b/rpython/jit/metainterp/resumecode.py
@@ -1,96 +1,74 @@
 
 """ Resume bytecode. It goes as following:
 
-<numb> <numb> <pc> <jitcode> <numb> <numb> <numb> <pc> <jitcode>
+  [<length> <virtualizable object> <numb> <numb> <numb>]    if vinfo is not None
+   -OR-
+  [1 <ginfo object>]                                        if ginfo is not None
+   -OR-
+  [0]                                                       if both are None
 
-until the length of the array.
+  [<length> <virtual> <vref> <virtual> <vref>]     for virtualrefs
 
-The interface is only create_numbering/numb_next_item, but! there is a trick
-that uses first_snapshot_size + some knowledge about inside to decode
-virtualref/virtualizable_fields/virtualizable in that order in resume.py.
+  [<pc> <jitcode> <numb> <numb> <numb>]            the frames
+  [<pc> <jitcode> <numb> <numb>]
+  ...
 
-If the algorithm changes, the part about how to find where virtualizable
-and virtualrefs are to be found
+  until the length of the array.
 """
 
 from rpython.rtyper.lltypesystem import rffi, lltype
 
 NUMBERINGP = lltype.Ptr(lltype.GcForwardReference())
 NUMBERING = lltype.GcStruct('Numbering',
-#                            ('prev', NUMBERINGP),
-#                            ('prev_index', rffi.USHORT),
-                            ('first_snapshot_size', rffi.USHORT), # ugh, ugly
-                            ('code', lltype.Array(rffi.SHORT)))
+                            ('code', lltype.Array(rffi.UCHAR)))
 NUMBERINGP.TO.become(NUMBERING)
 NULL_NUMBER = lltype.nullptr(NUMBERING)
 
-# this is the actually used version
+def create_numbering(lst):
+    result = []
+    for item in lst:
+        item = rffi.cast(lltype.Signed, item)
+        item *= 2
+        if item < 0:
+            item = -1 - item
 
-def create_numbering(lst, first_snapshot_size):
-    numb = lltype.malloc(NUMBERING, len(lst))
-    for i in range(len(lst)):
-        numb.code[i] = rffi.cast(rffi.SHORT, lst[i])
-    numb.first_snapshot_size = rffi.cast(rffi.USHORT, first_snapshot_size)
+        assert item >= 0
+        if item < 2**7:
+            result.append(rffi.cast(rffi.UCHAR, item))
+        elif item < 2**14:
+            result.append(rffi.cast(rffi.UCHAR, item | 0x80))
+            result.append(rffi.cast(rffi.UCHAR, item >> 7))
+        else:
+            assert item < 2**16
+            result.append(rffi.cast(rffi.UCHAR, item | 0x80))
+            result.append(rffi.cast(rffi.UCHAR, (item >> 7) | 0x80))
+            result.append(rffi.cast(rffi.UCHAR, item >> 14))
+
+    numb = lltype.malloc(NUMBERING, len(result))
+    for i in range(len(result)):
+        numb.code[i] = result[i]
     return numb
 
 def numb_next_item(numb, index):
-    return rffi.cast(lltype.Signed, numb.code[index]), index + 1
+    value = rffi.cast(lltype.Signed, numb.code[index])
+    index += 1
+    if value & (2**7):
+        value &= 2**7 - 1
+        value |= rffi.cast(lltype.Signed, numb.code[index]) << 7
+        index += 1
+        if value & (2**14):
+            value &= 2**14 - 1
+            value |= rffi.cast(lltype.Signed, numb.code[index]) << 14
+            index += 1
+    if value & 1:
+        value = -1 - value
+    value >>= 1
+    return value, index
 
-# this is the version that can be potentially used
-
-def _create_numbering(lst, prev, prev_index, first_snapshot_size):
-    count = 0
-    for item in lst:
-        if item < 0:
-            if item < -63:
-                count += 1
-        if item > 127:
-            count += 1
-        count += 1
-    numb = lltype.malloc(NUMBERING, count)
-    numb.prev = prev
-    numb.prev_index = rffi.cast(rffi.USHORT, prev_index)
-    numb.first_snapshot_size = rffi.cast(rffi.USHORT, first_snapshot_size)
-    index = 0
-    for item in lst:
-        if 0 <= item <= 128:
-            numb.code[index] = rffi.cast(rffi.UCHAR, item)
-            index += 1
-        else:
-            assert (item >> 8) <= 63
-            if item < 0:
-                item = -item
-                if item <= 63:
-                    numb.code[index] = rffi.cast(rffi.UCHAR, item | 0x40)
-                    index += 1
-                else:
-                    numb.code[index] = rffi.cast(rffi.UCHAR, (item >> 8) | 0x80 | 0x40)
-                    numb.code[index + 1] = rffi.cast(rffi.UCHAR, item & 0xff)
-                    index += 2
-            else:
-                numb.code[index] = rffi.cast(rffi.UCHAR, (item >> 8) | 0x80)
-                numb.code[index + 1] = rffi.cast(rffi.UCHAR, item & 0xff)
-                index += 2
-    return numb
-
-def copy_from_list_to_numb(lst, numb, index):
-    i = 0
-    while i < len(lst):
-        numb.code[i + index] = lst[i]
-        i += 1
-
-def _numb_next_item(numb, index):
-    one = rffi.cast(lltype.Signed, numb.code[index])
-    if one & 0x40:
-        if one & 0x80:
-            two = rffi.cast(lltype.Signed, numb.code[index + 1])
-            return -(((one & ~(0x80 | 0x40)) << 8) | two), index + 2
-        else:
-            return -(one & (~0x40)), index + 1
-    if one & 0x80:
-        two = rffi.cast(lltype.Signed, numb.code[index + 1])
-        return ((one & 0x7f) << 8) | two, index + 2
-    return one, index + 1
+def numb_next_n_items(numb, size, index):
+    for i in range(size):
+        _, index = numb_next_item(numb, index)
+    return index
 
 def unpack_numbering(numb):
     l = []
diff --git a/rpython/jit/metainterp/test/test_resume.py b/rpython/jit/metainterp/test/test_resume.py
--- a/rpython/jit/metainterp/test/test_resume.py
+++ b/rpython/jit/metainterp/test/test_resume.py
@@ -10,7 +10,7 @@
      VArrayInfoNotClear, VStrPlainInfo, VStrConcatInfo, VStrSliceInfo,\
      VUniPlainInfo, VUniConcatInfo, VUniSliceInfo, Snapshot, FrameInfo,\
      capture_resumedata, ResumeDataLoopMemo, UNASSIGNEDVIRTUAL, INT,\
-     annlowlevel, PENDINGFIELDSP, unpack_uint, TAG_CONST_OFFSET
+     annlowlevel, PENDINGFIELDSP, unpack_uint, TAG_CONST_OFFSET, TopSnapshot
 from rpython.jit.metainterp.resumecode import unpack_numbering,\
      create_numbering, NULL_NUMBER
 
@@ -22,9 +22,12 @@
 from rpython.jit.codewriter import heaptracker, longlong
 from rpython.jit.metainterp.resoperation import ResOperation, InputArgInt,\
      InputArgRef, rop
+from rpython.jit.metainterp.test.strategies import boxlists
 from rpython.rlib.debug import debug_start, debug_stop, debug_print,\
      have_debug_prints
 
+from hypothesis import given
+
 class Storage:
     rd_frame_info_list = None
     rd_numb = None
@@ -278,9 +281,7 @@
     assert bh.written_f == expected_f
 
 
-def Numbering(nums):
-    numb = create_numbering(nums, 0)
-    return numb
+Numbering = create_numbering
 
 def tagconst(i):
     return tag(i + TAG_CONST_OFFSET, TAGCONST)
@@ -610,7 +611,8 @@
     assert unpack_uint(frame_info_list.packed_jitcode_pc) == (2, 15)
     
     snapshot = storage.rd_snapshot
-    assert snapshot.boxes == vrs + vbs      # in the same list
+    assert snapshot.boxes == vrs
+    assert snapshot.vable_boxes == [b2, b1]
 
     snapshot = snapshot.prev
     assert snapshot.prev is fs[2].parent_resumedata_snapshot
@@ -904,9 +906,9 @@
     env = [b1, c1, b2, b1, c2]
     snap = Snapshot(None, env)
     env1 = [c3, b3, b1, c1]
-    snap1 = Snapshot(snap, env1)
+    snap1 = TopSnapshot(snap, env1, [])
     env2 = [c3, b3, b1, c3]
-    snap2 = Snapshot(snap, env2)
+    snap2 = TopSnapshot(snap, env2, [])
 
     memo = ResumeDataLoopMemo(FakeMetaInterpStaticData())
     frameinfo = FrameInfo(None, FakeJitCode("jitcode", 0), 0)
@@ -916,10 +918,11 @@
 
     assert liveboxes == {b1: tag(0, TAGBOX), b2: tag(1, TAGBOX),
                          b3: tag(2, TAGBOX)}
-    base = [tag(0, TAGBOX), tag(1, TAGINT), tag(1, TAGBOX), tag(0, TAGBOX), tag(2, TAGINT)]
+    base = [0, 0, tag(0, TAGBOX), tag(1, TAGINT),
+            tag(1, TAGBOX), tag(0, TAGBOX), tag(2, TAGINT)]
 
-    assert unpack_numbering(numb) == [
-          tag(3, TAGINT), tag(2, TAGBOX), tag(0, TAGBOX), tag(1, TAGINT), 0, 0] + base
+    assert unpack_numbering(numb) == [0, 2, tag(3, TAGINT), tag(2, TAGBOX),
+                                      tag(0, TAGBOX), tag(1, TAGINT)] + base
 
     numb2, liveboxes2, v = memo.number(FakeOptimizer(), snap2, frameinfo)
     assert v == 0
@@ -927,11 +930,11 @@
     assert liveboxes2 == {b1: tag(0, TAGBOX), b2: tag(1, TAGBOX),
                          b3: tag(2, TAGBOX)}
     assert liveboxes2 is not liveboxes
-    assert unpack_numbering(numb2) == [
-         tag(3, TAGINT), tag(2, TAGBOX), tag(0, TAGBOX), tag(3, TAGINT), 0, 0] + base
+    assert unpack_numbering(numb2) == [0, 2, tag(3, TAGINT), tag(2, TAGBOX),
+                                       tag(0, TAGBOX), tag(3, TAGINT)] + base
 
     env3 = [c3, b3, b1, c3]
-    snap3 = Snapshot(snap, env3)
+    snap3 = TopSnapshot(snap, env3, [])
 
     class FakeVirtualInfo(info.AbstractInfo):
         def __init__(self, virt):
@@ -946,13 +949,12 @@
     assert v == 0
     
     assert liveboxes3 == {b1: tag(0, TAGBOX), b2: tag(1, TAGBOX)}
-    assert unpack_numbering(numb3) == [tag(3, TAGINT), tag(4, TAGINT),
-                                       tag(0, TAGBOX),
-                                       tag(3, TAGINT), 0, 0] + base
+    assert unpack_numbering(numb3) == [0, 2, tag(3, TAGINT), tag(4, TAGINT),
+                                       tag(0, TAGBOX), tag(3, TAGINT)] + base
 
     # virtual
     env4 = [c3, b4, b1, c3]
-    snap4 = Snapshot(snap, env4)
+    snap4 = TopSnapshot(snap, env4, [])
 
     b4.set_forwarded(FakeVirtualInfo(True))
     numb4, liveboxes4, v = memo.number(FakeOptimizer(), snap4, frameinfo)
@@ -960,11 +962,11 @@
     
     assert liveboxes4 == {b1: tag(0, TAGBOX), b2: tag(1, TAGBOX),
                           b4: tag(0, TAGVIRTUAL)}
-    assert unpack_numbering(numb4) == [tag(3, TAGINT), tag(0, TAGVIRTUAL),
-                                tag(0, TAGBOX), tag(3, TAGINT), 0, 0] + base
+    assert unpack_numbering(numb4) == [0, 2, tag(3, TAGINT), tag(0, TAGVIRTUAL),
+                                       tag(0, TAGBOX), tag(3, TAGINT)] + base
 
     env5 = [b1, b4, b5]
-    snap5 = Snapshot(snap4, env5)
+    snap5 = TopSnapshot(snap4, [], env5)
 
     b4.set_forwarded(FakeVirtualInfo(True))
     b5.set_forwarded(FakeVirtualInfo(True))
@@ -974,9 +976,30 @@
     
     assert liveboxes5 == {b1: tag(0, TAGBOX), b2: tag(1, TAGBOX),
                           b4: tag(0, TAGVIRTUAL), b5: tag(1, TAGVIRTUAL)}
-    assert unpack_numbering(numb5) == [tag(0, TAGBOX), tag(0, TAGVIRTUAL),
-                           tag(1, TAGVIRTUAL), 2, 1] + unpack_numbering(numb4)
+    assert unpack_numbering(numb5) == [
+        3, tag(0, TAGBOX), tag(0, TAGVIRTUAL), tag(1, TAGVIRTUAL),
+        0,
+        2, 1, tag(3, TAGINT), tag(0, TAGVIRTUAL), tag(0, TAGBOX), tag(3, TAGINT)
+        ] + base
 
+ at given(boxlists)
+def test_ResumeDataLoopMemo_random(lst):
+    s = TopSnapshot(None, [], lst)
+    frameinfo = FrameInfo(None, FakeJitCode("foo", 0), 0)
+    memo = ResumeDataLoopMemo(FakeMetaInterpStaticData())
+    num, liveboxes, v = memo.number(FakeOptimizer(), s, frameinfo)
+    l = unpack_numbering(num)
+    assert l[-1] == 0
+    assert l[0] == len(lst)
+    for i, item in enumerate(lst):
+        v, tag = untag(l[i + 1])
+        if tag == TAGBOX:
+            assert l[i + 1] == liveboxes[item]
+        elif tag == TAGCONST:
+            assert memo.consts[v].getint() == item.getint()
+        elif tag == TAGINT:
+            assert v == item.getint()
+    
 def test_ResumeDataLoopMemo_number_boxes():
     memo = ResumeDataLoopMemo(FakeMetaInterpStaticData())
     b1, b2 = [InputArgInt(), InputArgInt()]
@@ -1060,10 +1083,11 @@
     storage = Storage()
     snapshot = Snapshot(None, [b1, ConstInt(1), b1, b2])
     snapshot = Snapshot(snapshot, [ConstInt(2), ConstInt(3)])
-    snapshot = Snapshot(snapshot, [b1, b2, b3])    
-    frameinfo = FrameInfo(FrameInfo(None, FakeJitCode("code1", 21), 22),
-        FakeJitCode("code2", 31), 32)
-    storage.rd_snapshot = snapshot
+    snapshot = Snapshot(snapshot, [b1, b2, b3])
+    top_snapshot = TopSnapshot(snapshot, [], [])
+    frameinfo = FrameInfo(FrameInfo(FrameInfo(None, FakeJitCode("code1", 21), 22),
+        FakeJitCode("code2", 31), 32), FakeJitCode("code3", 41), 42)
+    storage.rd_snapshot = top_snapshot
     storage.rd_frame_info_list = frameinfo
     return storage
 
@@ -1076,6 +1100,8 @@
     assert storage.rd_snapshot is None
     cpu = MyCPU([])
     reader = ResumeDataDirectReader(MyMetaInterp(cpu), storage, "deadframe")
+    reader.consume_vref_and_vable(None, None, None)
+    reader.cur_index += 2 # framestack
     _next_section(reader, sys.maxint, 2**16, -65)
     reader.cur_index += 2 # framestack
     _next_section(reader, 2, 3)
diff --git a/rpython/jit/metainterp/test/test_resumecode.py b/rpython/jit/metainterp/test/test_resumecode.py
--- a/rpython/jit/metainterp/test/test_resumecode.py
+++ b/rpython/jit/metainterp/test/test_resumecode.py
@@ -1,9 +1,12 @@
 
 from rpython.jit.metainterp.resumecode import NUMBERING, NULL_NUMBER
 from rpython.jit.metainterp.resumecode import create_numbering,\
-    unpack_numbering, copy_from_list_to_numb
+    unpack_numbering
 from rpython.rtyper.lltypesystem import lltype
 
+from hypothesis import strategies, given
+
+
 def test_pack_unpack():
     examples = [
         [1, 2, 3, 4, 257, 10000, 13, 15],
@@ -12,5 +15,15 @@
         [13000, 12000, 10000, 256, 255, 254, 257, -3, -1000]
     ]
     for l in examples:
-        n = create_numbering(l, 0)
+        n = create_numbering(l)
         assert unpack_numbering(n) == l
+
+ at given(strategies.lists(strategies.integers(-2**15, 2**15-1)))
+def test_roundtrip(l):
+    n = create_numbering(l)
+    assert unpack_numbering(n) == l
+
+ at given(strategies.lists(strategies.integers(-2**15, 2**15-1)))
+def test_compressing(l):
+    n = create_numbering(l)
+    assert len(n.code) <= len(l) * 3
diff --git a/rpython/jit/metainterp/virtualizable.py b/rpython/jit/metainterp/virtualizable.py
--- a/rpython/jit/metainterp/virtualizable.py
+++ b/rpython/jit/metainterp/virtualizable.py
@@ -142,6 +142,7 @@
                     item, index = numb_next_item(numb, index)                    
                     x = reader.load_value_of_type(ARRAYITEMTYPE, item)
                     setarrayitem(lst, j, x)
+            return index
 
         def load_list_of_boxes(virtualizable, reader, vable_box, numb, index):
             virtualizable = cast_gcref_to_vtype(virtualizable)
@@ -161,7 +162,7 @@
                     box = reader.decode_box_of_type(ARRAYITEMTYPE, item)
                     boxes.append(box)
             boxes.append(vable_box)
-            return boxes
+            return boxes, index
 
         def check_boxes(virtualizable, boxes):
             virtualizable = cast_gcref_to_vtype(virtualizable)
diff --git a/rpython/rtyper/lltypesystem/ll2ctypes.py b/rpython/rtyper/lltypesystem/ll2ctypes.py
--- a/rpython/rtyper/lltypesystem/ll2ctypes.py
+++ b/rpython/rtyper/lltypesystem/ll2ctypes.py
@@ -426,7 +426,12 @@
         else:
             n = None
         cstruct = cls._malloc(n)
-    add_storage(container, _struct_mixin, ctypes.pointer(cstruct))
+
+    if isinstance(container, lltype._fixedsizearray):
+        cls_mixin = _fixedsizedarray_mixin
+    else:
+        cls_mixin = _struct_mixin
+    add_storage(container, cls_mixin, ctypes.pointer(cstruct))
 
     if delayed_converters is None:
         delayed_converters_was_None = True
@@ -506,7 +511,11 @@
 def struct_use_ctypes_storage(container, ctypes_storage):
     STRUCT = container._TYPE
     assert isinstance(STRUCT, lltype.Struct)
-    add_storage(container, _struct_mixin, ctypes_storage)
+    if isinstance(container, lltype._fixedsizearray):
+        cls_mixin = _fixedsizedarray_mixin
+    else:
+        cls_mixin = _struct_mixin
+    add_storage(container, cls_mixin, ctypes_storage)
     remove_regular_struct_content(container)
     for field_name in STRUCT._names:
         FIELDTYPE = getattr(STRUCT, field_name)
@@ -645,11 +654,44 @@
             cobj = lltype2ctypes(value)
             setattr(self._storage.contents, field_name, cobj)
 
+class _fixedsizedarray_mixin(_parentable_mixin):
+    """Mixin added to _fixedsizearray containers when they become ctypes-based."""
+    __slots__ = ()
+
+    def __getattr__(self, field_name):
+        if hasattr(self, '_items'):
+            obj = lltype._fixedsizearray.__getattr__.im_func(self, field_name)
+            return obj
+        else:
+            cobj = getattr(self._storage.contents, field_name)
+            T = getattr(self._TYPE, field_name)
+            return ctypes2lltype(T, cobj)
+
+    def __setattr__(self, field_name, value):
+        if field_name.startswith('_'):
+            object.__setattr__(self, field_name, value)  # '_xxx' attributes
+        else:
+            cobj = lltype2ctypes(value)
+            if hasattr(self, '_items'):
+                lltype._fixedsizearray.__setattr__.im_func(self, field_name, cobj)
+            else:
+                setattr(self._storage.contents, field_name, cobj)
+
+
     def getitem(self, index, uninitialized_ok=False):
-        return getattr(self, "item%s" % index)
+        if hasattr(self, '_items'):
+            obj = lltype._fixedsizearray.getitem.im_func(self, 
+                                     index, uninitialized_ok=uninitialized_ok)
+            return obj
+        else:
+            return getattr(self, 'item%d' % index)
 
     def setitem(self, index, value):
-        setattr(self, "item%s" % index, value)
+        cobj = lltype2ctypes(value)
+        if hasattr(self, '_items'):
+            lltype._fixedsizearray.setitem.im_func(self, index, value)
+        else:
+            setattr(self, 'item%d' % index, cobj)
 
 class _array_mixin(_parentable_mixin):
     """Mixin added to _array containers when they become ctypes-based."""
diff --git a/rpython/rtyper/lltypesystem/test/test_ll2ctypes.py b/rpython/rtyper/lltypesystem/test/test_ll2ctypes.py
--- a/rpython/rtyper/lltypesystem/test/test_ll2ctypes.py
+++ b/rpython/rtyper/lltypesystem/test/test_ll2ctypes.py
@@ -1461,3 +1461,20 @@
         assert a[3].a == 17
         #lltype.free(a, flavor='raw')
         py.test.skip("free() not working correctly here...")
+
+    def test_fixedsizedarray_to_ctypes(self):
+        T = lltype.Ptr(rffi.CFixedArray(rffi.INT, 1))
+        inst = lltype.malloc(T.TO, flavor='raw')
+        inst[0] = rffi.cast(rffi.INT, 42)
+        assert inst[0] == 42
+        cinst = lltype2ctypes(inst)
+        assert rffi.cast(lltype.Signed, inst[0]) == 42
+        assert cinst.contents.item0 == 42
+        lltype.free(inst, flavor='raw')
+
+    def test_fixedsizedarray_to_ctypes(self):
+        T = lltype.Ptr(rffi.CFixedArray(rffi.CHAR, 123))
+        inst = lltype.malloc(T.TO, flavor='raw', zero=True)
+        cinst = lltype2ctypes(inst)
+        assert cinst.contents.item0 == 0
+        lltype.free(inst, flavor='raw')


More information about the pypy-commit mailing list