Author: Armin Rigo <ar...@tunes.org>
Branch: py3.5
Changeset: r88641:84e8172fc396
Date: 2016-11-24 17:11 +0100
http://bitbucket.org/pypy/pypy/changeset/84e8172fc396/

Log:    hg merge default

diff too long, truncating to 2000 out of 2923 lines

diff --git a/lib-python/2.7/distutils/sysconfig_pypy.py 
b/lib-python/2.7/distutils/sysconfig_pypy.py
--- a/lib-python/2.7/distutils/sysconfig_pypy.py
+++ b/lib-python/2.7/distutils/sysconfig_pypy.py
@@ -67,6 +67,7 @@
     g['LIBDIR'] = os.path.join(sys.prefix, 'lib')
     g['CC'] = "gcc -pthread" # -pthread might not be valid on OS/X, check
     g['OPT'] = "" 
+    g['VERSION'] = get_python_version()
 
     global _config_vars
     _config_vars = g
diff --git a/pypy/interpreter/argument.py b/pypy/interpreter/argument.py
--- a/pypy/interpreter/argument.py
+++ b/pypy/interpreter/argument.py
@@ -113,7 +113,9 @@
                 self.keywords = self.keywords + keywords
                 self.keywords_w = self.keywords_w + values_w
             return
+        is_dict = False
         if space.isinstance_w(w_starstararg, space.w_dict):
+            is_dict = True
             keys_w = space.unpackiterable(w_starstararg)
         else:
             try:
@@ -127,7 +129,9 @@
             keys_w = space.unpackiterable(w_keys)
         keywords_w = [None] * len(keys_w)
         keywords = [None] * len(keys_w)
-        _do_combine_starstarargs_wrapped(space, keys_w, w_starstararg, 
keywords, keywords_w, self.keywords)
+        _do_combine_starstarargs_wrapped(
+            space, keys_w, w_starstararg, keywords, keywords_w, self.keywords,
+            is_dict)
         self.keyword_names_w = keys_w
         if self.keywords is None:
             self.keywords = keywords
@@ -396,7 +400,7 @@
                             key)
 
 def _do_combine_starstarargs_wrapped(space, keys_w, w_starstararg, keywords,
-        keywords_w, existingkeywords):
+        keywords_w, existingkeywords, is_dict):
     i = 0
     for w_key in keys_w:
         try:
@@ -417,7 +421,16 @@
                             "got multiple values for keyword argument '%s'",
                             key)
         keywords[i] = key
-        keywords_w[i] = space.getitem(w_starstararg, w_key)
+        if is_dict:
+            # issue 2435: bug-to-bug compatibility with cpython. for a 
subclass of
+            # dict, just ignore the __getitem__ and access the underlying dict
+            # directly
+            from pypy.objspace.descroperation import dict_getitem
+            w_descr = dict_getitem(space)
+            w_value = space.get_and_call_function(w_descr, w_starstararg, 
w_key)
+        else:
+            w_value = space.getitem(w_starstararg, w_key)
+        keywords_w[i] = w_value
         i += 1
 
 @jit.look_inside_iff(
diff --git a/pypy/interpreter/test/test_argument.py 
b/pypy/interpreter/test/test_argument.py
--- a/pypy/interpreter/test/test_argument.py
+++ b/pypy/interpreter/test/test_argument.py
@@ -127,6 +127,12 @@
             raise OperationError(AttributeError, name)
         return method(*args)
 
+    def lookup_in_type(self, cls, name):
+        return getattr(cls, name)
+
+    def get_and_call_function(self, w_descr, w_obj, *args):
+        return w_descr.__get__(w_obj)(*args)
+
     def type(self, obj):
         class Type:
             def getname(self, space):
@@ -886,3 +892,19 @@
         assert "keywords must be strings" in str(e.value)
         e = raises(TypeError, "f(y=2, **{'x': 5}, x=6)")
         assert "got multiple values for keyword argument 'x'" in str(e.value)
+
+    def test_dict_subclass_with_weird_getitem(self):
+        # issue 2435: bug-to-bug compatibility with cpython. for a subclass of
+        # dict, just ignore the __getitem__ and behave like ext_do_call in 
ceval.c
+        # which just uses the underlying dict
+        class d(dict):
+            def __getitem__(self, key):
+                return key
+
+        for key in ["foo", u"foo"]:
+            q = d()
+            q[key] = "bar"
+
+            def test(**kwargs):
+                return kwargs
+            assert test(**q) == {"foo": "bar"}
diff --git a/pypy/module/cpyext/dictobject.py b/pypy/module/cpyext/dictobject.py
--- a/pypy/module/cpyext/dictobject.py
+++ b/pypy/module/cpyext/dictobject.py
@@ -137,8 +137,7 @@
     """This is the same as PyDict_Merge(a, b, 1) in C, or a.update(b) in
     Python.  Return 0 on success or -1 if an exception was raised.
     """
-    space.call_method(space.w_dict, "update", w_obj, w_other)
-    return 0
+    return PyDict_Merge(space, w_obj, w_other, 1)
 
 @cpython_api([PyObject], PyObject)
 def PyDict_Keys(space, w_obj):
diff --git a/pypy/module/cpyext/listobject.py b/pypy/module/cpyext/listobject.py
--- a/pypy/module/cpyext/listobject.py
+++ b/pypy/module/cpyext/listobject.py
@@ -62,12 +62,14 @@
     position must be positive, indexing from the end of the list is not
     supported.  If pos is out of bounds, return NULL and set an
     IndexError exception."""
+    from pypy.module.cpyext.sequence import CPyListStrategy
     if not isinstance(w_list, W_ListObject):
         PyErr_BadInternalCall(space)
     if index < 0 or index >= w_list.length():
         raise oefmt(space.w_IndexError, "list index out of range")
-    w_list.ensure_object_strategy()  # make sure we can return a borrowed obj
-    # XXX ^^^ how does this interact with CPyListStrategy?
+    cpy_strategy = space.fromcache(CPyListStrategy)
+    if w_list.strategy is not cpy_strategy:
+        w_list.ensure_object_strategy() # make sure we can return a borrowed 
obj
     w_res = w_list.getitem(index)
     return w_res     # borrowed ref
 
diff --git a/pypy/module/cpyext/modsupport.py b/pypy/module/cpyext/modsupport.py
--- a/pypy/module/cpyext/modsupport.py
+++ b/pypy/module/cpyext/modsupport.py
@@ -1,7 +1,7 @@
 from rpython.rtyper.lltypesystem import rffi, lltype
 from pypy.module.cpyext.api import cpython_api, cpython_struct, \
         METH_STATIC, METH_CLASS, METH_COEXIST, CANNOT_FAIL, CONST_STRING
-from pypy.module.cpyext.pyobject import PyObject
+from pypy.module.cpyext.pyobject import PyObject, as_pyobj
 from pypy.interpreter.module import Module
 from pypy.module.cpyext.methodobject import (
     W_PyCFunctionObject, PyCFunction_NewEx, PyDescr_NewMethod,
@@ -111,11 +111,17 @@
     else:
         PyErr_BadInternalCall(space)
 
-@cpython_api([PyObject], rffi.CCHARP, error=0)
-def PyModule_GetName(space, module):
+@cpython_api([PyObject], rffi.CCHARP)
+def PyModule_GetName(space, w_mod):
     """
     Return module's __name__ value.  If the module does not provide one,
-    or if it is not a string, SystemError is raised and NULL is returned."""
-    raise NotImplementedError
-
-
+    or if it is not a string, SystemError is raised and NULL is returned.
+    """
+    # NOTE: this version of the code works only because w_mod.w_name is
+    # a wrapped string object attached to w_mod; so it makes a
+    # PyStringObject that will live as long as the module itself,
+    # and returns a "char *" inside this PyStringObject.
+    if not isinstance(w_mod, Module):
+        raise oefmt(space.w_SystemError, "PyModule_GetName(): not a module")
+    from pypy.module.cpyext.bytesobject import PyString_AsString
+    return PyString_AsString(space, as_pyobj(space, w_mod.w_name))
diff --git a/pypy/module/cpyext/object.py b/pypy/module/cpyext/object.py
--- a/pypy/module/cpyext/object.py
+++ b/pypy/module/cpyext/object.py
@@ -260,7 +260,10 @@
 def PyObject_Format(space, w_obj, w_format_spec):
     if w_format_spec is None:
         w_format_spec = space.wrap('')
-    return space.call_method(w_obj, '__format__', w_format_spec)
+    w_ret = space.call_method(w_obj, '__format__', w_format_spec)
+    if space.isinstance_w(w_format_spec, space.w_unicode):
+        return space.unicode_from_object(w_ret)
+    return w_ret
 
 @cpython_api([PyObject], PyObject)
 def PyObject_ASCII(space, w_obj):
diff --git a/pypy/module/cpyext/test/test_dictobject.py 
b/pypy/module/cpyext/test/test_dictobject.py
--- a/pypy/module/cpyext/test/test_dictobject.py
+++ b/pypy/module/cpyext/test/test_dictobject.py
@@ -103,6 +103,17 @@
         api.PyDict_Update(w_d, w_d2)
         assert space.unwrap(w_d) == dict(a='b', c='d', e='f')
 
+    def test_update_doesnt_accept_list_of_tuples(self, space, api):
+        w_d = space.newdict()
+        space.setitem(w_d, space.wrap("a"), space.wrap("b"))
+
+        w_d2 = space.wrap([("c", "d"), ("e", "f")])
+
+        api.PyDict_Update(w_d, w_d2)
+        assert api.PyErr_Occurred() is space.w_AttributeError
+        api.PyErr_Clear()
+        assert space.unwrap(w_d) == dict(a='b') # unchanged
+
     def test_iter(self, space, api):
         w_dict = space.sys.getdict(space)
         py_dict = make_ref(space, w_dict)
@@ -203,3 +214,18 @@
              """),
             ])
         assert module.dict_proxy({'a': 1, 'b': 2}) == 2
+
+    def test_update(self):
+        module = self.import_extension('foo', [
+            ("update", "METH_VARARGS",
+             '''
+             if (PyDict_Update(PyTuple_GetItem(args, 0), PyTuple_GetItem(args, 
1)))
+                return NULL;
+             Py_RETURN_NONE;
+             ''')])
+        d = {"a": 1}
+        module.update(d, {"c": 2})
+        assert d == dict(a=1, c=2)
+        d = {"a": 1}
+        raises(AttributeError, module.update, d, [("c", 2)])
+
diff --git a/pypy/module/cpyext/test/test_module.py 
b/pypy/module/cpyext/test/test_module.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/cpyext/test/test_module.py
@@ -0,0 +1,12 @@
+from pypy.module.cpyext.test.test_api import BaseApiTest
+from rpython.rtyper.lltypesystem import rffi
+
+
+class TestModuleObject(BaseApiTest):
+    def test_module_getname(self, space, api):
+        w_sys = space.wrap(space.sys)
+        p = api.PyModule_GetName(w_sys)
+        assert rffi.charp2str(p) == 'sys'
+        p2 = api.PyModule_GetName(w_sys)
+        assert p2 == p
+        self.raises(space, api, SystemError, api.PyModule_GetName, 
space.w_True)
diff --git a/pypy/module/cpyext/test/test_object.py 
b/pypy/module/cpyext/test/test_object.py
--- a/pypy/module/cpyext/test/test_object.py
+++ b/pypy/module/cpyext/test/test_object.py
@@ -285,6 +285,16 @@
         assert isinstance(dict(), collections.Mapping)
         assert module.ismapping(dict())
 
+    def test_format_returns_unicode(self):
+        module = self.import_extension('foo', [
+            ("empty_format", "METH_O",
+            """
+                PyObject* empty_unicode = PyUnicode_FromStringAndSize("", 0);
+                PyObject* obj = PyObject_Format(args, empty_unicode);
+                return obj;
+            """)])
+        a = module.empty_format('hello')
+        assert isinstance(a, unicode)
 
 class AppTestPyBuffer_FillInfo(AppTestCpythonExtensionBase):
     """
diff --git a/pypy/module/cpyext/test/test_typeobject.py 
b/pypy/module/cpyext/test/test_typeobject.py
--- a/pypy/module/cpyext/test/test_typeobject.py
+++ b/pypy/module/cpyext/test/test_typeobject.py
@@ -306,12 +306,34 @@
                  PyHeapTypeObject *heaptype = (PyHeapTypeObject *)args;
                  Py_INCREF(heaptype->ht_name);
                  return heaptype->ht_name;
+             '''),
+            ("setattr", "METH_O",
              '''
-             )
+                int ret;
+                PyObject* name = PyString_FromString("mymodule");
+                PyObject *obj = PyType_Type.tp_alloc(&PyType_Type, 0);
+                PyHeapTypeObject *type = (PyHeapTypeObject*)obj;
+                if ((type->ht_type.tp_flags & Py_TPFLAGS_HEAPTYPE) == 0)
+                {
+                    PyErr_SetString(PyExc_ValueError,
+                                    "Py_TPFLAGS_HEAPTYPE not set");
+                    return NULL;
+                }
+                type->ht_type.tp_name = ((PyTypeObject*)args)->tp_name;
+                PyType_Ready(&type->ht_type);
+                ret = PyObject_SetAttrString((PyObject*)&type->ht_type,
+                                    "__module__", name);
+                Py_DECREF(name);
+                if (ret < 0)
+                    return NULL;
+                return PyLong_FromLong(ret);
+             '''),
             ])
         class C(object):
             pass
         assert module.name_by_heaptype(C) == "C"
+        assert module.setattr(C) == 0
+
 
     def test_type_dict(self):
         foo = self.import_module("foo")
diff --git a/pypy/module/cpyext/typeobject.py b/pypy/module/cpyext/typeobject.py
--- a/pypy/module/cpyext/typeobject.py
+++ b/pypy/module/cpyext/typeobject.py
@@ -467,7 +467,7 @@
         W_TypeObject.__init__(self, space, name,
             bases_w or [space.w_object], dict_w, force_new_layout=new_layout)
         self.flag_cpytype = True
-        self.flag_heaptype = False
+        self.flag_heaptype = pto.c_tp_flags & Py_TPFLAGS_HEAPTYPE
         # if a sequence or a mapping, then set the flag to force it
         if pto.c_tp_as_sequence and pto.c_tp_as_sequence.c_sq_item:
             self.flag_map_or_seq = 'S'
@@ -725,14 +725,14 @@
     w_obj = space.allocate_instance(W_PyCTypeObject, w_metatype)
     track_reference(space, py_obj, w_obj)
     # __init__ wraps all slotdefs functions from py_type via add_operators
-    w_obj.__init__(space, py_type) 
+    w_obj.__init__(space, py_type)
     w_obj.ready()
 
     finish_type_2(space, py_type, w_obj)
     base = py_type.c_tp_base
     if base:
         # XXX refactor - parts of this are done in finish_type_2 -> 
inherit_slots
-        if not py_type.c_tp_as_number: 
+        if not py_type.c_tp_as_number:
             py_type.c_tp_as_number = base.c_tp_as_number
             py_type.c_tp_flags |= base.c_tp_flags & Py_TPFLAGS_HAVE_INPLACEOPS
         if not py_type.c_tp_as_sequence:
diff --git a/pypy/module/faulthandler/test/test_ztranslation.py 
b/pypy/module/faulthandler/test/test_ztranslation.py
--- a/pypy/module/faulthandler/test/test_ztranslation.py
+++ b/pypy/module/faulthandler/test/test_ztranslation.py
@@ -1,4 +1,5 @@
 from pypy.objspace.fake.checkmodule import checkmodule
 
 def test_faulthandler_translates():
+    import pypy.module._vmprof.interp_vmprof   # register_code_object_class()
     checkmodule('faulthandler')
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/pypy/objspace/descroperation.py b/pypy/objspace/descroperation.py
--- a/pypy/objspace/descroperation.py
+++ b/pypy/objspace/descroperation.py
@@ -11,58 +11,48 @@
 @specialize.memo()
 def object_getattribute(space):
     "Utility that returns the app-level descriptor object.__getattribute__."
-    w_src, w_getattribute = space.lookup_in_type_where(space.w_object,
-                                                       '__getattribute__')
-    return w_getattribute
+    return space.lookup_in_type(space.w_object, '__getattribute__')
 
 @specialize.memo()
 def object_setattr(space):
     "Utility that returns the app-level descriptor object.__setattr__."
-    w_src, w_setattr = space.lookup_in_type_where(space.w_object,
-                                                  '__setattr__')
-    return w_setattr
+    return space.lookup_in_type(space.w_object, '__setattr__')
 
 @specialize.memo()
 def object_delattr(space):
     "Utility that returns the app-level descriptor object.__delattr__."
-    w_src, w_delattr = space.lookup_in_type_where(space.w_object,
-                                                  '__delattr__')
-    return w_delattr
+    return space.lookup_in_type(space.w_object, '__delattr__')
 
 @specialize.memo()
 def object_hash(space):
     "Utility that returns the app-level descriptor object.__hash__."
-    w_src, w_hash = space.lookup_in_type_where(space.w_object,
-                                                  '__hash__')
-    return w_hash
+    return space.lookup_in_type(space.w_object, '__hash__')
 
 @specialize.memo()
 def type_eq(space):
     "Utility that returns the app-level descriptor type.__eq__."
-    w_src, w_eq = space.lookup_in_type_where(space.w_type,
-                                             '__eq__')
-    return w_eq
+    return space.lookup_in_type(space.w_type, '__eq__')
 
 @specialize.memo()
 def list_iter(space):
     "Utility that returns the app-level descriptor list.__iter__."
-    w_src, w_iter = space.lookup_in_type_where(space.w_list,
-                                               '__iter__')
-    return w_iter
+    return space.lookup_in_type(space.w_list, '__iter__')
 
 @specialize.memo()
 def tuple_iter(space):
     "Utility that returns the app-level descriptor tuple.__iter__."
-    w_src, w_iter = space.lookup_in_type_where(space.w_tuple,
-                                               '__iter__')
-    return w_iter
+    return space.lookup_in_type(space.w_tuple, '__iter__')
 
 @specialize.memo()
 def unicode_iter(space):
     "Utility that returns the app-level descriptor str.__iter__."
-    w_src, w_iter = space.lookup_in_type_where(space.w_unicode,
-                                               '__iter__')
-    return w_iter
+    return space.lookup_in_type(space.w_unicode, '__iter__')
+
+@specialize.memo()
+def dict_getitem(space):
+    "Utility that returns the app-level descriptor dict.__getitem__."
+    return space.lookup_in_type(space.w_dict, '__getitem__')
+
 
 def raiseattrerror(space, w_obj, w_name, w_descr=None):
     # space.repr always returns an encodable string.
diff --git a/pypy/objspace/fake/objspace.py b/pypy/objspace/fake/objspace.py
--- a/pypy/objspace/fake/objspace.py
+++ b/pypy/objspace/fake/objspace.py
@@ -339,12 +339,18 @@
     def unicode_from_object(self, w_obj):
         return w_some_obj()
 
+    def encode_unicode_object(self, w_unicode, encoding, errors):
+        return w_some_obj()
+
     def _try_fetch_pycode(self, w_func):
         return None
 
     def is_generator(self, w_obj):
         return NonConstant(False)
 
+    def lookup_in_type(self, w_type, name):
+        return w_some_obj()
+
     # ----------
 
     def translates(self, func=None, argtypes=None, seeobj_w=[], **kwds):
diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py
--- a/pypy/objspace/std/objspace.py
+++ b/pypy/objspace/std/objspace.py
@@ -374,6 +374,10 @@
         return w_type.lookup(name)
     lookup._annspecialcase_ = 'specialize:lookup'
 
+    def lookup_in_type(self, w_type, name):
+        w_src, w_descr = self.lookup_in_type_where(w_type, name)
+        return w_descr
+
     def lookup_in_type_where(self, w_type, name):
         return w_type.lookup_where(name)
     lookup_in_type_where._annspecialcase_ = 'specialize:lookup_in_type_where'
@@ -671,6 +675,10 @@
         from pypy.objspace.std.unicodeobject import unicode_from_object
         return unicode_from_object(self, w_obj)
 
+    def encode_unicode_object(self, w_unicode, encoding, errors):
+        from pypy.objspace.std.unicodeobject import encode_object
+        return encode_object(self, w_unicode, encoding, errors)
+
     def call_method(self, w_obj, methname, *arg_w):
         return callmethod.call_method_opt(self, w_obj, methname, *arg_w)
 
@@ -700,4 +708,4 @@
     @specialize.arg(2, 3)
     def is_overloaded(self, w_obj, tp, method):
         return (self.lookup(w_obj, method) is not
-                self.lookup_in_type_where(tp, method)[1])
+                self.lookup_in_type(tp, method))
diff --git a/rpython/annotator/annrpython.py b/rpython/annotator/annrpython.py
--- a/rpython/annotator/annrpython.py
+++ b/rpython/annotator/annrpython.py
@@ -22,7 +22,8 @@
     """Block annotator for RPython.
     See description in doc/translation.txt."""
 
-    def __init__(self, translator=None, policy=None, bookkeeper=None):
+    def __init__(self, translator=None, policy=None, bookkeeper=None,
+            keepgoing=False):
         import rpython.rtyper.extfuncregistry # has side effects
 
         if translator is None:
@@ -50,6 +51,9 @@
         if bookkeeper is None:
             bookkeeper = Bookkeeper(self)
         self.bookkeeper = bookkeeper
+        self.keepgoing = keepgoing
+        self.failed_blocks = set()
+        self.errors = []
 
     def __getstate__(self):
         attrs = """translator pendingblocks annotated links_followed
@@ -202,6 +206,12 @@
         else:
             newgraphs = self.translator.graphs  #all of them
             got_blocked_blocks = False in self.annotated.values()
+        if self.failed_blocks:
+            text = ('Annotation failed, %s errors were recorded:' %
+                    len(self.errors))
+            text += '\n-----'.join(str(e) for e in self.errors)
+            raise annmodel.AnnotatorError(text)
+
         if got_blocked_blocks:
             for graph in self.blocked_graphs.values():
                 self.blocked_graphs[graph] = True
@@ -348,6 +358,8 @@
 
         #print '* processblock', block, cells
         self.annotated[block] = graph
+        if block in self.failed_blocks:
+            return
         if block in self.blocked_blocks:
             del self.blocked_blocks[block]
         try:
@@ -392,6 +404,10 @@
         except annmodel.UnionError as e:
             # Add source code to the UnionError
             e.source = '\n'.join(source_lines(graph, block, None, long=True))
+            if self.keepgoing:
+                self.errors.append(e)
+                self.failed_blocks.add(block)
+                return
             raise
         # if the merged cells changed, we must redo the analysis
         if unions != oldcells:
@@ -482,6 +498,10 @@
 
         except annmodel.AnnotatorError as e: # note that UnionError is a 
subclass
             e.source = gather_error(self, graph, block, i)
+            if self.keepgoing:
+                self.errors.append(e)
+                self.failed_blocks.add(block)
+                return
             raise
 
         else:
diff --git a/rpython/annotator/test/test_model.py 
b/rpython/annotator/test/test_model.py
--- a/rpython/annotator/test/test_model.py
+++ b/rpython/annotator/test/test_model.py
@@ -192,6 +192,20 @@
     assert union(union(s1, s2), s3) == union(s1, union(s2, s3))
 
 
+@pytest.mark.xfail
+@given(st_annotation, st_annotation)
+def test_generalize_isinstance(annotator, s1, s2):
+    try:
+        s_12 = union(s1, s2)
+    except UnionError:
+        assume(False)
+    assume(s1 != s_ImpossibleValue)
+    from rpython.annotator.unaryop import s_isinstance
+    s_int = annotator.bookkeeper.immutablevalue(int)
+    s_res_12 = s_isinstance(annotator, s_12, s_int, [])
+    s_res_1 = s_isinstance(annotator, s1, s_int, [])
+    assert s_res_12.contains(s_res_1)
+
 def compile_function(function, annotation=[]):
     t = TranslationContext()
     t.buildannotator().build_types(function, annotation)
diff --git a/rpython/config/translationoption.py 
b/rpython/config/translationoption.py
--- a/rpython/config/translationoption.py
+++ b/rpython/config/translationoption.py
@@ -188,6 +188,10 @@
                "When true, enable the use of tagged pointers. "
                "If false, use normal boxing",
                default=False),
+    BoolOption("keepgoing",
+               "Continue annotating when errors are encountered, and report "
+               "them all at the end of the annotation phase",
+               default=False, cmdline="--keepgoing"),
     BoolOption("lldebug",
                "If true, makes an lldebug build", default=False,
                cmdline="--lldebug"),
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/intbounds.py 
b/rpython/jit/metainterp/optimizeopt/intbounds.py
--- a/rpython/jit/metainterp/optimizeopt/intbounds.py
+++ b/rpython/jit/metainterp/optimizeopt/intbounds.py
@@ -399,9 +399,9 @@
 
     def optimize_INT_EQ(self, op):
         arg0 = self.get_box_replacement(op.getarg(0))
+        b1 = self.getintbound(arg0)
         arg1 = self.get_box_replacement(op.getarg(1))
-        b1 = self.getintbound(op.getarg(0))
-        b2 = self.getintbound(op.getarg(1))
+        b2 = self.getintbound(arg1)
         if b1.known_gt(b2):
             self.make_constant_int(op, 0)
         elif b1.known_lt(b2):
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
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to