[pypy-svn] r70324 - in pypy/trunk/pypy: jit/metainterp jit/metainterp/test rpython rpython/test translator/backendopt translator/backendopt/test

arigo at codespeak.net arigo at codespeak.net
Tue Dec 29 10:03:15 CET 2009


Author: arigo
Date: Tue Dec 29 10:03:14 2009
New Revision: 70324

Modified:
   pypy/trunk/pypy/jit/metainterp/codewriter.py
   pypy/trunk/pypy/jit/metainterp/effectinfo.py
   pypy/trunk/pypy/jit/metainterp/optimizeopt.py
   pypy/trunk/pypy/jit/metainterp/resume.py
   pypy/trunk/pypy/jit/metainterp/test/test_effectinfo.py
   pypy/trunk/pypy/jit/metainterp/test/test_optimizefindnode.py
   pypy/trunk/pypy/jit/metainterp/test/test_optimizeopt.py
   pypy/trunk/pypy/jit/metainterp/test/test_resume.py
   pypy/trunk/pypy/rpython/rptr.py
   pypy/trunk/pypy/rpython/test/test_rptr.py
   pypy/trunk/pypy/translator/backendopt/test/test_writeanalyze.py
   pypy/trunk/pypy/translator/backendopt/writeanalyze.py
Log:
Merge jit-delayed-write.  Allows repeated setfield_gc's to get
optimized out.  The main result is that if a setfield_gc's
would stick a virtual structure on a non-virtual object, and
if that setfield_gc is later overridden, then optimizing it
out allows the complete structure to remain virtual.


Modified: pypy/trunk/pypy/jit/metainterp/codewriter.py
==============================================================================
--- pypy/trunk/pypy/jit/metainterp/codewriter.py	(original)
+++ pypy/trunk/pypy/jit/metainterp/codewriter.py	Tue Dec 29 10:03:14 2009
@@ -11,7 +11,7 @@
 from pypy.tool.udir import udir
 from pypy.translator.simplify import get_funcobj, get_functype
 from pypy.translator.backendopt.canraise import RaiseAnalyzer
-from pypy.translator.backendopt.writeanalyze import WriteAnalyzer
+from pypy.translator.backendopt.writeanalyze import ReadWriteAnalyzer
 from pypy.jit.metainterp.typesystem import deref, arrayItem, fieldType
 from pypy.jit.metainterp.effectinfo import effectinfo_from_writeanalyze
 from pypy.jit.metainterp.effectinfo import VirtualizableAnalyzer
@@ -185,7 +185,7 @@
         self.portal_runner_ptr = portal_runner_ptr
         translator = self.rtyper.annotator.translator
         self.raise_analyzer = RaiseAnalyzer(translator)
-        self.write_analyzer = WriteAnalyzer(translator)
+        self.readwrite_analyzer = ReadWriteAnalyzer(translator)
         self.virtualizable_analyzer = VirtualizableAnalyzer(translator)
 
     def make_portal_bytecode(self, graph):
@@ -326,7 +326,7 @@
         # ok
         if consider_effects_of is not None:
             effectinfo = effectinfo_from_writeanalyze(
-                    self.write_analyzer.analyze(consider_effects_of),
+                    self.readwrite_analyzer.analyze(consider_effects_of),
                     self.cpu,
                     self.virtualizable_analyzer.analyze(consider_effects_of))
             calldescr = self.cpu.calldescrof(FUNC, tuple(NON_VOID_ARGS), RESULT, effectinfo)

Modified: pypy/trunk/pypy/jit/metainterp/effectinfo.py
==============================================================================
--- pypy/trunk/pypy/jit/metainterp/effectinfo.py	(original)
+++ pypy/trunk/pypy/jit/metainterp/effectinfo.py	Tue Dec 29 10:03:14 2009
@@ -7,13 +7,16 @@
 class EffectInfo(object):
     _cache = {}
 
-    def __new__(cls, write_descrs_fields, write_descrs_arrays,
-                promotes_virtualizables=False):
-        key = (frozenset(write_descrs_fields), frozenset(write_descrs_arrays),
+    def __new__(cls, readonly_descrs_fields, write_descrs_fields,
+                write_descrs_arrays, promotes_virtualizables=False):
+        key = (frozenset(readonly_descrs_fields),
+               frozenset(write_descrs_fields),
+               frozenset(write_descrs_arrays),
                promotes_virtualizables)
         if key in cls._cache:
             return cls._cache[key]
         result = object.__new__(cls)
+        result.readonly_descrs_fields = readonly_descrs_fields
         result.write_descrs_fields = write_descrs_fields
         result.write_descrs_arrays = write_descrs_arrays
         result.promotes_virtualizables = promotes_virtualizables
@@ -24,26 +27,39 @@
     from pypy.translator.backendopt.writeanalyze import top_set
     if effects is top_set:
         return None
+    readonly_descrs_fields = []
+    # readonly_descrs_arrays = [] --- not enabled for now
     write_descrs_fields = []
     write_descrs_arrays = []
+
+    def add_struct(descrs_fields, (_, T, fieldname)):
+        T = deref(T)
+        if consider_struct(T, fieldname):
+            descr = cpu.fielddescrof(T, fieldname)
+            descrs_fields.append(descr)
+
+    def add_array(descrs_arrays, (_, T)):
+        ARRAY = deref(T)
+        if consider_array(ARRAY):
+            descr = cpu.arraydescrof(ARRAY)
+            descrs_arrays.append(descr)
+
     for tup in effects:
         if tup[0] == "struct":
-            _, T, fieldname = tup
-            T = deref(T)
-            if not consider_struct(T, fieldname):
-                continue
-            descr = cpu.fielddescrof(T, fieldname)
-            write_descrs_fields.append(descr)
+            add_struct(write_descrs_fields, tup)
+        elif tup[0] == "readstruct":
+            tupw = ("struct",) + tup[1:]
+            if tupw not in effects:
+                add_struct(readonly_descrs_fields, tup)
         elif tup[0] == "array":
-            _, T = tup
-            ARRAY = deref(T)
-            if not consider_array(ARRAY):
-                continue
-            descr = cpu.arraydescrof(ARRAY)
-            write_descrs_arrays.append(descr)
+            add_array(write_descrs_arrays, tup)
+        elif tup[0] == "readarray":
+            pass
         else:
             assert 0
-    return EffectInfo(write_descrs_fields, write_descrs_arrays,
+    return EffectInfo(readonly_descrs_fields,
+                      write_descrs_fields,
+                      write_descrs_arrays,
                       promotes_virtualizables)
 
 def consider_struct(TYPE, fieldname):

Modified: pypy/trunk/pypy/jit/metainterp/optimizeopt.py
==============================================================================
--- pypy/trunk/pypy/jit/metainterp/optimizeopt.py	(original)
+++ pypy/trunk/pypy/jit/metainterp/optimizeopt.py	Tue Dec 29 10:03:14 2009
@@ -161,18 +161,18 @@
         return self.box
 
     def make_virtual_info(self, modifier, fieldnums):
-        vinfo = self._cached_vinfo 
-        if vinfo is not None and resume.tagged_list_eq(
-                vinfo.fieldnums, fieldnums):
+        vinfo = self._cached_vinfo
+        if vinfo is not None and vinfo.equals(fieldnums):
             return vinfo
         vinfo = self._make_virtual(modifier)
-        vinfo.fieldnums = fieldnums
+        vinfo.set_content(fieldnums)
         self._cached_vinfo = vinfo
         return vinfo
 
     def _make_virtual(self, modifier):
         raise NotImplementedError("abstract base")
 
+
 def get_fielddescrlist_cache(cpu):
     if not hasattr(cpu, '_optimizeopt_fielddescrlist_cache'):
         result = descrlist_dict()
@@ -512,6 +512,9 @@
 
     def emit_operation(self, op, must_clone=True):
         self.heap_op_optimizer.emitting_operation(op)
+        self._emit_operation(op, must_clone)
+
+    def _emit_operation(self, op, must_clone=True):
         for i in range(len(op.args)):
             arg = op.args[i]
             if arg in self.values:
@@ -532,10 +535,11 @@
         self.newoperations.append(op)
 
     def store_final_boxes_in_guard(self, op):
+        pendingfields = self.heap_op_optimizer.force_lazy_setfields_for_guard()
         descr = op.descr
         assert isinstance(descr, compile.ResumeGuardDescr)
         modifier = resume.ResumeDataVirtualAdder(descr, self.resumedata_memo)
-        newboxes = modifier.finish(self.values)
+        newboxes = modifier.finish(self.values, pendingfields)
         if len(newboxes) > self.metainterp_sd.options.failargs_limit: # XXX be careful here
             raise compile.GiveUp
         descr.store_final_boxes(op, newboxes)
@@ -836,11 +840,13 @@
 class HeapOpOptimizer(object):
     def __init__(self, optimizer):
         self.optimizer = optimizer
-        # cached OptValues for each field descr
+        # cached fields:  {descr: {OptValue_instance: OptValue_fieldvalue}}
         self.cached_fields = {}
-
-        # cached OptValues for each field descr
+        # cached array items:  {descr: CachedArrayItems}
         self.cached_arrayitems = {}
+        # lazily written setfields (at most one per descr):  {descr: op}
+        self.lazy_setfields = {}
+        self.lazy_setfields_descrs = []     # keys (at least) of previous dict
 
     def clean_caches(self):
         self.cached_fields.clear()
@@ -848,6 +854,9 @@
 
     def cache_field_value(self, descr, value, fieldvalue, write=False):
         if write:
+            # when seeing a setfield, we have to clear the cache for the same
+            # field on any other structure, just in case they are aliasing
+            # each other
             d = self.cached_fields[descr] = {}
         else:
             d = self.cached_fields.setdefault(descr, {})
@@ -920,7 +929,12 @@
             opnum == rop.CALL_MAY_FORCE):
             effectinfo = op.descr.get_extra_info()
             if effectinfo is not None:
+                # XXX we can get the wrong complexity here, if the lists
+                # XXX stored on effectinfo are large
+                for fielddescr in effectinfo.readonly_descrs_fields:
+                    self.force_lazy_setfield(fielddescr)
                 for fielddescr in effectinfo.write_descrs_fields:
+                    self.force_lazy_setfield(fielddescr)
                     try:
                         del self.cached_fields[fielddescr]
                     except KeyError:
@@ -931,9 +945,73 @@
                     except KeyError:
                         pass
                 return
+            self.force_all_lazy_setfields()
+        elif op.is_final() or (not we_are_translated() and
+                               op.opnum < 0):   # escape() operations
+            self.force_all_lazy_setfields()
         self.clean_caches()
 
+    def force_lazy_setfield(self, descr, before_guard=False):
+        try:
+            op = self.lazy_setfields[descr]
+        except KeyError:
+            return
+        del self.lazy_setfields[descr]
+        self.optimizer._emit_operation(op)
+        #
+        # hackish: reverse the order of the last two operations if it makes
+        # sense to avoid a situation like "int_eq/setfield_gc/guard_true",
+        # which the backend (at least the x86 backend) does not handle well.
+        newoperations = self.optimizer.newoperations
+        if before_guard and len(newoperations) >= 2:
+            lastop = newoperations[-1]
+            prevop = newoperations[-2]
+            # - is_comparison() for cases like "int_eq/setfield_gc/guard_true"
+            # - CALL_MAY_FORCE: "call_may_force/setfield_gc/guard_not_forced"
+            if ((prevop.is_comparison() or prevop.opnum == rop.CALL_MAY_FORCE)
+                and prevop.result not in lastop.args):
+                newoperations[-2] = lastop
+                newoperations[-1] = prevop
+
+    def force_all_lazy_setfields(self):
+        if len(self.lazy_setfields_descrs) > 0:
+            for descr in self.lazy_setfields_descrs:
+                self.force_lazy_setfield(descr)
+            del self.lazy_setfields_descrs[:]
+
+    def force_lazy_setfields_for_guard(self):
+        pendingfields = []
+        for descr in self.lazy_setfields_descrs:
+            try:
+                op = self.lazy_setfields[descr]
+            except KeyError:
+                continue
+            # the only really interesting case that we need to handle in the
+            # guards' resume data is that of a virtual object that is stored
+            # into a field of a non-virtual object.
+            value = self.optimizer.getvalue(op.args[0])
+            assert not value.is_virtual()      # it must be a non-virtual
+            fieldvalue = self.optimizer.getvalue(op.args[1])
+            if fieldvalue.is_virtual():
+                # this is the case that we leave to resume.py
+                pendingfields.append((descr, value.box,
+                                      fieldvalue.get_key_box()))
+            else:
+                self.force_lazy_setfield(descr, before_guard=True)
+        return pendingfields
+
+    def force_lazy_setfield_if_necessary(self, op, value, write=False):
+        try:
+            op1 = self.lazy_setfields[op.descr]
+        except KeyError:
+            if write:
+                self.lazy_setfields_descrs.append(op.descr)
+        else:
+            if self.optimizer.getvalue(op1.args[0]) is not value:
+                self.force_lazy_setfield(op.descr)
+
     def optimize_GETFIELD_GC(self, op, value):
+        self.force_lazy_setfield_if_necessary(op, value)
         # check if the field was read from another getfield_gc just before
         # or has been written to recently
         fieldvalue = self.read_cached_field(op.descr, value)
@@ -948,7 +1026,8 @@
         self.cache_field_value(op.descr, value, fieldvalue)
 
     def optimize_SETFIELD_GC(self, op, value, fieldvalue):
-        self.optimizer.emit_operation(op)
+        self.force_lazy_setfield_if_necessary(op, value, write=True)
+        self.lazy_setfields[op.descr] = op
         # remember the result of future reads of the field
         self.cache_field_value(op.descr, value, fieldvalue, write=True)
 

Modified: pypy/trunk/pypy/jit/metainterp/resume.py
==============================================================================
--- pypy/trunk/pypy/jit/metainterp/resume.py	(original)
+++ pypy/trunk/pypy/jit/metainterp/resume.py	Tue Dec 29 10:03:14 2009
@@ -252,8 +252,6 @@
         if (isinstance(box, Box) and box not in self.liveboxes_from_env
                                  and box not in self.liveboxes):
             self.liveboxes[box] = UNASSIGNED
-            return True
-        return False
 
     def _register_boxes(self, boxes):
         for box in boxes:
@@ -268,7 +266,7 @@
         _, tagbits = untag(tagged)
         return tagbits == TAGVIRTUAL
 
-    def finish(self, values):
+    def finish(self, values, pending_setfields=[]):
         # compute the numbering
         storage = self.storage
         numb, liveboxes_from_env, v = self.memo.number(values,
@@ -291,13 +289,21 @@
                 value = values[box]
                 value.get_args_for_fail(self)
 
+        for _, box, fieldbox in pending_setfields:
+            self.register_box(box)
+            self.register_box(fieldbox)
+            value = values[fieldbox]
+            value.get_args_for_fail(self)
+
         self._number_virtuals(liveboxes, values, v)
+        self._add_pending_fields(pending_setfields)
 
         storage.rd_consts = self.memo.consts
         dump_storage(storage, liveboxes)
         return liveboxes[:]
 
     def _number_virtuals(self, liveboxes, values, num_env_virtuals):
+        # !! 'liveboxes' is a list that is extend()ed in-place !!
         memo = self.memo
         new_liveboxes = [None] * memo.num_cached_boxes()
         count = 0
@@ -358,6 +364,16 @@
                 return True
         return False
 
+    def _add_pending_fields(self, pending_setfields):
+        rd_pendingfields = None
+        if pending_setfields:
+            rd_pendingfields = []
+            for descr, box, fieldbox in pending_setfields:
+                num = self._gettagged(box)
+                fieldnum = self._gettagged(fieldbox)
+                rd_pendingfields.append((descr, num, fieldnum))
+        self.storage.rd_pendingfields = rd_pendingfields
+
     def _gettagged(self, box):
         if isinstance(box, Const):
             return self.memo.getconst(box)
@@ -366,11 +382,16 @@
                 return self.liveboxes_from_env[box]
             return self.liveboxes[box]
 
+
 class AbstractVirtualInfo(object):
     def allocate(self, metainterp):
         raise NotImplementedError
     def setfields(self, metainterp, box, fn_decode_box):
         raise NotImplementedError
+    def equals(self, fieldnums):
+        return tagged_list_eq(self.fieldnums, fieldnums)
+    def set_content(self, fieldnums):
+        self.fieldnums = fieldnums
 
 
 class AbstractVirtualStructInfo(AbstractVirtualInfo):
@@ -471,6 +492,7 @@
         self.liveboxes = liveboxes
         self.cpu = metainterp.cpu
         self._prepare_virtuals(metainterp, storage.rd_virtuals)
+        self._prepare_pendingfields(metainterp, storage.rd_pendingfields)
 
     def _prepare_virtuals(self, metainterp, virtuals):
         if virtuals:
@@ -489,6 +511,16 @@
                     vinfo.setfields(metainterp, self.virtuals[i],
                                     self._decode_box)
 
+    def _prepare_pendingfields(self, metainterp, pendingfields):
+        if pendingfields:
+            if metainterp._already_allocated_resume_virtuals is not None:
+                return
+            for descr, num, fieldnum in pendingfields:
+                box = self._decode_box(num)
+                fieldbox = self._decode_box(fieldnum)
+                metainterp.execute_and_record(rop.SETFIELD_GC,
+                                              descr, box, fieldbox)
+
     def consume_boxes(self):
         numb = self.cur_numb
         assert numb is not None

Modified: pypy/trunk/pypy/jit/metainterp/test/test_effectinfo.py
==============================================================================
--- pypy/trunk/pypy/jit/metainterp/test/test_effectinfo.py	(original)
+++ pypy/trunk/pypy/jit/metainterp/test/test_effectinfo.py	Tue Dec 29 10:03:14 2009
@@ -3,32 +3,77 @@
 from pypy.rpython.ootypesystem import ootype
 from pypy.jit.metainterp.effectinfo import effectinfo_from_writeanalyze
 
+class FakeCPU:
+    def fielddescrof(self, T, fieldname):
+        return ('fielddescr', T, fieldname)
+    def arraydescrof(self, A):
+        return ('arraydescr', A)
+
+def test_include_read_field():
+    S = lltype.GcStruct("S", ("a", lltype.Signed))
+    effects = frozenset([("readstruct", lltype.Ptr(S), "a")])
+    effectinfo = effectinfo_from_writeanalyze(effects, FakeCPU())
+    assert list(effectinfo.readonly_descrs_fields) == [('fielddescr', S, "a")]
+    assert not effectinfo.write_descrs_fields
+    assert not effectinfo.write_descrs_arrays
+
+def test_include_write_field():
+    S = lltype.GcStruct("S", ("a", lltype.Signed))
+    effects = frozenset([("struct", lltype.Ptr(S), "a")])
+    effectinfo = effectinfo_from_writeanalyze(effects, FakeCPU())
+    assert list(effectinfo.write_descrs_fields) == [('fielddescr', S, "a")]
+    assert not effectinfo.readonly_descrs_fields
+    assert not effectinfo.write_descrs_arrays
+
+def test_include_write_array():
+    A = lltype.GcArray(lltype.Signed)
+    effects = frozenset([("array", lltype.Ptr(A))])
+    effectinfo = effectinfo_from_writeanalyze(effects, FakeCPU())
+    assert not effectinfo.readonly_descrs_fields
+    assert not effectinfo.write_descrs_fields
+    assert list(effectinfo.write_descrs_arrays) == [('arraydescr', A)]
+
+def test_dont_include_read_and_write_field():
+    S = lltype.GcStruct("S", ("a", lltype.Signed))
+    effects = frozenset([("readstruct", lltype.Ptr(S), "a"),
+                         ("struct", lltype.Ptr(S), "a")])
+    effectinfo = effectinfo_from_writeanalyze(effects, FakeCPU())
+    assert not effectinfo.readonly_descrs_fields
+    assert list(effectinfo.write_descrs_fields) == [('fielddescr', S, "a")]
+    assert not effectinfo.write_descrs_arrays
+
+
 def test_filter_out_typeptr():
     effects = frozenset([("struct", lltype.Ptr(OBJECT), "typeptr")])
     effectinfo = effectinfo_from_writeanalyze(effects, None)
+    assert not effectinfo.readonly_descrs_fields
     assert not effectinfo.write_descrs_fields
     assert not effectinfo.write_descrs_arrays
 
 def test_filter_out_array_of_void():
     effects = frozenset([("array", lltype.Ptr(lltype.GcArray(lltype.Void)))])
     effectinfo = effectinfo_from_writeanalyze(effects, None)
+    assert not effectinfo.readonly_descrs_fields
     assert not effectinfo.write_descrs_fields
     assert not effectinfo.write_descrs_arrays
 
 def test_filter_out_struct_with_void():
     effects = frozenset([("struct", lltype.Ptr(lltype.GcStruct("x", ("a", lltype.Void))), "a")])
     effectinfo = effectinfo_from_writeanalyze(effects, None)
+    assert not effectinfo.readonly_descrs_fields
     assert not effectinfo.write_descrs_fields
     assert not effectinfo.write_descrs_arrays
 
 def test_filter_out_ooarray_of_void():
     effects = frozenset([("array", ootype.Array(ootype.Void))])
     effectinfo = effectinfo_from_writeanalyze(effects, None)
+    assert not effectinfo.readonly_descrs_fields
     assert not effectinfo.write_descrs_fields
     assert not effectinfo.write_descrs_arrays
 
 def test_filter_out_instance_with_void():
     effects = frozenset([("struct", ootype.Instance("x", ootype.ROOT, {"a": ootype.Void}), "a")])
     effectinfo = effectinfo_from_writeanalyze(effects, None)
+    assert not effectinfo.readonly_descrs_fields
     assert not effectinfo.write_descrs_fields
     assert not effectinfo.write_descrs_arrays

Modified: pypy/trunk/pypy/jit/metainterp/test/test_optimizefindnode.py
==============================================================================
--- pypy/trunk/pypy/jit/metainterp/test/test_optimizefindnode.py	(original)
+++ pypy/trunk/pypy/jit/metainterp/test/test_optimizefindnode.py	Tue Dec 29 10:03:14 2009
@@ -95,9 +95,15 @@
     onedescr = cpu.fielddescrof(U, 'one')
 
     FUNC = lltype.FuncType([lltype.Signed], lltype.Signed)
-    nonwritedescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT, EffectInfo([], []))
-    writeadescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT, EffectInfo([adescr], []))
-    writearraydescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT, EffectInfo([adescr], [arraydescr]))
+    plaincalldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT)
+    nonwritedescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+                                    EffectInfo([], [], []))
+    writeadescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+                                  EffectInfo([], [adescr], []))
+    writearraydescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+                                      EffectInfo([], [adescr], [arraydescr]))
+    readadescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
+                                 EffectInfo([adescr], [], []))
 
     cpu.class_sizes = {cpu.cast_adr_to_int(node_vtable_adr): cpu.sizeof(NODE),
                       cpu.cast_adr_to_int(node_vtable_adr2): cpu.sizeof(NODE2),

Modified: pypy/trunk/pypy/jit/metainterp/test/test_optimizeopt.py
==============================================================================
--- pypy/trunk/pypy/jit/metainterp/test/test_optimizeopt.py	(original)
+++ pypy/trunk/pypy/jit/metainterp/test/test_optimizeopt.py	Tue Dec 29 10:03:14 2009
@@ -87,7 +87,10 @@
 
 def test_reuse_vinfo():
     class FakeVInfo(object):
-        pass
+        def set_content(self, fieldnums):
+            self.fieldnums = fieldnums
+        def equals(self, fieldnums):
+            return self.fieldnums == fieldnums
     class FakeVirtualValue(optimizeopt.AbstractVirtualValue):
         def _make_virtual(self, *args):
             return FakeVInfo()
@@ -606,10 +609,10 @@
         p3sub = getfield_gc(p3, descr=nextdescr)
         i3 = getfield_gc(p3sub, descr=valuedescr)
         escape(i3)
+        p1 = new_with_vtable(ConstClass(node_vtable))
         p2sub = new_with_vtable(ConstClass(node_vtable2))
         setfield_gc(p2sub, i1, descr=valuedescr)
         setfield_gc(p2, p2sub, descr=nextdescr)
-        p1 = new_with_vtable(ConstClass(node_vtable))
         jump(i1, p1, p2)
         """
         # The same as test_p123_simple, but in the end the "old" p2 contains
@@ -1293,6 +1296,182 @@
         """
         self.optimize_loop(ops, 'Not, Not', ops)
 
+    def test_duplicate_setfield_1(self):
+        ops = """
+        [p1, i1, i2]
+        setfield_gc(p1, i1, descr=valuedescr)
+        setfield_gc(p1, i2, descr=valuedescr)
+        jump(p1, i1, i2)
+        """
+        expected = """
+        [p1, i1, i2]
+        setfield_gc(p1, i2, descr=valuedescr)
+        jump(p1, i1, i2)
+        """
+        self.optimize_loop(ops, 'Not, Not, Not', expected)
+
+    def test_duplicate_setfield_2(self):
+        ops = """
+        [p1, i1, i3]
+        setfield_gc(p1, i1, descr=valuedescr)
+        i2 = getfield_gc(p1, descr=valuedescr)
+        setfield_gc(p1, i3, descr=valuedescr)
+        escape(i2)
+        jump(p1, i1, i3)
+        """
+        expected = """
+        [p1, i1, i3]
+        setfield_gc(p1, i3, descr=valuedescr)
+        escape(i1)
+        jump(p1, i1, i3)
+        """
+        self.optimize_loop(ops, 'Not, Not, Not', expected)
+
+    def test_duplicate_setfield_3(self):
+        ops = """
+        [p1, p2, i1, i3]
+        setfield_gc(p1, i1, descr=valuedescr)
+        i2 = getfield_gc(p2, descr=valuedescr)
+        setfield_gc(p1, i3, descr=valuedescr)
+        escape(i2)
+        jump(p1, p2, i1, i3)
+        """
+        # potential aliasing of p1 and p2 means that we cannot kill the
+        # the setfield_gc
+        self.optimize_loop(ops, 'Not, Not, Not, Not', ops)
+
+    def test_duplicate_setfield_4(self):
+        ops = """
+        [p1, i1, i2, p3]
+        setfield_gc(p1, i1, descr=valuedescr)
+        #
+        # some operations on which the above setfield_gc cannot have effect
+        i3 = getarrayitem_gc_pure(p3, 1, descr=arraydescr)
+        i4 = getarrayitem_gc(p3, i3, descr=arraydescr)
+        i5 = int_add(i3, i4)
+        setarrayitem_gc(p3, 0, i5, descr=arraydescr)
+        setfield_gc(p1, i4, descr=nextdescr)
+        #
+        setfield_gc(p1, i2, descr=valuedescr)
+        jump(p1, i1, i2, p3)
+        """
+        expected = """
+        [p1, i1, i2, p3]
+        #
+        i3 = getarrayitem_gc_pure(p3, 1, descr=arraydescr)
+        i4 = getarrayitem_gc(p3, i3, descr=arraydescr)
+        i5 = int_add(i3, i4)
+        setarrayitem_gc(p3, 0, i5, descr=arraydescr)
+        #
+        setfield_gc(p1, i2, descr=valuedescr)
+        setfield_gc(p1, i4, descr=nextdescr)
+        jump(p1, i1, i2, p3)
+        """
+        self.optimize_loop(ops, 'Not, Not, Not, Not', expected)
+
+    def test_duplicate_setfield_sideeffects_1(self):
+        ops = """
+        [p1, i1, i2]
+        setfield_gc(p1, i1, descr=valuedescr)
+        escape()
+        setfield_gc(p1, i2, descr=valuedescr)
+        jump(p1, i1, i2)
+        """
+        self.optimize_loop(ops, 'Not, Not, Not', ops)
+
+    def test_duplicate_setfield_residual_guard_1(self):
+        ops = """
+        [p1, i1, i2, i3]
+        setfield_gc(p1, i1, descr=valuedescr)
+        guard_true(i3) []
+        i4 = int_neg(i2)
+        setfield_gc(p1, i2, descr=valuedescr)
+        jump(p1, i1, i2, i4)
+        """
+        self.optimize_loop(ops, 'Not, Not, Not, Not', ops)
+
+    def test_duplicate_setfield_residual_guard_2(self):
+        # the difference with the previous test is that the field value is
+        # a virtual, which we try hard to keep virtual
+        ops = """
+        [p1, i2, i3]
+        p2 = new_with_vtable(ConstClass(node_vtable))
+        setfield_gc(p1, p2, descr=nextdescr)
+        guard_true(i3) []
+        i4 = int_neg(i2)
+        setfield_gc(p1, NULL, descr=nextdescr)
+        jump(p1, i2, i4)
+        """
+        expected = """
+        [p1, i2, i3]
+        guard_true(i3) [p1]
+        i4 = int_neg(i2)
+        setfield_gc(p1, NULL, descr=nextdescr)
+        jump(p1, i2, i4)
+        """
+        self.optimize_loop(ops, 'Not, Not, Not', expected)
+
+    def test_duplicate_setfield_residual_guard_3(self):
+        ops = """
+        [p1, i2, i3]
+        p2 = new_with_vtable(ConstClass(node_vtable))
+        setfield_gc(p2, i2, descr=valuedescr)
+        setfield_gc(p1, p2, descr=nextdescr)
+        guard_true(i3) []
+        i4 = int_neg(i2)
+        setfield_gc(p1, NULL, descr=nextdescr)
+        jump(p1, i2, i4)
+        """
+        expected = """
+        [p1, i2, i3]
+        guard_true(i3) [p1, i2]
+        i4 = int_neg(i2)
+        setfield_gc(p1, NULL, descr=nextdescr)
+        jump(p1, i2, i4)
+        """
+        self.optimize_loop(ops, 'Not, Not, Not', expected)
+
+    def test_duplicate_setfield_residual_guard_4(self):
+        # test that the setfield_gc does not end up between int_eq and
+        # the following guard_true
+        ops = """
+        [p1, i1, i2, i3]
+        setfield_gc(p1, i1, descr=valuedescr)
+        i5 = int_eq(i3, 5)
+        guard_true(i5) []
+        i4 = int_neg(i2)
+        setfield_gc(p1, i2, descr=valuedescr)
+        jump(p1, i1, i2, i4)
+        """
+        self.optimize_loop(ops, 'Not, Not, Not, Not', ops)
+
+    def test_duplicate_setfield_aliasing(self):
+        # a case where aliasing issues (and not enough cleverness) mean
+        # that we fail to remove any setfield_gc
+        ops = """
+        [p1, p2, i1, i2, i3]
+        setfield_gc(p1, i1, descr=valuedescr)
+        setfield_gc(p2, i2, descr=valuedescr)
+        setfield_gc(p1, i3, descr=valuedescr)
+        jump(p1, p2, i1, i2, i3)
+        """
+        self.optimize_loop(ops, 'Not, Not, Not, Not, Not', ops)
+
+    def test_duplicate_setfield_guard_value_const(self):
+        ops = """
+        [p1, i1, i2]
+        guard_value(p1, ConstPtr(myptr)) []
+        setfield_gc(p1, i1, descr=valuedescr)
+        setfield_gc(ConstPtr(myptr), i2, descr=valuedescr)
+        jump(p1, i1, i2)
+        """
+        expected = """
+        [i1, i2]
+        setfield_gc(ConstPtr(myptr), i2, descr=valuedescr)
+        jump(i1, i2)
+        """
+        self.optimize_loop(ops, 'Constant(myptr), Not, Not', expected)
+
     def test_duplicate_getarrayitem_1(self):
         ops = """
         [p1]
@@ -1634,6 +1813,14 @@
                 tag = ('virtual', self.namespace[match.group(2)])
             virtuals[pvar] = (tag, None, fieldstext)
         #
+        r2 = re.compile(r"([\w\d()]+)[.](\w+)\s*=\s*([\w\d()]+)")
+        pendingfields = []
+        for match in r2.finditer(text):
+            pvar = match.group(1)
+            pfieldname = match.group(2)
+            pfieldvar = match.group(3)
+            pendingfields.append((pvar, pfieldname, pfieldvar))
+        #
         def _variables_equal(box, varname, strict):
             if varname not in virtuals:
                 if strict:
@@ -1655,11 +1842,21 @@
                 else:
                     virtuals[varname] = tag, box, fieldstext
         #
-        basetext = text[:ends[0]]
+        basetext = text.splitlines()[0]
         varnames = [s.strip() for s in basetext.split(',')]
+        if varnames == ['']:
+            varnames = []
         assert len(boxes) == len(varnames)
         for box, varname in zip(boxes, varnames):
             _variables_equal(box, varname, strict=True)
+        for pvar, pfieldname, pfieldvar in pendingfields:
+            box = oparse.getvar(pvar)
+            fielddescr = self.namespace[pfieldname.strip()]
+            fieldbox = executor.execute(self.cpu,
+                                        rop.GETFIELD_GC,
+                                        fielddescr,
+                                        box)
+            _variables_equal(fieldbox, pfieldvar, strict=True)
         #
         for match in parts:
             pvar = match.group(1)
@@ -1918,6 +2115,57 @@
             where p7v is a node_vtable, valuedescr=iv
             ''')
 
+    def test_expand_fail_lazy_setfield_1(self):
+        self.make_fail_descr()
+        ops = """
+        [p1, i2, i3]
+        p2 = new_with_vtable(ConstClass(node_vtable))
+        setfield_gc(p2, i2, descr=valuedescr)
+        setfield_gc(p1, p2, descr=nextdescr)
+        guard_true(i3, descr=fdescr) []
+        i4 = int_neg(i2)
+        setfield_gc(p1, NULL, descr=nextdescr)
+        jump(p1, i2, i4)
+        """
+        expected = """
+        [p1, i2, i3]
+        guard_true(i3, descr=fdescr) [p1, i2]
+        i4 = int_neg(i2)
+        setfield_gc(p1, NULL, descr=nextdescr)
+        jump(p1, i2, i4)
+        """
+        self.optimize_loop(ops, 'Not, Not, Not', expected)
+        self.loop.inputargs[0].value = self.nodebox.value
+        self.check_expanded_fail_descr('''
+            p1.nextdescr = p2
+            where p2 is a node_vtable, valuedescr=i2
+            ''')
+
+    def test_expand_fail_lazy_setfield_2(self):
+        self.make_fail_descr()
+        ops = """
+        [i2, i3]
+        p2 = new_with_vtable(ConstClass(node_vtable))
+        setfield_gc(p2, i2, descr=valuedescr)
+        setfield_gc(ConstPtr(myptr), p2, descr=nextdescr)
+        guard_true(i3, descr=fdescr) []
+        i4 = int_neg(i2)
+        setfield_gc(ConstPtr(myptr), NULL, descr=nextdescr)
+        jump(i2, i4)
+        """
+        expected = """
+        [i2, i3]
+        guard_true(i3, descr=fdescr) [i2]
+        i4 = int_neg(i2)
+        setfield_gc(ConstPtr(myptr), NULL, descr=nextdescr)
+        jump(i2, i4)
+        """
+        self.optimize_loop(ops, 'Not, Not', expected)
+        self.check_expanded_fail_descr('''
+            ConstPtr(myptr).nextdescr = p2
+            where p2 is a node_vtable, valuedescr=i2
+            ''')
+
 
 class TestLLtype(BaseTestOptimizeOpt, LLtypeMixin):
 
@@ -2031,6 +2279,58 @@
         """
         self.optimize_loop(ops, 'Not, Not, Not', expected)
 
+    def test_residual_call_invalidates_some_read_caches_1(self):
+        ops = """
+        [p1, i1, p2, i2]
+        setfield_gc(p1, i1, descr=valuedescr)
+        setfield_gc(p2, i2, descr=adescr)
+        i3 = call(i1, descr=readadescr)
+        setfield_gc(p1, i3, descr=valuedescr)
+        setfield_gc(p2, i3, descr=adescr)
+        jump(p1, i1, p2, i2)
+        """
+        expected = """
+        [p1, i1, p2, i2]
+        setfield_gc(p2, i2, descr=adescr)
+        i3 = call(i1, descr=readadescr)
+        setfield_gc(p1, i3, descr=valuedescr)
+        setfield_gc(p2, i3, descr=adescr)
+        jump(p1, i1, p2, i2)
+        """
+        self.optimize_loop(ops, 'Not, Not, Not, Not', expected)
+
+    def test_residual_call_invalidates_some_read_caches_2(self):
+        ops = """
+        [p1, i1, p2, i2]
+        setfield_gc(p1, i1, descr=valuedescr)
+        setfield_gc(p2, i2, descr=adescr)
+        i3 = call(i1, descr=writeadescr)
+        setfield_gc(p1, i3, descr=valuedescr)
+        setfield_gc(p2, i3, descr=adescr)
+        jump(p1, i1, p2, i2)
+        """
+        expected = """
+        [p1, i1, p2, i2]
+        setfield_gc(p2, i2, descr=adescr)
+        i3 = call(i1, descr=writeadescr)
+        setfield_gc(p1, i3, descr=valuedescr)
+        setfield_gc(p2, i3, descr=adescr)
+        jump(p1, i1, p2, i2)
+        """
+        self.optimize_loop(ops, 'Not, Not, Not, Not', expected)
+
+    def test_residual_call_invalidates_some_read_caches_3(self):
+        ops = """
+        [p1, i1, p2, i2]
+        setfield_gc(p1, i1, descr=valuedescr)
+        setfield_gc(p2, i2, descr=adescr)
+        i3 = call(i1, descr=plaincalldescr)
+        setfield_gc(p1, i3, descr=valuedescr)
+        setfield_gc(p2, i3, descr=adescr)
+        jump(p1, i1, p2, i2)
+        """
+        self.optimize_loop(ops, 'Not, Not, Not, Not', ops)
+
 
 class TestOOtype(BaseTestOptimizeOpt, OOtypeMixin):
 

Modified: pypy/trunk/pypy/jit/metainterp/test/test_resume.py
==============================================================================
--- pypy/trunk/pypy/jit/metainterp/test/test_resume.py	(original)
+++ pypy/trunk/pypy/jit/metainterp/test/test_resume.py	Tue Dec 29 10:03:14 2009
@@ -12,6 +12,8 @@
     rd_frame_info_list = None
     rd_numb = None
     rd_consts = []
+    rd_virtuals = None
+    rd_pendingfields = None
 
 def test_tag():
     assert tag(3, 1) == rffi.r_short(3<<2|1)
@@ -40,6 +42,12 @@
     assert not tagged_list_eq([tag(1, TAGBOX)], [tag(-2, TAGBOX)])
     assert not tagged_list_eq([tag(1, TAGBOX), tag(-2, TAGBOX)], [tag(1, TAGBOX)])
 
+def test_vinfo():
+    v1 = AbstractVirtualInfo()
+    v1.set_content([1, 2, 4])
+    assert v1.equals([1, 2, 4])
+    assert not v1.equals([1, 2, 6])
+
 class MyMetaInterp:
     _already_allocated_resume_virtuals = None
 
@@ -80,7 +88,6 @@
                             tag(0, TAGBOX),
                             tag(1, TAGBOX)])
     storage.rd_numb = numb
-    storage.rd_virtuals = None
 
     b1s, b2s, b3s = [BoxInt(), BoxPtr(), BoxInt()]
     assert b1s != b3s
@@ -103,7 +110,6 @@
                             tag(0, TAGBOX),
                             tag(1, TAGBOX)])
     storage.rd_numb = numb
-    storage.rd_virtuals = None
     b1s, b2s, b3s = [BoxInt(), BoxPtr(), BoxInt()]
     assert b1s != b3s
     reader = ResumeDataReader(storage, [b1s, b2s, b3s], MyMetaInterp())
@@ -125,6 +131,7 @@
         rd_virtuals = [FakeVinfo(), None]
         rd_numb = []
         rd_consts = []
+        rd_pendingfields = None
     class FakeMetainterp(object):
         _already_allocated_resume_virtuals = None
         cpu = None
@@ -960,6 +967,46 @@
     assert ptr.a == 111
     assert ptr.b == lltype.nullptr(LLtypeMixin.NODE)
 
+
+def test_virtual_adder_pending_fields():
+    b2s, b4s = [BoxPtr(), BoxPtr()]
+    storage = Storage()
+    memo = ResumeDataLoopMemo(FakeMetaInterpStaticData())
+    modifier = ResumeDataVirtualAdder(storage, memo)
+    modifier.liveboxes_from_env = {}
+    modifier.liveboxes = {}
+    modifier.vfieldboxes = {}
+
+    v2 = OptValue(b2s)
+    v4 = OptValue(b4s)
+    modifier.register_box(b2s)
+    modifier.register_box(b4s)
+
+    values = {b4s: v4, b2s: v2}
+    liveboxes = []
+    modifier._number_virtuals(liveboxes, values, 0)
+    assert liveboxes == [b2s, b4s]
+    modifier._add_pending_fields([(LLtypeMixin.nextdescr, b2s, b4s)])
+    storage.rd_consts = memo.consts[:]
+    storage.rd_numb = None
+    # resume
+    demo55.next = lltype.nullptr(LLtypeMixin.NODE)
+    b2t = BoxPtr(demo55o)
+    b4t = BoxPtr(demo66o)
+    newboxes = _resume_remap(liveboxes, [b2s, b4s], b2t, b4t)
+
+    metainterp = MyMetaInterp()
+    reader = ResumeDataReader(storage, newboxes, metainterp)
+    assert reader.virtuals is None
+    trace = metainterp.trace
+    b2set = (rop.SETFIELD_GC, [b2t, b4t], None, LLtypeMixin.nextdescr)
+    expected = [b2set]
+
+    for x, y in zip(expected, trace):
+        assert x == y
+    assert demo55.next == demo66
+
+
 def test_invalidation_needed():
     class options:
         failargs_limit = 10

Modified: pypy/trunk/pypy/rpython/rptr.py
==============================================================================
--- pypy/trunk/pypy/rpython/rptr.py	(original)
+++ pypy/trunk/pypy/rpython/rptr.py	Tue Dec 29 10:03:14 2009
@@ -39,6 +39,14 @@
         attr = hop.args_s[1].const
         if isinstance(hop.s_result, annmodel.SomeLLADTMeth):
             return hop.inputarg(hop.r_result, arg=0)
+        try:
+            self.lowleveltype._example()._lookup_adtmeth(attr)
+        except AttributeError:
+            pass
+        else:
+            assert hop.s_result.is_constant()
+            return hop.inputconst(hop.r_result, hop.s_result.const)
+        assert attr in self.lowleveltype.TO._flds # check that the field exists
         FIELD_TYPE = getattr(self.lowleveltype.TO, attr)
         if isinstance(FIELD_TYPE, lltype.ContainerType):
             if (attr, FIELD_TYPE) == self.lowleveltype.TO._first_struct():

Modified: pypy/trunk/pypy/rpython/test/test_rptr.py
==============================================================================
--- pypy/trunk/pypy/rpython/test/test_rptr.py	(original)
+++ pypy/trunk/pypy/rpython/test/test_rptr.py	Tue Dec 29 10:03:14 2009
@@ -337,3 +337,13 @@
         return f([1])
     s, t = ll_rtype(lltest, [])
     assert s.is_constant() == False
+
+def test_staticadtmeths():
+    ll_func = staticAdtMethod(lambda x: x + 42)
+    S = GcStruct('S', adtmeths={'ll_func': ll_func})
+    def f():
+        return malloc(S).ll_func(5)
+    s, t = ll_rtype(f, [])
+    graphf = t.graphs[0]
+    for op in graphf.startblock.operations:
+        assert op.opname != 'getfield'

Modified: pypy/trunk/pypy/translator/backendopt/test/test_writeanalyze.py
==============================================================================
--- pypy/trunk/pypy/translator/backendopt/test/test_writeanalyze.py	(original)
+++ pypy/trunk/pypy/translator/backendopt/test/test_writeanalyze.py	Tue Dec 29 10:03:14 2009
@@ -4,14 +4,15 @@
 from pypy.translator.translator import TranslationContext, graphof
 from pypy.translator.simplify import get_funcobj
 from pypy.translator.backendopt.writeanalyze import WriteAnalyzer, top_set
+from pypy.translator.backendopt.writeanalyze import ReadWriteAnalyzer
 from pypy.translator.backendopt.all import backend_optimizations
 from pypy.conftest import option
 
 
-class BaseTestCanRaise(object):
+class BaseTest(object):
 
     type_system = None
-
+    Analyzer = WriteAnalyzer
     
     def translate(self, func, sig):
         t = TranslationContext()
@@ -19,7 +20,10 @@
         t.buildrtyper(type_system=self.type_system).specialize()
         if option.view:
             t.view()
-        return t, WriteAnalyzer(t)
+        return t, self.Analyzer(t)
+
+
+class BaseTestWriteAnalyze(BaseTest):
 
     def test_writes_simple(self):
         def g(x):
@@ -146,7 +150,7 @@
         assert not result
 
 
-class TestLLtype(BaseTestCanRaise):
+class TestLLtype(BaseTestWriteAnalyze):
     type_system = 'lltype'
 
     def test_list(self):
@@ -205,7 +209,7 @@
         assert name.endswith("foobar")
 
 
-class TestOOtype(BaseTestCanRaise):
+class TestOOtype(BaseTestWriteAnalyze):
     type_system = 'ootype'
     
     def test_array(self):
@@ -240,3 +244,88 @@
 
         result = wa.analyze(ggraph.startblock.operations[0])
         assert result is top_set
+
+
+class TestLLtypeReadWriteAnalyze(BaseTest):
+    Analyzer = ReadWriteAnalyzer
+    type_system = 'lltype'
+
+    def test_read_simple(self):
+        def g(x):
+            return True
+
+        def f(x):
+            return g(x - 1)
+        t, wa = self.translate(f, [int])
+        fgraph = graphof(t, f)
+        result = wa.analyze(fgraph.startblock.operations[0])
+        assert not result
+
+    def test_read_really(self):
+        class A(object):
+            def __init__(self, y):
+                self.y = y
+            def f(self):
+                self.x = 1
+                return self.y
+        def h(flag):
+            obj = A(flag)
+            return obj.f()
+        
+        t, wa = self.translate(h, [int])
+        hgraph = graphof(t, h)
+        op_call_f = hgraph.startblock.operations[-1]
+
+        # check that we fished the expected ops
+        assert op_call_f.opname == "direct_call"
+        assert get_funcobj(op_call_f.args[0].value)._name == 'A.f'
+
+        result = wa.analyze(op_call_f)
+        assert len(result) == 2
+        result = list(result)
+        result.sort()
+        [(struct1, T1, name1), (struct2, T2, name2)] = result
+        assert struct1 == "readstruct"
+        assert name1.endswith("y")
+        assert struct2 == "struct"
+        assert name2.endswith("x")
+        assert T1 == T2
+
+    def test_contains(self):
+        def g(x, y, z):
+            l = [x]
+            return f(l, y, z)
+        def f(x, y, z):
+            return y in x
+
+        t, wa = self.translate(g, [int, int, int])
+        ggraph = graphof(t, g)
+        assert ggraph.startblock.operations[-1].opname == 'direct_call'
+
+        result = wa.analyze(ggraph.startblock.operations[-1])
+        ARRAYPTR = list(result)[0][1]
+        assert list(result) == [("readarray", ARRAYPTR)]
+        assert isinstance(ARRAYPTR.TO, lltype.GcArray)
+
+    def test_adt_method(self):
+        def ll_callme(n):
+            return n
+        ll_callme = lltype.staticAdtMethod(ll_callme)
+        S = lltype.GcStruct('S', ('x', lltype.Signed),
+                            adtmeths = {'yep': True,
+                                        'callme': ll_callme})
+        def g(x, y, z):
+            p = lltype.malloc(S)
+            p.x = x
+            if p.yep:
+                z *= p.callme(y)
+            return z
+        def f(x, y, z):
+            return g(x, y, z)
+
+        t, wa = self.translate(f, [int, int, int])
+        fgraph = graphof(t, f)
+        assert fgraph.startblock.operations[-1].opname == 'direct_call'
+
+        result = wa.analyze(fgraph.startblock.operations[-1])
+        assert list(result) == [("struct", lltype.Ptr(S), "x")]

Modified: pypy/trunk/pypy/translator/backendopt/writeanalyze.py
==============================================================================
--- pypy/trunk/pypy/translator/backendopt/writeanalyze.py	(original)
+++ pypy/trunk/pypy/translator/backendopt/writeanalyze.py	Tue Dec 29 10:03:14 2009
@@ -45,3 +45,15 @@
             elif methname in ('ll_getitem_fast', 'll_length'):
                 return self.bottom_result()
         return graphanalyze.GraphAnalyzer.analyze_external_method(self, op, TYPE, meth)
+
+
+class ReadWriteAnalyzer(WriteAnalyzer):
+
+    def analyze_simple_operation(self, op):
+        if op.opname == "getfield":
+            return frozenset([
+                ("readstruct", op.args[0].concretetype, op.args[1].value)])
+        elif op.opname == "getarrayitem":
+            return frozenset([
+                ("readarray", op.args[0].concretetype)])
+        return WriteAnalyzer.analyze_simple_operation(self, op)



More information about the Pypy-commit mailing list