[pypy-commit] pypy ppc-vsx-support: catchup with default
plan_rich
pypy.commits at gmail.com
Mon Sep 19 03:44:18 EDT 2016
Author: Richard Plangger <planrichi at 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
More information about the pypy-commit
mailing list