Author: Armin Rigo <[email protected]>
Branch: 
Changeset: r88634:a7f012406e30
Date: 2016-11-24 12:53 +0100
http://bitbucket.org/pypy/pypy/changeset/a7f012406e30/

Log:    hg merge conditional_call_value_4 (arigo, cfbolz reviewing)

        Get rid of call_shortcut (branch conditional_call_value_3) and
        instead make a real jit.conditional_call_elidable(). Implement it on
        all backends. The single use so far is for ll_strhash(), like the
        @call_shortcut before it, but the plan is to use that also in py3.5
        for unicode objects caching the utf8 version.

diff too long, truncating to 2000 out of 2095 lines

diff --git a/pypy/module/pypyjit/test_pypy_c/test_containers.py 
b/pypy/module/pypyjit/test_pypy_c/test_containers.py
--- a/pypy/module/pypyjit/test_pypy_c/test_containers.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_containers.py
@@ -67,7 +67,8 @@
             p10 = call_r(ConstClass(ll_str__IntegerR_SignedConst_Signed), i5, 
descr=<Callr . i EF=3>)
             guard_no_exception(descr=...)
             guard_nonnull(p10, descr=...)
-            i12 = call_i(ConstClass(_ll_strhash__rpy_stringPtr), p10, 
descr=<Calli . r EF=0>)
+            i99 = strhash(p10)
+            i12 = cond_call_value_i(i99, 
ConstClass(_ll_strhash__rpy_stringPtr), p10, descr=<Calli . r EF=2>)
             p13 = new(descr=...)
             p15 = new_array_clear(16, descr=<ArrayU 1>)
             {{{
@@ -86,6 +87,7 @@
             call_n(ConstClass(_ll_dict_setitem_lookup_done_trampoline), p13, 
p10, p20, i12, i17, descr=<Callv 0 rrrii EF=5>)
             setfield_gc(p20, i5, descr=<FieldS .*W_IntObject.inst_intval .* 
pure>)
             guard_no_exception(descr=...)
+            i98 = strhash(p10)
             i23 = call_i(ConstClass(ll_call_lookup_function), p13, p10, i12, 
0, descr=<Calli . rrii EF=5 OS=4>)
             guard_no_exception(descr=...)
             i27 = int_lt(i23, 0)
diff --git a/rpython/jit/backend/arm/assembler.py 
b/rpython/jit/backend/arm/assembler.py
--- a/rpython/jit/backend/arm/assembler.py
+++ b/rpython/jit/backend/arm/assembler.py
@@ -268,12 +268,15 @@
         """
         mc = InstrBuilder(self.cpu.cpuinfo.arch_version)
         #
-        self._push_all_regs_to_jitframe(mc, [], self.cpu.supports_floats, 
callee_only)
+        # We don't save/restore r4; instead the return value (if any)
+        # will be stored there.
+        self._push_all_regs_to_jitframe(mc, [r.r4], self.cpu.supports_floats, 
callee_only)
         ## args are in their respective positions
         mc.PUSH([r.ip.value, r.lr.value])
         mc.BLX(r.r4.value)
+        mc.MOV_rr(r.r4.value, r.r0.value)
         self._reload_frame_if_necessary(mc)
-        self._pop_all_regs_from_jitframe(mc, [], supports_floats,
+        self._pop_all_regs_from_jitframe(mc, [r.r4], supports_floats,
                                       callee_only)
         # return
         mc.POP([r.ip.value, r.pc.value])
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
@@ -357,7 +357,13 @@
         return fcond
 
     def emit_op_cond_call(self, op, arglocs, regalloc, fcond):
-        [call_loc] = arglocs
+        call_loc = arglocs[0]
+        if len(arglocs) == 2:
+            res_loc = arglocs[1]     # cond_call_value
+        else:
+            res_loc = None           # cond_call
+        # useless to list res_loc in the gcmap, because if the call is
+        # done it means res_loc was initially NULL
         gcmap = regalloc.get_gcmap([call_loc])
 
         assert call_loc is r.r4
@@ -378,8 +384,13 @@
                 floats = True
         cond_call_adr = self.cond_call_slowpath[floats * 2 + callee_only]
         self.mc.BL(cond_call_adr)
+        # if this is a COND_CALL_VALUE, we need to move the result in place
+        # from its current location (which is, unusually, in r4: see
+        # cond_call_slowpath)
+        if res_loc is not None and res_loc is not r.r4:
+            self.mc.MOV_rr(res_loc.value, r.r4.value)
+        #
         self.pop_gcmap(self.mc)
-        # never any result value
         cond = c.get_opposite_of(self.guard_success_cc)
         self.guard_success_cc = c.cond_none
         pmc = OverwritingBuilder(self.mc, jmp_adr, WORD)
@@ -389,6 +400,9 @@
         self.previous_cond_call_jcond = jmp_adr, cond
         return fcond
 
+    emit_op_cond_call_value_i = emit_op_cond_call
+    emit_op_cond_call_value_r = emit_op_cond_call
+
     def emit_op_jump(self, op, arglocs, regalloc, fcond):
         target_token = op.getdescr()
         assert isinstance(target_token, TargetToken)
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
@@ -1004,7 +1004,6 @@
     def prepare_op_cond_call(self, op, fcond):
         # XXX don't force the arguments to be loaded in specific
         # locations before knowing if we can take the fast path
-        # XXX add cond_call_value support
         assert 2 <= op.numargs() <= 4 + 2
         tmpreg = self.get_scratch_reg(INT, selected_reg=r.r4)
         v = op.getarg(1)
@@ -1017,8 +1016,33 @@
             arg = op.getarg(i)
             self.make_sure_var_in_reg(arg, args_so_far, selected_reg=reg)
             args_so_far.append(arg)
-        self.load_condition_into_cc(op.getarg(0))
-        return [tmpreg]
+
+        if op.type == 'v':
+            # a plain COND_CALL.  Calls the function when args[0] is
+            # true.  Often used just after a comparison operation.
+            self.load_condition_into_cc(op.getarg(0))
+            return [tmpreg]
+        else:
+            # COND_CALL_VALUE_I/R.  Calls the function when args[0]
+            # is equal to 0 or NULL.  Returns the result from the
+            # function call if done, or args[0] if it was not 0/NULL.
+            # Implemented by forcing the result to live in the same
+            # register as args[0], and overwriting it if we really do
+            # the call.
+
+            # Load the register for the result.  Possibly reuse 'args[0]'.
+            # But the old value of args[0], if it survives, is first
+            # spilled away.  We can't overwrite any of op.args[2:] here.
+            args = op.getarglist()
+            resloc = self.rm.force_result_in_reg(op, args[0],
+                                                 forbidden_vars=args[2:])
+            # Test the register for the result.
+            self.assembler.mc.CMP_ri(resloc.value, 0)
+            self.assembler.guard_success_cc = c.EQ
+            return [tmpreg, resloc]
+
+    prepare_op_cond_call_value_i = prepare_op_cond_call
+    prepare_op_cond_call_value_r = prepare_op_cond_call
 
     def prepare_op_force_token(self, op, fcond):
         # XXX for now we return a regular reg
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
@@ -15,11 +15,12 @@
 from rpython.rtyper.llinterp import LLInterpreter, LLException
 from rpython.rtyper.lltypesystem import lltype, llmemory, rffi, rstr
 from rpython.rtyper.lltypesystem.lloperation import llop
+from rpython.rtyper.annlowlevel import hlstr, hlunicode
 from rpython.rtyper import rclass
 
 from rpython.rlib.clibffi import FFI_DEFAULT_ABI
 from rpython.rlib.rarithmetic import ovfcheck, r_uint, r_ulonglong, intmask
-from rpython.rlib.objectmodel import Symbolic
+from rpython.rlib.objectmodel import Symbolic, compute_hash
 
 class LLAsmInfo(object):
     def __init__(self, lltrace):
@@ -326,7 +327,6 @@
     supports_longlong = r_uint is not r_ulonglong
     supports_singlefloats = True
     supports_guard_gc_type = True
-    supports_cond_call_value = True
     translate_support_code = False
     is_llgraph = True
     vector_ext = VectorExt()
@@ -789,6 +789,10 @@
         assert 0 <= dststart <= dststart + length <= len(dst.chars)
         rstr.copy_string_contents(src, dst, srcstart, dststart, length)
 
+    def bh_strhash(self, s):
+        lls = s._obj.container
+        return compute_hash(hlstr(lls._as_ptr()))
+
     def bh_newunicode(self, length):
         return lltype.cast_opaque_ptr(llmemory.GCREF,
                                       lltype.malloc(rstr.UNICODE, length,
@@ -811,6 +815,10 @@
         assert 0 <= dststart <= dststart + length <= len(dst.chars)
         rstr.copy_unicode_contents(src, dst, srcstart, dststart, length)
 
+    def bh_unicodehash(self, s):
+        lls = s._obj.container
+        return compute_hash(hlunicode(lls._as_ptr()))
+
     def bh_new(self, sizedescr):
         return lltype.cast_opaque_ptr(llmemory.GCREF,
                                       lltype.malloc(sizedescr.S, zero=True))
diff --git a/rpython/jit/backend/llsupport/llmodel.py 
b/rpython/jit/backend/llsupport/llmodel.py
--- a/rpython/jit/backend/llsupport/llmodel.py
+++ b/rpython/jit/backend/llsupport/llmodel.py
@@ -3,8 +3,9 @@
 from rpython.rtyper.lltypesystem.lloperation import llop
 from rpython.rtyper.llinterp import LLInterpreter
 from rpython.rtyper.annlowlevel import llhelper, MixLevelHelperAnnotator
+from rpython.rtyper.annlowlevel import hlstr, hlunicode
 from rpython.rtyper.llannotation import lltype_to_annotation
-from rpython.rlib.objectmodel import we_are_translated, specialize
+from rpython.rlib.objectmodel import we_are_translated, specialize, 
compute_hash
 from rpython.jit.metainterp import history, compile
 from rpython.jit.metainterp.optimize import SpeculativeError
 from rpython.jit.codewriter import heaptracker, longlong
@@ -663,6 +664,14 @@
         u = lltype.cast_opaque_ptr(lltype.Ptr(rstr.UNICODE), string)
         return len(u.chars)
 
+    def bh_strhash(self, string):
+        s = lltype.cast_opaque_ptr(lltype.Ptr(rstr.STR), string)
+        return compute_hash(hlstr(s))
+
+    def bh_unicodehash(self, string):
+        u = lltype.cast_opaque_ptr(lltype.Ptr(rstr.UNICODE), string)
+        return compute_hash(hlunicode(u))
+
     def bh_strgetitem(self, string, index):
         s = lltype.cast_opaque_ptr(lltype.Ptr(rstr.STR), string)
         return ord(s.chars[index])
diff --git a/rpython/jit/backend/llsupport/rewrite.py 
b/rpython/jit/backend/llsupport/rewrite.py
--- a/rpython/jit/backend/llsupport/rewrite.py
+++ b/rpython/jit/backend/llsupport/rewrite.py
@@ -9,9 +9,9 @@
 from rpython.jit.metainterp.typesystem import rd_eq, rd_hash
 from rpython.jit.codewriter import heaptracker
 from rpython.jit.backend.llsupport.symbolic import (WORD,
-        get_array_token)
+        get_field_token, get_array_token)
 from rpython.jit.backend.llsupport.descr import SizeDescr, ArrayDescr,\
-     FLAG_POINTER, CallDescr
+     FLAG_POINTER
 from rpython.jit.metainterp.history import JitCellToken
 from rpython.jit.backend.llsupport.descr import (unpack_arraydescr,
         unpack_fielddescr, unpack_interiorfielddescr)
@@ -262,6 +262,18 @@
                                                  
self.cpu.translate_support_code)
             self.emit_gc_load_or_indexed(op, op.getarg(0), ConstInt(0),
                                          WORD, 1, ofs_length, NOT_SIGNED)
+        elif opnum == rop.STRHASH:
+            offset, size = get_field_token(rstr.STR,
+                                        'hash', 
self.cpu.translate_support_code)
+            assert size == WORD
+            self.emit_gc_load_or_indexed(op, op.getarg(0), ConstInt(0),
+                                         WORD, 1, offset, sign=True)
+        elif opnum == rop.UNICODEHASH:
+            offset, size = get_field_token(rstr.UNICODE,
+                                        'hash', 
self.cpu.translate_support_code)
+            assert size == WORD
+            self.emit_gc_load_or_indexed(op, op.getarg(0), ConstInt(0),
+                                         WORD, 1, offset, sign=True)
         elif opnum == rop.STRGETITEM:
             basesize, itemsize, ofs_length = get_array_token(rstr.STR,
                                                  
self.cpu.translate_support_code)
@@ -347,9 +359,7 @@
                     self.consider_setfield_gc(op)
                 elif op.getopnum() == rop.SETARRAYITEM_GC:
                     self.consider_setarrayitem_gc(op)
-            # ---------- calls -----------
-            if OpHelpers.is_plain_call(op.getopnum()):
-                self.expand_call_shortcut(op)
+            # ---------- call assembler -----------
             if OpHelpers.is_call_assembler(op.getopnum()):
                 self.handle_call_assembler(op)
                 continue
@@ -595,33 +605,6 @@
         self.emit_gc_store_or_indexed(None, ptr, ConstInt(0), value,
                                       size, 1, ofs)
 
-    def expand_call_shortcut(self, op):
-        if not self.cpu.supports_cond_call_value:
-            return
-        descr = op.getdescr()
-        if descr is None:
-            return
-        assert isinstance(descr, CallDescr)
-        effectinfo = descr.get_extra_info()
-        if effectinfo is None or effectinfo.call_shortcut is None:
-            return
-        if op.type == 'r':
-            cond_call_opnum = rop.COND_CALL_VALUE_R
-        elif op.type == 'i':
-            cond_call_opnum = rop.COND_CALL_VALUE_I
-        else:
-            return
-        cs = effectinfo.call_shortcut
-        ptr_box = op.getarg(1 + cs.argnum)
-        if cs.fielddescr is not None:
-            value_box = self.emit_getfield(ptr_box, descr=cs.fielddescr,
-                                           raw=(ptr_box.type == 'i'))
-        else:
-            value_box = ptr_box
-        self.replace_op_with(op, ResOperation(cond_call_opnum,
-                                              [value_box] + op.getarglist(),
-                                              descr=descr))
-
     def handle_call_assembler(self, op):
         descrs = self.gc_ll_descr.getframedescrs(self.cpu)
         loop_token = op.getdescr()
diff --git a/rpython/jit/backend/llsupport/test/test_rewrite.py 
b/rpython/jit/backend/llsupport/test/test_rewrite.py
--- a/rpython/jit/backend/llsupport/test/test_rewrite.py
+++ b/rpython/jit/backend/llsupport/test/test_rewrite.py
@@ -1,8 +1,7 @@
 import py
 from rpython.jit.backend.llsupport.descr import get_size_descr,\
      get_field_descr, get_array_descr, ArrayDescr, FieldDescr,\
-     SizeDescr, get_interiorfield_descr, get_call_descr
-from rpython.jit.codewriter.effectinfo import EffectInfo, CallShortcut
+     SizeDescr, get_interiorfield_descr
 from rpython.jit.backend.llsupport.gc import GcLLDescr_boehm,\
      GcLLDescr_framework
 from rpython.jit.backend.llsupport import jitframe
@@ -81,21 +80,6 @@
                                      lltype.malloc(T, zero=True))
         self.myT = myT
         #
-        call_shortcut = CallShortcut(0, tzdescr)
-        effectinfo = EffectInfo(None, None, None, None, None, None,
-                                EffectInfo.EF_RANDOM_EFFECTS,
-                                call_shortcut=call_shortcut)
-        call_shortcut_descr = get_call_descr(self.gc_ll_descr,
-            [lltype.Ptr(T)], lltype.Signed,
-            effectinfo)
-        call_shortcut_2 = CallShortcut(0, None)
-        effectinfo_2 = EffectInfo(None, None, None, None, None, None,
-                                EffectInfo.EF_RANDOM_EFFECTS,
-                                call_shortcut=call_shortcut_2)
-        call_shortcut_descr_2 = get_call_descr(self.gc_ll_descr,
-            [lltype.Signed], lltype.Signed,
-            effectinfo_2)
-        #
         A = lltype.GcArray(lltype.Signed)
         adescr = get_array_descr(self.gc_ll_descr, A)
         adescr.tid = 4321
@@ -216,7 +200,6 @@
 
     load_constant_offset = True
     load_supported_factors = (1,2,4,8)
-    supports_cond_call_value = True
 
     translate_support_code = None
 
@@ -1239,6 +1222,10 @@
                        'i3 = gc_load_i(p0,'
                                  '%(strlendescr.offset)s,'
                                  '%(strlendescr.field_size)s)'],
+        [True,  (1,),  'i3 = strhash(p0)' '->'
+                       'i3 = gc_load_i(p0,'
+                                 '%(strhashdescr.offset)s,'
+                                 '-%(strhashdescr.field_size)s)'],
         #[False, (1,),  'i3 = unicodelen(p0)' '->'
         #               'i3 = gc_load_i(p0,'
         #                       '%(unicodelendescr.offset)s,'
@@ -1247,7 +1234,10 @@
                        'i3 = gc_load_i(p0,'
                                '%(unicodelendescr.offset)s,'
                                '%(unicodelendescr.field_size)s)'],
-
+        [True,  (1,),  'i3 = unicodehash(p0)' '->'
+                       'i3 = gc_load_i(p0,'
+                                 '%(unicodehashdescr.offset)s,'
+                                 '-%(unicodehashdescr.field_size)s)'],
         ## getitem str/unicode
         [True,  (2,4), 'i3 = unicodegetitem(p0,i1)' '->'
                        'i3 = gc_load_indexed_i(p0,i1,'
@@ -1446,26 +1436,3 @@
             jump()
         """)
         assert len(self.gcrefs) == 2
-
-    def test_handle_call_shortcut(self):
-        self.check_rewrite("""
-            [p0]
-            i1 = call_i(123, p0, descr=call_shortcut_descr)
-            jump(i1)
-        """, """
-            [p0]
-            i2 = gc_load_i(p0, %(tzdescr.offset)s, %(tzdescr.field_size)s)
-            i1 = cond_call_value_i(i2, 123, p0, descr=call_shortcut_descr)
-            jump(i1)
-        """)
-
-    def test_handle_call_shortcut_2(self):
-        self.check_rewrite("""
-            [i0]
-            i1 = call_i(123, i0, descr=call_shortcut_descr_2)
-            jump(i1)
-        """, """
-            [i0]
-            i1 = cond_call_value_i(i0, 123, i0, descr=call_shortcut_descr_2)
-            jump(i1)
-        """)
diff --git a/rpython/jit/backend/model.py b/rpython/jit/backend/model.py
--- a/rpython/jit/backend/model.py
+++ b/rpython/jit/backend/model.py
@@ -16,7 +16,6 @@
     # Boxes and Consts are BoxFloats and ConstFloats.
     supports_singlefloats = False
     supports_guard_gc_type = False
-    supports_cond_call_value = False
 
     propagate_exception_descr = None
 
diff --git a/rpython/jit/backend/ppc/opassembler.py 
b/rpython/jit/backend/ppc/opassembler.py
--- a/rpython/jit/backend/ppc/opassembler.py
+++ b/rpython/jit/backend/ppc/opassembler.py
@@ -656,10 +656,12 @@
     _COND_CALL_SAVE_REGS = [r.r3, r.r4, r.r5, r.r6, r.r12]
 
     def emit_cond_call(self, op, arglocs, regalloc):
+        resloc = arglocs[0]
+        arglocs = arglocs[1:]
+
         fcond = self.guard_success_cc
         self.guard_success_cc = c.cond_none
         assert fcond != c.cond_none
-        fcond = c.negate(fcond)
 
         jmp_adr = self.mc.get_relative_pos()
         self.mc.trap()        # patched later to a 'bc'
@@ -691,7 +693,10 @@
         cond_call_adr = self.cond_call_slowpath[floats * 2 + callee_only]
         self.mc.bl_abs(cond_call_adr)
         # restoring the registers saved above, and doing pop_gcmap(), is left
-        # to the cond_call_slowpath helper.  We never have any result value.
+        # to the cond_call_slowpath helper.  If we have a result, move
+        # it from r2 to its expected location.
+        if resloc is not None:
+            self.mc.mr(resloc.value, r.SCRATCH2.value)
         relative_target = self.mc.currpos() - jmp_adr
         pmc = OverwritingBuilder(self.mc, jmp_adr, 1)
         BI, BO = c.encoding[fcond]
@@ -701,6 +706,9 @@
         # guard_no_exception too
         self.previous_cond_call_jcond = jmp_adr, BI, BO
 
+    emit_cond_call_value_i = emit_cond_call
+    emit_cond_call_value_r = emit_cond_call
+
 
 class FieldOpAssembler(object):
 
diff --git a/rpython/jit/backend/ppc/ppc_assembler.py 
b/rpython/jit/backend/ppc/ppc_assembler.py
--- a/rpython/jit/backend/ppc/ppc_assembler.py
+++ b/rpython/jit/backend/ppc/ppc_assembler.py
@@ -315,6 +315,7 @@
         #   * r2 is the gcmap
         #   * the old value of these regs must already be stored in the 
jitframe
         #   * on exit, all registers are restored from the jitframe
+        #   * the result of the call, if any, is moved to r2
 
         mc = PPCBuilder()
         self.mc = mc
@@ -347,7 +348,11 @@
         # Finish
         self._reload_frame_if_necessary(mc)
 
+        # Move the result, if any, to r2
+        mc.mr(r.SCRATCH2.value, r.r3.value)
+
         mc.mtlr(r.RCS1.value)     # restore LR
+
         self._pop_core_regs_from_jitframe(mc, saved_regs)
         if supports_floats:
             self._pop_fp_regs_from_jitframe(mc)
diff --git a/rpython/jit/backend/ppc/regalloc.py 
b/rpython/jit/backend/ppc/regalloc.py
--- a/rpython/jit/backend/ppc/regalloc.py
+++ b/rpython/jit/backend/ppc/regalloc.py
@@ -1038,14 +1038,34 @@
 
     def prepare_cond_call(self, op):
         self.load_condition_into_cc(op.getarg(0))
-        locs = []
+        self.assembler.guard_success_cc = c.negate(
+            self.assembler.guard_success_cc)
+        # ^^^ if arg0==0, we jump over the next block of code (the call)
+        locs = [None]
         # support between 0 and 4 integer arguments
         assert 2 <= op.numargs() <= 2 + 4
         for i in range(1, op.numargs()):
             loc = self.loc(op.getarg(i))
             assert loc.type != FLOAT
             locs.append(loc)
-        return locs
+        return locs     # [None, function, args...]
+
+    def prepare_cond_call_value_i(self, op):
+        x = self.ensure_reg(op.getarg(0))
+        self.load_condition_into_cc(op.getarg(0))
+        self.rm.force_allocate_reg(op, selected_reg=x)   # spilled if survives
+        # ^^^ if arg0!=0, we jump over the next block of code (the call)
+        locs = [x]
+        # support between 0 and 4 integer arguments
+        assert 2 <= op.numargs() <= 2 + 4
+        for i in range(1, op.numargs()):
+            loc = self.loc(op.getarg(i))
+            assert loc.type != FLOAT
+            locs.append(loc)
+        return locs     # [res, function, args...]
+
+    prepare_cond_call_value_r = prepare_cond_call_value_i
+
 
 def notimplemented(self, op):
     msg = '[PPC/regalloc] %s not implemented\n' % op.getopname()
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
@@ -2448,9 +2448,6 @@
             assert called == [(67, 89)]
 
     def test_cond_call_value(self):
-        if not self.cpu.supports_cond_call_value:
-            py.test.skip("missing supports_cond_call_value")
-
         def func_int(*args):
             called.append(args)
             return len(args) * 100 + 1000
diff --git a/rpython/jit/backend/test/test_ll_random.py 
b/rpython/jit/backend/test/test_ll_random.py
--- a/rpython/jit/backend/test/test_ll_random.py
+++ b/rpython/jit/backend/test/test_ll_random.py
@@ -711,11 +711,6 @@
 # 6. a conditional call (for now always with no exception raised)
 class CondCallOperation(BaseCallOperation):
 
-    def filter(self, builder):
-        if not builder.cpu.supports_cond_call_value and \
-           self.opnum == rop.COND_CALL_VALUE_I:
-            raise test_random.CannotProduceOperation
-
     def produce_into(self, builder, r):
         fail_subset = builder.subset_of_intvars(r)
         if self.opnum == rop.COND_CALL:
diff --git a/rpython/jit/backend/test/zll_stress.py 
b/rpython/jit/backend/test/zll_stress.py
--- a/rpython/jit/backend/test/zll_stress.py
+++ b/rpython/jit/backend/test/zll_stress.py
@@ -20,11 +20,6 @@
     r = Random()
     r.jumpahead(piece*99999999)
     OPERATIONS = LLtypeOperationBuilder.OPERATIONS[:]
-    if not cpu.supports_cond_call_value:
-        # remove COND_CALL_VALUE_I if the cpu does not support it
-        ops = LLtypeOperationBuilder.OPERATIONS
-        LLtypeOperationBuilder.OPERATIONS = [op for op in ops \
-                if op.opnum != rop.COND_CALL_VALUE_I]
     for i in range(piece*per_piece, (piece+1)*per_piece):
         print "        i = %d; r.setstate(%s)" % (i, r.getstate())
         check_random_function(cpu, LLtypeOperationBuilder, r, i, 
total_iterations)
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
@@ -16,7 +16,6 @@
     debug = True
     supports_floats = True
     supports_singlefloats = True
-    supports_cond_call_value = True
 
     dont_keepalive_stuff = False # for tests
     with_threads = False
diff --git a/rpython/jit/backend/x86/test/test_call.py 
b/rpython/jit/backend/x86/test/test_call.py
new file mode 100644
--- /dev/null
+++ b/rpython/jit/backend/x86/test/test_call.py
@@ -0,0 +1,7 @@
+from rpython.jit.backend.x86.test.test_basic import Jit386Mixin
+from rpython.jit.metainterp.test import test_call
+
+class TestCall(Jit386Mixin, test_call.CallTest):
+    # for the individual tests see
+    # ====> ../../../metainterp/test/test_call.py
+    pass
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
@@ -7,10 +7,9 @@
 from rpython.jit.codewriter.jitcode import JitCode
 from rpython.jit.codewriter.effectinfo import (VirtualizableAnalyzer,
     QuasiImmutAnalyzer, RandomEffectsAnalyzer, effectinfo_from_writeanalyze,
-    EffectInfo, CallInfoCollection, CallShortcut)
+    EffectInfo, CallInfoCollection)
 from rpython.rtyper.lltypesystem import lltype, llmemory
 from rpython.rtyper.lltypesystem.lltype import getfunctionptr
-from rpython.flowspace.model import Constant, Variable
 from rpython.rlib import rposix
 from rpython.translator.backendopt.canraise import RaiseAnalyzer
 from rpython.translator.backendopt.writeanalyze import ReadWriteAnalyzer
@@ -215,7 +214,6 @@
         elidable = False
         loopinvariant = False
         call_release_gil_target = EffectInfo._NO_CALL_RELEASE_GIL_TARGET
-        call_shortcut = None
         if op.opname == "direct_call":
             funcobj = op.args[0].value._obj
             assert getattr(funcobj, 'calling_conv', 'c') == 'c', (
@@ -230,12 +228,6 @@
                 tgt_func, tgt_saveerr = func._call_aroundstate_target_
                 tgt_func = llmemory.cast_ptr_to_adr(tgt_func)
                 call_release_gil_target = (tgt_func, tgt_saveerr)
-            if hasattr(funcobj, 'graph'):
-                call_shortcut = self.find_call_shortcut(funcobj.graph)
-            if getattr(func, "_call_shortcut_", False):
-                assert call_shortcut is not None, (
-                    "%r: marked as @jit.call_shortcut but shortcut not found"
-                    % (func,))
         elif op.opname == 'indirect_call':
             # check that we're not trying to call indirectly some
             # function with the special flags
@@ -250,8 +242,6 @@
                     error = '@jit.loop_invariant'
                 if hasattr(graph.func, '_call_aroundstate_target_'):
                     error = '_call_aroundstate_target_'
-                if hasattr(graph.func, '_call_shortcut_'):
-                    error = '@jit.call_shortcut'
                 if not error:
                     continue
                 raise Exception(
@@ -308,7 +298,6 @@
             self.readwrite_analyzer.analyze(op, self.seen_rw), self.cpu,
             extraeffect, oopspecindex, can_invalidate, call_release_gil_target,
             extradescr, self.collect_analyzer.analyze(op, self.seen_gc),
-            call_shortcut,
         )
         #
         assert effectinfo is not None
@@ -379,76 +368,3 @@
                 if GTYPE_fieldname in jd.greenfield_info.green_fields:
                     return True
         return False
-
-    def find_call_shortcut(self, graph):
-        """Identifies graphs that start like this:
-
-           def graph(x, y, z):         def graph(x, y, z):
-               if y.field:                 r = y.field
-                   return y.field          if r: return r
-        """
-        block = graph.startblock
-        operations = block.operations
-        c_fieldname = None
-        if not operations:
-            v_inst = v_result = block.exitswitch
-        else:
-            op = operations[0]
-            if len(op.args) == 0:
-                return
-            if op.opname != 'getfield':  # check for this form:
-                v_inst = op.args[0]      #     if y is not None;
-                v_result = v_inst        #          return y
-            else:
-                operations = operations[1:]
-                [v_inst, c_fieldname] = op.args
-                v_result = op.result
-        if not isinstance(v_inst, Variable):
-            return
-        if v_result.concretetype != graph.getreturnvar().concretetype:
-            return
-        if v_result.concretetype == lltype.Void:
-            return
-        argnum = i = 0
-        while block.inputargs[i] is not v_inst:
-            if block.inputargs[i].concretetype != lltype.Void:
-                argnum += 1
-            i += 1
-        PSTRUCT = v_inst.concretetype
-        v_check = v_result
-        fastcase = True
-        for op in operations:
-            if (op.opname in ('int_is_true', 'ptr_nonzero', 'same_as')
-                    and v_check is op.args[0]):
-                v_check = op.result
-            elif op.opname == 'ptr_iszero' and v_check is op.args[0]:
-                v_check = op.result
-                fastcase = not fastcase
-            elif (op.opname in ('int_eq', 'int_ne')
-                    and v_check is op.args[0]
-                    and isinstance(op.args[1], Constant)
-                    and op.args[1].value == 0):
-                v_check = op.result
-                if op.opname == 'int_eq':
-                    fastcase = not fastcase
-            else:
-                return
-        if v_check.concretetype is not lltype.Bool:
-            return
-        if block.exitswitch is not v_check:
-            return
-
-        links = [link for link in block.exits if link.exitcase == fastcase]
-        if len(links) != 1:
-            return
-        [link] = links
-        if link.args != [v_result]:
-            return
-        if not link.target.is_final_block():
-            return
-
-        if c_fieldname is not None:
-            fielddescr = self.cpu.fielddescrof(PSTRUCT.TO, c_fieldname.value)
-        else:
-            fielddescr = None
-        return CallShortcut(argnum, fielddescr)
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
@@ -117,8 +117,7 @@
                 can_invalidate=False,
                 call_release_gil_target=_NO_CALL_RELEASE_GIL_TARGET,
                 extradescrs=None,
-                can_collect=True,
-                call_shortcut=None):
+                can_collect=True):
         readonly_descrs_fields = frozenset_or_none(readonly_descrs_fields)
         readonly_descrs_arrays = frozenset_or_none(readonly_descrs_arrays)
         readonly_descrs_interiorfields = frozenset_or_none(
@@ -136,8 +135,7 @@
                extraeffect,
                oopspecindex,
                can_invalidate,
-               can_collect,
-               call_shortcut)
+               can_collect)
         tgt_func, tgt_saveerr = call_release_gil_target
         if tgt_func:
             key += (object(),)    # don't care about caching in this case
@@ -192,7 +190,6 @@
         result.oopspecindex = oopspecindex
         result.extradescrs = extradescrs
         result.call_release_gil_target = call_release_gil_target
-        result.call_shortcut = call_shortcut
         if result.check_can_raise(ignore_memoryerror=True):
             assert oopspecindex in cls._OS_CANRAISE
 
@@ -278,8 +275,7 @@
                                  call_release_gil_target=
                                      EffectInfo._NO_CALL_RELEASE_GIL_TARGET,
                                  extradescr=None,
-                                 can_collect=True,
-                                 call_shortcut=None):
+                                 can_collect=True):
     from rpython.translator.backendopt.writeanalyze import top_set
     if effects is top_set or extraeffect == EffectInfo.EF_RANDOM_EFFECTS:
         readonly_descrs_fields = None
@@ -368,8 +364,7 @@
                       can_invalidate,
                       call_release_gil_target,
                       extradescr,
-                      can_collect,
-                      call_shortcut)
+                      can_collect)
 
 def consider_struct(TYPE, fieldname):
     if fieldType(TYPE, fieldname) is lltype.Void:
@@ -392,24 +387,6 @@
 
 # ____________________________________________________________
 
-
-class CallShortcut(object):
-    def __init__(self, argnum, fielddescr):
-        self.argnum = argnum
-        self.fielddescr = fielddescr
-
-    def __eq__(self, other):
-        return (isinstance(other, CallShortcut) and
-                self.argnum == other.argnum and
-                self.fielddescr == other.fielddescr)
-    def __ne__(self, other):
-        return not (self == other)
-    def __hash__(self):
-        return hash((self.argnum, self.fielddescr))
-
-# ____________________________________________________________
-
-
 class VirtualizableAnalyzer(BoolGraphAnalyzer):
     def analyze_simple_operation(self, op, graphinfo):
         return op.opname in ('jit_force_virtualizable',
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
@@ -789,12 +789,20 @@
                                                 arrayfielddescr,
                                                 arraydescr)
             return []
+        # check for the string or unicode hash field
+        STRUCT = v_inst.concretetype.TO
+        if STRUCT == rstr.STR:
+            assert c_fieldname.value == 'hash'
+            return SpaceOperation('strhash', [v_inst], op.result)
+        elif STRUCT == rstr.UNICODE:
+            assert c_fieldname.value == 'hash'
+            return SpaceOperation('unicodehash', [v_inst], op.result)
         # check for _immutable_fields_ hints
-        immut = v_inst.concretetype.TO._immutable_field(c_fieldname.value)
+        immut = STRUCT._immutable_field(c_fieldname.value)
         need_live = False
         if immut:
             if (self.callcontrol is not None and
-                self.callcontrol.could_be_green_field(v_inst.concretetype.TO,
+                self.callcontrol.could_be_green_field(STRUCT,
                                                       c_fieldname.value)):
                 pure = '_greenfield'
                 need_live = True
@@ -802,10 +810,9 @@
                 pure = '_pure'
         else:
             pure = ''
-        self.check_field_access(v_inst.concretetype.TO)
-        argname = getattr(v_inst.concretetype.TO, '_gckind', 'gc')
-        descr = self.cpu.fielddescrof(v_inst.concretetype.TO,
-                                      c_fieldname.value)
+        self.check_field_access(STRUCT)
+        argname = getattr(STRUCT, '_gckind', 'gc')
+        descr = self.cpu.fielddescrof(STRUCT, c_fieldname.value)
         kind = getkind(RESULT)[0]
         if argname != 'gc':
             assert argname == 'raw'
@@ -822,7 +829,7 @@
         if immut in (IR_QUASIIMMUTABLE, IR_QUASIIMMUTABLE_ARRAY):
             op1.opname += "_pure"
             descr1 = self.cpu.fielddescrof(
-                v_inst.concretetype.TO,
+                STRUCT,
                 quasiimmut.get_mutate_field_name(c_fieldname.value))
             return [SpaceOperation('-live-', [], None),
                    SpaceOperation('record_quasiimmut_field',
@@ -1569,7 +1576,7 @@
             return []
         return getattr(self, 'handle_jit_marker__%s' % key)(op, jitdriver)
 
-    def rewrite_op_jit_conditional_call(self, op):
+    def _rewrite_op_cond_call(self, op, rewritten_opname):
         have_floats = False
         for arg in op.args:
             if getkind(arg.concretetype) == 'float':
@@ -1580,13 +1587,18 @@
         callop = SpaceOperation('direct_call', op.args[1:], op.result)
         calldescr = self.callcontrol.getcalldescr(callop)
         assert not 
calldescr.get_extra_info().check_forces_virtual_or_virtualizable()
-        op1 = self.rewrite_call(op, 'conditional_call',
+        op1 = self.rewrite_call(op, rewritten_opname,
                                 op.args[:2], args=op.args[2:],
                                 calldescr=calldescr, force_ir=True)
         if self.callcontrol.calldescr_canraise(calldescr):
             op1 = [op1, SpaceOperation('-live-', [], None)]
         return op1
 
+    def rewrite_op_jit_conditional_call(self, op):
+        return self._rewrite_op_cond_call(op, 'conditional_call')
+    def rewrite_op_jit_conditional_call_value(self, op):
+        return self._rewrite_op_cond_call(op, 'conditional_call_value')
+
     def handle_jit_marker__jit_merge_point(self, op, jitdriver):
         assert self.portal_jd is not None, (
             "'jit_merge_point' in non-portal graph!")
diff --git a/rpython/jit/codewriter/test/test_call.py 
b/rpython/jit/codewriter/test/test_call.py
--- a/rpython/jit/codewriter/test/test_call.py
+++ b/rpython/jit/codewriter/test/test_call.py
@@ -6,7 +6,7 @@
 from rpython.rlib import jit
 from rpython.jit.codewriter import support, call
 from rpython.jit.codewriter.call import CallControl
-from rpython.jit.codewriter.effectinfo import EffectInfo, CallShortcut
+from rpython.jit.codewriter.effectinfo import EffectInfo
 
 
 class FakePolicy:
@@ -368,121 +368,3 @@
         assert call_op.opname == 'direct_call'
         call_descr = cc.getcalldescr(call_op)
         assert call_descr.extrainfo.check_can_collect() == expected
-
-def test_find_call_shortcut():
-    class FakeCPU:
-        def fielddescrof(self, TYPE, fieldname):
-            if isinstance(TYPE, lltype.GcStruct):
-                if fieldname == 'inst_foobar':
-                    return 'foobardescr'
-                if fieldname == 'inst_fooref':
-                    return 'foorefdescr'
-            if TYPE == RAW and fieldname == 'x':
-                return 'xdescr'
-            assert False, (TYPE, fieldname)
-    cc = CallControl(FakeCPU())
-
-    class B(object):
-        foobar = 0
-        fooref = None
-
-    def f1(a, b, c):
-        if b.foobar:
-            return b.foobar
-        b.foobar = a + c
-        return b.foobar
-
-    def f2(x, y, z, b):
-        r = b.fooref
-        if r is not None:
-            return r
-        r = b.fooref = B()
-        return r
-
-    class Space(object):
-        def _freeze_(self):
-            return True
-    space = Space()
-
-    def f3(space, b):
-        r = b.foobar
-        if not r:
-            r = b.foobar = 123
-        return r
-
-    def f4(raw):
-        r = raw.x
-        if r != 0:
-            return r
-        raw.x = 123
-        return 123
-    RAW = lltype.Struct('RAW', ('x', lltype.Signed))
-
-    def f5(b):
-        r = b.foobar
-        if r == 0:
-            r = b.foobar = 123
-        return r
-
-    def f6(b):
-        if b is not None:
-            return b
-        return B()
-
-    def f7(c, a):
-        if a:
-            return a
-        return 123
-
-    def b_or_none(c):
-        if c > 15:
-            return B()
-        return None
-
-    def f(a, c):
-        b = B()
-        f1(a, b, c)
-        f2(a, c, a, b)
-        f3(space, b)
-        r = lltype.malloc(RAW, flavor='raw')
-        f4(r)
-        f5(b)
-        f6(b_or_none(c))
-        f7(c, a)
-
-    rtyper = support.annotate(f, [10, 20])
-    f1_graph = rtyper.annotator.translator._graphof(f1)
-    assert cc.find_call_shortcut(f1_graph) == CallShortcut(1, "foobardescr")
-    f2_graph = rtyper.annotator.translator._graphof(f2)
-    assert cc.find_call_shortcut(f2_graph) == CallShortcut(3, "foorefdescr")
-    f3_graph = rtyper.annotator.translator._graphof(f3)
-    assert cc.find_call_shortcut(f3_graph) == CallShortcut(0, "foobardescr")
-    f4_graph = rtyper.annotator.translator._graphof(f4)
-    assert cc.find_call_shortcut(f4_graph) == CallShortcut(0, "xdescr")
-    f5_graph = rtyper.annotator.translator._graphof(f5)
-    assert cc.find_call_shortcut(f5_graph) == CallShortcut(0, "foobardescr")
-    f6_graph = rtyper.annotator.translator._graphof(f6)
-    assert cc.find_call_shortcut(f6_graph) == CallShortcut(0, None)
-    f7_graph = rtyper.annotator.translator._graphof(f7)
-    assert cc.find_call_shortcut(f7_graph) == CallShortcut(1, None)
-
-def test_cant_find_call_shortcut():
-    from rpython.jit.backend.llgraph.runner import LLGraphCPU
-
-    @jit.dont_look_inside
-    @jit.call_shortcut
-    def f1(n):
-        return n + 17   # no call shortcut found
-
-    def f(n):
-        return f1(n)
-
-    rtyper = support.annotate(f, [1])
-    jitdriver_sd = FakeJitDriverSD(rtyper.annotator.translator.graphs[0])
-    cc = CallControl(LLGraphCPU(rtyper), jitdrivers_sd=[jitdriver_sd])
-    res = cc.find_all_graphs(FakePolicy())
-    [f_graph] = [x for x in res if x.func is f]
-    call_op = f_graph.startblock.operations[0]
-    assert call_op.opname == 'direct_call'
-    e = py.test.raises(AssertionError, cc.getcalldescr, call_op)
-    assert "shortcut not found" in str(e.value)
diff --git a/rpython/jit/metainterp/blackhole.py 
b/rpython/jit/metainterp/blackhole.py
--- a/rpython/jit/metainterp/blackhole.py
+++ b/rpython/jit/metainterp/blackhole.py
@@ -1199,13 +1199,27 @@
     def bhimpl_residual_call_irf_v(cpu, func, args_i,args_r,args_f,calldescr):
         return cpu.bh_call_v(func, args_i, args_r, args_f, calldescr)
 
-    # conditional calls - note that they cannot return stuff
     @arguments("cpu", "i", "i", "I", "R", "d")
     def bhimpl_conditional_call_ir_v(cpu, condition, func, args_i, args_r,
                                      calldescr):
+        # conditional calls - condition is a flag, and they cannot return stuff
         if condition:
             cpu.bh_call_v(func, args_i, args_r, None, calldescr)
 
+    @arguments("cpu", "i", "i", "I", "R", "d", returns="i")
+    def bhimpl_conditional_call_value_ir_i(cpu, value, func, args_i, args_r,
+                                           calldescr):
+        if value == 0:
+            value = cpu.bh_call_i(func, args_i, args_r, None, calldescr)
+        return value
+
+    @arguments("cpu", "r", "i", "I", "R", "d", returns="r")
+    def bhimpl_conditional_call_value_ir_r(cpu, value, func, args_i, args_r,
+                                           calldescr):
+        if not value:
+            value = cpu.bh_call_r(func, args_i, args_r, None, calldescr)
+        return value
+
     @arguments("cpu", "j", "R", returns="i")
     def bhimpl_inline_call_r_i(cpu, jitcode, args_r):
         return cpu.bh_call_i(jitcode.get_fnaddr_as_int(),
@@ -1493,6 +1507,9 @@
     @arguments("cpu", "r", "r", "i", "i", "i")
     def bhimpl_copystrcontent(cpu, src, dst, srcstart, dststart, length):
         cpu.bh_copystrcontent(src, dst, srcstart, dststart, length)
+    @arguments("cpu", "r", returns="i")
+    def bhimpl_strhash(cpu, string):
+        return cpu.bh_strhash(string)
 
     @arguments("cpu", "i", returns="r")
     def bhimpl_newunicode(cpu, length):
@@ -1509,6 +1526,9 @@
     @arguments("cpu", "r", "r", "i", "i", "i")
     def bhimpl_copyunicodecontent(cpu, src, dst, srcstart, dststart, length):
         cpu.bh_copyunicodecontent(src, dst, srcstart, dststart, length)
+    @arguments("cpu", "r", returns="i")
+    def bhimpl_unicodehash(cpu, unicode):
+        return cpu.bh_unicodehash(unicode)
 
     @arguments("i", "i")
     def bhimpl_rvmprof_code(leaving, unique_id):
diff --git a/rpython/jit/metainterp/heapcache.py 
b/rpython/jit/metainterp/heapcache.py
--- a/rpython/jit/metainterp/heapcache.py
+++ b/rpython/jit/metainterp/heapcache.py
@@ -271,6 +271,7 @@
             return
         if (OpHelpers.is_plain_call(opnum) or
             OpHelpers.is_call_loopinvariant(opnum) or
+            OpHelpers.is_cond_call_value(opnum) or
             opnum == rop.COND_CALL):
             effectinfo = descr.get_extra_info()
             ef = effectinfo.extraeffect
diff --git a/rpython/jit/metainterp/optimizeopt/info.py 
b/rpython/jit/metainterp/optimizeopt/info.py
--- a/rpython/jit/metainterp/optimizeopt/info.py
+++ b/rpython/jit/metainterp/optimizeopt/info.py
@@ -1,5 +1,5 @@
 
-from rpython.rlib.objectmodel import specialize, we_are_translated
+from rpython.rlib.objectmodel import specialize, we_are_translated, 
compute_hash
 from rpython.jit.metainterp.resoperation import AbstractValue, ResOperation,\
      rop, OpHelpers
 from rpython.jit.metainterp.history import ConstInt, Const
@@ -73,6 +73,9 @@
     def getstrlen(self, op, string_optimizer, mode, create_ops=True):
         return None
 
+    def getstrhash(self, op, mode):
+        return None
+
     def copy_fields_to_const(self, constinfo, optheap):
         pass
 
@@ -790,6 +793,20 @@
                 return None
             return ConstInt(len(s))
 
+    def getstrhash(self, op, mode):
+        from rpython.jit.metainterp.optimizeopt import vstring
+
+        if mode is vstring.mode_string:
+            s = self._unpack_str(vstring.mode_string)
+            if s is None:
+                return None
+            return ConstInt(compute_hash(s))
+        else:
+            s = self._unpack_str(vstring.mode_unicode)
+            if s is None:
+                return None
+            return ConstInt(compute_hash(s))
+
     def string_copy_parts(self, op, string_optimizer, targetbox, offsetbox,
                           mode):
         from rpython.jit.metainterp.optimizeopt import vstring
diff --git a/rpython/jit/metainterp/optimizeopt/optimizer.py 
b/rpython/jit/metainterp/optimizeopt/optimizer.py
--- a/rpython/jit/metainterp/optimizeopt/optimizer.py
+++ b/rpython/jit/metainterp/optimizeopt/optimizer.py
@@ -261,9 +261,9 @@
     def produce_potential_short_preamble_ops(self, potential_ops):
         pass
 
-    def _can_optimize_call_pure(self, op):
+    def _can_optimize_call_pure(self, op, start_index=0):
         arg_consts = []
-        for i in range(op.numargs()):
+        for i in range(start_index, op.numargs()):
             arg = op.getarg(i)
             const = self.optimizer.get_constant_box(arg)
             if const is None:
diff --git a/rpython/jit/metainterp/optimizeopt/pure.py 
b/rpython/jit/metainterp/optimizeopt/pure.py
--- a/rpython/jit/metainterp/optimizeopt/pure.py
+++ b/rpython/jit/metainterp/optimizeopt/pure.py
@@ -151,13 +151,13 @@
             self._pure_operations[opnum] = recentops = RecentPureOps()
         return recentops
 
-    def optimize_CALL_PURE_I(self, op):
+    def optimize_call_pure(self, op, start_index=0):
         # Step 1: check if all arguments are constant
-        for arg in op.getarglist():
-            self.optimizer.force_box(arg)
+        for i in range(start_index, op.numargs()):
+            self.optimizer.force_box(op.getarg(i))
             # XXX hack to ensure that virtuals that are
             #     constant are presented that way
-        result = self._can_optimize_call_pure(op)
+        result = self._can_optimize_call_pure(op, start_index=start_index)
         if result is not None:
             # this removes a CALL_PURE with all constant arguments.
             self.make_constant(op, result)
@@ -168,32 +168,46 @@
         # CALL_PURE.
         for pos in self.call_pure_positions:
             old_op = self.optimizer._newoperations[pos]
-            if self.optimize_call_pure(op, old_op):
+            if self.optimize_call_pure_old(op, old_op, start_index):
                 return
         if self.extra_call_pure:
             for i, old_op in enumerate(self.extra_call_pure):
-                if self.optimize_call_pure(op, old_op):
+                if self.optimize_call_pure_old(op, old_op, start_index):
                     if isinstance(old_op, PreambleOp):
                         old_op = self.optimizer.force_op_from_preamble(old_op)
                         self.extra_call_pure[i] = old_op
                     return
 
-        # replace CALL_PURE with just CALL
-        opnum = OpHelpers.call_for_descr(op.getdescr())
-        newop = self.optimizer.replace_op_with(op, opnum)
+        # replace CALL_PURE with just CALL (but keep COND_CALL_VALUE)
+        if start_index == 0:
+            opnum = OpHelpers.call_for_descr(op.getdescr())
+            newop = self.optimizer.replace_op_with(op, opnum)
+        else:
+            newop = op
         return self.emit_result(CallPureOptimizationResult(self, newop))
 
+    def optimize_CALL_PURE_I(self, op):
+        return self.optimize_call_pure(op)
     optimize_CALL_PURE_R = optimize_CALL_PURE_I
     optimize_CALL_PURE_F = optimize_CALL_PURE_I
     optimize_CALL_PURE_N = optimize_CALL_PURE_I
 
-    def optimize_call_pure(self, op, old_op):
-        if (op.numargs() != old_op.numargs() or
-            op.getdescr() is not old_op.getdescr()):
+    def optimize_COND_CALL_VALUE_I(self, op):
+        return self.optimize_call_pure(op, start_index=1)
+    optimize_COND_CALL_VALUE_R = optimize_COND_CALL_VALUE_I
+
+    def optimize_call_pure_old(self, op, old_op, start_index):
+        if op.getdescr() is not old_op.getdescr():
             return False
-        for i, box in enumerate(old_op.getarglist()):
-            if not self.get_box_replacement(op.getarg(i)).same_box(box):
+        # this will match a call_pure and a cond_call_value with
+        # the same function and arguments
+        j = start_index
+        old_start_index = OpHelpers.is_cond_call_value(old_op.opnum)
+        for i in range(old_start_index, old_op.numargs()):
+            box = old_op.getarg(i)
+            if not self.get_box_replacement(op.getarg(j)).same_box(box):
                 break
+            j += 1
         else:
             # all identical
             # this removes a CALL_PURE that has the same (non-constant)
@@ -250,10 +264,17 @@
             # don't move call_pure_with_exception in the short preamble...
             # issue #2015
 
+            # Also, don't move cond_call_value in the short preamble.
+            # The issue there is that it's usually pointless to try to
+            # because the 'value' argument is typically not a loop
+            # invariant, and would really need to be in order to end up
+            # in the short preamble.  Maybe the code works anyway in the
+            # other rare case, but better safe than sorry and don't try.
             effectinfo = op.getdescr().get_extra_info()
             if not effectinfo.check_can_raise(ignore_memoryerror=True):
                 assert rop.is_call(op.opnum)
-                sb.add_pure_op(op)
+                if not OpHelpers.is_cond_call_value(op.opnum):
+                    sb.add_pure_op(op)
 
 dispatch_opt = make_dispatcher_method(OptPure, 'optimize_',
                                       default=OptPure.optimize_default)
diff --git a/rpython/jit/metainterp/optimizeopt/rewrite.py 
b/rpython/jit/metainterp/optimizeopt/rewrite.py
--- a/rpython/jit/metainterp/optimizeopt/rewrite.py
+++ b/rpython/jit/metainterp/optimizeopt/rewrite.py
@@ -596,6 +596,19 @@
             op = op.copy_and_change(opnum, args=op.getarglist()[1:])
         return self.emit(op)
 
+    def optimize_COND_CALL_VALUE_I(self, op):
+        # look if we know the nullness of the first argument
+        info = self.getnullness(op.getarg(0))
+        if info == INFO_NONNULL:
+            self.make_equal_to(op, op.getarg(0))
+            self.last_emitted_operation = REMOVED
+            return
+        if info == INFO_NULL:
+            opnum = OpHelpers.call_pure_for_type(op.type)
+            op = self.replace_op_with(op, opnum, args=op.getarglist()[1:])
+        return self.emit(op)
+    optimize_COND_CALL_VALUE_R = optimize_COND_CALL_VALUE_I
+
     def _optimize_nullness(self, op, box, expect_nonnull):
         info = self.getnullness(box)
         if info == INFO_NONNULL:
diff --git a/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py 
b/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py
--- a/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py
+++ b/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py
@@ -4,6 +4,7 @@
 from rpython.rtyper.lltypesystem import lltype
 from rpython.jit.metainterp import compile, resume
 from rpython.jit.metainterp.history import AbstractDescr, ConstInt, TreeLoop
+from rpython.jit.metainterp.history import ConstPtr
 from rpython.jit.metainterp.optimize import InvalidLoop
 from rpython.jit.metainterp.optimizeopt import build_opt_chain
 from rpython.jit.metainterp.optimizeopt.test.test_util import (
@@ -8674,6 +8675,75 @@
         """
         self.optimize_loop(ops, expected)
 
+    def test_cond_call_with_a_constant_i(self):
+        ops = """
+        [p1]
+        i2 = cond_call_value_i(0, 123, p1, descr=plaincalldescr)
+        escape_n(i2)
+        jump(p1)
+        """
+        expected = """
+        [p1]
+        i2 = call_i(123, p1, descr=plaincalldescr)
+        escape_n(i2)
+        jump(p1)
+        """
+        self.optimize_loop(ops, expected)
+
+    def test_cond_call_with_a_constant_i2(self):
+        ops = """
+        [p1]
+        i2 = cond_call_value_i(12, 123, p1, descr=plaincalldescr)
+        escape_n(i2)
+        jump(p1)
+        """
+        expected = """
+        [p1]
+        escape_n(12)
+        jump(p1)
+        """
+        self.optimize_loop(ops, expected)
+
+    def test_cond_call_r1(self):
+        ops = """
+        [p1]
+        p2 = cond_call_value_r(p1, 123, p1, descr=plain_r_calldescr)
+        jump(p2)
+        """
+        self.optimize_loop(ops, ops)
+
+    def test_cond_call_r2(self):
+        ops = """
+        [p1]
+        guard_nonnull(p1) []
+        p2 = cond_call_value_r(p1, 123, p1, descr=plain_r_calldescr)
+        p3 = escape_r(p2)
+        jump(p3)
+        """
+        expected = """
+        [p1]
+        guard_nonnull(p1) []
+        p3 = escape_r(p1)
+        jump(p3)
+        """
+        self.optimize_loop(ops, expected)
+
+    def test_cond_call_r3(self):
+        arg_consts = [ConstInt(i) for i in (123, 4, 5, 6)]
+        call_pure_results = {tuple(arg_consts): ConstPtr(self.myptr)}
+        ops = """
+        [p1]
+        p2 = cond_call_value_r(p1, 123, 4, 5, 6, descr=plain_r_calldescr)
+        p3 = escape_r(p2)
+        jump(p3)
+        """
+        expected = """
+        [p1]
+        p3 = escape_r(ConstPtr(myptr))
+        jump(p3)
+        """
+        self.optimize_loop(ops, expected, call_pure_results=call_pure_results)
+
     def test_hippyvm_unroll_bug(self):
         ops = """
         [p0, i1, i2]
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
@@ -438,6 +438,10 @@
                     oopspecindex=EffectInfo.OS_INT_PY_MOD)
     int_py_mod_descr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT, ei)
 
+    FUNC = lltype.FuncType([], llmemory.GCREF)
+    ei = EffectInfo([], [], [], [], [], [], EffectInfo.EF_ELIDABLE_CAN_RAISE)
+    plain_r_calldescr = cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT, ei)
+
     namespace = locals()
 
 
diff --git a/rpython/jit/metainterp/optimizeopt/vstring.py 
b/rpython/jit/metainterp/optimizeopt/vstring.py
--- a/rpython/jit/metainterp/optimizeopt/vstring.py
+++ b/rpython/jit/metainterp/optimizeopt/vstring.py
@@ -537,6 +537,20 @@
                 return
         return self.emit(op)
 
+    def optimize_STRHASH(self, op):
+        return self._optimize_STRHASH(op, mode_string)
+    def optimize_UNICODEHASH(self, op):
+        return self._optimize_STRHASH(op, mode_unicode)
+
+    def _optimize_STRHASH(self, op, mode):
+        opinfo = self.getptrinfo(op.getarg(0))
+        if opinfo:
+            lgtop = opinfo.getstrhash(op, mode)
+            if lgtop is not None:
+                self.make_equal_to(op, lgtop)
+                return
+        return self.emit(op)
+
     def optimize_COPYSTRCONTENT(self, op):
         return self._optimize_COPYSTRCONTENT(op, mode_string)
 
diff --git a/rpython/jit/metainterp/pyjitpl.py 
b/rpython/jit/metainterp/pyjitpl.py
--- a/rpython/jit/metainterp/pyjitpl.py
+++ b/rpython/jit/metainterp/pyjitpl.py
@@ -1059,8 +1059,21 @@
     @arguments("box", "box", "boxes2", "descr", "orgpc")
     def opimpl_conditional_call_ir_v(self, condbox, funcbox, argboxes,
                                      calldescr, pc):
+        if isinstance(condbox, ConstInt) and condbox.value == 0:
+            return   # so that the heapcache can keep argboxes virtual
         self.do_conditional_call(condbox, funcbox, argboxes, calldescr, pc)
 
+    @arguments("box", "box", "boxes2", "descr", "orgpc")
+    def _opimpl_conditional_call_value(self, valuebox, funcbox, argboxes,
+                                       calldescr, pc):
+        if isinstance(valuebox, Const) and valuebox.nonnull():
+            return valuebox
+        return self.do_conditional_call(valuebox, funcbox, argboxes,
+                                        calldescr, pc, is_value=True)
+
+    opimpl_conditional_call_value_ir_i = _opimpl_conditional_call_value
+    opimpl_conditional_call_value_ir_r = _opimpl_conditional_call_value
+
     @arguments("int", "boxes3", "boxes3", "orgpc")
     def _opimpl_recursive_call(self, jdindex, greenboxes, redboxes, pc):
         targetjitdriver_sd = self.metainterp.staticdata.jitdrivers_sd[jdindex]
@@ -1150,6 +1163,20 @@
         self.execute(rop.UNICODESETITEM, unicodebox, indexbox, newcharbox)
 
     @arguments("box")
+    def opimpl_strhash(self, strbox):
+        if isinstance(strbox, ConstPtr):
+            h = self.metainterp.cpu.bh_strhash(strbox.getref_base())
+            return ConstInt(h)
+        return self.execute(rop.STRHASH, strbox)
+
+    @arguments("box")
+    def opimpl_unicodehash(self, unicodebox):
+        if isinstance(unicodebox, ConstPtr):
+            h = self.metainterp.cpu.bh_unicodehash(unicodebox.getref_base())
+            return ConstInt(h)
+        return self.execute(rop.UNICODEHASH, unicodebox)
+
+    @arguments("box")
     def opimpl_newstr(self, lengthbox):
         return self.execute(rop.NEWSTR, lengthbox)
 
@@ -1538,7 +1565,7 @@
                                                             descr=descr)
         if pure and not self.metainterp.last_exc_value and op:
             op = self.metainterp.record_result_of_call_pure(op, argboxes, 
descr,
-                patch_pos)
+                patch_pos, opnum)
             exc = exc and not isinstance(op, Const)
         if exc:
             if op is not None:
@@ -1712,16 +1739,31 @@
             else:
                 assert False
 
-    def do_conditional_call(self, condbox, funcbox, argboxes, descr, pc):
-        if isinstance(condbox, ConstInt) and condbox.value == 0:
-            return   # so that the heapcache can keep argboxes virtual
+    def do_conditional_call(self, condbox, funcbox, argboxes, descr, pc,
+                            is_value=False):
         allboxes = self._build_allboxes(funcbox, argboxes, descr)
         effectinfo = descr.get_extra_info()
         assert not effectinfo.check_forces_virtual_or_virtualizable()
         exc = effectinfo.check_can_raise()
-        pure = effectinfo.check_is_elidable()
-        return self.execute_varargs(rop.COND_CALL, [condbox] + allboxes, descr,
-                                    exc, pure)
+        allboxes = [condbox] + allboxes
+        # COND_CALL cannot be pure (=elidable): it has no result.
+        # On the other hand, COND_CALL_VALUE is always calling a pure
+        # function.
+        if not is_value:
+            return self.execute_varargs(rop.COND_CALL, allboxes, descr,
+                                        exc, pure=False)
+        else:
+            opnum = OpHelpers.cond_call_value_for_descr(descr)
+            # work around the fact that execute_varargs() wants a
+            # constant for first argument
+            if opnum == rop.COND_CALL_VALUE_I:
+                return self.execute_varargs(rop.COND_CALL_VALUE_I, allboxes,
+                                            descr, exc, pure=True)
+            elif opnum == rop.COND_CALL_VALUE_R:
+                return self.execute_varargs(rop.COND_CALL_VALUE_R, allboxes,
+                                            descr, exc, pure=True)
+            else:
+                raise AssertionError
 
     def _do_jit_force_virtual(self, allboxes, descr, pc):
         assert len(allboxes) == 2
@@ -3061,11 +3103,16 @@
         debug_stop("jit-abort-longest-function")
         return max_jdsd, max_key
 
-    def record_result_of_call_pure(self, op, argboxes, descr, patch_pos):
+    def record_result_of_call_pure(self, op, argboxes, descr, patch_pos, 
opnum):
         """ Patch a CALL into a CALL_PURE.
         """
         resbox_as_const = executor.constant_from_op(op)
-        for argbox in argboxes:
+        is_cond_value = OpHelpers.is_cond_call_value(opnum)
+        if is_cond_value:
+            normargboxes = argboxes[1:]    # ingore the 'value' arg
+        else:
+            normargboxes = argboxes
+        for argbox in normargboxes:
             if not isinstance(argbox, Const):
                 break
         else:
@@ -3075,8 +3122,10 @@
             return resbox_as_const
         # not all constants (so far): turn CALL into CALL_PURE, which might
         # be either removed later by optimizeopt or turned back into CALL.
-        arg_consts = [executor.constant_from_op(a) for a in argboxes]
+        arg_consts = [executor.constant_from_op(a) for a in normargboxes]
         self.call_pure_results[arg_consts] = resbox_as_const
+        if is_cond_value:
+            return op       # but COND_CALL_VALUE remains
         opnum = OpHelpers.call_pure_for_descr(descr)
         self.history.cut(patch_pos)
         newop = self.history.record_nospec(opnum, argboxes, descr)
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
@@ -1097,6 +1097,8 @@
     '_MALLOC_LAST',
     'FORCE_TOKEN/0/r',    # historical name; nowadays, returns the jitframe
     'VIRTUAL_REF/2/r',    # removed before it's passed to the backend
+    'STRHASH/1/i',        # only reading the .hash field, might be zero so far
+    'UNICODEHASH/1/i',    #     (unless applied on consts, where .hash is 
forced)
     # this one has no *visible* side effect, since the virtualizable
     # must be forced, however we need to execute it anyway
     '_NOSIDEEFFECT_LAST', # ----- end of no_side_effect operations -----
@@ -1151,7 +1153,7 @@
     '_CALL_FIRST',
     'CALL/*d/rfin',
     'COND_CALL/*d/n',   # a conditional call, with first argument as a 
condition
-    'COND_CALL_VALUE/*d/ri',  # same but returns a result; emitted by rewrite
+    'COND_CALL_VALUE/*d/ri',  # "return a0 or a1(a2, ..)", a1 elidable
     'CALL_ASSEMBLER/*d/rfin',  # call already compiled assembler
     'CALL_MAY_FORCE/*d/rfin',
     'CALL_LOOPINVARIANT/*d/rfin',
@@ -1274,6 +1276,15 @@
         return rop.CALL_LOOPINVARIANT_N
 
     @staticmethod
+    def cond_call_value_for_descr(descr):
+        tp = descr.get_normalized_result_type()
+        if tp == 'i':
+            return rop.COND_CALL_VALUE_I
+        elif tp == 'r':
+            return rop.COND_CALL_VALUE_R
+        assert False, tp
+
+    @staticmethod
     def getfield_pure_for_descr(descr):
         if descr.is_pointer_field():
             return rop.GETFIELD_GC_PURE_R
@@ -1326,6 +1337,16 @@
         return rop.CALL_N
 
     @staticmethod
+    def call_pure_for_type(tp):
+        if tp == 'i':
+            return rop.CALL_PURE_I
+        elif tp == 'r':
+            return rop.CALL_PURE_R
+        elif tp == 'f':
+            return rop.CALL_PURE_F
+        return rop.CALL_PURE_N
+
+    @staticmethod
     def is_guard(opnum):
         return rop._GUARD_FIRST <= opnum <= rop._GUARD_LAST
 
@@ -1447,6 +1468,11 @@
                 opnum == rop.CALL_RELEASE_GIL_N)
 
     @staticmethod
+    def is_cond_call_value(opnum):
+        return (opnum == rop.COND_CALL_VALUE_I or
+                opnum == rop.COND_CALL_VALUE_R)
+
+    @staticmethod
     def is_ovf(opnum):
         return rop._OVF_FIRST <= opnum <= rop._OVF_LAST
 
diff --git a/rpython/jit/metainterp/test/test_ajit.py 
b/rpython/jit/metainterp/test/test_ajit.py
--- a/rpython/jit/metainterp/test/test_ajit.py
+++ b/rpython/jit/metainterp/test/test_ajit.py
@@ -3775,9 +3775,8 @@
             return n
         res = self.meta_interp(f, [10])
         assert res == 0
-        self.check_resops({'int_gt': 2, 'getfield_gc_i': 1, 'int_eq': 1,
-                           'guard_true': 2, 'int_sub': 2, 'jump': 1,
-                           'guard_false': 1})
+        self.check_resops({'int_sub': 2, 'int_gt': 2, 'guard_true': 2,
+                           'jump': 1})
 
     def test_virtual_after_bridge(self):
         myjitdriver = JitDriver(greens = [], reds = ["n"])
@@ -4575,3 +4574,14 @@
 
         self.meta_interp(g, [5, 5, 5])
         self.check_resops(guard_true=10)   # 5 unrolled, plus 5 unrelated
+
+    def test_conditional_call_value(self):
+        from rpython.rlib.jit import conditional_call_elidable
+        def g(j):
+            return j + 5
+        def f(i, j):
+            return conditional_call_elidable(i, g, j)
+        res = self.interp_operations(f, [-42, 200])
+        assert res == -42
+        res = self.interp_operations(f, [0, 200])
+        assert res == 205
diff --git a/rpython/jit/metainterp/test/test_call.py 
b/rpython/jit/metainterp/test/test_call.py
--- a/rpython/jit/metainterp/test/test_call.py
+++ b/rpython/jit/metainterp/test/test_call.py
@@ -1,8 +1,8 @@
 
-from rpython.jit.metainterp.test.support import LLJitMixin
+from rpython.jit.metainterp.test.support import LLJitMixin, noConst
 from rpython.rlib import jit
 
-class TestCall(LLJitMixin):
+class CallTest(object):
     def test_indirect_call(self):
         @jit.dont_look_inside
         def f1(x):
@@ -52,3 +52,230 @@
 
         assert self.meta_interp(main, [10]) == 42
         self.check_resops(guard_no_exception=0)
+
+    def test_cond_call_i(self):
+        def f(n):
+            return n * 200
+
+        def main(n, m):
+            return jit.conditional_call_elidable(n, f, m)
+
+        assert self.interp_operations(main, [0, 10]) == 2000
+        assert self.interp_operations(main, [15, 42]) == 15
+
+    def test_cond_call_r(self):
+        def f(n):
+            return [n]
+
+        def main(n):
+            if n == 10:
+                l = []
+            else:
+                l = None
+            l = jit.conditional_call_elidable(l, f, n)
+            return len(l)
+
+        assert main(10) == 0
+        assert main(5) == 1
+        assert self.interp_operations(main, [10]) == 0
+        assert self.interp_operations(main, [5]) == 1
+
+    def test_cond_call_constant_in_pyjitpl(self):
+        def f(a, b):
+            return a + b
+        def main(n):
+            # this is completely constant-folded because the arguments
+            # to f() are constants.
+            return jit.conditional_call_elidable(n, f, 40, 2)
+
+        assert main(12) == 12
+        assert main(0) == 42
+        assert self.interp_operations(main, [12]) == 12
+        self.check_operations_history({'finish': 1})   # empty history
+        assert self.interp_operations(main, [0]) == 42
+        self.check_operations_history({'finish': 1})   # empty history
+
+    def test_cond_call_constant_in_optimizer(self):
+        myjitdriver = jit.JitDriver(greens = ['m'], reds = ['n', 'p'])
+        def externfn(x):
+            return x - 3
+        class V:
+            def __init__(self, value):
+                self.value = value
+        def f(n, m, p):
+            while n > 0:
+                myjitdriver.can_enter_jit(n=n, p=p, m=m)
+                myjitdriver.jit_merge_point(n=n, p=p, m=m)
+                m1 = noConst(m)
+                n -= jit.conditional_call_elidable(p, externfn, m1)
+            return n
+        res = self.meta_interp(f, [21, 5, 0])
+        assert res == -1
+        # the COND_CALL_VALUE is constant-folded away by optimizeopt.py
+        self.check_resops({'int_sub': 2, 'int_gt': 2, 'guard_true': 2,
+                           'jump': 1})
+
+    def test_cond_call_constant_in_optimizer_1(self):
+        # same as test_cond_call_constant_in_optimizer, but the 'value'
+        # argument changes
+        myjitdriver = jit.JitDriver(greens = ['m'], reds = ['n', 'p'])
+        def externfn(x):
+            return x - 3
+        class V:
+            def __init__(self, value):
+                self.value = value
+        def f(n, m, p):
+            while n > 0:
+                myjitdriver.can_enter_jit(n=n, p=p, m=m)
+                myjitdriver.jit_merge_point(n=n, p=p, m=m)
+                m1 = noConst(m)
+                n -= jit.conditional_call_elidable(p, externfn, m1)
+            return n
+        assert f(21, 5, 0) == -1
+        res = self.meta_interp(f, [21, 5, 0])
+        assert res == -1
+        # the COND_CALL_VALUE is constant-folded away by optimizeopt.py
+        self.check_resops({'int_sub': 2, 'int_gt': 2, 'guard_true': 2,
+                           'jump': 1})
+
+    def test_cond_call_constant_in_optimizer_2(self):
+        myjitdriver = jit.JitDriver(greens = ['m'], reds = ['n', 'p'])
+        def externfn(x):
+            return 2
+        def f(n, m, p):
+            while n > 0:
+                myjitdriver.can_enter_jit(n=n, p=p, m=m)
+                myjitdriver.jit_merge_point(n=n, p=p, m=m)
+                assert p > -1
+                assert p < 1
+                n -= jit.conditional_call_elidable(p, externfn, n)
+            return n
+        res = self.meta_interp(f, [21, 5, 0])
+        assert res == -1
+        # optimizer: the COND_CALL_VALUE is turned into a regular
+        # CALL_PURE, which itself becomes a CALL
+        self.check_resops(call_pure_i=0, cond_call_value_i=0, call_i=2,
+                          int_sub=2)
+
+    def test_cond_call_constant_in_optimizer_3(self):
+        myjitdriver = jit.JitDriver(greens = ['m'], reds = ['n', 'p'])
+        def externfn(x):
+            return 1
+        def f(n, m, p):
+            while n > 0:
+                myjitdriver.can_enter_jit(n=n, p=p, m=m)
+                myjitdriver.jit_merge_point(n=n, p=p, m=m)
+                assert p > -1
+                assert p < 1
+                n0 = n
+                n -= jit.conditional_call_elidable(p, externfn, n0)
+                n -= jit.conditional_call_elidable(p, externfn, n0)
+            return n
+        res = self.meta_interp(f, [21, 5, 0])
+        assert res == -1
+        # same as test_cond_call_constant_in_optimizer_2, but the two
+        # intermediate CALL_PUREs are replaced with only one, because
+        # they are called with the same arguments
+        self.check_resops(call_pure_i=0, cond_call_value_i=0, call_i=2,
+                          int_sub=4)
+
+    def test_cond_call_constant_in_optimizer_4(self):
+        class X:
+            def __init__(self, value):
+                self.value = value
+                self.triple = 0
+            def _compute_triple(self):
+                self.triple = self.value * 3
+                return self.triple
+            def get_triple(self):
+                return jit.conditional_call_elidable(self.triple,
+                                                  X._compute_triple, self)
+
+        myjitdriver = jit.JitDriver(greens = [], reds = 'auto')
+        def main(n):
+            total = 0
+            while n > 1:
+                myjitdriver.jit_merge_point()
+                x = X(n)
+                total += x.get_triple() + x.get_triple() + x.get_triple()
+                n -= 10
+            return total
+
+        res = self.meta_interp(main, [100])
+        assert res == main(100)
+        # remaining: only the first call to get_triple(), as a call_i
+        # because we know that x.triple == 0 here.  The remaining calls
+        # are removed because equal to the first one.
+        self.check_resops(call_i=2, cond_call_value_i=0)
+
+    def test_cond_call_multiple_in_optimizer_1(self):
+        # test called several times with the same arguments, but
+        # the condition is not available to the short preamble.
+        # This means that the second cond_call_value after unrolling
+        # can't be removed.
+        myjitdriver = jit.JitDriver(greens = [], reds = ['n', 'p', 'm'])
+        def externfn(x):
+            return 2000      # never actually called
+        @jit.dont_look_inside
+        def randomish(p):
+            return p + 1
+        def f(n, m, p):
+            while n > 0:
+                myjitdriver.can_enter_jit(n=n, p=p, m=m)
+                myjitdriver.jit_merge_point(n=n, p=p, m=m)
+                n -= jit.conditional_call_elidable(randomish(p), externfn, m)
+            return n
+        assert f(21, 5, 1) == -1
+        res = self.meta_interp(f, [21, 5, 1])
+        assert res == -1
+        self.check_resops(call_pure_i=0, cond_call_value_i=2,
+                          call_i=2,    # randomish()
+                          int_sub=2)
+
+    def test_cond_call_multiple_in_optimizer_2(self):
+        # test called several times with the same arguments.  Ideally
+        # we would like them to be consolidated into one call even if
+        # the 'value' are different but available from the short
+        # preamble.  We don't do it so far---it's a mess, because the
+        # short preamble is supposed to depend only on loop-invariant
+        # things, and 'value' is (most of the time) not loop-invariant.
+        myjitdriver = jit.JitDriver(greens = [], reds = ['n', 'p', 'm'])
+        def externfn(x):
+            return 2      # called only the first time
+        def f(n, m, p):
+            while n > 0:
+                myjitdriver.can_enter_jit(n=n, p=p, m=m)
+                myjitdriver.jit_merge_point(n=n, p=p, m=m)
+                p = jit.conditional_call_elidable(p, externfn, m)
+                n -= p
+            return n
+        assert f(21, 5, 0) == -1
+        res = self.meta_interp(f, [21, 5, 0])
+        assert res == -1
+        self.check_resops(call_pure_i=0,
+                          cond_call_value_i=2,   # ideally 1, but see above
+                          int_sub=2)
+
+    def test_cond_call_in_blackhole(self):
+        myjitdriver = jit.JitDriver(greens = [], reds = ['n', 'p', 'm'])
+        def externfn(x):
+            return 2
+        def f(n, m, p):
+            while n > 0:
+                myjitdriver.can_enter_jit(n=n, p=p, m=m)
+                myjitdriver.jit_merge_point(n=n, p=p, m=m)
+                if n > 6:    # will fail and finish in the blackhole
+                    pass
+                if jit.we_are_jitted():   # manually inline here
+                    p = jit._jit_conditional_call_value(p, externfn, m)
+                else:
+                    p = jit.conditional_call_elidable(p, externfn, m)
+                n -= p
+            return n
+        assert f(21, 5, 0) == -1
+        res = self.meta_interp(f, [21, 5, 0])
+        assert res == -1
+
+
+class TestCall(LLJitMixin, CallTest):
+    pass
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
@@ -194,7 +194,8 @@
                            'guard_true': 4, 'jump': 1,
                            'new_with_vtable': 2, 'getinteriorfield_gc_i': 2,
                            'setfield_gc': 14, 'int_gt': 2, 'int_sub': 2,
-                           'call_i': 6, 'call_n': 2, 'call_r': 2, 'int_ge': 2,
+                           'call_i': 4, 'call_n': 2, 'call_r': 2, 'int_ge': 2,
+                           'cond_call_value_i': 2, 'strhash': 4,
                            'guard_no_exception': 8, 'new': 2,
                            'guard_nonnull': 2})
 
@@ -273,7 +274,7 @@
 
         res = self.meta_interp(f, [10])
         assert res == f(10)
-        self.check_simple_loop(call_i=4, call_n=1)
+        self.check_simple_loop(call_i=3, cond_call_value_i=1, call_n=1)
 
     def test_dict_array_write_invalidates_caches(self):
         driver = JitDriver(greens = [], reds = 'auto')
@@ -295,7 +296,7 @@
         exp = f(10)
         res = self.meta_interp(f, [10])
         assert res == exp
-        self.check_simple_loop(call_i=5, call_n=2)
+        self.check_simple_loop(call_i=4, cond_call_value_i=1, call_n=2)
 
     def test_dict_double_lookup_2(self):
         driver = JitDriver(greens = [], reds = 'auto')
@@ -314,7 +315,7 @@
 
         res = self.meta_interp(f, [10])
         assert res == f(10)
-        self.check_simple_loop(call_i=2, call_n=1)
+        self.check_simple_loop(call_i=1, cond_call_value_i=1, call_n=1)
 
     def test_dict_eq_can_release_gil(self):
         from rpython.rtyper.lltypesystem import lltype, rffi
diff --git a/rpython/jit/metainterp/test/test_string.py 
b/rpython/jit/metainterp/test/test_string.py
--- a/rpython/jit/metainterp/test/test_string.py
+++ b/rpython/jit/metainterp/test/test_string.py
@@ -951,3 +951,10 @@
         self.meta_interp(f, [222, 3333])
         self.check_simple_loop({'guard_true': 1, 'int_add': 1,
                                 'int_lt': 1, 'jump': 1})
+
+    def test_string_hashing(self):
+        def f(i):
+            s = str(i)
+            d = {s: s + s}
+            return len(d[s])
+        assert self.interp_operations(f, [222]) == 6
diff --git a/rpython/rlib/jit.py b/rpython/rlib/jit.py
--- a/rpython/rlib/jit.py
+++ b/rpython/rlib/jit.py
@@ -257,27 +257,6 @@
     func.oopspec = "jit.not_in_trace()"   # note that 'func' may take arguments
     return func
 
-def call_shortcut(func):
-    """A decorator to ensure that a function has a fast-path.
-    DOES NOT RELIABLY WORK ON METHODS, USE ONLY ON FUNCTIONS!
-
-    Only useful on functions that the JIT doesn't normally look inside.
-    It still replaces residual calls to that function with inline code
-    that checks for a fast path, and only does the call if not.  For
-    now, graphs made by the following kinds of functions are detected:
-
-           def func(x, y, z):         def func(x, y, z):
-               if y.field:                 r = y.field
-                   return y.field          if r is None:
-               ...                             ...
-                                           return r
-
-    Fast-path detection is always on, but this decorator makes the
-    codewriter complain if it cannot find the promized fast-path.
-    """
-    func._call_shortcut_ = True
-    return func
-
 
 @oopspec("jit.isconstant(value)")
 @specialize.call_location()
@@ -1194,10 +1173,19 @@
         return hop.gendirectcall(ll_record_exact_class, v_inst, v_cls)
 
 def _jit_conditional_call(condition, function, *args):
-    pass
+    pass           # special-cased below
 
 @specialize.call_location()
 def conditional_call(condition, function, *args):
+    """Does the same as:
+
+         if condition:
+             function(*args)
+
+    but is better for the JIT, in case the condition is often false
+    but could be true occasionally.  It allows the JIT to always produce
+    bridge-free code.  The function is never looked inside.
+    """
     if we_are_jitted():
         _jit_conditional_call(condition, function, *args)
     else:
@@ -1205,21 +1193,76 @@
             function(*args)
 conditional_call._always_inline_ = True
 
+def _jit_conditional_call_value(value, function, *args):
+    return value    # special-cased below
+
[email protected]_location()
+def conditional_call_elidable(value, function, *args):
+    """Does the same as:
+
+        if value == <0 or None>:
+            value = function(*args)
+        return value
+
+    For the JIT.  Allows one branch which doesn't create a bridge,
+    typically used for caching.  The value and the function's return
+    type must match and cannot be a float: they must be either regular
+    'int', or something that turns into a pointer.
+
+    Even if the function is not marked @elidable, it is still treated
+    mostly like one.  The only difference is that (in heapcache.py)
+    we don't assume this function won't change anything observable.
+    This is useful for caches, as you can write:
+
+        def _compute_and_cache(...):
+            self.cache = ...compute...
+            return self.cache
+
+        x = jit.conditional_call_elidable(self.cache, _compute_and_cache, ...)
+
+    """
+    if we_are_jitted():
+        return _jit_conditional_call_value(value, function, *args)
+    else:
+        if isinstance(value, int):
+            if value == 0:
+                value = function(*args)
+                assert isinstance(value, int)
+        else:
+            if value is None:
+                value = function(*args)
+                assert not isinstance(value, int)
+        return value
+conditional_call_elidable._always_inline_ = True
+
 class ConditionalCallEntry(ExtRegistryEntry):
-    _about_ = _jit_conditional_call
+    _about_ = _jit_conditional_call, _jit_conditional_call_value
 
     def compute_result_annotation(self, *args_s):
-        self.bookkeeper.emulate_pbc_call(self.bookkeeper.position_key,
-                                         args_s[1], args_s[2:])
+        s_res = self.bookkeeper.emulate_pbc_call(self.bookkeeper.position_key,
+                                                 args_s[1], args_s[2:])
+        if self.instance == _jit_conditional_call_value:
+            from rpython.annotator import model as annmodel
+            return annmodel.unionof(s_res, args_s[0])
 
     def specialize_call(self, hop):
         from rpython.rtyper.lltypesystem import lltype
 
-        args_v = hop.inputargs(lltype.Bool, lltype.Void, *hop.args_r[2:])
+        if self.instance == _jit_conditional_call:
+            opname = 'jit_conditional_call'
+            COND = lltype.Bool
+            resulttype = None
+        elif self.instance == _jit_conditional_call_value:
+            opname = 'jit_conditional_call_value'
+            COND = hop.r_result
+            resulttype = hop.r_result.lowleveltype
+        else:
+            assert False
+        args_v = hop.inputargs(COND, lltype.Void, *hop.args_r[2:])
         args_v[1] = hop.args_r[1].get_concrete_llfn(hop.args_s[1],
                                                     hop.args_s[2:], 
hop.spaceop)
         hop.exception_is_here()
-        return hop.genop('jit_conditional_call', args_v)
+        return hop.genop(opname, args_v, resulttype=resulttype)
 
 def enter_portal_frame(unique_id):
     """call this when starting to interpret a function. calling this is not
diff --git a/rpython/rlib/rvmprof/rvmprof.py b/rpython/rlib/rvmprof/rvmprof.py
--- a/rpython/rlib/rvmprof/rvmprof.py
+++ b/rpython/rlib/rvmprof/rvmprof.py
@@ -28,6 +28,8 @@
 class FakeWeakCodeObjectList(object):
     def add_handle(self, handle):
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to