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