[pypy-commit] pypy py3k: merge default

pjenvey noreply at buildbot.pypy.org
Fri Mar 21 00:02:33 CET 2014


Author: Philip Jenvey <pjenvey at underboss.org>
Branch: py3k
Changeset: r70135:706e3a4c1ffa
Date: 2014-03-20 15:41 -0700
http://bitbucket.org/pypy/pypy/changeset/706e3a4c1ffa/

Log:	merge default

diff --git a/pypy/doc/cpython_differences.rst b/pypy/doc/cpython_differences.rst
--- a/pypy/doc/cpython_differences.rst
+++ b/pypy/doc/cpython_differences.rst
@@ -292,6 +292,10 @@
   depending on the compiler settings, the default of 768KB is enough
   for about 1400 calls.
 
+* since the implementation of dictionary is different, the exact number
+  which ``__hash__`` and ``__eq__`` are called is different. Since CPython
+  does not give any specific guarantees either, don't rely on it.
+
 * assignment to ``__class__`` is limited to the cases where it
   works on CPython 2.5.  On CPython 2.6 and 2.7 it works in a bit
   more cases, which are not supported by PyPy so far.  (If needed,
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
@@ -114,3 +114,6 @@
 app-level.  The `Buffer` class is now used by `W_MemoryView` and
 `W_Buffer`, which is not present in Python 3.  Previously `W_Buffer` was
 an alias to `Buffer`, which was wrappable itself.
+
+.. branch: improve-consecutive-dict-lookups
+Improve the situation when dict lookups of the same key are performed in a chain
diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -441,11 +441,10 @@
 
         return name
 
-    def getbuiltinmodule(self, name, force_init=False, reuse=True):
+    def getbuiltinmodule(self, name, force_init=False):
         w_name = self.wrap(name)
         w_modules = self.sys.get('modules')
         if not force_init:
-            assert reuse is True
             try:
                 return self.getitem(w_modules, w_name)
             except OperationError, e:
@@ -464,15 +463,7 @@
             # Initialize the module
             from pypy.interpreter.module import Module
             if isinstance(w_mod, Module):
-                if not reuse and w_mod.startup_called:
-                    # Create a copy of the module
-                    w_mod.getdict(self)  # unlazy w_initialdict
-                    w_new = self.wrap(Module(self, w_name))
-                    self.call_method(w_new.getdict(self), 'update',
-                                     w_mod.w_initialdict)
-                    w_mod = w_new
-                else:
-                    w_mod.init(self)
+                w_mod.init(self)
 
             # Add the module to sys.modules
             self.setitem(w_modules, w_name, w_mod)
diff --git a/pypy/module/imp/importing.py b/pypy/module/imp/importing.py
--- a/pypy/module/imp/importing.py
+++ b/pypy/module/imp/importing.py
@@ -585,8 +585,7 @@
         return space.call_method(find_info.w_loader, "load_module", w_modulename)
 
     if find_info.modtype == C_BUILTIN:
-        return space.getbuiltinmodule(find_info.filename, force_init=True,
-                                      reuse=reuse)
+        return space.getbuiltinmodule(find_info.filename, force_init=True)
 
     if find_info.modtype in (PY_SOURCE, PY_COMPILED, C_EXTENSION, PKG_DIRECTORY):
         w_mod = None
diff --git a/pypy/module/imp/test/test_app.py b/pypy/module/imp/test/test_app.py
--- a/pypy/module/imp/test/test_app.py
+++ b/pypy/module/imp/test/test_app.py
@@ -219,6 +219,7 @@
 
     def test_builtin_reimport(self):
         # from https://bugs.pypy.org/issue1514
+        skip("fix me")
         import sys, marshal
 
         old = marshal.loads
@@ -238,6 +239,7 @@
         # taken from https://bugs.pypy.org/issue1514, with extra cases
         # that show a difference with CPython: we can get on CPython
         # several module objects for the same built-in module :-(
+        skip("several built-in module objects: not supported by pypy")
         import sys, marshal
 
         old = marshal.loads
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
@@ -652,6 +652,7 @@
         assert hasattr(time, 'clock')
 
     def test_reimport_builtin_simple_case_2(self):
+        skip("fix me")
         import sys, time
         time.foo = "bar"
         del sys.modules['time']
@@ -660,6 +661,7 @@
 
     def test_reimport_builtin(self):
         import imp, sys, time
+        skip("fix me")
         oldpath = sys.path
         time.tzset = "<test_reimport_builtin removed this>"
 
diff --git a/rpython/jit/backend/arm/opassembler.py b/rpython/jit/backend/arm/opassembler.py
--- a/rpython/jit/backend/arm/opassembler.py
+++ b/rpython/jit/backend/arm/opassembler.py
@@ -583,6 +583,10 @@
     emit_op_getfield_raw_pure = emit_op_getfield_gc
     emit_op_getfield_gc_pure = emit_op_getfield_gc
 
+    def emit_op_increment_debug_counter(self, op, arglocs, regalloc, fcond):
+        # XXX implement me
+        return fcond
+
     def emit_op_getinteriorfield_gc(self, op, arglocs, regalloc, fcond):
         (base_loc, index_loc, res_loc,
             ofs_loc, ofs, itemsize, fieldsize) = arglocs
diff --git a/rpython/jit/backend/arm/regalloc.py b/rpython/jit/backend/arm/regalloc.py
--- a/rpython/jit/backend/arm/regalloc.py
+++ b/rpython/jit/backend/arm/regalloc.py
@@ -849,6 +849,10 @@
     prepare_op_getfield_raw_pure = prepare_op_getfield_gc
     prepare_op_getfield_gc_pure = prepare_op_getfield_gc
 
+    def prepare_op_increment_debug_counter(self, op, fcond):
+        # XXX implement me
+        return []
+
     def prepare_op_getinteriorfield_gc(self, op, fcond):
         t = unpack_interiorfielddescr(op.getdescr())
         ofs, itemsize, fieldsize, sign = t
diff --git a/rpython/jit/backend/llgraph/runner.py b/rpython/jit/backend/llgraph/runner.py
--- a/rpython/jit/backend/llgraph/runner.py
+++ b/rpython/jit/backend/llgraph/runner.py
@@ -553,6 +553,10 @@
         else:
             return self.bh_raw_load_i(struct, offset, descr)
 
+    def bh_increment_debug_counter(self, addr):
+        p = rffi.cast(rffi.CArrayPtr(lltype.Signed), addr)
+        p[0] += 1
+
     def unpack_arraydescr_size(self, arraydescr):
         from rpython.jit.backend.llsupport.symbolic import get_array_token
         from rpython.jit.backend.llsupport.descr import get_type_flag, FLAG_SIGNED
diff --git a/rpython/jit/backend/llsupport/assembler.py b/rpython/jit/backend/llsupport/assembler.py
--- a/rpython/jit/backend/llsupport/assembler.py
+++ b/rpython/jit/backend/llsupport/assembler.py
@@ -15,7 +15,7 @@
 
 DEBUG_COUNTER = lltype.Struct('DEBUG_COUNTER',
     # 'b'ridge, 'l'abel or # 'e'ntry point
-    ('i', lltype.Signed),
+    ('i', lltype.Signed),      # first field, at offset 0
     ('type', lltype.Char),
     ('number', lltype.Signed)
 )
@@ -64,7 +64,6 @@
         self.cpu = cpu
         self.memcpy_addr = 0
         self.rtyper = cpu.rtyper
-        self.debug_counter_descr = cpu.fielddescrof(DEBUG_COUNTER, 'i')
         self._debug = False
 
     def setup_once(self):
@@ -265,14 +264,8 @@
     def _append_debugging_code(self, operations, tp, number, token):
         counter = self._register_counter(tp, number, token)
         c_adr = ConstInt(rffi.cast(lltype.Signed, counter))
-        box = BoxInt()
-        box2 = BoxInt()
-        ops = [ResOperation(rop.GETFIELD_RAW, [c_adr],
-                            box, descr=self.debug_counter_descr),
-               ResOperation(rop.INT_ADD, [box, ConstInt(1)], box2),
-               ResOperation(rop.SETFIELD_RAW, [c_adr, box2],
-                            None, descr=self.debug_counter_descr)]
-        operations.extend(ops)
+        operations.append(
+            ResOperation(rop.INCREMENT_DEBUG_COUNTER, [c_adr], None))
 
     def _register_counter(self, tp, number, token):
         # YYY very minor leak -- we need the counters to stay alive
diff --git a/rpython/jit/backend/test/runner_test.py b/rpython/jit/backend/test/runner_test.py
--- a/rpython/jit/backend/test/runner_test.py
+++ b/rpython/jit/backend/test/runner_test.py
@@ -3736,7 +3736,7 @@
             assert False, 'should not be called'
         from rpython.jit.codewriter.effectinfo import EffectInfo
 
-        effectinfo = EffectInfo([], [], [], [], EffectInfo.EF_CANNOT_RAISE, EffectInfo.OS_MATH_SQRT)
+        effectinfo = EffectInfo([], [], [], [], [], [], EffectInfo.EF_CANNOT_RAISE, EffectInfo.OS_MATH_SQRT)
         FPTR = self.Ptr(self.FuncType([lltype.Float], lltype.Float))
         func_ptr = llhelper(FPTR, math_sqrt)
         FUNC = deref(FPTR)
@@ -4338,3 +4338,12 @@
         assert rffi.cast(lltype.Signed, a[0]) == -7654
         assert rffi.cast(lltype.Signed, a[1]) == 777
         lltype.free(a, flavor='raw')
+
+    def test_increment_debug_counter(self):
+        foo = lltype.malloc(rffi.CArray(lltype.Signed), 1, flavor='raw')
+        foo[0] = 1789200
+        self.execute_operation(rop.INCREMENT_DEBUG_COUNTER,
+                               [ConstInt(rffi.cast(lltype.Signed, foo))],
+                               'void')
+        assert foo[0] == 1789201
+        lltype.free(foo, flavor='raw')
diff --git a/rpython/jit/backend/x86/assembler.py b/rpython/jit/backend/x86/assembler.py
--- a/rpython/jit/backend/x86/assembler.py
+++ b/rpython/jit/backend/x86/assembler.py
@@ -287,7 +287,6 @@
                   cast_instance_to_gcref(self.cpu.propagate_exception_descr))
         ofs = self.cpu.get_ofs_of_frame_field('jf_descr')
         self.mc.MOV(RawEbpLoc(ofs), imm(propagate_exception_descr))
-        self.mc.MOV_rr(eax.value, ebp.value)
         #
         self._call_footer()
         rawstart = self.mc.materialize(self.cpu.asmmemmgr, [])
@@ -435,8 +434,8 @@
             self.wb_slowpath[withcards + 2 * withfloats] = rawstart
 
     @rgc.no_release_gil
-    def assemble_loop(self, logger, loopname, inputargs, operations, looptoken,
-                      log):
+    def assemble_loop(self, inputargs, operations, looptoken, log,
+                      loopname, logger):
         '''adds the following attributes to looptoken:
                _ll_function_addr    (address of the generated func, as an int)
                _ll_loop_code       (debug: addr of the start of the ResOps)
@@ -515,8 +514,8 @@
                        size_excluding_failure_stuff - looppos)
 
     @rgc.no_release_gil
-    def assemble_bridge(self, logger, faildescr, inputargs, operations,
-                        original_loop_token, log):
+    def assemble_bridge(self, faildescr, inputargs, operations,
+                        original_loop_token, log, logger):
         if not we_are_translated():
             # Arguments should be unique
             assert len(set(inputargs)) == len(inputargs)
@@ -761,6 +760,9 @@
             #
 
     def _call_footer(self):
+        # the return value is the jitframe
+        self.mc.MOV_rr(eax.value, ebp.value)
+
         gcrootmap = self.cpu.gc_ll_descr.gcrootmap
         if gcrootmap and gcrootmap.is_shadow_stack:
             self._call_footer_shadowstack(gcrootmap)
@@ -1467,6 +1469,14 @@
                                                 ofs_loc)
         self.load_from_mem(resloc, src_addr, fieldsize_loc, sign_loc)
 
+    def genop_discard_increment_debug_counter(self, op, arglocs):
+        # The argument should be an immediate address.  This should
+        # generate code equivalent to a GETFIELD_RAW, an ADD(1), and a
+        # SETFIELD_RAW.  Here we use the direct from-memory-to-memory
+        # increment operation of x86.
+        base_loc, = arglocs
+        self.mc.INC(mem(base_loc, 0))
+
     def genop_discard_setfield_gc(self, op, arglocs):
         base_loc, ofs_loc, size_loc, value_loc = arglocs
         assert isinstance(size_loc, ImmedLoc)
@@ -1823,12 +1833,8 @@
         # did just above.
         ofs = self.cpu.get_ofs_of_frame_field('jf_descr')
         ofs2 = self.cpu.get_ofs_of_frame_field('jf_gcmap')
-        mc.POP(eax)
-        mc.MOV_br(ofs2, eax.value)
-        mc.POP(eax)
-        mc.MOV_br(ofs, eax.value)
-        # the return value is the jitframe
-        mc.MOV_rr(eax.value, ebp.value)
+        mc.POP_b(ofs2)
+        mc.POP_b(ofs)
 
         self._call_footer()
         rawstart = mc.materialize(self.cpu.asmmemmgr, [])
@@ -1861,7 +1867,6 @@
             # keep that one and kill all the others
             ofs = self.cpu.get_ofs_of_frame_field('jf_gcmap')
             self.mc.MOV_bi(ofs, 0)
-        self.mc.MOV_rr(eax.value, ebp.value)
         # exit function
         self._call_footer()
 
diff --git a/rpython/jit/backend/x86/regalloc.py b/rpython/jit/backend/x86/regalloc.py
--- a/rpython/jit/backend/x86/regalloc.py
+++ b/rpython/jit/backend/x86/regalloc.py
@@ -1003,6 +1003,10 @@
     consider_getfield_raw_pure = consider_getfield_gc
     consider_getfield_gc_pure = consider_getfield_gc
 
+    def consider_increment_debug_counter(self, op):
+        base_loc = self.loc(op.getarg(0))
+        self.perform_discard(op, [base_loc])
+
     def consider_getarrayitem_gc(self, op):
         itemsize, ofs, sign = unpack_arraydescr(op.getdescr())
         args = op.getarglist()
diff --git a/rpython/jit/backend/x86/regloc.py b/rpython/jit/backend/x86/regloc.py
--- a/rpython/jit/backend/x86/regloc.py
+++ b/rpython/jit/backend/x86/regloc.py
@@ -488,12 +488,22 @@
             for possible_code in unrolling_location_codes:
                 if code == possible_code:
                     val = getattr(loc, "value_" + possible_code)()
-                    if self.WORD == 8 and possible_code == 'i' and not rx86.fits_in_32bits(val):
-                        self._load_scratch(val)
+                    # Faking out of certain operations for x86_64
+                    fits32 = rx86.fits_in_32bits
+                    if possible_code == 'i' and not fits32(val):
+                        self._load_scratch(val)    # for 'PUSH(imm)'
                         _rx86_getattr(self, name + "_r")(X86_64_SCRATCH_REG.value)
-                    else:
-                        methname = name + "_" + possible_code
-                        _rx86_getattr(self, methname)(val)
+                        return
+                    if possible_code == 'j' and not fits32(val):
+                        val = self._addr_as_reg_offset(val)
+                        _rx86_getattr(self, name + "_m")(val)
+                        return
+                    if possible_code == 'm' and not fits32(val[1]):
+                        val = self._fix_static_offset_64_m(val)
+                    if possible_code == 'a' and not fits32(val[3]):
+                        val = self._fix_static_offset_64_a(val)
+                    methname = name + "_" + possible_code
+                    _rx86_getattr(self, methname)(val)
 
         return func_with_new_name(INSN, "INSN_" + name)
 
@@ -600,6 +610,7 @@
     TEST8 = _binaryop('TEST8')
     BTS = _binaryop('BTS')
 
+    INC = _unaryop('INC')
     ADD = _binaryop('ADD')
     SUB = _binaryop('SUB')
     IMUL = _binaryop('IMUL')
diff --git a/rpython/jit/backend/x86/runner.py b/rpython/jit/backend/x86/runner.py
--- a/rpython/jit/backend/x86/runner.py
+++ b/rpython/jit/backend/x86/runner.py
@@ -93,16 +93,15 @@
 
     def compile_loop(self, inputargs, operations, looptoken, log=True,
                      name='', logger=None):
-        return self.assembler.assemble_loop(logger, name, inputargs, operations,
-                                            looptoken, log=log)
+        return self.assembler.assemble_loop(inputargs, operations, looptoken, log,
+                                            name, logger)
 
     def compile_bridge(self, faildescr, inputargs, operations,
                        original_loop_token, log=True, logger=None):
         clt = original_loop_token.compiled_loop_token
         clt.compiling_a_bridge()
-        return self.assembler.assemble_bridge(logger, faildescr, inputargs,
-                                              operations,
-                                              original_loop_token, log=log)
+        return self.assembler.assemble_bridge(faildescr, inputargs, operations,
+                                              original_loop_token, log, logger)
 
     def clear_latest_values(self, count):
         setitem = self.assembler.fail_boxes_ptr.setitem
diff --git a/rpython/jit/backend/x86/rx86.py b/rpython/jit/backend/x86/rx86.py
--- a/rpython/jit/backend/x86/rx86.py
+++ b/rpython/jit/backend/x86/rx86.py
@@ -470,6 +470,9 @@
 
     # ------------------------------ Arithmetic ------------------------------
 
+    INC_m = insn(rex_w, '\xFF', orbyte(0), mem_reg_plus_const(1))
+    INC_j = insn(rex_w, '\xFF', orbyte(0), abs_(1))
+
     ADD_ri,ADD_rr,ADD_rb,_,_,ADD_rm,ADD_rj,_,_ = common_modes(0)
     OR_ri, OR_rr, OR_rb, _,_,OR_rm, OR_rj, _,_ = common_modes(1)
     AND_ri,AND_rr,AND_rb,_,_,AND_rm,AND_rj,_,_ = common_modes(4)
diff --git a/rpython/jit/backend/x86/test/test_assembler.py b/rpython/jit/backend/x86/test/test_assembler.py
--- a/rpython/jit/backend/x86/test/test_assembler.py
+++ b/rpython/jit/backend/x86/test/test_assembler.py
@@ -55,9 +55,7 @@
         asm = cpu.assembler
         asm.setup_once()
         asm.setup(looptoken)
-        self.fm = X86FrameManager(0)
-        self.xrm = X86XMMRegisterManager(None, frame_manager=self.fm,
-                                         assembler=asm)
+        self.xrm = X86XMMRegisterManager(None, assembler=asm)
         callback(asm)
         asm.mc.RET()
         rawstart = asm.materialize_loop(looptoken)
@@ -75,29 +73,6 @@
         res = self.do_test(callback)
         assert res == 42
 
-    def test_push_stack(self):
-        def callback(asm):
-            loc = self.fm.frame_pos(5, INT)
-            asm.mc.SUB_ri(esp.value, 64)
-            asm.mov(imm(42), loc)
-            asm.regalloc_push(loc)
-            asm.regalloc_pop(eax)
-            asm.mc.ADD_ri(esp.value, 64)
-        res = self.do_test(callback)
-        assert res == 42
-
-    def test_pop_stack(self):
-        def callback(asm):
-            loc = self.fm.frame_pos(5, INT)
-            asm.mc.SUB_ri(esp.value, 64)
-            asm.mov(imm(42), edx)
-            asm.regalloc_push(edx)
-            asm.regalloc_pop(loc)
-            asm.mov(loc, eax)
-            asm.mc.ADD_ri(esp.value, 64)
-        res = self.do_test(callback)
-        assert res == 42
-
     def test_simple_xmm(self):
         def callback(asm):
             c = ConstFloat(longlong.getfloatstorage(-42.5))
@@ -109,32 +84,8 @@
         res = self.do_test(callback)
         assert res == -42
 
-    def test_push_stack_xmm(self):
+    def test_xmm_pushes_8_bytes(self):
         def callback(asm):
-            c = ConstFloat(longlong.getfloatstorage(-42.5))
-            loc = self.xrm.convert_to_imm(c)
-            loc2 = self.fm.frame_pos(4, FLOAT)
-            asm.mc.SUB_ri(esp.value, 64)
-            asm.mov(loc, xmm5)
-            asm.mov(xmm5, loc2)
-            asm.regalloc_push(loc2)
-            asm.regalloc_pop(xmm0)
-            asm.mc.ADD_ri(esp.value, 64)
-            asm.mc.CVTTSD2SI(eax, xmm0)
-        res = self.do_test(callback)
-        assert res == -42
-
-    def test_pop_stack_xmm(self):
-        def callback(asm):
-            c = ConstFloat(longlong.getfloatstorage(-42.5))
-            loc = self.xrm.convert_to_imm(c)
-            loc2 = self.fm.frame_pos(4, FLOAT)
-            asm.mc.SUB_ri(esp.value, 64)
-            asm.mov(loc, xmm5)
             asm.regalloc_push(xmm5)
-            asm.regalloc_pop(loc2)
-            asm.mov(loc2, xmm0)
-            asm.mc.ADD_ri(esp.value, 64)
-            asm.mc.CVTTSD2SI(eax, xmm0)
-        res = self.do_test(callback)
-        assert res == -42
+            asm.mc.ADD(esp, imm(8))
+        self.do_test(callback)
diff --git a/rpython/jit/backend/x86/test/test_regloc.py b/rpython/jit/backend/x86/test/test_regloc.py
--- a/rpython/jit/backend/x86/test/test_regloc.py
+++ b/rpython/jit/backend/x86/test/test_regloc.py
@@ -373,3 +373,56 @@
                 '\x59'
         )
         assert cb.getvalue() == expected_instructions
+
+    # ------------------------------------------------------------
+
+    def test_push_immed64(self):
+        immed = 0x0123456789ABCDEF
+        cb = LocationCodeBuilder64()
+        cb.PUSH(imm(immed))
+        #
+        expected_instructions = (
+                # mov r11, 0x0123456789ABCDEF
+                '\x49\xBB\xEF\xCD\xAB\x89\x67\x45\x23\x01'
+                # push r11
+                '\x41\x53'
+        )
+        assert cb.getvalue() == expected_instructions
+
+    def test_inc_64bit_address_1(self):
+        base_addr = 0x0123456789ABCDEF
+        cb = LocationCodeBuilder64()
+        cb.INC(AddressLoc(ImmedLoc(0), ImmedLoc(0), 0, base_addr))
+        # this case is a INC_j
+        #
+        expected_instructions = (
+                # mov r11, 0x0123456789ABCDEF
+                '\x49\xBB\xEF\xCD\xAB\x89\x67\x45\x23\x01'
+                # inc [r11]
+                '\x49\xFF\x03'
+        )
+        assert cb.getvalue() == expected_instructions
+
+    def test_inc_64bit_address_2(self):
+        py.test.skip("there is no unary instruction INSN_a so far")
+        base_addr = 0x0123456789ABCDEF
+        cb = LocationCodeBuilder64()
+        cb.INC(AddressLoc(ImmedLoc(0), edx, 3, base_addr))
+        # this case would be a INC_a
+        xxx
+
+    def test_inc_64bit_address_3(self):
+        base_addr = 0x0123456789ABCDEF
+        cb = LocationCodeBuilder64()
+        cb.INC(AddressLoc(eax, ImmedLoc(0), 0, base_addr))
+        # this case is a INC_m
+        #
+        expected_instructions = (
+                # mov r11, 0x0123456789ABCDEF
+                '\x49\xBB\xEF\xCD\xAB\x89\x67\x45\x23\x01'
+                # lea r11, [rax+r11]
+                '\x4E\x8D\x1C\x18'
+                # inc [r11]
+                '\x49\xFF\x03'
+        )
+        assert cb.getvalue() == expected_instructions
diff --git a/rpython/jit/backend/x86/test/test_runner.py b/rpython/jit/backend/x86/test/test_runner.py
--- a/rpython/jit/backend/x86/test/test_runner.py
+++ b/rpython/jit/backend/x86/test/test_runner.py
@@ -427,8 +427,8 @@
         debug._log = None
         #
         assert ops_offset is looptoken._x86_ops_offset
-        # 2*(getfield_raw/int_add/setfield_raw) + ops + None
-        assert len(ops_offset) == 2*3 + len(operations) + 1
+        # 2*increment_debug_counter + ops + None
+        assert len(ops_offset) == 2 + len(operations) + 1
         assert (ops_offset[operations[0]] <=
                 ops_offset[operations[1]] <=
                 ops_offset[operations[2]] <=
diff --git a/rpython/jit/codewriter/call.py b/rpython/jit/codewriter/call.py
--- a/rpython/jit/codewriter/call.py
+++ b/rpython/jit/codewriter/call.py
@@ -178,7 +178,7 @@
         return (fnaddr, calldescr)
 
     def getcalldescr(self, op, oopspecindex=EffectInfo.OS_NONE,
-                     extraeffect=None):
+                     extraeffect=None, extradescr=None):
         """Return the calldescr that describes all calls done by 'op'.
         This returns a calldescr that we can put in the corresponding
         call operation in the calling jitcode.  It gets an effectinfo
@@ -259,6 +259,7 @@
         effectinfo = effectinfo_from_writeanalyze(
             self.readwrite_analyzer.analyze(op, self.seen), self.cpu,
             extraeffect, oopspecindex, can_invalidate, call_release_gil_target,
+            extradescr,
         )
         #
         assert effectinfo is not None
diff --git a/rpython/jit/codewriter/effectinfo.py b/rpython/jit/codewriter/effectinfo.py
--- a/rpython/jit/codewriter/effectinfo.py
+++ b/rpython/jit/codewriter/effectinfo.py
@@ -21,6 +21,7 @@
     OS_ARRAYCOPY                = 1    # "list.ll_arraycopy"
     OS_STR2UNICODE              = 2    # "str.str2unicode"
     OS_SHRINK_ARRAY             = 3    # rgc.ll_shrink_array
+    OS_DICT_LOOKUP              = 4    # ll_dict_lookup
     #
     OS_STR_CONCAT               = 22   # "stroruni.concat"
     OS_STR_SLICE                = 23   # "stroruni.slice"
@@ -88,15 +89,18 @@
     # for debugging:
     _OS_CANRAISE = set([
         OS_NONE, OS_STR2UNICODE, OS_LIBFFI_CALL, OS_RAW_MALLOC_VARSIZE_CHAR,
-        OS_JIT_FORCE_VIRTUAL, OS_SHRINK_ARRAY,
+        OS_JIT_FORCE_VIRTUAL, OS_SHRINK_ARRAY, OS_DICT_LOOKUP,
     ])
 
     def __new__(cls, readonly_descrs_fields, readonly_descrs_arrays,
+                readonly_descrs_interiorfields,
                 write_descrs_fields, write_descrs_arrays,
+                write_descrs_interiorfields,
                 extraeffect=EF_CAN_RAISE,
                 oopspecindex=OS_NONE,
                 can_invalidate=False,
-                call_release_gil_target=llmemory.NULL):
+                call_release_gil_target=llmemory.NULL,
+                extradescrs=None):
         key = (frozenset_or_none(readonly_descrs_fields),
                frozenset_or_none(readonly_descrs_arrays),
                frozenset_or_none(write_descrs_fields),
@@ -121,17 +125,21 @@
         result = object.__new__(cls)
         result.readonly_descrs_fields = readonly_descrs_fields
         result.readonly_descrs_arrays = readonly_descrs_arrays
+        result.readonly_descrs_interiorfields = readonly_descrs_interiorfields
         if extraeffect == EffectInfo.EF_LOOPINVARIANT or \
            extraeffect == EffectInfo.EF_ELIDABLE_CANNOT_RAISE or \
            extraeffect == EffectInfo.EF_ELIDABLE_CAN_RAISE:
             result.write_descrs_fields = []
             result.write_descrs_arrays = []
+            result.write_descrs_interiorfields = []
         else:
             result.write_descrs_fields = write_descrs_fields
             result.write_descrs_arrays = write_descrs_arrays
+            result.write_descrs_interiorfields = write_descrs_interiorfields
         result.extraeffect = extraeffect
         result.can_invalidate = can_invalidate
         result.oopspecindex = oopspecindex
+        result.extradescrs = extradescrs
         result.call_release_gil_target = call_release_gil_target
         if result.check_can_raise():
             assert oopspecindex in cls._OS_CANRAISE
@@ -163,7 +171,7 @@
         return None
     return frozenset(x)
 
-EffectInfo.MOST_GENERAL = EffectInfo(None, None, None, None,
+EffectInfo.MOST_GENERAL = EffectInfo(None, None, None, None, None, None,
                                      EffectInfo.EF_RANDOM_EFFECTS,
                                      can_invalidate=True)
 
@@ -172,19 +180,24 @@
                                  extraeffect=EffectInfo.EF_CAN_RAISE,
                                  oopspecindex=EffectInfo.OS_NONE,
                                  can_invalidate=False,
-                                 call_release_gil_target=llmemory.NULL):
+                                 call_release_gil_target=llmemory.NULL,
+                                 extradescr=None):
     from rpython.translator.backendopt.writeanalyze import top_set
     if effects is top_set or extraeffect == EffectInfo.EF_RANDOM_EFFECTS:
         readonly_descrs_fields = None
         readonly_descrs_arrays = None
+        readonly_descrs_interiorfields = None
         write_descrs_fields = None
         write_descrs_arrays = None
+        write_descrs_interiorfields = None
         extraeffect = EffectInfo.EF_RANDOM_EFFECTS
     else:
         readonly_descrs_fields = []
         readonly_descrs_arrays = []
+        readonly_descrs_interiorfields = []
         write_descrs_fields = []
         write_descrs_arrays = []
+        write_descrs_interiorfields = []
 
         def add_struct(descrs_fields, (_, T, fieldname)):
             T = deref(T)
@@ -198,6 +211,17 @@
                 descr = cpu.arraydescrof(ARRAY)
                 descrs_arrays.append(descr)
 
+        def add_interiorfield(descrs_interiorfields, (_, T, fieldname)):
+            T = deref(T)
+            if not isinstance(T, lltype.Array):
+                return # let's not consider structs for now
+            if not consider_array(T):
+                return
+            if getattr(T.OF, fieldname) is lltype.Void:
+                return
+            descr = cpu.interiorfielddescrof(T, fieldname)
+            descrs_interiorfields.append(descr)
+
         for tup in effects:
             if tup[0] == "struct":
                 add_struct(write_descrs_fields, tup)
@@ -205,6 +229,12 @@
                 tupw = ("struct",) + tup[1:]
                 if tupw not in effects:
                     add_struct(readonly_descrs_fields, tup)
+            elif tup[0] == "interiorfield":
+                add_interiorfield(write_descrs_interiorfields, tup)
+            elif tup[0] == "readinteriorfield":
+                tupw = ('interiorfield',) + tup[1:]
+                if tupw not in effects:
+                    add_interiorfield(readonly_descrs_interiorfields, tup)
             elif tup[0] == "array":
                 add_array(write_descrs_arrays, tup)
             elif tup[0] == "readarray":
@@ -216,12 +246,15 @@
     #
     return EffectInfo(readonly_descrs_fields,
                       readonly_descrs_arrays,
+                      readonly_descrs_interiorfields,
                       write_descrs_fields,
                       write_descrs_arrays,
+                      write_descrs_interiorfields,
                       extraeffect,
                       oopspecindex,
                       can_invalidate,
-                      call_release_gil_target)
+                      call_release_gil_target,
+                      extradescr)
 
 def consider_struct(TYPE, fieldname):
     if fieldType(TYPE, fieldname) is lltype.Void:
diff --git a/rpython/jit/codewriter/jtransform.py b/rpython/jit/codewriter/jtransform.py
--- a/rpython/jit/codewriter/jtransform.py
+++ b/rpython/jit/codewriter/jtransform.py
@@ -403,6 +403,9 @@
             prepare = self._handle_math_sqrt_call
         elif oopspec_name.startswith('rgc.'):
             prepare = self._handle_rgc_call
+        elif oopspec_name.endswith('dict.lookup'):
+            # also ordereddict.lookup
+            prepare = self._handle_dict_lookup_call
         else:
             prepare = self.prepare_builtin_call
         try:
@@ -1680,9 +1683,11 @@
     # ----------
     # Strings and Unicodes.
 
-    def _handle_oopspec_call(self, op, args, oopspecindex, extraeffect=None):
+    def _handle_oopspec_call(self, op, args, oopspecindex, extraeffect=None,
+                             extradescr=None):
         calldescr = self.callcontrol.getcalldescr(op, oopspecindex,
-                                                  extraeffect)
+                                                  extraeffect,
+                                                  extradescr=extradescr)
         if extraeffect is not None:
             assert (is_test_calldescr(calldescr)      # for tests
                     or calldescr.get_extra_info().extraeffect == extraeffect)
@@ -1846,6 +1851,14 @@
         return self._handle_oopspec_call(op, args, EffectInfo.OS_MATH_SQRT,
                                          EffectInfo.EF_ELIDABLE_CANNOT_RAISE)
 
+    def _handle_dict_lookup_call(self, op, oopspec_name, args):
+        extradescr1 = self.cpu.fielddescrof(op.args[1].concretetype.TO,
+                                            'entries')
+        extradescr2 = self.cpu.interiorfielddescrof(
+            op.args[1].concretetype.TO.entries.TO, 'key')
+        return self._handle_oopspec_call(op, args, EffectInfo.OS_DICT_LOOKUP,
+                                         extradescr=[extradescr1, extradescr2])
+
     def _handle_rgc_call(self, op, oopspec_name, args):
         if oopspec_name == 'rgc.ll_shrink_array':
             return self._handle_oopspec_call(op, args, EffectInfo.OS_SHRINK_ARRAY, EffectInfo.EF_CAN_RAISE)
diff --git a/rpython/jit/codewriter/test/test_jtransform.py b/rpython/jit/codewriter/test/test_jtransform.py
--- a/rpython/jit/codewriter/test/test_jtransform.py
+++ b/rpython/jit/codewriter/test/test_jtransform.py
@@ -60,7 +60,8 @@
 class FakeResidualCallControl:
     def guess_call_kind(self, op):
         return 'residual'
-    def getcalldescr(self, op, oopspecindex=None, extraeffect=None):
+    def getcalldescr(self, op, oopspecindex=None, extraeffect=None,
+                     extradescr=None):
         return 'calldescr'
     def calldescr_canraise(self, calldescr):
         return True
@@ -117,7 +118,8 @@
         self.callinfocollection = FakeCallInfoCollection()
     def guess_call_kind(self, op):
         return 'builtin'
-    def getcalldescr(self, op, oopspecindex=None, extraeffect=None):
+    def getcalldescr(self, op, oopspecindex=None, extraeffect=None,
+                     extradescr=None):
         assert oopspecindex is not None    # in this test
         EI = effectinfo.EffectInfo
         if oopspecindex != EI.OS_ARRAYCOPY:
diff --git a/rpython/jit/codewriter/test/test_list.py b/rpython/jit/codewriter/test/test_list.py
--- a/rpython/jit/codewriter/test/test_list.py
+++ b/rpython/jit/codewriter/test/test_list.py
@@ -37,7 +37,8 @@
 
 class FakeCallControl:
     class getcalldescr(AbstractDescr):
-        def __init__(self, op, oopspecindex=0, extraeffect=None):
+        def __init__(self, op, oopspecindex=0, extraeffect=None,
+                     extradescr=None):
             self.op = op
             self.oopspecindex = oopspecindex
         def __repr__(self):
diff --git a/rpython/jit/metainterp/executor.py b/rpython/jit/metainterp/executor.py
--- a/rpython/jit/metainterp/executor.py
+++ b/rpython/jit/metainterp/executor.py
@@ -332,6 +332,7 @@
                     continue
             if value in (rop.FORCE_TOKEN,
                          rop.CALL_ASSEMBLER,
+                         rop.INCREMENT_DEBUG_COUNTER,
                          rop.COND_CALL_GC_WB,
                          rop.COND_CALL_GC_WB_ARRAY,
                          rop.DEBUG_MERGE_POINT,
diff --git a/rpython/jit/metainterp/optimizeopt/heap.py b/rpython/jit/metainterp/optimizeopt/heap.py
--- a/rpython/jit/metainterp/optimizeopt/heap.py
+++ b/rpython/jit/metainterp/optimizeopt/heap.py
@@ -1,8 +1,10 @@
 import os
 
+from rpython.jit.codewriter.effectinfo import EffectInfo
+from rpython.jit.metainterp.optimizeopt.util import args_dict
 from rpython.jit.metainterp.history import Const
 from rpython.jit.metainterp.jitexc import JitException
-from rpython.jit.metainterp.optimizeopt.optimizer import Optimization, MODE_ARRAY, LEVEL_KNOWNCLASS
+from rpython.jit.metainterp.optimizeopt.optimizer import Optimization, MODE_ARRAY, LEVEL_KNOWNCLASS, REMOVED
 from rpython.jit.metainterp.optimizeopt.util import make_dispatcher_method
 from rpython.jit.metainterp.resoperation import rop, ResOperation
 from rpython.rlib.objectmodel import we_are_translated
@@ -173,6 +175,10 @@
         self.cached_fields = {}
         # cached array items:  {array descr: {index: CachedField}}
         self.cached_arrayitems = {}
+        # cached dict items: {dict descr: {(optval, index): box-or-const}}
+        self.cached_dict_reads = {}
+        # cache of corresponding array descrs
+        self.corresponding_array_descrs = {}
         #
         self._lazy_setfields_and_arrayitems = []
         self._remove_guard_not_invalidated = False
@@ -180,9 +186,13 @@
         self.postponed_op = None
 
     def force_at_end_of_preamble(self):
+        self.cached_dict_reads.clear()
+        self.corresponding_array_descrs.clear()
         self.force_all_lazy_setfields_and_arrayitems()
 
     def flush(self):
+        self.cached_dict_reads.clear()
+        self.corresponding_array_descrs.clear()
         self.force_all_lazy_setfields_and_arrayitems()
         self.emit_postponed_op()
 
@@ -214,6 +224,7 @@
         del self._lazy_setfields_and_arrayitems[:]
         self.cached_fields.clear()
         self.cached_arrayitems.clear()
+        self.cached_dict_reads.clear()
 
     def field_cache(self, descr):
         try:
@@ -282,6 +293,44 @@
         self.force_all_lazy_setfields_and_arrayitems()
         self.clean_caches()
 
+    def optimize_CALL(self, op):
+        # dispatch based on 'oopspecindex' to a method that handles
+        # specifically the given oopspec call.  For non-oopspec calls,
+        # oopspecindex is just zero.
+        effectinfo = op.getdescr().get_extra_info()
+        oopspecindex = effectinfo.oopspecindex
+        if oopspecindex == EffectInfo.OS_DICT_LOOKUP:
+            if self._optimize_CALL_DICT_LOOKUP(op):
+                return
+        self.emit_operation(op)
+
+    def _optimize_CALL_DICT_LOOKUP(self, op):
+        descrs = op.getdescr().get_extra_info().extradescrs
+        assert descrs        # translation hint
+        descr1 = descrs[0]
+        try:
+            d = self.cached_dict_reads[descr1]
+        except KeyError:
+            d = self.cached_dict_reads[descr1] = args_dict()
+            self.corresponding_array_descrs[descrs[1]] = descr1
+        args = self.optimizer.make_args_key(op)
+        try:
+            res_v = d[args]
+        except KeyError:
+            d[args] = self.getvalue(op.result)
+            return False
+        else:
+            self.make_equal_to(op.result, res_v)
+            self.last_emitted_operation = REMOVED
+            return True
+
+    def optimize_GUARD_NO_EXCEPTION(self, op):
+        if self.last_emitted_operation is REMOVED:
+            return
+        self.emit_operation(op)
+
+    optimize_GUARD_EXCEPTION = optimize_GUARD_NO_EXCEPTION
+
     def force_from_effectinfo(self, effectinfo):
         # XXX we can get the wrong complexity here, if the lists
         # XXX stored on effectinfo are large
@@ -290,9 +339,20 @@
         for arraydescr in effectinfo.readonly_descrs_arrays:
             self.force_lazy_setarrayitem(arraydescr)
         for fielddescr in effectinfo.write_descrs_fields:
+            try:
+                del self.cached_dict_reads[fielddescr]
+            except KeyError:
+                pass
             self.force_lazy_setfield(fielddescr, can_cache=False)
         for arraydescr in effectinfo.write_descrs_arrays:
             self.force_lazy_setarrayitem(arraydescr, can_cache=False)
+        for descr in effectinfo.write_descrs_interiorfields:
+            if descr in self.corresponding_array_descrs:
+                dictdescr = self.corresponding_array_descrs.pop(descr)
+                try:
+                    del self.cached_dict_reads[dictdescr]
+                except KeyError:
+                    pass # someone did it already
         if effectinfo.check_forces_virtual_or_virtualizable():
             vrefinfo = self.optimizer.metainterp_sd.virtualref_info
             self.force_lazy_setfield(vrefinfo.descr_forced)
diff --git a/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py b/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py
--- a/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py
+++ b/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py
@@ -5444,6 +5444,21 @@
         """
         self.optimize_loop(ops, expected)
 
+    def test_consecutive_getinteriorfields(self):
+        py.test.skip("we want this to pass")
+        ops = """
+        [p0, i0]
+        i1 = getinteriorfield_gc(p0, i0, descr=valuedescr)
+        i2 = getinteriorfield_gc(p0, i0, descr=valuedescr)
+        jump(i1, i2)
+        """
+        expected = """
+        [p0, i0]
+        i1 = getinteriorfield_gc(p0, i0, descr=valuedescr)
+        jump(i1, i1)
+        """
+        self.optimize_loop(ops, expected)
+
 
 class TestLLtype(BaseTestOptimizeBasic, LLtypeMixin):
     pass
diff --git a/rpython/jit/metainterp/optimizeopt/test/test_util.py b/rpython/jit/metainterp/optimizeopt/test/test_util.py
--- a/rpython/jit/metainterp/optimizeopt/test/test_util.py
+++ b/rpython/jit/metainterp/optimizeopt/test/test_util.py
@@ -181,28 +181,29 @@
     plaincalldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
                                      EffectInfo.MOST_GENERAL)
     nonwritedescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
-                                    EffectInfo([], [], [], []))
+                                    EffectInfo([], [], [], [], [], []))
     writeadescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
-                                  EffectInfo([], [], [adescr], []))
+                                  EffectInfo([], [], [], [adescr], [], []))
     writearraydescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
-                                  EffectInfo([], [], [adescr], [arraydescr]))
+                                  EffectInfo([], [], [], [adescr], [arraydescr],
+                                             []))
     readadescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
-                                 EffectInfo([adescr], [], [], []))
+                                 EffectInfo([adescr], [], [], [], [], []))
     mayforcevirtdescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
-                 EffectInfo([nextdescr], [], [], [],
+                 EffectInfo([nextdescr], [], [], [], [], [],
                             EffectInfo.EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE,
                             can_invalidate=True))
     arraycopydescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
-             EffectInfo([], [arraydescr], [], [arraydescr],
+             EffectInfo([], [arraydescr], [], [], [arraydescr], [],
                         EffectInfo.EF_CANNOT_RAISE,
                         oopspecindex=EffectInfo.OS_ARRAYCOPY))
 
     raw_malloc_descr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
-             EffectInfo([], [], [], [],
+             EffectInfo([], [], [], [], [], [],
                         EffectInfo.EF_CAN_RAISE,
                         oopspecindex=EffectInfo.OS_RAW_MALLOC_VARSIZE_CHAR))
     raw_free_descr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
-             EffectInfo([], [], [], [],
+             EffectInfo([], [], [], [], [], [],
                         EffectInfo.EF_CANNOT_RAISE,
                         oopspecindex=EffectInfo.OS_RAW_FREE))
 
@@ -251,17 +252,18 @@
         _oopspecindex = getattr(EffectInfo, _os)
         locals()[_name] = \
             cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
-                EffectInfo([], [], [], [], EffectInfo.EF_CANNOT_RAISE,
+                EffectInfo([], [], [], [], [], [], EffectInfo.EF_CANNOT_RAISE,
                            oopspecindex=_oopspecindex))
         #
         _oopspecindex = getattr(EffectInfo, _os.replace('STR', 'UNI'))
         locals()[_name.replace('str', 'unicode')] = \
             cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
-                EffectInfo([], [], [], [], EffectInfo.EF_CANNOT_RAISE,
+                EffectInfo([], [], [], [], [], [], EffectInfo.EF_CANNOT_RAISE,
                            oopspecindex=_oopspecindex))
 
     s2u_descr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT,
-            EffectInfo([], [], [], [], oopspecindex=EffectInfo.OS_STR2UNICODE))
+            EffectInfo([], [], [], [], [], [],
+                       oopspecindex=EffectInfo.OS_STR2UNICODE))
     #
 
     class LoopToken(AbstractDescr):
@@ -277,7 +279,7 @@
     virtualtokendescr = vrefinfo.descr_virtual_token
     virtualforceddescr = vrefinfo.descr_forced
     FUNC = lltype.FuncType([], lltype.Void)
-    ei = EffectInfo([], [], [], [], EffectInfo.EF_CANNOT_RAISE,
+    ei = EffectInfo([], [], [], [], [], [], EffectInfo.EF_CANNOT_RAISE,
                     can_invalidate=False,
                     oopspecindex=EffectInfo.OS_JIT_FORCE_VIRTUALIZABLE)
     clear_vable = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT, ei)
diff --git a/rpython/jit/metainterp/resoperation.py b/rpython/jit/metainterp/resoperation.py
--- a/rpython/jit/metainterp/resoperation.py
+++ b/rpython/jit/metainterp/resoperation.py
@@ -494,6 +494,7 @@
     # must be forced, however we need to execute it anyway
     '_NOSIDEEFFECT_LAST', # ----- end of no_side_effect operations -----
 
+    'INCREMENT_DEBUG_COUNTER/1',
     'SETARRAYITEM_GC/3d',
     'SETARRAYITEM_RAW/3d',
     'SETINTERIORFIELD_GC/3d',
diff --git a/rpython/jit/metainterp/test/test_dict.py b/rpython/jit/metainterp/test/test_dict.py
--- a/rpython/jit/metainterp/test/test_dict.py
+++ b/rpython/jit/metainterp/test/test_dict.py
@@ -193,6 +193,107 @@
         self.check_simple_loop({'int_sub': 1, 'int_gt': 1, 'guard_true': 1,
                                 'jump': 1})
 
+    def test_dict_two_lookups(self):
+        driver = JitDriver(greens = [], reds = 'auto')
+        d = {'a': 3, 'b': 4}
+        indexes = ['a', 'b']
+
+        def f(n):
+            s = 0
+            while n > 0:
+                driver.jit_merge_point()
+                s += d[indexes[n & 1]]
+                s += d[indexes[n & 1]]
+                n -= 1
+            return s
+
+        self.meta_interp(f, [10])
+        # XXX should be one getinteriorfield_gc
+        self.check_simple_loop(call=1, getinteriorfield_gc=2,
+                               guard_no_exception=1)
+
+    def test_ordered_dict_two_lookups(self):
+        driver = JitDriver(greens = [], reds = 'auto')
+        d = OrderedDict()
+        d['a'] = 3
+        d['b'] = 4
+        indexes = ['a', 'b']
+
+        def f(n):
+            s = 0
+            while n > 0:
+                driver.jit_merge_point()
+                s += d[indexes[n & 1]]
+                s += d[indexes[n & 1]]
+                n -= 1
+            return s
+
+        self.meta_interp(f, [10])
+        # XXX should be one getinteriorfield_gc
+        self.check_simple_loop(call=1, getinteriorfield_gc=2,
+                               guard_no_exception=1)
+
+    def test_dict_insert_invalidates_caches(self):
+        driver = JitDriver(greens = [], reds = 'auto')
+        indexes = ['aa', 'b', 'cc']
+
+        def f(n):
+            d = {'aa': 3, 'b': 4, 'cc': 5}
+            s = 0
+            while n > 0:
+                driver.jit_merge_point()
+                index = indexes[n & 1]
+                s += d[index]
+                d['aa'] += 1 # this will invalidate the index
+                s += d[index]
+                n -= 1
+            return s
+
+        res = self.meta_interp(f, [10])
+        assert res == f(10)
+        self.check_simple_loop(call=5)
+
+    def test_dict_array_write_invalidates_caches(self):
+        driver = JitDriver(greens = [], reds = 'auto')
+        indexes = ['aa', 'b', 'cc']
+
+        def f(n):
+            d = {'aa': 3, 'b': 4, 'cc': 5}
+            s = 0
+            while n > 0:
+                driver.jit_merge_point()
+                index = indexes[n & 1]
+                s += d[index]
+                del d['cc']
+                s += d[index]
+                d['cc'] = 3
+                n -= 1
+            return s
+
+        exp = f(10)
+        res = self.meta_interp(f, [10])
+        assert res == exp
+        self.check_simple_loop(call=7)
+
+    def test_dict_double_lookup_2(self):
+        driver = JitDriver(greens = [], reds = 'auto')
+        indexes = ['aa', 'b', 'cc']
+
+        def f(n):
+            d = {'aa': 3, 'b': 4, 'cc': 5}
+            s = 0
+            while n > 0:
+                driver.jit_merge_point()
+                index = indexes[n & 1]
+                s += d[index]
+                d[index] += 1
+                n -= 1
+            return s
+
+        res = self.meta_interp(f, [10])
+        assert res == f(10)
+        self.check_simple_loop(call=3)
+
 
 class TestLLtype(DictTests, LLJitMixin):
     pass
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
@@ -302,7 +302,7 @@
         self.clear_vable_ptr = self.warmrunnerdesc.helper_func(
             FUNCPTR, self.clear_vable_token)
         FUNC = FUNCPTR.TO
-        ei = EffectInfo([], [], [], [], EffectInfo.EF_CANNOT_RAISE,
+        ei = EffectInfo([], [], [], [], [], [], EffectInfo.EF_CANNOT_RAISE,
                         can_invalidate=False,
                         oopspecindex=EffectInfo.OS_JIT_FORCE_VIRTUALIZABLE)
 
diff --git a/rpython/rlib/rfile.py b/rpython/rlib/rfile.py
--- a/rpython/rlib/rfile.py
+++ b/rpython/rlib/rfile.py
@@ -14,6 +14,11 @@
 includes = ['stdio.h', 'sys/types.h']
 if os.name == "posix":
     includes += ['unistd.h']
+    ftruncate = 'ftruncate'
+    fileno = 'fileno'
+else:
+    ftruncate = '_chsize'
+    fileno = '_fileno'
 eci = ExternalCompilationInfo(includes=includes)
 
 def llexternal(*args, **kwargs):
@@ -41,10 +46,10 @@
 c_fseek = llexternal('fseek', [lltype.Ptr(FILE), rffi.LONG, rffi.INT],
                      rffi.INT)
 c_tmpfile = llexternal('tmpfile', [], lltype.Ptr(FILE))
-c_fileno = llexternal('fileno', [lltype.Ptr(FILE)], rffi.INT)
+c_fileno = llexternal(fileno, [lltype.Ptr(FILE)], rffi.INT)
 c_ftell = llexternal('ftell', [lltype.Ptr(FILE)], rffi.LONG)
 c_fflush = llexternal('fflush', [lltype.Ptr(FILE)], rffi.INT)
-c_ftruncate = llexternal('ftruncate', [rffi.INT, OFF_T], rffi.INT, macro=True)
+c_ftruncate = llexternal(ftruncate, [rffi.INT, OFF_T], rffi.INT, macro=True)
 
 c_fgets = llexternal('fgets', [rffi.CCHARP, rffi.INT, lltype.Ptr(FILE)],
                      rffi.CCHARP)
diff --git a/rpython/rtyper/lltypesystem/rdict.py b/rpython/rtyper/lltypesystem/rdict.py
--- a/rpython/rtyper/lltypesystem/rdict.py
+++ b/rpython/rtyper/lltypesystem/rdict.py
@@ -569,6 +569,7 @@
 PERTURB_SHIFT = 5
 
 @jit.look_inside_iff(lambda d, key, hash: jit.isvirtual(d) and jit.isconstant(key))
+ at jit.oopspec('dict.lookup(d, key, hash)')
 def ll_dict_lookup(d, key, hash):
     entries = d.entries
     ENTRIES = lltype.typeOf(entries).TO
diff --git a/rpython/rtyper/lltypesystem/rordereddict.py b/rpython/rtyper/lltypesystem/rordereddict.py
--- a/rpython/rtyper/lltypesystem/rordereddict.py
+++ b/rpython/rtyper/lltypesystem/rordereddict.py
@@ -709,6 +709,7 @@
 
 @jit.look_inside_iff(lambda d, key, hash, store_flag, T:
                      jit.isvirtual(d) and jit.isconstant(key))
+ at jit.oopspec('ordereddict.lookup(d, key, hash, store_flag, T)')
 def ll_dict_lookup(d, key, hash, store_flag, T):
     INDEXES = _ll_ptr_to_array_of(T)
     entries = d.entries
diff --git a/rpython/translator/backendopt/test/test_writeanalyze.py b/rpython/translator/backendopt/test/test_writeanalyze.py
--- a/rpython/translator/backendopt/test/test_writeanalyze.py
+++ b/rpython/translator/backendopt/test/test_writeanalyze.py
@@ -353,3 +353,23 @@
 
         result = wa.analyze(fgraph.startblock.operations[-1])
         assert list(result) == [("struct", lltype.Ptr(S), "x")]
+
+    def test_interiorfield(self):
+        A = lltype.GcArray(lltype.Struct('x', ('x', lltype.Signed),
+                                         ('y', lltype.Signed)))
+
+        def g(x):
+            a = lltype.malloc(A, 1)
+            a[0].y = 3
+            return f(a, x)
+
+        def f(a, x):
+            a[0].x = x
+            return a[0].y
+
+        t, wa = self.translate(g, [int])
+        ggraph = graphof(t, g)
+        result = wa.analyze(ggraph.startblock.operations[-1])
+        res = list(result)
+        assert ('readinteriorfield', lltype.Ptr(A), 'y') in res
+        assert ('interiorfield', lltype.Ptr(A), 'x') in res
diff --git a/rpython/translator/backendopt/writeanalyze.py b/rpython/translator/backendopt/writeanalyze.py
--- a/rpython/translator/backendopt/writeanalyze.py
+++ b/rpython/translator/backendopt/writeanalyze.py
@@ -1,4 +1,4 @@
-from rpython.flowspace.model import Variable
+from rpython.flowspace.model import Variable, Constant
 from rpython.translator.backendopt import graphanalyze
 
 top_set = object()
@@ -37,6 +37,12 @@
             return top_set
         return result1.union(result2)
 
+    def _getinteriorname(self, op):
+        if (isinstance(op.args[1], Constant) and
+            isinstance(op.args[1].value, str)):
+            return op.args[1].value
+        return op.args[2].value
+
     def analyze_simple_operation(self, op, graphinfo):
         if op.opname == "setfield":
             if graphinfo is None or not graphinfo.is_fresh_malloc(op.args[0]):
@@ -45,11 +51,18 @@
         elif op.opname == "setarrayitem":
             if graphinfo is None or not graphinfo.is_fresh_malloc(op.args[0]):
                 return self._array_result(op.args[0].concretetype)
+        elif op.opname == "setinteriorfield":
+            if graphinfo is None or not graphinfo.is_fresh_malloc(op.args[0]):
+                name = self._getinteriorname(op)
+                return self._interiorfield_result(op.args[0].concretetype, name)
         return empty_set
 
     def _array_result(self, TYPE):
         return frozenset([("array", TYPE)])
 
+    def _interiorfield_result(self, TYPE, fieldname):
+        return frozenset([("interiorfield", TYPE, fieldname)])
+
     def compute_graph_info(self, graph):
         return FreshMallocs(graph)
 
@@ -99,4 +112,8 @@
         elif op.opname == "getarrayitem":
             return frozenset([
                 ("readarray", op.args[0].concretetype)])
+        elif op.opname == "getinteriorfield":
+            name = self._getinteriorname(op)
+            return frozenset([("readinteriorfield", op.args[0].concretetype,
+                            name)])
         return WriteAnalyzer.analyze_simple_operation(self, op, graphinfo)


More information about the pypy-commit mailing list