Author: Richard Plangger <planri...@gmail.com> Branch: ppc-vsx-support Changeset: r87221:950c5eb14f89 Date: 2016-09-19 09:43 +0200 http://bitbucket.org/pypy/pypy/changeset/950c5eb14f89/
Log: catchup with default diff too long, truncating to 2000 out of 2146 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 @@ -13,6 +13,7 @@ import sys import os import shlex +import imp from distutils.errors import DistutilsPlatformError @@ -62,8 +63,8 @@ """Initialize the module as appropriate for POSIX systems.""" g = {} g['EXE'] = "" - g['SO'] = ".so" - g['SOABI'] = g['SO'].rsplit('.')[0] + g['SOABI'] = [s[0] for s in imp.get_suffixes() if s[2] == imp.C_EXTENSION] + g['SO'] = g['SOABI'][0] g['LIBDIR'] = os.path.join(sys.prefix, 'lib') g['CC'] = "gcc -pthread" # -pthread might not be valid on OS/X, check @@ -75,8 +76,8 @@ """Initialize the module as appropriate for NT""" g = {} g['EXE'] = ".exe" - g['SO'] = ".pyd" - g['SOABI'] = g['SO'].rsplit('.')[0] + g['SOABI'] = [s[0] for s in imp.get_suffixes() if s[2] == imp.C_EXTENSION] + g['SO'] = g['SOABI'][0] global _config_vars _config_vars = g diff --git a/lib-python/2.7/sysconfig.py b/lib-python/2.7/sysconfig.py --- a/lib-python/2.7/sysconfig.py +++ b/lib-python/2.7/sysconfig.py @@ -526,10 +526,7 @@ # PyPy: import imp - for suffix, mode, type_ in imp.get_suffixes(): - if type_ == imp.C_EXTENSION: - _CONFIG_VARS['SOABI'] = suffix.split('.')[1] - break + _CONFIG_VARS['SOABI'] = [s[0] for s in imp.get_suffixes() if s[2] == imp.C_EXTENSION] if args: vals = [] diff --git a/lib_pypy/cffi.egg-info/PKG-INFO b/lib_pypy/cffi.egg-info/PKG-INFO --- a/lib_pypy/cffi.egg-info/PKG-INFO +++ b/lib_pypy/cffi.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: cffi -Version: 1.8.2 +Version: 1.8.3 Summary: Foreign Function Interface for Python calling C code. Home-page: http://cffi.readthedocs.org Author: Armin Rigo, Maciej Fijalkowski diff --git a/lib_pypy/cffi/__init__.py b/lib_pypy/cffi/__init__.py --- a/lib_pypy/cffi/__init__.py +++ b/lib_pypy/cffi/__init__.py @@ -4,8 +4,8 @@ from .api import FFI, CDefError, FFIError from .ffiplatform import VerificationError, VerificationMissing -__version__ = "1.8.2" -__version_info__ = (1, 8, 2) +__version__ = "1.8.3" +__version_info__ = (1, 8, 3) # The verifier module file names are based on the CRC32 of a string that # contains the following version number. It may be older than __version__ diff --git a/lib_pypy/cffi/_embedding.h b/lib_pypy/cffi/_embedding.h --- a/lib_pypy/cffi/_embedding.h +++ b/lib_pypy/cffi/_embedding.h @@ -233,7 +233,7 @@ f = PySys_GetObject((char *)"stderr"); if (f != NULL && f != Py_None) { PyFile_WriteString("\nFrom: " _CFFI_MODULE_NAME - "\ncompiled with cffi version: 1.8.2" + "\ncompiled with cffi version: 1.8.3" "\n_cffi_backend module: ", f); modules = PyImport_GetModuleDict(); mod = PyDict_GetItemString(modules, "_cffi_backend"); diff --git a/pypy/doc/config/translation.profopt.txt b/pypy/doc/config/translation.profopt.txt --- a/pypy/doc/config/translation.profopt.txt +++ b/pypy/doc/config/translation.profopt.txt @@ -3,3 +3,14 @@ RPython program) to gather profile data. Example for pypy-c: "-c 'from richards import main;main(); from test import pystone; pystone.main()'" + +NOTE: be aware of what this does in JIT-enabled executables. What it +does is instrument and later optimize the C code that happens to run in +the example you specify, ignoring any execution of the JIT-generated +assembler. That means that you have to choose the example wisely. If +it is something that will just generate assembler and stay there, there +is little value. If it is something that exercises heavily library +routines that are anyway written in C, then it will optimize that. Most +interesting would be something that causes a lot of JIT-compilation, +like running a medium-sized test suite several times in a row, in order +to optimize the warm-up in general. diff --git a/pypy/doc/cpython_differences.rst b/pypy/doc/cpython_differences.rst --- a/pypy/doc/cpython_differences.rst +++ b/pypy/doc/cpython_differences.rst @@ -449,6 +449,27 @@ support (see ``multiline_input()``). On the other hand, ``parse_and_bind()`` calls are ignored (issue `#2072`_). +* ``sys.getsizeof()`` always raises ``TypeError``. This is because a + memory profiler using this function is most likely to give results + inconsistent with reality on PyPy. It would be possible to have + ``sys.getsizeof()`` return a number (with enough work), but that may + or may not represent how much memory the object uses. It doesn't even + make really sense to ask how much *one* object uses, in isolation with + the rest of the system. For example, instances have maps, which are + often shared across many instances; in this case the maps would + probably be ignored by an implementation of ``sys.getsizeof()``, but + their overhead is important in some cases if they are many instances + with unique maps. Conversely, equal strings may share their internal + string data even if they are different objects---or empty containers + may share parts of their internals as long as they are empty. Even + stranger, some lists create objects as you read them; if you try to + estimate the size in memory of ``range(10**6)`` as the sum of all + items' size, that operation will by itself create one million integer + objects that never existed in the first place. Note that some of + these concerns also exist on CPython, just less so. For this reason + we explicitly don't implement ``sys.getsizeof()``. + + .. _`is ignored in PyPy`: http://bugs.python.org/issue14621 .. _`little point`: http://events.ccc.de/congress/2012/Fahrplan/events/5152.en.html .. _`#2072`: https://bitbucket.org/pypy/pypy/issue/2072/ diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst --- a/pypy/doc/whatsnew-head.rst +++ b/pypy/doc/whatsnew-head.rst @@ -17,6 +17,11 @@ preamble. Accomplished by allocating virtual objects where non-virtuals are expected. +.. branch: conditional_call_value_3 +JIT residual calls: if the called function starts with a fast-path +like "if x.foo != 0: return x.foo", then inline the check before +doing the CALL. For now, string hashing is about the only case. + .. branch: zarch-simd-support s390x implementation for vector operations used in VecOpt diff --git a/pypy/goal/targetpypystandalone.py b/pypy/goal/targetpypystandalone.py --- a/pypy/goal/targetpypystandalone.py +++ b/pypy/goal/targetpypystandalone.py @@ -239,6 +239,10 @@ raise Exception("Cannot use the --output option with PyPy " "when --shared is on (it is by default). " "See issue #1971.") + if config.translation.profopt is not None: + raise Exception("Cannot use the --profopt option " + "when --shared is on (it is by default). " + "See issue #2398.") if sys.platform == 'win32': libdir = thisdir.join('..', '..', 'libs') libdir.ensure(dir=1) diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py --- a/pypy/interpreter/baseobjspace.py +++ b/pypy/interpreter/baseobjspace.py @@ -1974,6 +1974,7 @@ 'ZeroDivisionError', 'RuntimeWarning', 'PendingDeprecationWarning', + 'UserWarning', ] if sys.platform.startswith("win"): diff --git a/pypy/interpreter/generator.py b/pypy/interpreter/generator.py --- a/pypy/interpreter/generator.py +++ b/pypy/interpreter/generator.py @@ -63,7 +63,7 @@ """x.__iter__() <==> iter(x)""" return self.space.wrap(self) - def descr_send(self, w_arg=None): + def descr_send(self, w_arg): """send(arg) -> send 'arg' into generator, return next yielded value or raise StopIteration.""" return self.send_ex(w_arg) diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py --- a/pypy/interpreter/pyframe.py +++ b/pypy/interpreter/pyframe.py @@ -264,25 +264,22 @@ try: executioncontext.call_trace(self) # - if operr is not None: - ec = self.space.getexecutioncontext() - next_instr = self.handle_operation_error(ec, operr) - self.last_instr = intmask(next_instr - 1) - else: - # Execution starts just after the last_instr. Initially, - # last_instr is -1. After a generator suspends it points to - # the YIELD_VALUE instruction. - next_instr = r_uint(self.last_instr + 1) - if next_instr != 0: - self.pushvalue(w_inputvalue) - # try: + if operr is not None: + ec = self.space.getexecutioncontext() + next_instr = self.handle_operation_error(ec, operr) + self.last_instr = intmask(next_instr - 1) + else: + # Execution starts just after the last_instr. Initially, + # last_instr is -1. After a generator suspends it points to + # the YIELD_VALUE instruction. + next_instr = r_uint(self.last_instr + 1) + if next_instr != 0: + self.pushvalue(w_inputvalue) w_exitvalue = self.dispatch(self.pycode, next_instr, executioncontext) - except Exception: - executioncontext.return_trace(self, self.space.w_None) - raise - executioncontext.return_trace(self, w_exitvalue) + finally: + executioncontext.return_trace(self, w_exitvalue) # it used to say self.last_exception = None # this is now done by the code in pypyjit module # since we don't want to invalidate the virtualizable diff --git a/pypy/interpreter/test/test_generator.py b/pypy/interpreter/test/test_generator.py --- a/pypy/interpreter/test/test_generator.py +++ b/pypy/interpreter/test/test_generator.py @@ -57,12 +57,14 @@ def f(): yield 2 g = f() + # two arguments version raises(NameError, g.throw, NameError, "Error") def test_throw2(self): def f(): yield 2 g = f() + # single argument version raises(NameError, g.throw, NameError("Error")) def test_throw3(self): @@ -221,7 +223,8 @@ def f(): yield 1 g = f() - raises(TypeError, g.send, 1) + raises(TypeError, g.send) # one argument required + raises(TypeError, g.send, 1) # not started, must send None def test_generator_explicit_stopiteration(self): def f(): diff --git a/pypy/interpreter/test/test_pyframe.py b/pypy/interpreter/test/test_pyframe.py --- a/pypy/interpreter/test/test_pyframe.py +++ b/pypy/interpreter/test/test_pyframe.py @@ -562,3 +562,21 @@ res = f(10).g() sys.settrace(None) assert res == 10 + + def test_throw_trace_bug(self): + import sys + def f(): + yield 5 + gen = f() + assert next(gen) == 5 + seen = [] + def trace_func(frame, event, *args): + seen.append(event) + return trace_func + sys.settrace(trace_func) + try: + gen.throw(ValueError) + except ValueError: + pass + sys.settrace(None) + assert seen == ['call', 'exception', 'return'] diff --git a/pypy/module/_cffi_backend/__init__.py b/pypy/module/_cffi_backend/__init__.py --- a/pypy/module/_cffi_backend/__init__.py +++ b/pypy/module/_cffi_backend/__init__.py @@ -3,7 +3,7 @@ from rpython.rlib import rdynload, clibffi, entrypoint from rpython.rtyper.lltypesystem import rffi -VERSION = "1.8.2" +VERSION = "1.8.3" FFI_DEFAULT_ABI = clibffi.FFI_DEFAULT_ABI try: diff --git a/pypy/module/_cffi_backend/ctypearray.py b/pypy/module/_cffi_backend/ctypearray.py --- a/pypy/module/_cffi_backend/ctypearray.py +++ b/pypy/module/_cffi_backend/ctypearray.py @@ -11,7 +11,7 @@ from rpython.rlib.rarithmetic import ovfcheck from pypy.module._cffi_backend import cdataobj -from pypy.module._cffi_backend.ctypeptr import W_CTypePtrOrArray +from pypy.module._cffi_backend.ctypeptr import W_CTypePtrOrArray, W_CTypePointer from pypy.module._cffi_backend import ctypeprim @@ -22,6 +22,7 @@ is_nonfunc_pointer_or_array = True def __init__(self, space, ctptr, length, arraysize, extra): + assert isinstance(ctptr, W_CTypePointer) W_CTypePtrOrArray.__init__(self, space, arraysize, extra, 0, ctptr.ctitem) self.length = length diff --git a/pypy/module/_cffi_backend/ctypefunc.py b/pypy/module/_cffi_backend/ctypefunc.py --- a/pypy/module/_cffi_backend/ctypefunc.py +++ b/pypy/module/_cffi_backend/ctypefunc.py @@ -35,8 +35,7 @@ assert isinstance(ellipsis, bool) extra, xpos = self._compute_extra_text(fargs, fresult, ellipsis, abi) size = rffi.sizeof(rffi.VOIDP) - W_CTypePtrBase.__init__(self, space, size, extra, xpos, fresult, - could_cast_anything=False) + W_CTypePtrBase.__init__(self, space, size, extra, xpos, fresult) self.fargs = fargs self.ellipsis = ellipsis self.abi = abi @@ -59,6 +58,16 @@ lltype.free(self.cif_descr, flavor='raw') self.cif_descr = lltype.nullptr(CIF_DESCRIPTION) + def is_unichar_ptr_or_array(self): + return False + + def is_char_or_unichar_ptr_or_array(self): + return False + + def string(self, cdataobj, maxlen): + # Can't use ffi.string() on a function pointer + return W_CType.string(self, cdataobj, maxlen) + def new_ctypefunc_completing_argtypes(self, args_w): space = self.space nargs_declared = len(self.fargs) diff --git a/pypy/module/_cffi_backend/ctypeobj.py b/pypy/module/_cffi_backend/ctypeobj.py --- a/pypy/module/_cffi_backend/ctypeobj.py +++ b/pypy/module/_cffi_backend/ctypeobj.py @@ -19,7 +19,6 @@ # XXX this could be improved with an elidable method get_size() # that raises in case it's still -1... - cast_anything = False is_primitive_integer = False is_nonfunc_pointer_or_array = False is_indirect_arg_for_call_python = False diff --git a/pypy/module/_cffi_backend/ctypeprim.py b/pypy/module/_cffi_backend/ctypeprim.py --- a/pypy/module/_cffi_backend/ctypeprim.py +++ b/pypy/module/_cffi_backend/ctypeprim.py @@ -120,7 +120,6 @@ class W_CTypePrimitiveChar(W_CTypePrimitiveCharOrUniChar): _attrs_ = [] - cast_anything = True def cast_to_int(self, cdata): return self.space.wrap(ord(cdata[0])) diff --git a/pypy/module/_cffi_backend/ctypeptr.py b/pypy/module/_cffi_backend/ctypeptr.py --- a/pypy/module/_cffi_backend/ctypeptr.py +++ b/pypy/module/_cffi_backend/ctypeptr.py @@ -14,12 +14,11 @@ class W_CTypePtrOrArray(W_CType): - _attrs_ = ['ctitem', 'can_cast_anything', 'accept_str', 'length'] - _immutable_fields_ = ['ctitem', 'can_cast_anything', 'accept_str', 'length'] + _attrs_ = ['ctitem', 'accept_str', 'length'] + _immutable_fields_ = ['ctitem', 'accept_str', 'length'] length = -1 - def __init__(self, space, size, extra, extra_position, ctitem, - could_cast_anything=True): + def __init__(self, space, size, extra, extra_position, ctitem): name, name_position = ctitem.insert_name(extra, extra_position) W_CType.__init__(self, space, size, name, name_position) # this is the "underlying type": @@ -27,10 +26,11 @@ # - for arrays, it is the array item type # - for functions, it is the return type self.ctitem = ctitem - self.can_cast_anything = could_cast_anything and ctitem.cast_anything - self.accept_str = (self.can_cast_anything or - (ctitem.is_primitive_integer and - ctitem.size == rffi.sizeof(lltype.Char))) + self.accept_str = (self.is_nonfunc_pointer_or_array and + (isinstance(ctitem, ctypevoid.W_CTypeVoid) or + isinstance(ctitem, ctypeprim.W_CTypePrimitiveChar) or + (ctitem.is_primitive_integer and + ctitem.size == rffi.sizeof(lltype.Char)))) def is_unichar_ptr_or_array(self): return isinstance(self.ctitem, ctypeprim.W_CTypePrimitiveUniChar) @@ -137,7 +137,10 @@ class W_CTypePtrBase(W_CTypePtrOrArray): # base class for both pointers and pointers-to-functions - _attrs_ = [] + _attrs_ = ['is_void_ptr', 'is_voidchar_ptr'] + _immutable_fields_ = ['is_void_ptr', 'is_voidchar_ptr'] + is_void_ptr = False + is_voidchar_ptr = False def convert_to_object(self, cdata): ptrdata = rffi.cast(rffi.CCHARPP, cdata)[0] @@ -154,7 +157,16 @@ else: raise self._convert_error("compatible pointer", w_ob) if self is not other: - if not (self.can_cast_anything or other.can_cast_anything): + if self.is_void_ptr or other.is_void_ptr: + pass # cast from or to 'void *' + elif self.is_voidchar_ptr or other.is_voidchar_ptr: + space = self.space + msg = ("implicit cast from '%s' to '%s' " + "will be forbidden in the future (check that the types " + "are as you expect; use an explicit ffi.cast() if they " + "are correct)" % (other.name, self.name)) + space.warn(space.wrap(msg), space.w_UserWarning, stacklevel=1) + else: raise self._convert_error("compatible pointer", w_ob) rffi.cast(rffi.CCHARPP, cdata)[0] = w_ob.unsafe_escaping_ptr() @@ -165,8 +177,8 @@ class W_CTypePointer(W_CTypePtrBase): - _attrs_ = ['is_file', 'cache_array_type', 'is_void_ptr', '_array_types'] - _immutable_fields_ = ['is_file', 'cache_array_type?', 'is_void_ptr'] + _attrs_ = ['is_file', 'cache_array_type', '_array_types'] + _immutable_fields_ = ['is_file', 'cache_array_type?'] kind = "pointer" cache_array_type = None is_nonfunc_pointer_or_array = True @@ -181,6 +193,8 @@ self.is_file = (ctitem.name == "struct _IO_FILE" or ctitem.name == "FILE") self.is_void_ptr = isinstance(ctitem, ctypevoid.W_CTypeVoid) + self.is_voidchar_ptr = (self.is_void_ptr or + isinstance(ctitem, ctypeprim.W_CTypePrimitiveChar)) W_CTypePtrBase.__init__(self, space, size, extra, 2, ctitem) def newp(self, w_init, allocator): diff --git a/pypy/module/_cffi_backend/ctypevoid.py b/pypy/module/_cffi_backend/ctypevoid.py --- a/pypy/module/_cffi_backend/ctypevoid.py +++ b/pypy/module/_cffi_backend/ctypevoid.py @@ -7,7 +7,6 @@ class W_CTypeVoid(W_CType): _attrs_ = [] - cast_anything = True kind = "void" def __init__(self, space): diff --git a/pypy/module/_cffi_backend/handle.py b/pypy/module/_cffi_backend/handle.py --- a/pypy/module/_cffi_backend/handle.py +++ b/pypy/module/_cffi_backend/handle.py @@ -32,8 +32,8 @@ @unwrap_spec(w_cdata=cdataobj.W_CData) def from_handle(space, w_cdata): ctype = w_cdata.ctype - if (not isinstance(ctype, ctypeptr.W_CTypePtrOrArray) or - not ctype.can_cast_anything): + if (not isinstance(ctype, ctypeptr.W_CTypePointer) or + not ctype.is_voidchar_ptr): raise oefmt(space.w_TypeError, "expected a 'cdata' object with a 'void *' out of " "new_handle(), got '%s'", ctype.name) diff --git a/pypy/module/_cffi_backend/test/_backend_test_c.py b/pypy/module/_cffi_backend/test/_backend_test_c.py --- a/pypy/module/_cffi_backend/test/_backend_test_c.py +++ b/pypy/module/_cffi_backend/test/_backend_test_c.py @@ -1,7 +1,7 @@ # ____________________________________________________________ import sys -assert __version__ == "1.8.2", ("This test_c.py file is for testing a version" +assert __version__ == "1.8.3", ("This test_c.py file is for testing a version" " of cffi that differs from the one that we" " get from 'import _cffi_backend'") if sys.version_info < (3,): @@ -3665,3 +3665,27 @@ check_dir(pp, []) check_dir(pp[0], ['a1', 'a2']) check_dir(pp[0][0], ['a1', 'a2']) + +def test_char_pointer_conversion(): + import warnings + assert __version__.startswith(("1.8", "1.9")), ( + "consider turning the warning into an error") + BCharP = new_pointer_type(new_primitive_type("char")) + BIntP = new_pointer_type(new_primitive_type("int")) + BVoidP = new_pointer_type(new_void_type()) + z1 = cast(BCharP, 0) + z2 = cast(BIntP, 0) + z3 = cast(BVoidP, 0) + with warnings.catch_warnings(record=True) as w: + newp(new_pointer_type(BIntP), z1) # warn + assert len(w) == 1 + newp(new_pointer_type(BVoidP), z1) # fine + assert len(w) == 1 + newp(new_pointer_type(BCharP), z2) # warn + assert len(w) == 2 + newp(new_pointer_type(BVoidP), z2) # fine + assert len(w) == 2 + newp(new_pointer_type(BCharP), z3) # fine + assert len(w) == 2 + newp(new_pointer_type(BIntP), z3) # fine + assert len(w) == 2 diff --git a/pypy/module/_cffi_backend/test/test_ffi_obj.py b/pypy/module/_cffi_backend/test/test_ffi_obj.py --- a/pypy/module/_cffi_backend/test/test_ffi_obj.py +++ b/pypy/module/_cffi_backend/test/test_ffi_obj.py @@ -503,3 +503,10 @@ assert ffi.unpack(p+1, 7) == b"bc\x00def\x00" p = ffi.new("int[]", [-123456789]) assert ffi.unpack(p, 1) == [-123456789] + + def test_bug_1(self): + import _cffi_backend as _cffi1_backend + ffi = _cffi1_backend.FFI() + q = ffi.new("char[]", "abcd") + p = ffi.cast("char(*)(void)", q) + raises(TypeError, ffi.string, p) diff --git a/pypy/module/array/interp_array.py b/pypy/module/array/interp_array.py --- a/pypy/module/array/interp_array.py +++ b/pypy/module/array/interp_array.py @@ -30,16 +30,19 @@ raise oefmt(space.w_TypeError, "array.array() does not take keyword arguments") + w_initializer_type = None + w_initializer = None + if len(__args__.arguments_w) > 0: + w_initializer = __args__.arguments_w[0] + w_initializer_type = space.type(w_initializer) for tc in unroll_typecodes: if typecode == tc: a = space.allocate_instance(types[tc].w_class, w_cls) a.__init__(space) - - if len(__args__.arguments_w) > 0: - w_initializer = __args__.arguments_w[0] - if space.type(w_initializer) is space.w_str: + if w_initializer is not None: + if w_initializer_type is space.w_str: a.descr_fromstring(space, w_initializer) - elif space.type(w_initializer) is space.w_list: + elif w_initializer_type is space.w_list: a.descr_fromlist(space, w_initializer) else: a.extend(w_initializer, True) diff --git a/pypy/module/cpyext/longobject.py b/pypy/module/cpyext/longobject.py --- a/pypy/module/cpyext/longobject.py +++ b/pypy/module/cpyext/longobject.py @@ -6,7 +6,6 @@ from pypy.interpreter.error import OperationError from pypy.module.cpyext.intobject import PyInt_AsUnsignedLongMask from rpython.rlib.rbigint import rbigint -from rpython.rlib.rarithmetic import intmask PyLong_Check, PyLong_CheckExact = build_type_checkers("Long") @@ -28,25 +27,25 @@ """Return a new PyLongObject object from a C size_t, or NULL on failure. """ - return space.wrap(val) + return space.newlong_from_rarith_int(val) @cpython_api([rffi.LONGLONG], PyObject) def PyLong_FromLongLong(space, val): """Return a new PyLongObject object from a C long long, or NULL on failure.""" - return space.wrap(val) + return space.newlong_from_rarith_int(val) @cpython_api([rffi.ULONG], PyObject) def PyLong_FromUnsignedLong(space, val): """Return a new PyLongObject object from a C unsigned long, or NULL on failure.""" - return space.wrap(val) + return space.newlong_from_rarith_int(val) @cpython_api([rffi.ULONGLONG], PyObject) def PyLong_FromUnsignedLongLong(space, val): """Return a new PyLongObject object from a C unsigned long long, or NULL on failure.""" - return space.wrap(val) + return space.newlong_from_rarith_int(val) @cpython_api([PyObject], rffi.ULONG, error=-1) def PyLong_AsUnsignedLong(space, w_long): @@ -203,7 +202,10 @@ can be retrieved from the resulting value using PyLong_AsVoidPtr(). If the integer is larger than LONG_MAX, a positive long integer is returned.""" - return space.wrap(rffi.cast(ADDR, p)) + value = rffi.cast(ADDR, p) # signed integer + if value < 0: + return space.newlong_from_rarith_int(rffi.cast(lltype.Unsigned, p)) + return space.wrap(value) @cpython_api([PyObject], rffi.VOIDP, error=lltype.nullptr(rffi.VOIDP.TO)) def PyLong_AsVoidPtr(space, w_long): diff --git a/pypy/module/cpyext/memoryobject.py b/pypy/module/cpyext/memoryobject.py --- a/pypy/module/cpyext/memoryobject.py +++ b/pypy/module/cpyext/memoryobject.py @@ -53,10 +53,7 @@ else: n = len(fmt) for i in range(n): - if ord(fmt[i]) > 255: - view.c_format[i] = '*' - else: - view.c_format[i] = fmt[i] + view.c_format[i] = fmt[i] view.c_format[n] = '\x00' shape = buf.getshape() strides = buf.getstrides() diff --git a/pypy/module/cpyext/pytraceback.py b/pypy/module/cpyext/pytraceback.py --- a/pypy/module/cpyext/pytraceback.py +++ b/pypy/module/cpyext/pytraceback.py @@ -5,7 +5,6 @@ from pypy.module.cpyext.pyobject import ( PyObject, make_ref, from_ref, Py_DecRef, make_typedescr) from pypy.module.cpyext.frameobject import PyFrameObject -from rpython.rlib.unroll import unrolling_iterable from pypy.interpreter.error import OperationError from pypy.interpreter.pytraceback import PyTraceback from pypy.interpreter import pycode diff --git a/pypy/module/cpyext/test/foo3.c b/pypy/module/cpyext/test/foo3.c --- a/pypy/module/cpyext/test/foo3.c +++ b/pypy/module/cpyext/test/foo3.c @@ -4,9 +4,7 @@ PyObject* foo3type_tp_new(PyTypeObject* metatype, PyObject* args, PyObject* kwds) { PyObject* newType; - /*printf("in foo3type_tp_new, preprocessing...\n"); */ newType = PyType_Type.tp_new(metatype, args, kwds); - /*printf("in foo3type_tp_new, postprocessing...\n"); */ return newType; } @@ -81,4 +79,5 @@ return; if (PyDict_SetItemString(d, "footype", (PyObject *)&footype) < 0) return; + Py_INCREF(&footype); } diff --git a/pypy/module/cpyext/test/test_longobject.py b/pypy/module/cpyext/test/test_longobject.py --- a/pypy/module/cpyext/test/test_longobject.py +++ b/pypy/module/cpyext/test/test_longobject.py @@ -1,5 +1,6 @@ import sys, py from rpython.rtyper.lltypesystem import rffi, lltype +from rpython.rlib.rarithmetic import maxint from pypy.objspace.std.intobject import W_IntObject from pypy.objspace.std.longobject import W_LongObject from pypy.module.cpyext.test.test_api import BaseApiTest @@ -8,18 +9,20 @@ class TestLongObject(BaseApiTest): def test_FromLong(self, space, api): - value = api.PyLong_FromLong(3) - assert isinstance(value, W_LongObject) - assert space.unwrap(value) == 3 + w_value = api.PyLong_FromLong(3) + assert isinstance(w_value, W_LongObject) + assert space.unwrap(w_value) == 3 - value = api.PyLong_FromLong(sys.maxint) - assert isinstance(value, W_LongObject) - assert space.unwrap(value) == sys.maxint + w_value = api.PyLong_FromLong(sys.maxint) + assert isinstance(w_value, W_LongObject) + assert space.unwrap(w_value) == sys.maxint def test_aslong(self, space, api): w_value = api.PyLong_FromLong((sys.maxint - 1) / 2) + assert isinstance(w_value, W_LongObject) w_value = space.mul(w_value, space.wrap(2)) + assert isinstance(w_value, W_LongObject) value = api.PyLong_AsLong(w_value) assert value == (sys.maxint - 1) @@ -35,12 +38,16 @@ def test_as_ssize_t(self, space, api): w_value = space.newlong(2) + assert isinstance(w_value, W_LongObject) value = api.PyLong_AsSsize_t(w_value) assert value == 2 - assert space.eq_w(w_value, api.PyLong_FromSsize_t(2)) + w_val2 = api.PyLong_FromSsize_t(2) + assert isinstance(w_val2, W_LongObject) + assert space.eq_w(w_value, w_val2) def test_fromdouble(self, space, api): w_value = api.PyLong_FromDouble(-12.74) + assert isinstance(w_value, W_LongObject) assert space.unwrap(w_value) == -12 assert api.PyLong_AsDouble(w_value) == -12 @@ -102,9 +109,26 @@ lltype.free(overflow, flavor='raw') def test_as_voidptr(self, space, api): + # CPython returns an int (not a long) depending on the value + # passed to PyLong_FromVoidPtr(). In all cases, NULL becomes + # the int 0. w_l = api.PyLong_FromVoidPtr(lltype.nullptr(rffi.VOIDP.TO)) - assert space.unwrap(w_l) == 0L + assert space.is_w(space.type(w_l), space.w_int) + assert space.unwrap(w_l) == 0 assert api.PyLong_AsVoidPtr(w_l) == lltype.nullptr(rffi.VOIDP.TO) + # Positive values also return an int (assuming, like always in + # PyPy, that an int is big enough to store any pointer). + p = rffi.cast(rffi.VOIDP, maxint) + w_l = api.PyLong_FromVoidPtr(p) + assert space.is_w(space.type(w_l), space.w_int) + assert space.unwrap(w_l) == maxint + assert api.PyLong_AsVoidPtr(w_l) == p + # Negative values always return a long. + p = rffi.cast(rffi.VOIDP, -maxint-1) + w_l = api.PyLong_FromVoidPtr(p) + assert space.is_w(space.type(w_l), space.w_long) + assert space.unwrap(w_l) == maxint+1 + assert api.PyLong_AsVoidPtr(w_l) == p def test_sign_and_bits(self, space, api): if space.is_true(space.lt(space.sys.get('version_info'), @@ -128,23 +152,58 @@ module = self.import_extension('foo', [ ("from_unsignedlong", "METH_NOARGS", """ - return PyLong_FromUnsignedLong((unsigned long)-1); + PyObject * obj; + obj = PyLong_FromUnsignedLong((unsigned long)-1); + if (obj->ob_type != &PyLong_Type) + { + Py_DECREF(obj); + PyErr_SetString(PyExc_ValueError, + "PyLong_FromLongLong did not return PyLongObject"); + return NULL; + } + return obj; """)]) import sys assert module.from_unsignedlong() == 2 * sys.maxint + 1 def test_fromlonglong(self): module = self.import_extension('foo', [ - ("from_longlong", "METH_NOARGS", + ("from_longlong", "METH_VARARGS", """ - return PyLong_FromLongLong((long long)-1); + int val; + PyObject * obj; + if (!PyArg_ParseTuple(args, "i", &val)) + return NULL; + obj = PyLong_FromLongLong((long long)val); + if (obj->ob_type != &PyLong_Type) + { + Py_DECREF(obj); + PyErr_SetString(PyExc_ValueError, + "PyLong_FromLongLong did not return PyLongObject"); + return NULL; + } + return obj; """), - ("from_unsignedlonglong", "METH_NOARGS", + ("from_unsignedlonglong", "METH_VARARGS", """ - return PyLong_FromUnsignedLongLong((unsigned long long)-1); + int val; + PyObject * obj; + if (!PyArg_ParseTuple(args, "i", &val)) + return NULL; + obj = PyLong_FromUnsignedLongLong((long long)val); + if (obj->ob_type != &PyLong_Type) + { + Py_DECREF(obj); + PyErr_SetString(PyExc_ValueError, + "PyLong_FromLongLong did not return PyLongObject"); + return NULL; + } + return obj; """)]) - assert module.from_longlong() == -1 - assert module.from_unsignedlonglong() == (1<<64) - 1 + assert module.from_longlong(-1) == -1 + assert module.from_longlong(0) == 0 + assert module.from_unsignedlonglong(0) == 0 + assert module.from_unsignedlonglong(-1) == (1<<64) - 1 def test_from_size_t(self): module = self.import_extension('foo', [ @@ -232,10 +291,15 @@ ("has_sub", "METH_NOARGS", """ PyObject *ret, *obj = PyLong_FromLong(42); - if (obj->ob_type->tp_as_number->nb_subtract) - ret = obj->ob_type->tp_as_number->nb_subtract(obj, obj); + if (obj->ob_type != &PyLong_Type) + ret = PyLong_FromLong(-2); else - ret = PyLong_FromLong(-1); + { + if (obj->ob_type->tp_as_number->nb_subtract) + ret = obj->ob_type->tp_as_number->nb_subtract(obj, obj); + else + ret = PyLong_FromLong(-1); + } Py_DECREF(obj); return ret; """), 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 @@ -961,7 +961,6 @@ def test_tp_new_in_subclass_of_type(self): module = self.import_module(name='foo3') - #print('calling module.footype()...') module.footype("X", (object,), {}) def test_app_subclass_of_c_type(self): 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,7 @@ 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), p10, descr=<Calli . r EF=0>) + i12 = call_i(ConstClass(_ll_strhash__rpy_stringPtr), p10, descr=<Calli . r EF=0>) p13 = new(descr=...) p15 = new_array_clear(16, descr=<ArrayU 1>) {{{ diff --git a/pypy/module/sys/__init__.py b/pypy/module/sys/__init__.py --- a/pypy/module/sys/__init__.py +++ b/pypy/module/sys/__init__.py @@ -15,7 +15,7 @@ if space.config.translating: del self.__class__.interpleveldefs['pypy_getudir'] super(Module, self).__init__(space, w_name) - self.recursionlimit = 100 + self.recursionlimit = 1000 self.w_default_encoder = None self.defaultencoding = "ascii" self.filesystemencoding = None diff --git a/pypy/module/sys/vm.py b/pypy/module/sys/vm.py --- a/pypy/module/sys/vm.py +++ b/pypy/module/sys/vm.py @@ -253,9 +253,28 @@ from rpython.rtyper.lltypesystem import lltype, rffi return space.wrap(rffi.cast(lltype.Signed, handle)) +getsizeof_missing = """sys.getsizeof() is not implemented on PyPy. + +A memory profiler using this function is most likely to give results +inconsistent with reality on PyPy. It would be possible to have +sys.getsizeof() return a number (with enough work), but that may or +may not represent how much memory the object uses. It doesn't even +make really sense to ask how much *one* object uses, in isolation +with the rest of the system. For example, instances have maps, +which are often shared across many instances; in this case the maps +would probably be ignored by an implementation of sys.getsizeof(), +but their overhead is important in some cases if they are many +instances with unique maps. Conversely, equal strings may share +their internal string data even if they are different objects---or +empty containers may share parts of their internals as long as they +are empty. Even stranger, some lists create objects as you read +them; if you try to estimate the size in memory of range(10**6) as +the sum of all items' size, that operation will by itself create one +million integer objects that never existed in the first place. +""" + def getsizeof(space, w_object, w_default=None): - """Not implemented on PyPy.""" if w_default is None: - raise oefmt(space.w_TypeError, - "sys.getsizeof() not implemented on PyPy") + raise oefmt(space.w_TypeError, getsizeof_missing) return w_default +getsizeof.__doc__ = getsizeof_missing 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 @@ -270,6 +270,10 @@ return W_SmallLongObject.fromint(val) return W_LongObject.fromint(self, val) + @specialize.argtype(1) + def newlong_from_rarith_int(self, val): # val is an rarithmetic type + return W_LongObject.fromrarith_int(val) + def newlong_from_rbigint(self, val): return newlong(self, val) diff --git a/pypy/objspace/std/test/test_longobject.py b/pypy/objspace/std/test/test_longobject.py --- a/pypy/objspace/std/test/test_longobject.py +++ b/pypy/objspace/std/test/test_longobject.py @@ -25,7 +25,6 @@ space.raises_w(space.w_OverflowError, space.float_w, w_big) def test_rint_variants(self): - py.test.skip("XXX broken!") from rpython.rtyper.tool.rfficache import platform space = self.space for r in platform.numbertype_to_rclass.values(): @@ -36,8 +35,8 @@ for x in values: if not r.SIGNED: x &= r.MASK - w_obj = space.wrap(r(x)) - assert space.bigint_w(w_obj).eq(rbigint.fromint(x)) + w_obj = space.newlong_from_rarith_int(r(x)) + assert space.bigint_w(w_obj).eq(rbigint.fromlong(x)) class AppTestLong: 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 @@ -1002,6 +1002,9 @@ prepare_op_cond_call_gc_wb_array = prepare_op_cond_call_gc_wb 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) 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 @@ -326,6 +326,7 @@ 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() @@ -1318,6 +1319,16 @@ # cond_call can't have a return value self.execute_call_n(calldescr, func, *args) + def execute_cond_call_value_i(self, calldescr, value, func, *args): + if not value: + value = self.execute_call_i(calldescr, func, *args) + return value + + def execute_cond_call_value_r(self, calldescr, value, func, *args): + if not value: + value = self.execute_call_r(calldescr, func, *args) + return value + def _execute_call(self, calldescr, func, *args): effectinfo = calldescr.get_extra_info() if effectinfo is not None and hasattr(effectinfo, 'oopspecindex'): diff --git a/rpython/jit/backend/llsupport/regalloc.py b/rpython/jit/backend/llsupport/regalloc.py --- a/rpython/jit/backend/llsupport/regalloc.py +++ b/rpython/jit/backend/llsupport/regalloc.py @@ -759,6 +759,8 @@ if (opnum != rop.GUARD_TRUE and opnum != rop.GUARD_FALSE and opnum != rop.COND_CALL): return False + # NB: don't list COND_CALL_VALUE_I/R here, these two variants + # of COND_CALL don't accept a cc as input if next_op.getarg(0) is not op: return False if self.longevity[op][1] > i + 1: 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 @@ -11,7 +11,7 @@ from rpython.jit.backend.llsupport.symbolic import (WORD, get_array_token) from rpython.jit.backend.llsupport.descr import SizeDescr, ArrayDescr,\ - FLAG_POINTER + FLAG_POINTER, CallDescr from rpython.jit.metainterp.history import JitCellToken from rpython.jit.backend.llsupport.descr import (unpack_arraydescr, unpack_fielddescr, unpack_interiorfielddescr) @@ -342,7 +342,9 @@ self.consider_setfield_gc(op) elif op.getopnum() == rop.SETARRAYITEM_GC: self.consider_setarrayitem_gc(op) - # ---------- call assembler ----------- + # ---------- calls ----------- + if OpHelpers.is_plain_call(op.getopnum()): + self.expand_call_shortcut(op) if OpHelpers.is_call_assembler(op.getopnum()): self.handle_call_assembler(op) continue @@ -588,6 +590,30 @@ 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) + value_box = self.emit_getfield(ptr_box, descr=cs.fielddescr, + raw=(ptr_box.type == 'i')) + 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,7 +1,8 @@ import py from rpython.jit.backend.llsupport.descr import get_size_descr,\ get_field_descr, get_array_descr, ArrayDescr, FieldDescr,\ - SizeDescr, get_interiorfield_descr + SizeDescr, get_interiorfield_descr, get_call_descr +from rpython.jit.codewriter.effectinfo import EffectInfo, CallShortcut from rpython.jit.backend.llsupport.gc import GcLLDescr_boehm,\ GcLLDescr_framework from rpython.jit.backend.llsupport import jitframe @@ -80,6 +81,14 @@ 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) + # A = lltype.GcArray(lltype.Signed) adescr = get_array_descr(self.gc_ll_descr, A) adescr.tid = 4321 @@ -200,6 +209,7 @@ load_constant_offset = True load_supported_factors = (1,2,4,8) + supports_cond_call_value = True translate_support_code = None @@ -1429,3 +1439,15 @@ 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) + """) 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,6 +16,7 @@ # 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/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 @@ -2389,7 +2389,7 @@ f2 = longlong.getfloatstorage(3.4) frame = self.cpu.execute_token(looptoken, 1, 0, 1, 2, 3, 4, 5, f1, f2) assert not called - for j in range(5): + for j in range(6): assert self.cpu.get_int_value(frame, j) == j assert longlong.getrealfloat(self.cpu.get_float_value(frame, 6)) == 1.2 assert longlong.getrealfloat(self.cpu.get_float_value(frame, 7)) == 3.4 @@ -2447,6 +2447,54 @@ 67, 89) 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 + + for i in range(5): + called = [] + + FUNC = self.FuncType([lltype.Signed] * i, lltype.Signed) + func_ptr = llhelper(lltype.Ptr(FUNC), func_int) + calldescr = self.cpu.calldescrof(FUNC, FUNC.ARGS, FUNC.RESULT, + EffectInfo.MOST_GENERAL) + + ops = ''' + [i0, i1, i2, i3, i4, i5, i6, f0, f1] + i15 = cond_call_value_i(i1, ConstClass(func_ptr), %s) + guard_false(i0, descr=faildescr) [i1,i2,i3,i4,i5,i6,i15, f0,f1] + finish(i15) + ''' % ', '.join(['i%d' % (j + 2) for j in range(i)] + + ["descr=calldescr"]) + loop = parse(ops, namespace={'faildescr': BasicFailDescr(), + 'func_ptr': func_ptr, + 'calldescr': calldescr}) + looptoken = JitCellToken() + self.cpu.compile_loop(loop.inputargs, loop.operations, looptoken) + f1 = longlong.getfloatstorage(1.2) + f2 = longlong.getfloatstorage(3.4) + frame = self.cpu.execute_token(looptoken, 1, 50, 1, 2, 3, 4, 5, + f1, f2) + assert not called + assert [self.cpu.get_int_value(frame, j) for j in range(7)] == [ + 50, 1, 2, 3, 4, 5, 50] + assert longlong.getrealfloat( + self.cpu.get_float_value(frame, 7)) == 1.2 + assert longlong.getrealfloat( + self.cpu.get_float_value(frame, 8)) == 3.4 + # + frame = self.cpu.execute_token(looptoken, 1, 0, 1, 2, 3, 4, 5, + f1, f2) + assert called == [(1, 2, 3, 4)[:i]] + assert [self.cpu.get_int_value(frame, j) for j in range(7)] == [ + 0, 1, 2, 3, 4, 5, i * 100 + 1000] + assert longlong.getrealfloat(self.cpu.get_float_value(frame, 7)) == 1.2 + assert longlong.getrealfloat(self.cpu.get_float_value(frame, 8)) == 3.4 + def test_force_operations_returning_void(self): values = [] def maybe_force(token, flag): 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 @@ -594,7 +594,7 @@ return subset, d['f'], vtableptr def getresulttype(self): - if self.opnum == rop.CALL_I: + if self.opnum == rop.CALL_I or self.opnum == rop.COND_CALL_VALUE_I: return lltype.Signed elif self.opnum == rop.CALL_F: return lltype.Float @@ -712,7 +712,12 @@ class CondCallOperation(BaseCallOperation): def produce_into(self, builder, r): fail_subset = builder.subset_of_intvars(r) - v_cond = builder.get_bool_var(r) + if self.opnum == rop.COND_CALL: + RESULT_TYPE = lltype.Void + v_cond = builder.get_bool_var(r) + else: + RESULT_TYPE = lltype.Signed + v_cond = r.choice(builder.intvars) subset = builder.subset_of_intvars(r)[:4] for i in range(len(subset)): if r.random() < 0.35: @@ -724,8 +729,10 @@ seen.append(args) else: assert seen[0] == args + if RESULT_TYPE is lltype.Signed: + return len(args) - 42000 # - TP = lltype.FuncType([lltype.Signed] * len(subset), lltype.Void) + TP = lltype.FuncType([lltype.Signed] * len(subset), RESULT_TYPE) ptr = llhelper(lltype.Ptr(TP), call_me) c_addr = ConstAddr(llmemory.cast_ptr_to_adr(ptr), builder.cpu) args = [v_cond, c_addr] + subset @@ -769,6 +776,7 @@ for i in range(2): OPERATIONS.append(GuardClassOperation(rop.GUARD_CLASS)) OPERATIONS.append(CondCallOperation(rop.COND_CALL)) + OPERATIONS.append(CondCallOperation(rop.COND_CALL_VALUE_I)) OPERATIONS.append(RaisingCallOperation(rop.CALL_N)) OPERATIONS.append(RaisingCallOperationGuardNoException(rop.CALL_N)) OPERATIONS.append(RaisingCallOperationWrongGuardException(rop.CALL_N)) diff --git a/rpython/jit/backend/x86/assembler.py b/rpython/jit/backend/x86/assembler.py --- a/rpython/jit/backend/x86/assembler.py +++ b/rpython/jit/backend/x86/assembler.py @@ -186,8 +186,8 @@ # copy registers to the frame, with the exception of the # 'cond_call_register_arguments' and eax, because these have already # been saved by the caller. Note that this is not symmetrical: - # these 5 registers are saved by the caller but restored here at - # the end of this function. + # these 5 registers are saved by the caller but 4 of them are + # restored here at the end of this function. self._push_all_regs_to_frame(mc, cond_call_register_arguments + [eax], supports_floats, callee_only) # the caller already did push_gcmap(store=True) @@ -210,7 +210,7 @@ mc.ADD(esp, imm(WORD * 7)) self.set_extra_stack_depth(mc, 0) self.pop_gcmap(mc) # cancel the push_gcmap(store=True) in the caller - self._pop_all_regs_from_frame(mc, [], supports_floats, callee_only) + self._pop_all_regs_from_frame(mc, [eax], supports_floats, callee_only) mc.RET() return mc.materialize(self.cpu, []) @@ -1715,7 +1715,8 @@ self.implement_guard(guard_token) # If the previous operation was a COND_CALL, overwrite its conditional # jump to jump over this GUARD_NO_EXCEPTION as well, if we can - if self._find_nearby_operation(-1).getopnum() == rop.COND_CALL: + if self._find_nearby_operation(-1).getopnum() in ( + rop.COND_CALL, rop.COND_CALL_VALUE_I, rop.COND_CALL_VALUE_R): jmp_adr = self.previous_cond_call_jcond offset = self.mc.get_relative_pos() - jmp_adr if offset <= 127: @@ -2393,7 +2394,7 @@ def label(self): self._check_frame_depth_debug(self.mc) - def cond_call(self, op, gcmap, imm_func, arglocs): + def cond_call(self, gcmap, imm_func, arglocs, resloc=None): assert self.guard_success_cc >= 0 self.mc.J_il8(rx86.invert_condition(self.guard_success_cc), 0) # patched later @@ -2406,11 +2407,14 @@ # plus the register 'eax' base_ofs = self.cpu.get_baseofs_of_frame_field() should_be_saved = self._regalloc.rm.reg_bindings.values() + restore_eax = False for gpr in cond_call_register_arguments + [eax]: - if gpr not in should_be_saved: + if gpr not in should_be_saved or gpr is resloc: continue v = gpr_reg_mgr_cls.all_reg_indexes[gpr.value] self.mc.MOV_br(v * WORD + base_ofs, gpr.value) + if gpr is eax: + restore_eax = True # # load the 0-to-4 arguments into these registers from rpython.jit.backend.x86.jump import remap_frame_layout @@ -2434,8 +2438,16 @@ floats = True cond_call_adr = self.cond_call_slowpath[floats * 2 + callee_only] self.mc.CALL(imm(follow_jump(cond_call_adr))) + # if this is a COND_CALL_VALUE, we need to move the result in place + if resloc is not None and resloc is not eax: + self.mc.MOV(resloc, eax) # 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. We must only restore eax, if + # needed. + if restore_eax: + v = gpr_reg_mgr_cls.all_reg_indexes[eax.value] + self.mc.MOV_rb(eax.value, v * WORD + base_ofs) + # offset = self.mc.get_relative_pos() - jmp_adr assert 0 < offset <= 127 self.mc.overwrite(jmp_adr-1, chr(offset)) diff --git a/rpython/jit/backend/x86/regalloc.py b/rpython/jit/backend/x86/regalloc.py --- a/rpython/jit/backend/x86/regalloc.py +++ b/rpython/jit/backend/x86/regalloc.py @@ -938,16 +938,45 @@ self.rm.force_spill_var(box) assert box not in self.rm.reg_bindings # - assert op.type == 'v' args = op.getarglist() assert 2 <= len(args) <= 4 + 2 # maximum 4 arguments - v = args[1] - assert isinstance(v, Const) - imm_func = self.rm.convert_to_imm(v) + v_func = args[1] + assert isinstance(v_func, Const) + imm_func = self.rm.convert_to_imm(v_func) + + # Delicate ordering here. First get the argument's locations. + # If this also contains args[0], this returns the current + # location too. arglocs = [self.loc(args[i]) for i in range(2, len(args))] gcmap = self.get_gcmap() - self.load_condition_into_cc(op.getarg(0)) - self.assembler.cond_call(op, gcmap, imm_func, arglocs) + + 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)) + resloc = None + 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. + resloc = self.rm.force_result_in_reg(op, args[0], + forbidden_vars=args[2:]) + + # Test the register for the result. + self.assembler.test_location(resloc) + self.assembler.guard_success_cc = rx86.Conditions['Z'] + + self.assembler.cond_call(gcmap, imm_func, arglocs, resloc) + + consider_cond_call_value_i = consider_cond_call + consider_cond_call_value_r = consider_cond_call def consider_call_malloc_nursery(self, op): size_box = op.getarg(0) 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,6 +16,7 @@ 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/codewriter/call.py b/rpython/jit/codewriter/call.py --- a/rpython/jit/codewriter/call.py +++ b/rpython/jit/codewriter/call.py @@ -7,9 +7,10 @@ from rpython.jit.codewriter.jitcode import JitCode from rpython.jit.codewriter.effectinfo import (VirtualizableAnalyzer, QuasiImmutAnalyzer, RandomEffectsAnalyzer, effectinfo_from_writeanalyze, - EffectInfo, CallInfoCollection) + EffectInfo, CallInfoCollection, CallShortcut) 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 @@ -214,6 +215,7 @@ 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', ( @@ -228,6 +230,12 @@ 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 @@ -242,6 +250,8 @@ 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( @@ -298,6 +308,7 @@ 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 @@ -368,3 +379,65 @@ 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 + if len(block.operations) == 0: + return + op = block.operations[0] + if op.opname != 'getfield': + return + [v_inst, c_fieldname] = op.args + if not isinstance(v_inst, Variable): + return + v_result = op.result + 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 block.operations[1:]: + 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 + + fielddescr = self.cpu.fielddescrof(PSTRUCT.TO, c_fieldname.value) + 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,7 +117,8 @@ can_invalidate=False, call_release_gil_target=_NO_CALL_RELEASE_GIL_TARGET, extradescrs=None, - can_collect=True): + can_collect=True, + call_shortcut=None): readonly_descrs_fields = frozenset_or_none(readonly_descrs_fields) readonly_descrs_arrays = frozenset_or_none(readonly_descrs_arrays) readonly_descrs_interiorfields = frozenset_or_none( @@ -135,7 +136,8 @@ extraeffect, oopspecindex, can_invalidate, - can_collect) + can_collect, + call_shortcut) tgt_func, tgt_saveerr = call_release_gil_target if tgt_func: key += (object(),) # don't care about caching in this case @@ -190,6 +192,7 @@ 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 @@ -275,7 +278,8 @@ call_release_gil_target= EffectInfo._NO_CALL_RELEASE_GIL_TARGET, extradescr=None, - can_collect=True): + can_collect=True, + call_shortcut=None): from rpython.translator.backendopt.writeanalyze import top_set if effects is top_set or extraeffect == EffectInfo.EF_RANDOM_EFFECTS: readonly_descrs_fields = None @@ -364,7 +368,8 @@ can_invalidate, call_release_gil_target, extradescr, - can_collect) + can_collect, + call_shortcut) def consider_struct(TYPE, fieldname): if fieldType(TYPE, fieldname) is lltype.Void: @@ -387,6 +392,24 @@ # ____________________________________________________________ + +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 @@ -200,8 +200,12 @@ or v.concretetype != lltype.Bool): return False for op in block.operations[::-1]: - if v in op.args: - return False # variable is also used in cur block + # check if variable is used in block + for arg in op.args: + if arg == v: + return False + if isinstance(arg, ListOfKind) and v in arg.content: + return False if v is op.result: if op.opname not in ('int_lt', 'int_le', 'int_eq', 'int_ne', 'int_gt', 'int_ge', 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 +from rpython.jit.codewriter.effectinfo import EffectInfo, CallShortcut class FakePolicy: @@ -368,3 +368,100 @@ 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 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) + + 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") + +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/codewriter/test/test_jtransform.py b/rpython/jit/codewriter/test/test_jtransform.py --- a/rpython/jit/codewriter/test/test_jtransform.py +++ b/rpython/jit/codewriter/test/test_jtransform.py @@ -243,6 +243,20 @@ assert block.exitswitch == (opname, v1, '-live-before') assert block.exits == exits +def test_optimize_goto_if_not__argument_to_call(): + for opname in ['ptr_iszero', 'ptr_nonzero']: + v1 = Variable() + v3 = Variable(); v3.concretetype = lltype.Bool + v4 = Variable() + block = Block([v1]) + callop = SpaceOperation('residual_call_r_i', + ["fake", ListOfKind('int', [v3])], v4) + block.operations = [SpaceOperation(opname, [v1], v3), callop] + block.exitswitch = v3 + block.exits = exits = [FakeLink(False), FakeLink(True)] + res = Transformer().optimize_goto_if_not(block) + assert not res + def test_symmetric(): ops = {'int_add': 'int_add', 'int_or': 'int_or', diff --git a/rpython/jit/metainterp/executor.py b/rpython/jit/metainterp/executor.py --- a/rpython/jit/metainterp/executor.py +++ b/rpython/jit/metainterp/executor.py @@ -101,6 +101,18 @@ if condbox.getint(): do_call_n(cpu, metainterp, argboxes[1:], descr) +def do_cond_call_value_i(cpu, metainterp, argboxes, descr): + value = argboxes[0].getint() + if value == 0: + value = do_call_i(cpu, metainterp, argboxes[1:], descr) + return value + +def do_cond_call_value_r(cpu, metainterp, argboxes, descr): + value = argboxes[0].getref_base() + if not value: + value = do_call_r(cpu, metainterp, argboxes[1:], descr) + return value + def do_getarrayitem_gc_i(cpu, _, arraybox, indexbox, arraydescr): array = arraybox.getref_base() index = indexbox.getint() @@ -366,6 +378,8 @@ rop.CALL_ASSEMBLER_I, rop.CALL_ASSEMBLER_N, rop.INCREMENT_DEBUG_COUNTER, + rop.COND_CALL_VALUE_R, + rop.COND_CALL_VALUE_I, rop.COND_CALL_GC_WB, rop.COND_CALL_GC_WB_ARRAY, rop.ZERO_ARRAY, 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 @@ -3,7 +3,7 @@ from rpython.jit.metainterp.resoperation import AbstractValue, ResOperation,\ rop, OpHelpers from rpython.jit.metainterp.history import ConstInt, Const -from rpython.rtyper.lltypesystem import lltype +from rpython.rtyper.lltypesystem import lltype, llmemory from rpython.jit.metainterp.optimizeopt.rawbuffer import RawBuffer, InvalidRawOperation from rpython.jit.metainterp.executor import execute from rpython.jit.metainterp.optimize import InvalidLoop 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 @@ -11,6 +11,8 @@ from rpython.jit.metainterp.typesystem import llhelper from rpython.rlib.objectmodel import specialize, we_are_translated from rpython.rlib.debug import debug_print +from rpython.rtyper import rclass +from rpython.rtyper.lltypesystem import llmemory from rpython.jit.metainterp.optimize import SpeculativeError @@ -799,6 +801,21 @@ if not (0 <= index < arraylength): raise SpeculativeError + @staticmethod + def _check_subclass(vtable1, vtable2): # checks that vtable1 is a subclass of vtable2 + known_class = llmemory.cast_adr_to_ptr( + llmemory.cast_int_to_adr(vtable1), + rclass.CLASSTYPE) + expected_class = llmemory.cast_adr_to_ptr( + llmemory.cast_int_to_adr(vtable2), + rclass.CLASSTYPE) + # note: the test is for a range including 'max', but 'max' + # should never be used for actual classes. Including it makes + # it easier to pass artificial tests. + return (expected_class.subclassrange_min + <= known_class.subclassrange_min + <= expected_class.subclassrange_max) + def is_virtual(self, op): if op.type == 'r': opinfo = self.getptrinfo(op) 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 @@ -324,37 +324,24 @@ return self.emit_operation(op) - def _check_subclass(self, vtable1, vtable2): - # checks that vtable1 is a subclass of vtable2 - known_class = llmemory.cast_adr_to_ptr( - llmemory.cast_int_to_adr(vtable1), - rclass.CLASSTYPE) - expected_class = llmemory.cast_adr_to_ptr( - llmemory.cast_int_to_adr(vtable2), - rclass.CLASSTYPE) - if (expected_class.subclassrange_min - <= known_class.subclassrange_min - <= expected_class.subclassrange_max): - return True - return False - def optimize_GUARD_SUBCLASS(self, op): info = self.getptrinfo(op.getarg(0)) + optimizer = self.optimizer if info and info.is_constant(): c = self.get_box_replacement(op.getarg(0)) - vtable = self.optimizer.cpu.ts.cls_of_box(c).getint() - if self._check_subclass(vtable, op.getarg(1).getint()): + vtable = optimizer.cpu.ts.cls_of_box(c).getint() + if optimizer._check_subclass(vtable, op.getarg(1).getint()): return raise InvalidLoop("GUARD_SUBCLASS(const) proven to always fail") if info is not None and info.is_about_object(): - known_class = info.get_known_class(self.optimizer.cpu) + known_class = info.get_known_class(optimizer.cpu) if known_class: - if self._check_subclass(known_class.getint(), - op.getarg(1).getint()): + if optimizer._check_subclass(known_class.getint(), + op.getarg(1).getint()): return elif info.get_descr() is not None: - if self._check_subclass(info.get_descr().get_vtable(), - op.getarg(1).getint()): + if optimizer._check_subclass(info.get_descr().get_vtable(), + op.getarg(1).getint()): return self.emit_operation(op) diff --git a/rpython/jit/metainterp/optimizeopt/test/test_unroll.py b/rpython/jit/metainterp/optimizeopt/test/test_unroll.py --- a/rpython/jit/metainterp/optimizeopt/test/test_unroll.py +++ b/rpython/jit/metainterp/optimizeopt/test/test_unroll.py @@ -17,12 +17,13 @@ from rpython.jit.metainterp.optimizeopt.virtualstate import \ NotVirtualStateInfo, LEVEL_CONSTANT, LEVEL_UNKNOWN, LEVEL_KNOWNCLASS,\ VirtualStateInfo -from rpython.jit.metainterp.optimizeopt import info +from rpython.jit.metainterp.optimizeopt import info, optimizer from rpython.jit.codewriter import heaptracker from rpython.jit.tool import oparser class FakeOptimizer(object): optearlyforce = None + optimizer = optimizer.Optimizer class cpu: remove_gctypeptr = True 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 @@ -102,6 +102,8 @@ node_vtable_adr2 = llmemory.cast_ptr_to_adr(node_vtable2) node_vtable3 = lltype.malloc(OBJECT_VTABLE, immortal=True) node_vtable3.name = rclass.alloc_array_name('node3') + node_vtable3.subclassrange_min = 3 + node_vtable3.subclassrange_max = 3 node_vtable_adr3 = llmemory.cast_ptr_to_adr(node_vtable3) cpu = runner.LLGraphCPU(None) diff --git a/rpython/jit/metainterp/optimizeopt/test/test_virtualstate.py b/rpython/jit/metainterp/optimizeopt/test/test_virtualstate.py --- a/rpython/jit/metainterp/optimizeopt/test/test_virtualstate.py +++ b/rpython/jit/metainterp/optimizeopt/test/test_virtualstate.py @@ -3,7 +3,8 @@ from rpython.jit.metainterp.optimizeopt.virtualstate import VirtualStateInfo,\ VStructStateInfo, LEVEL_CONSTANT,\ VArrayStateInfo, not_virtual, VirtualState,\ - GenerateGuardState, VirtualStatesCantMatch, VArrayStructStateInfo + GenerateGuardState, VirtualStatesCantMatch, VArrayStructStateInfo,\ + VirtualStateConstructor from rpython.jit.metainterp.history import ConstInt, ConstPtr, TargetToken from rpython.jit.metainterp.resoperation import InputArgInt, InputArgRef,\ InputArgFloat @@ -26,6 +27,7 @@ def __init__(self, cpu): self.cpu = cpu self.optearlyforce = None + self.optimizer = Optimizer class BaseTestGenerateGuards(BaseTest): def setup_class(self): @@ -87,6 +89,42 @@ vs = VirtualState([info0]) assert vs.make_inputargs(args, optimizer) == [] + def test_make_inputargs_2(self): + # Ensure that make_inputargs does not error when the lengths of the fields + # for the runtime box does not match what the virtual state expected. + # This can occur in unroll.py, as not all paths to make_inputargs are + # guareded with a generalization_of check. The property is validated + # subsequently in all cases, so we just need to ensure that this case does + # not cause segfaults. + optimizer = FakeOptimizer(self.cpu) + classbox1 = self.cpu.ts.cls_of_box(InputArgRef(self.nodeaddr)) + innervalue1 = info.InstancePtrInfo( + known_class=classbox1, is_virtual=True, + descr=self.valuedescr.get_parent_descr()) + for field in self.valuedescr.get_parent_descr().get_all_fielddescrs(): + innervalue1.setfield(field, None, ConstInt(42)) + classbox2 = self.cpu.ts.cls_of_box(InputArgRef(self.myptr3)) + innervalue2 = info.InstancePtrInfo( + known_class=classbox2, is_virtual=True, + descr=self.valuedescr3.get_parent_descr()) + for field in self.valuedescr3.get_parent_descr().get_all_fielddescrs(): + innervalue2.setfield(field, None, ConstInt(42)) + + nodebox1 = InputArgRef(self.nodeaddr) + nodebox2 = InputArgRef(self.myptr3) + nodebox1.set_forwarded(innervalue1) + nodebox2.set_forwarded(innervalue2) + + constr = VirtualStateConstructor(optimizer) + vs1 = constr.get_virtual_state([nodebox1]) + constr = VirtualStateConstructor(optimizer) + vs2 = constr.get_virtual_state([nodebox2]) + + # This should succeed with no exceptions + vs1.make_inputargs([nodebox2], optimizer, force_boxes=False) + assert not vs1.generalization_of(vs2, optimizer) + assert not vs2.generalization_of(vs1, optimizer) + def test_position_generalization(self): def postest(info1, info2): info1.position = 0 diff --git a/rpython/jit/metainterp/optimizeopt/unroll.py b/rpython/jit/metainterp/optimizeopt/unroll.py --- a/rpython/jit/metainterp/optimizeopt/unroll.py +++ b/rpython/jit/metainterp/optimizeopt/unroll.py @@ -167,7 +167,8 @@ [self.get_box_replacement(x) for x in end_jump.getarglist()], self.optimizer, force_boxes=True) for arg in args: - self.optimizer.force_box(arg) + if arg is not None: + self.optimizer.force_box(arg) except VirtualStatesCantMatch: raise InvalidLoop("Virtual states did not match " "after picking the virtual state, when forcing" diff --git a/rpython/jit/metainterp/optimizeopt/virtualstate.py b/rpython/jit/metainterp/optimizeopt/virtualstate.py --- a/rpython/jit/metainterp/optimizeopt/virtualstate.py +++ b/rpython/jit/metainterp/optimizeopt/virtualstate.py @@ -184,7 +184,10 @@ raise VirtualStatesCantMatch() else: assert isinstance(info, AbstractStructPtrInfo) - for i in range(len(self.fielddescrs)): + + # The min operation ensures we don't wander off either array, as not all + # to make_inputargs have validated their inputs with generate_guards. + for i in range(min(len(self.fielddescrs), len(info._fields))): state = self.fieldstate[i] if not state: continue diff --git a/rpython/jit/metainterp/resoperation.py b/rpython/jit/metainterp/resoperation.py --- a/rpython/jit/metainterp/resoperation.py +++ b/rpython/jit/metainterp/resoperation.py @@ -1146,8 +1146,8 @@ '_CANRAISE_FIRST', # ----- start of can_raise operations ----- '_CALL_FIRST', 'CALL/*d/rfin', - 'COND_CALL/*d/n', - # a conditional call, with first argument as a condition + 'COND_CALL/*d/n', # a conditional call, with first argument as a condition + 'COND_CALL_VALUE/*d/ri', # same but returns a result; emitted by rewrite 'CALL_ASSEMBLER/*d/rfin', # call already compiled assembler 'CALL_MAY_FORCE/*d/rfin', 'CALL_LOOPINVARIANT/*d/rfin', diff --git a/rpython/jit/metainterp/test/test_dict.py b/rpython/jit/metainterp/test/test_dict.py --- a/rpython/jit/metainterp/test/test_dict.py +++ b/rpython/jit/metainterp/test/test_dict.py @@ -195,7 +195,8 @@ 'new_with_vtable': 2, 'getinteriorfield_gc_i': 2, 'setfield_gc': 14, 'int_gt': 2, 'int_sub': 2, 'call_i': 6, 'call_n': 2, 'call_r': 2, 'int_ge': 2, - 'guard_no_exception': 8, 'new': 2}) + 'guard_no_exception': 8, 'new': 2, + 'guard_nonnull': 2}) def test_unrolling_of_dict_iter(self): driver = JitDriver(greens = [], reds = ['n']) diff --git a/rpython/rlib/jit.py b/rpython/rlib/jit.py --- a/rpython/rlib/jit.py +++ b/rpython/rlib/jit.py @@ -257,6 +257,28 @@ func.oopspec = "jit.not_in_trace()" # note that 'func' may take arguments return func +def call_shortcut(func): + """A decorator to ensure that a function has a fast-path. + DOES NOT RELIABLY WORK ON METHODS, USE ONLY ON FUNCTIONS! + + Only useful on functions that the JIT doesn't normally look inside. + It still replaces residual calls to that function with inline code + that checks for a fast path, and only does the call if not. For + now, graphs made by the following kinds of functions are detected: + + def func(x, y, z): def func(x, y, z): + if y.field: r = y.field + return y.field if r is None: + ... ... + return r _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit