Author: Christian Tismer <[email protected]> Branch: win64-stage1 Changeset: r54094:f8828970a964 Date: 2012-03-30 12:16 +0200 http://bitbucket.org/pypy/pypy/changeset/f8828970a964/
Log: hg merge default diff --git a/pypy/doc/project-ideas.rst b/pypy/doc/project-ideas.rst --- a/pypy/doc/project-ideas.rst +++ b/pypy/doc/project-ideas.rst @@ -149,6 +149,22 @@ exported. This would give us a one-size-fits-all generic .so file to be imported by any application that wants to load .so files :-) +Optimising cpyext (CPython C-API compatibility layer) +----------------------------------------------------- + +A lot of work has gone into PyPy's implementation of CPython's C-API over +the last years to let it reach a practical level of compatibility, so that +C extensions for CPython work on PyPy without major rewrites. However, +there are still many edges and corner cases where it misbehaves, and it has +not received any substantial optimisation so far. + +The objective of this project is to fix bugs in cpyext and to optimise +several performance critical parts of it, such as the reference counting +support and other heavily used C-API functions. The net result would be to +have CPython extensions run much faster on PyPy than they currently do, or +to make them work at all if they currently don't. A part of this work would +be to get cpyext into a shape where it supports running Cython generated +extensions. .. _`issue tracker`: http://bugs.pypy.org .. _`mailing list`: http://mail.python.org/mailman/listinfo/pypy-dev diff --git a/pypy/doc/stackless.rst b/pypy/doc/stackless.rst --- a/pypy/doc/stackless.rst +++ b/pypy/doc/stackless.rst @@ -199,17 +199,11 @@ The following features (present in some past Stackless version of PyPy) are for the time being not supported any more: -* Tasklets and channels (currently ``stackless.py`` seems to import, - but you have tasklets on top of coroutines on top of greenlets on - top of continulets on top of stacklets, and it's probably not too - hard to cut two of these levels by adapting ``stackless.py`` to - use directly continulets) - * Coroutines (could be rewritten at app-level) -* Pickling and unpickling continulets (*) - -* Continuing execution of a continulet in a different thread (*) +* Continuing execution of a continulet in a different thread + (but if it is "simple enough", you can pickle it and unpickle it + in the other thread). * Automatic unlimited stack (must be emulated__ so far) @@ -217,15 +211,6 @@ .. __: `recursion depth limit`_ -(*) Pickling, as well as changing threads, could be implemented by using -a "soft" stack switching mode again. We would get either "hard" or -"soft" switches, similarly to Stackless Python 3rd version: you get a -"hard" switch (like now) when the C stack contains non-trivial C frames -to save, and a "soft" switch (like previously) when it contains only -simple calls from Python to Python. Soft-switched continulets would -also consume a bit less RAM, and the switch might be a bit faster too -(unsure about that; what is the Stackless Python experience?). - Recursion depth limit +++++++++++++++++++++ diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py --- a/pypy/interpreter/baseobjspace.py +++ b/pypy/interpreter/baseobjspace.py @@ -296,6 +296,7 @@ self.check_signal_action = None # changed by the signal module self.user_del_action = UserDelAction(self) self.frame_trace_action = FrameTraceAction(self) + self._code_of_sys_exc_info = None from pypy.interpreter.pycode import cpython_magic, default_magic self.our_magic = default_magic @@ -467,9 +468,9 @@ if name not in modules: modules.append(name) - # a bit of custom logic: time2 or rctime take precedence over time + # a bit of custom logic: rctime take precedence over time # XXX this could probably be done as a "requires" in the config - if ('time2' in modules or 'rctime' in modules) and 'time' in modules: + if 'rctime' in modules and 'time' in modules: modules.remove('time') if not self.config.objspace.nofaking: diff --git a/pypy/interpreter/executioncontext.py b/pypy/interpreter/executioncontext.py --- a/pypy/interpreter/executioncontext.py +++ b/pypy/interpreter/executioncontext.py @@ -154,6 +154,7 @@ #operationerr.print_detailed_traceback(self.space) def _convert_exc(self, operr): + # Only for the flow object space return operr def sys_exc_info(self): # attn: the result is not the wrapped sys.exc_info() !!! diff --git a/pypy/interpreter/function.py b/pypy/interpreter/function.py --- a/pypy/interpreter/function.py +++ b/pypy/interpreter/function.py @@ -113,6 +113,12 @@ from pypy.interpreter.pycode import PyCode code = self.getcode() # hook for the jit + # + if (jit.we_are_jitted() and code is self.space._code_of_sys_exc_info + and nargs == 0): + from pypy.module.sys.vm import exc_info_direct + return exc_info_direct(self.space, frame) + # fast_natural_arity = code.fast_natural_arity if nargs == fast_natural_arity: if nargs == 0: diff --git a/pypy/interpreter/gateway.py b/pypy/interpreter/gateway.py --- a/pypy/interpreter/gateway.py +++ b/pypy/interpreter/gateway.py @@ -874,6 +874,12 @@ fn.add_to_table() if gateway.as_classmethod: fn = ClassMethod(space.wrap(fn)) + # + from pypy.module.sys.vm import exc_info + if code._bltin is exc_info: + assert space._code_of_sys_exc_info is None + space._code_of_sys_exc_info = code + # return fn diff --git a/pypy/interpreter/pyparser/parsestring.py b/pypy/interpreter/pyparser/parsestring.py --- a/pypy/interpreter/pyparser/parsestring.py +++ b/pypy/interpreter/pyparser/parsestring.py @@ -2,34 +2,39 @@ from pypy.interpreter import unicodehelper from pypy.rlib.rstring import StringBuilder -def parsestr(space, encoding, s, unicode_literals=False): - # compiler.transformer.Transformer.decode_literal depends on what - # might seem like minor details of this function -- changes here - # must be reflected there. +def parsestr(space, encoding, s, unicode_literal=False): + """Parses a string or unicode literal, and return a wrapped value. + + If encoding=iso8859-1, the source string is also in this encoding. + If encoding=None, the source string is ascii only. + In other cases, the source string is in utf-8 encoding. + + When a bytes string is returned, it will be encoded with the + original encoding. + + Yes, it's very inefficient. + Yes, CPython has very similar code. + """ # we use ps as "pointer to s" # q is the virtual last char index of the string ps = 0 quote = s[ps] rawmode = False - unicode = unicode_literals # string decoration handling - o = ord(quote) - isalpha = (o>=97 and o<=122) or (o>=65 and o<=90) - if isalpha or quote == '_': - if quote == 'b' or quote == 'B': - ps += 1 - quote = s[ps] - unicode = False - elif quote == 'u' or quote == 'U': - ps += 1 - quote = s[ps] - unicode = True - if quote == 'r' or quote == 'R': - ps += 1 - quote = s[ps] - rawmode = True + if quote == 'b' or quote == 'B': + ps += 1 + quote = s[ps] + unicode_literal = False + elif quote == 'u' or quote == 'U': + ps += 1 + quote = s[ps] + unicode_literal = True + if quote == 'r' or quote == 'R': + ps += 1 + quote = s[ps] + rawmode = True if quote != "'" and quote != '"': raise_app_valueerror(space, 'Internal error: parser passed unquoted literal') @@ -46,21 +51,28 @@ 'unmatched triple quotes in literal') q -= 2 - if unicode: # XXX Py_UnicodeFlag is ignored for now + if unicode_literal: # XXX Py_UnicodeFlag is ignored for now if encoding is None or encoding == "iso-8859-1": + # 'unicode_escape' expects latin-1 bytes, string is ready. buf = s bufp = ps bufq = q u = None else: - # "\XX" may become "\u005c\uHHLL" (12 bytes) + # String is utf8-encoded, but 'unicode_escape' expects + # latin-1; So multibyte sequences must be escaped. lis = [] # using a list to assemble the value end = q + # Worst case: "\XX" may become "\u005c\uHHLL" (12 bytes) while ps < end: if s[ps] == '\\': lis.append(s[ps]) ps += 1 if ord(s[ps]) & 0x80: + # A multibyte sequence will follow, it will be + # escaped like \u1234. To avoid confusion with + # the backslash we just wrote, we emit "\u005c" + # instead. lis.append("u005c") if ord(s[ps]) & 0x80: # XXX inefficient w, ps = decode_utf8(space, s, ps, end, "utf-16-be") @@ -86,13 +98,11 @@ need_encoding = (encoding is not None and encoding != "utf-8" and encoding != "iso-8859-1") - # XXX add strchr like interface to rtyper assert 0 <= ps <= q substr = s[ps : q] if rawmode or '\\' not in s[ps:]: if need_encoding: w_u = space.wrap(unicodehelper.PyUnicode_DecodeUTF8(space, substr)) - #w_v = space.wrap(space.unwrap(w_u).encode(encoding)) this works w_v = unicodehelper.PyUnicode_AsEncodedString(space, w_u, space.wrap(encoding)) return w_v else: diff --git a/pypy/jit/backend/test/runner_test.py b/pypy/jit/backend/test/runner_test.py --- a/pypy/jit/backend/test/runner_test.py +++ b/pypy/jit/backend/test/runner_test.py @@ -27,6 +27,12 @@ def constfloat(x): return ConstFloat(longlong.getfloatstorage(x)) +def boxlonglong(ll): + if longlong.is_64_bit: + return BoxInt(ll) + else: + return BoxFloat(ll) + class Runner(object): @@ -1623,6 +1629,11 @@ [boxfloat(2.5)], t).value assert res == longlong2float.float2longlong(2.5) + bytes = longlong2float.float2longlong(2.5) + res = self.execute_operation(rop.CONVERT_LONGLONG_BYTES_TO_FLOAT, + [boxlonglong(res)], 'float').value + assert longlong.getrealfloat(res) == 2.5 + def test_ooops_non_gc(self): x = lltype.malloc(lltype.Struct('x'), flavor='raw') v = heaptracker.adr2int(llmemory.cast_ptr_to_adr(x)) diff --git a/pypy/jit/backend/test/test_random.py b/pypy/jit/backend/test/test_random.py --- a/pypy/jit/backend/test/test_random.py +++ b/pypy/jit/backend/test/test_random.py @@ -328,6 +328,15 @@ def produce_into(self, builder, r): self.put(builder, [r.choice(builder.intvars)]) +class CastLongLongToFloatOperation(AbstractFloatOperation): + def produce_into(self, builder, r): + if longlong.is_64_bit: + self.put(builder, [r.choice(builder.intvars)]) + else: + if not builder.floatvars: + raise CannotProduceOperation + self.put(builder, [r.choice(builder.floatvars)]) + class CastFloatToIntOperation(AbstractFloatOperation): def produce_into(self, builder, r): if not builder.floatvars: @@ -450,6 +459,7 @@ OPERATIONS.append(CastFloatToIntOperation(rop.CAST_FLOAT_TO_INT)) OPERATIONS.append(CastIntToFloatOperation(rop.CAST_INT_TO_FLOAT)) OPERATIONS.append(CastFloatToIntOperation(rop.CONVERT_FLOAT_BYTES_TO_LONGLONG)) +OPERATIONS.append(CastLongLongToFloatOperation(rop.CONVERT_LONGLONG_BYTES_TO_FLOAT)) OperationBuilder.OPERATIONS = OPERATIONS diff --git a/pypy/jit/backend/x86/assembler.py b/pypy/jit/backend/x86/assembler.py --- a/pypy/jit/backend/x86/assembler.py +++ b/pypy/jit/backend/x86/assembler.py @@ -1251,6 +1251,15 @@ else: self.mov(loc0, resloc) + def genop_convert_longlong_bytes_to_float(self, op, arglocs, resloc): + loc0, = arglocs + if longlong.is_64_bit: + assert isinstance(resloc, RegLoc) + assert isinstance(loc0, RegLoc) + self.mc.MOVD(resloc, loc0) + else: + self.mov(loc0, resloc) + def genop_guard_int_is_true(self, op, guard_op, guard_token, arglocs, resloc): guard_opnum = guard_op.getopnum() self.mc.CMP(arglocs[0], imm0) diff --git a/pypy/jit/backend/x86/regalloc.py b/pypy/jit/backend/x86/regalloc.py --- a/pypy/jit/backend/x86/regalloc.py +++ b/pypy/jit/backend/x86/regalloc.py @@ -773,10 +773,24 @@ self.Perform(op, [loc0], loc1) self.xrm.possibly_free_var(op.getarg(0)) else: - loc0 = self.xrm.loc(op.getarg(0)) + arg0 = op.getarg(0) + loc0 = self.xrm.loc(arg0) + loc1 = self.xrm.force_allocate_reg(op.result, forbidden_vars=[arg0]) + self.Perform(op, [loc0], loc1) + self.xrm.possibly_free_var(arg0) + + def consider_convert_longlong_bytes_to_float(self, op): + if longlong.is_64_bit: + loc0 = self.rm.make_sure_var_in_reg(op.getarg(0)) loc1 = self.xrm.force_allocate_reg(op.result) self.Perform(op, [loc0], loc1) - self.xrm.possibly_free_var(op.getarg(0)) + self.rm.possibly_free_var(op.getarg(0)) + else: + arg0 = op.getarg(0) + loc0 = self.xrm.make_sure_var_in_reg(arg0) + loc1 = self.xrm.force_allocate_reg(op.result, forbidden_vars=[arg0]) + self.Perform(op, [loc0], loc1) + self.xrm.possibly_free_var(arg0) def _consider_llong_binop_xx(self, op): # must force both arguments into xmm registers, because we don't diff --git a/pypy/jit/codewriter/jtransform.py b/pypy/jit/codewriter/jtransform.py --- a/pypy/jit/codewriter/jtransform.py +++ b/pypy/jit/codewriter/jtransform.py @@ -295,6 +295,7 @@ return op rewrite_op_convert_float_bytes_to_longlong = _noop_rewrite + rewrite_op_convert_longlong_bytes_to_float = _noop_rewrite # ---------- # Various kinds of calls diff --git a/pypy/jit/codewriter/test/test_flatten.py b/pypy/jit/codewriter/test/test_flatten.py --- a/pypy/jit/codewriter/test/test_flatten.py +++ b/pypy/jit/codewriter/test/test_flatten.py @@ -968,20 +968,22 @@ int_return %i2 """, transform=True) - def test_convert_float_bytes_to_int(self): - from pypy.rlib.longlong2float import float2longlong + def test_convert_float_bytes(self): + from pypy.rlib.longlong2float import float2longlong, longlong2float def f(x): - return float2longlong(x) + ll = float2longlong(x) + return longlong2float(ll) if longlong.is_64_bit: - result_var = "%i0" - return_op = "int_return" + tmp_var = "%i0" + result_var = "%f1" else: - result_var = "%f1" - return_op = "float_return" + tmp_var = "%f1" + result_var = "%f2" self.encoding_test(f, [25.0], """ - convert_float_bytes_to_longlong %%f0 -> %(result_var)s - %(return_op)s %(result_var)s - """ % {"result_var": result_var, "return_op": return_op}) + convert_float_bytes_to_longlong %%f0 -> %(tmp_var)s + convert_longlong_bytes_to_float %(tmp_var)s -> %(result_var)s + float_return %(result_var)s + """ % {"result_var": result_var, "tmp_var": tmp_var}, transform=True) def check_force_cast(FROM, TO, operations, value): diff --git a/pypy/jit/metainterp/blackhole.py b/pypy/jit/metainterp/blackhole.py --- a/pypy/jit/metainterp/blackhole.py +++ b/pypy/jit/metainterp/blackhole.py @@ -672,6 +672,11 @@ a = longlong.getrealfloat(a) return longlong2float.float2longlong(a) + @arguments(LONGLONG_TYPECODE, returns="f") + def bhimpl_convert_longlong_bytes_to_float(a): + a = longlong2float.longlong2float(a) + return longlong.getfloatstorage(a) + # ---------- # control flow operations diff --git a/pypy/jit/metainterp/pyjitpl.py b/pypy/jit/metainterp/pyjitpl.py --- a/pypy/jit/metainterp/pyjitpl.py +++ b/pypy/jit/metainterp/pyjitpl.py @@ -224,6 +224,7 @@ 'float_neg', 'float_abs', 'cast_ptr_to_int', 'cast_int_to_ptr', 'convert_float_bytes_to_longlong', + 'convert_longlong_bytes_to_float', ]: exec py.code.Source(''' @arguments("box") diff --git a/pypy/jit/metainterp/resoperation.py b/pypy/jit/metainterp/resoperation.py --- a/pypy/jit/metainterp/resoperation.py +++ b/pypy/jit/metainterp/resoperation.py @@ -420,6 +420,7 @@ 'CAST_FLOAT_TO_SINGLEFLOAT/1', 'CAST_SINGLEFLOAT_TO_FLOAT/1', 'CONVERT_FLOAT_BYTES_TO_LONGLONG/1', + 'CONVERT_LONGLONG_BYTES_TO_FLOAT/1', # 'INT_LT/2b', 'INT_LE/2b', diff --git a/pypy/jit/metainterp/test/test_ajit.py b/pypy/jit/metainterp/test/test_ajit.py --- a/pypy/jit/metainterp/test/test_ajit.py +++ b/pypy/jit/metainterp/test/test_ajit.py @@ -1,3 +1,4 @@ +import math import sys import py @@ -15,7 +16,7 @@ loop_invariant, elidable, promote, jit_debug, assert_green, AssertGreenFailed, unroll_safe, current_trace_length, look_inside_iff, isconstant, isvirtual, promote_string, set_param, record_known_class) -from pypy.rlib.longlong2float import float2longlong +from pypy.rlib.longlong2float import float2longlong, longlong2float from pypy.rlib.rarithmetic import ovfcheck, is_valid_int from pypy.rpython.lltypesystem import lltype, llmemory, rffi from pypy.rpython.ootypesystem import ootype @@ -3795,15 +3796,15 @@ res = self.interp_operations(g, [1]) assert res == 3 - def test_float2longlong(self): + def test_float_bytes(self): def f(n): - return float2longlong(n) + ll = float2longlong(n) + return longlong2float(ll) for x in [2.5, float("nan"), -2.5, float("inf")]: # There are tests elsewhere to verify the correctness of this. - expected = float2longlong(x) res = self.interp_operations(f, [x]) - assert longlong.getfloatstorage(res) == expected + assert res == x or math.isnan(x) and math.isnan(res) class TestLLtype(BaseLLtypeTests, LLJitMixin): diff --git a/pypy/module/cpyext/bufferobject.py b/pypy/module/cpyext/bufferobject.py --- a/pypy/module/cpyext/bufferobject.py +++ b/pypy/module/cpyext/bufferobject.py @@ -4,6 +4,8 @@ PyObjectFields, PyObject) from pypy.module.cpyext.pyobject import make_typedescr, Py_DecRef from pypy.interpreter.buffer import Buffer, StringBuffer, SubBuffer +from pypy.interpreter.error import OperationError +from pypy.module.array.interp_array import ArrayBuffer PyBufferObjectStruct = lltype.ForwardReference() @@ -43,10 +45,15 @@ if isinstance(w_obj, StringBuffer): py_buf.c_b_base = rffi.cast(PyObject, 0) # space.wrap(w_obj.value) - py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, rffi.str2charp(w_obj.as_str())) + py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, rffi.str2charp(w_obj.value)) + py_buf.c_b_size = w_obj.getlength() + elif isinstance(w_obj, ArrayBuffer): + py_buf.c_b_base = rffi.cast(PyObject, 0) # space.wrap(w_obj.value) + py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, w_obj.data) py_buf.c_b_size = w_obj.getlength() else: - raise Exception("Fail fail fail fail fail") + raise OperationError(space.w_NotImplementedError, space.wrap( + "buffer flavor not supported")) def buffer_realize(space, py_obj): diff --git a/pypy/module/cpyext/test/test_bufferobject.py b/pypy/module/cpyext/test/test_bufferobject.py --- a/pypy/module/cpyext/test/test_bufferobject.py +++ b/pypy/module/cpyext/test/test_bufferobject.py @@ -48,3 +48,17 @@ ]) b = module.buffer_new() raises(AttributeError, getattr, b, 'x') + + def test_array_buffer(self): + module = self.import_extension('foo', [ + ("roundtrip", "METH_O", + """ + PyBufferObject *buf = (PyBufferObject *)args; + return PyString_FromStringAndSize(buf->b_ptr, buf->b_size); + """), + ]) + import array + a = array.array('c', 'text') + b = buffer(a) + assert module.roundtrip(b) == 'text' + diff --git a/pypy/module/micronumpy/__init__.py b/pypy/module/micronumpy/__init__.py --- a/pypy/module/micronumpy/__init__.py +++ b/pypy/module/micronumpy/__init__.py @@ -99,6 +99,8 @@ ("exp2", "exp2"), ("expm1", "expm1"), ("fabs", "fabs"), + ("fmax", "fmax"), + ("fmin", "fmin"), ("fmod", "fmod"), ("floor", "floor"), ("ceil", "ceil"), @@ -122,12 +124,14 @@ ("sinh", "sinh"), ("subtract", "subtract"), ('sqrt', 'sqrt'), + ('square', 'square'), ("tan", "tan"), ("tanh", "tanh"), ('bitwise_and', 'bitwise_and'), ('bitwise_or', 'bitwise_or'), ('bitwise_xor', 'bitwise_xor'), ('bitwise_not', 'invert'), + ('invert', 'invert'), ('isnan', 'isnan'), ('isinf', 'isinf'), ('isneginf', 'isneginf'), diff --git a/pypy/module/micronumpy/interp_ufuncs.py b/pypy/module/micronumpy/interp_ufuncs.py --- a/pypy/module/micronumpy/interp_ufuncs.py +++ b/pypy/module/micronumpy/interp_ufuncs.py @@ -3,9 +3,11 @@ from pypy.interpreter.gateway import interp2app, unwrap_spec, NoneNotWrapped from pypy.interpreter.typedef import TypeDef, GetSetProperty, interp_attrproperty from pypy.module.micronumpy import interp_boxes, interp_dtype, support, loop +from pypy.rlib import jit from pypy.rlib.rarithmetic import LONG_BIT from pypy.tool.sourcetools import func_with_new_name + class W_Ufunc(Wrappable): _attrs_ = ["name", "promote_to_float", "promote_bools", "identity"] _immutable_fields_ = ["promote_to_float", "promote_bools", "name"] @@ -28,7 +30,7 @@ return self.identity def descr_call(self, space, __args__): - from interp_numarray import BaseArray + from interp_numarray import BaseArray args_w, kwds_w = __args__.unpack() # it occurs to me that we don't support any datatypes that # require casting, change it later when we do @@ -179,7 +181,7 @@ elif out.shape != shape: raise operationerrfmt(space.w_ValueError, 'output parameter shape mismatch, expecting [%s]' + - ' , got [%s]', + ' , got [%s]', ",".join([str(x) for x in shape]), ",".join([str(x) for x in out.shape]), ) @@ -204,7 +206,7 @@ else: arr = ReduceArray(self.func, self.name, self.identity, obj, dtype) val = loop.compute(arr) - return val + return val def do_axis_reduce(self, obj, dtype, axis, result): from pypy.module.micronumpy.interp_numarray import AxisReduce @@ -253,7 +255,7 @@ if isinstance(w_obj, Scalar): arr = self.func(calc_dtype, w_obj.value.convert_to(calc_dtype)) if isinstance(out,Scalar): - out.value=arr + out.value = arr elif isinstance(out, BaseArray): out.fill(space, arr) else: @@ -265,7 +267,7 @@ if not broadcast_shape or broadcast_shape != out.shape: raise operationerrfmt(space.w_ValueError, 'output parameter shape mismatch, could not broadcast [%s]' + - ' to [%s]', + ' to [%s]', ",".join([str(x) for x in w_obj.shape]), ",".join([str(x) for x in out.shape]), ) @@ -292,10 +294,11 @@ self.func = func self.comparison_func = comparison_func + @jit.unroll_safe def call(self, space, args_w): from pypy.module.micronumpy.interp_numarray import (Call2, convert_to_array, Scalar, shape_agreement, BaseArray) - if len(args_w)>2: + if len(args_w) > 2: [w_lhs, w_rhs, w_out] = args_w else: [w_lhs, w_rhs] = args_w @@ -326,7 +329,7 @@ w_rhs.value.convert_to(calc_dtype) ) if isinstance(out,Scalar): - out.value=arr + out.value = arr elif isinstance(out, BaseArray): out.fill(space, arr) else: @@ -337,7 +340,7 @@ if out and out.shape != shape_agreement(space, new_shape, out.shape): raise operationerrfmt(space.w_ValueError, 'output parameter shape mismatch, could not broadcast [%s]' + - ' to [%s]', + ' to [%s]', ",".join([str(x) for x in new_shape]), ",".join([str(x) for x in out.shape]), ) @@ -347,7 +350,6 @@ w_lhs.add_invalidates(w_res) w_rhs.add_invalidates(w_res) if out: - #out.add_invalidates(w_res) #causes a recursion loop w_res.get_concrete() return w_res @@ -539,6 +541,8 @@ ("reciprocal", "reciprocal", 1), ("fabs", "fabs", 1, {"promote_to_float": True}), + ("fmax", "fmax", 2, {"promote_to_float": True}), + ("fmin", "fmin", 2, {"promote_to_float": True}), ("fmod", "fmod", 2, {"promote_to_float": True}), ("floor", "floor", 1, {"promote_to_float": True}), ("ceil", "ceil", 1, {"promote_to_float": True}), @@ -547,6 +551,7 @@ ("expm1", "expm1", 1, {"promote_to_float": True}), ('sqrt', 'sqrt', 1, {'promote_to_float': True}), + ('square', 'square', 1, {'promote_to_float': True}), ("sin", "sin", 1, {"promote_to_float": True}), ("cos", "cos", 1, {"promote_to_float": True}), diff --git a/pypy/module/micronumpy/strides.py b/pypy/module/micronumpy/strides.py --- a/pypy/module/micronumpy/strides.py +++ b/pypy/module/micronumpy/strides.py @@ -1,6 +1,7 @@ from pypy.rlib import jit from pypy.interpreter.error import OperationError [email protected]_inside_iff(lambda chunks: jit.isconstant(len(chunks))) def enumerate_chunks(chunks): result = [] i = -1 @@ -85,9 +86,9 @@ space.isinstance_w(w_item_or_slice, space.w_slice)): raise OperationError(space.w_IndexError, space.wrap('unsupported iterator index')) - + start, stop, step, lngth = space.decode_index4(w_item_or_slice, size) - + coords = [0] * len(shape) i = start if order == 'C': diff --git a/pypy/module/micronumpy/support.py b/pypy/module/micronumpy/support.py --- a/pypy/module/micronumpy/support.py +++ b/pypy/module/micronumpy/support.py @@ -1,3 +1,7 @@ +from pypy.rlib import jit + + [email protected]_inside_iff(lambda s: jit.isconstant(len(s))) def product(s): i = 1 for x in s: diff --git a/pypy/module/micronumpy/test/test_ufuncs.py b/pypy/module/micronumpy/test/test_ufuncs.py --- a/pypy/module/micronumpy/test/test_ufuncs.py +++ b/pypy/module/micronumpy/test/test_ufuncs.py @@ -135,6 +135,38 @@ assert fabs(float('-inf')) == float('inf') assert isnan(fabs(float('nan'))) + def test_fmax(self): + from _numpypy import fmax + import math + + nnan, nan, inf, ninf = float('-nan'), float('nan'), float('inf'), float('-inf') + + a = [ninf, -5, 0, 5, inf] + assert (fmax(a, [ninf]*5) == a).all() + assert (fmax(a, [inf]*5) == [inf]*5).all() + assert (fmax(a, [1]*5) == [1, 1, 1, 5, inf]).all() + assert math.isnan(fmax(nan, 0)) + assert math.isnan(fmax(0, nan)) + assert math.isnan(fmax(nan, nan)) + # The numpy docs specify that the FIRST NaN should be used if both are NaN + assert math.copysign(1.0, fmax(nnan, nan)) == -1.0 + + def test_fmin(self): + from _numpypy import fmin + import math + + nnan, nan, inf, ninf = float('-nan'), float('nan'), float('inf'), float('-inf') + + a = [ninf, -5, 0, 5, inf] + assert (fmin(a, [ninf]*5) == [ninf]*5).all() + assert (fmin(a, [inf]*5) == a).all() + assert (fmin(a, [1]*5) == [ninf, -5, 0, 1, 1]).all() + assert math.isnan(fmin(nan, 0)) + assert math.isnan(fmin(0, nan)) + assert math.isnan(fmin(nan, nan)) + # The numpy docs specify that the FIRST NaN should be used if both are NaN + assert math.copysign(1.0, fmin(nnan, nan)) == -1.0 + def test_fmod(self): from _numpypy import fmod import math @@ -455,6 +487,19 @@ assert math.isnan(sqrt(-1)) assert math.isnan(sqrt(nan)) + def test_square(self): + import math + from _numpypy import square + + nan, inf, ninf = float("nan"), float("inf"), float("-inf") + + assert math.isnan(square(nan)) + assert math.isinf(square(inf)) + assert math.isinf(square(ninf)) + assert square(ninf) > 0 + assert [square(x) for x in range(-5, 5)] == [x*x for x in range(-5, 5)] + assert math.isinf(square(1e300)) + def test_radians(self): import math from _numpypy import radians, array @@ -546,10 +591,11 @@ raises(TypeError, 'array([1.0]) & 1') def test_unary_bitops(self): - from _numpypy import bitwise_not, array + from _numpypy import bitwise_not, invert, array a = array([1, 2, 3, 4]) assert (~a == [-2, -3, -4, -5]).all() assert (bitwise_not(a) == ~a).all() + assert (invert(a) == ~a).all() def test_comparisons(self): import operator diff --git a/pypy/module/micronumpy/types.py b/pypy/module/micronumpy/types.py --- a/pypy/module/micronumpy/types.py +++ b/pypy/module/micronumpy/types.py @@ -631,6 +631,22 @@ return math.fabs(v) @simple_binary_op + def fmax(self, v1, v2): + if math.isnan(v1): + return v1 + elif math.isnan(v2): + return v2 + return max(v1, v2) + + @simple_binary_op + def fmin(self, v1, v2): + if math.isnan(v1): + return v1 + elif math.isnan(v2): + return v2 + return min(v1, v2) + + @simple_binary_op def fmod(self, v1, v2): try: return math.fmod(v1, v2) @@ -741,6 +757,10 @@ except ValueError: return rfloat.NAN + @simple_unary_op + def square(self, v): + return v*v + @raw_unary_op def isnan(self, v): return rfloat.isnan(v) diff --git a/pypy/module/pypyjit/test_pypy_c/test_misc.py b/pypy/module/pypyjit/test_pypy_c/test_misc.py --- a/pypy/module/pypyjit/test_pypy_c/test_misc.py +++ b/pypy/module/pypyjit/test_pypy_c/test_misc.py @@ -351,3 +351,23 @@ # the following assertion fails if the loop was cancelled due # to "abort: vable escape" assert len(log.loops_by_id("eval")) == 1 + + def test_sys_exc_info(self): + def main(): + i = 1 + lst = [i] + while i < 1000: + try: + return lst[i] + except: + e = sys.exc_info()[1] # ID: exc_info + if not isinstance(e, IndexError): + raise + i += 1 + return 42 + + log = self.run(main) + assert log.result == 42 + # the following assertion fails if the loop was cancelled due + # to "abort: vable escape" + assert len(log.loops_by_id("exc_info")) == 1 diff --git a/pypy/module/sys/test/test_sysmodule.py b/pypy/module/sys/test/test_sysmodule.py --- a/pypy/module/sys/test/test_sysmodule.py +++ b/pypy/module/sys/test/test_sysmodule.py @@ -595,3 +595,121 @@ assert len(frames) == 1 _, other_frame = frames.popitem() assert other_frame.f_code.co_name in ('other_thread', '?') + + +class AppTestSysExcInfoDirect: + + def setup_method(self, meth): + self.seen = [] + from pypy.module.sys import vm + def exc_info_with_tb(*args): + self.seen.append("n") # not optimized + return self.old[0](*args) + def exc_info_without_tb(*args): + self.seen.append("y") # optimized + return self.old[1](*args) + self.old = [vm.exc_info_with_tb, vm.exc_info_without_tb] + vm.exc_info_with_tb = exc_info_with_tb + vm.exc_info_without_tb = exc_info_without_tb + # + from pypy.rlib import jit + self.old2 = [jit.we_are_jitted] + jit.we_are_jitted = lambda: True + + def teardown_method(self, meth): + from pypy.module.sys import vm + from pypy.rlib import jit + vm.exc_info_with_tb = self.old[0] + vm.exc_info_without_tb = self.old[1] + jit.we_are_jitted = self.old2[0] + # + assert ''.join(self.seen) == meth.expected + + def test_returns_none(self): + import sys + assert sys.exc_info() == (None, None, None) + assert sys.exc_info()[0] is None + assert sys.exc_info()[1] is None + assert sys.exc_info()[2] is None + assert sys.exc_info()[:2] == (None, None) + assert sys.exc_info()[:3] == (None, None, None) + assert sys.exc_info()[0:2] == (None, None) + assert sys.exc_info()[2:4] == (None,) + test_returns_none.expected = 'nnnnnnnn' + + def test_returns_subscr(self): + import sys + e = KeyError("boom") + try: + raise e + except: + assert sys.exc_info()[0] is KeyError # y + assert sys.exc_info()[1] is e # y + assert sys.exc_info()[2] is not None # n + assert sys.exc_info()[-3] is KeyError # y + assert sys.exc_info()[-2] is e # y + assert sys.exc_info()[-1] is not None # n + test_returns_subscr.expected = 'yynyyn' + + def test_returns_slice_2(self): + import sys + e = KeyError("boom") + try: + raise e + except: + foo = sys.exc_info() # n + assert sys.exc_info()[:0] == () # y + assert sys.exc_info()[:1] == foo[:1] # y + assert sys.exc_info()[:2] == foo[:2] # y + assert sys.exc_info()[:3] == foo # n + assert sys.exc_info()[:4] == foo # n + assert sys.exc_info()[:-1] == foo[:2] # y + assert sys.exc_info()[:-2] == foo[:1] # y + assert sys.exc_info()[:-3] == () # y + test_returns_slice_2.expected = 'nyyynnyyy' + + def test_returns_slice_3(self): + import sys + e = KeyError("boom") + try: + raise e + except: + foo = sys.exc_info() # n + assert sys.exc_info()[2:2] == () # y + assert sys.exc_info()[0:1] == foo[:1] # y + assert sys.exc_info()[1:2] == foo[1:2] # y + assert sys.exc_info()[0:3] == foo # n + assert sys.exc_info()[2:4] == foo[2:] # n + assert sys.exc_info()[0:-1] == foo[:2] # y + assert sys.exc_info()[0:-2] == foo[:1] # y + assert sys.exc_info()[5:-3] == () # y + test_returns_slice_3.expected = 'nyyynnyyy' + + def test_strange_invocation(self): + import sys + e = KeyError("boom") + try: + raise e + except: + a = []; k = {} + assert sys.exc_info(*a)[:0] == () + assert sys.exc_info(**k)[:0] == () + test_strange_invocation.expected = 'nn' + + def test_call_in_subfunction(self): + import sys + def g(): + # this case is not optimized, because we need to search the + # frame chain. it's probably not worth the complications + return sys.exc_info()[1] + e = KeyError("boom") + try: + raise e + except: + assert g() is e + test_call_in_subfunction.expected = 'n' + + +class AppTestSysExcInfoDirectCallMethod(AppTestSysExcInfoDirect): + def setup_class(cls): + cls.space = gettestobjspace(**{"objspace.opcodes.CALL_METHOD": True}) 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 @@ -89,6 +89,9 @@ """Return the (type, value, traceback) of the most recent exception caught by an except clause in the current stack frame or in an older stack frame.""" + return exc_info_with_tb(space) # indirection for the tests + +def exc_info_with_tb(space): operror = space.getexecutioncontext().sys_exc_info() if operror is None: return space.newtuple([space.w_None,space.w_None,space.w_None]) @@ -96,6 +99,59 @@ return space.newtuple([operror.w_type, operror.get_w_value(space), space.wrap(operror.get_traceback())]) +def exc_info_without_tb(space, frame): + operror = frame.last_exception + return space.newtuple([operror.w_type, operror.get_w_value(space), + space.w_None]) + +def exc_info_direct(space, frame): + from pypy.tool import stdlib_opcode + # In order to make the JIT happy, we try to return (exc, val, None) + # instead of (exc, val, tb). We can do that only if we recognize + # the following pattern in the bytecode: + # CALL_FUNCTION/CALL_METHOD <-- invoking me + # LOAD_CONST 0, 1, -2 or -3 + # BINARY_SUBSCR + # or: + # CALL_FUNCTION/CALL_METHOD + # LOAD_CONST <=2 + # SLICE_2 + # or: + # CALL_FUNCTION/CALL_METHOD + # LOAD_CONST any integer + # LOAD_CONST <=2 + # SLICE_3 + need_all_three_args = True + co = frame.getcode().co_code + p = frame.last_instr + if (ord(co[p]) == stdlib_opcode.CALL_FUNCTION or + ord(co[p]) == stdlib_opcode.CALL_METHOD): + if ord(co[p+3]) == stdlib_opcode.LOAD_CONST: + lo = ord(co[p+4]) + hi = ord(co[p+5]) + w_constant = frame.getconstant_w((hi * 256) | lo) + if space.isinstance_w(w_constant, space.w_int): + constant = space.int_w(w_constant) + if ord(co[p+6]) == stdlib_opcode.BINARY_SUBSCR: + if -3 <= constant <= 1 and constant != -1: + need_all_three_args = False + elif ord(co[p+6]) == stdlib_opcode.SLICE+2: + if constant <= 2: + need_all_three_args = False + elif (ord(co[p+6]) == stdlib_opcode.LOAD_CONST and + ord(co[p+9]) == stdlib_opcode.SLICE+3): + lo = ord(co[p+7]) + hi = ord(co[p+8]) + w_constant = frame.getconstant_w((hi * 256) | lo) + if space.isinstance_w(w_constant, space.w_int): + if space.int_w(w_constant) <= 2: + need_all_three_args = False + # + if need_all_three_args or frame.last_exception is None or frame.hide(): + return exc_info_with_tb(space) + else: + return exc_info_without_tb(space, frame) + def exc_clear(space): """Clear global information on the current exception. Subsequent calls to exc_info() will return (None,None,None) until another exception is diff --git a/pypy/rlib/longlong2float.py b/pypy/rlib/longlong2float.py --- a/pypy/rlib/longlong2float.py +++ b/pypy/rlib/longlong2float.py @@ -21,7 +21,7 @@ FLOAT_ARRAY_PTR = lltype.Ptr(lltype.Array(rffi.FLOAT)) # these definitions are used only in tests, when not translated -def longlong2float_emulator(llval): +def longlong2float(llval): with lltype.scoped_alloc(DOUBLE_ARRAY_PTR.TO, 1) as d_array: ll_array = rffi.cast(LONGLONG_ARRAY_PTR, d_array) ll_array[0] = llval @@ -51,12 +51,6 @@ eci = ExternalCompilationInfo(includes=['string.h', 'assert.h'], post_include_bits=[""" -static double pypy__longlong2float(long long x) { - double dd; - assert(sizeof(double) == 8 && sizeof(long long) == 8); - memcpy(&dd, &x, 8); - return dd; -} static float pypy__uint2singlefloat(unsigned int x) { float ff; assert(sizeof(float) == 4 && sizeof(unsigned int) == 4); @@ -71,12 +65,6 @@ } """]) -longlong2float = rffi.llexternal( - "pypy__longlong2float", [rffi.LONGLONG], rffi.DOUBLE, - _callable=longlong2float_emulator, compilation_info=eci, - _nowrapper=True, elidable_function=True, sandboxsafe=True, - oo_primitive="pypy__longlong2float") - uint2singlefloat = rffi.llexternal( "pypy__uint2singlefloat", [rffi.UINT], rffi.FLOAT, _callable=uint2singlefloat_emulator, compilation_info=eci, @@ -99,4 +87,17 @@ def specialize_call(self, hop): [v_float] = hop.inputargs(lltype.Float) - return hop.genop("convert_float_bytes_to_longlong", [v_float], resulttype=hop.r_result) + hop.exception_cannot_occur() + return hop.genop("convert_float_bytes_to_longlong", [v_float], resulttype=lltype.SignedLongLong) + +class LongLong2FloatEntry(ExtRegistryEntry): + _about_ = longlong2float + + def compute_result_annotation(self, s_longlong): + assert annmodel.SomeInteger(knowntype=r_int64).contains(s_longlong) + return annmodel.SomeFloat() + + def specialize_call(self, hop): + [v_longlong] = hop.inputargs(lltype.SignedLongLong) + hop.exception_cannot_occur() + return hop.genop("convert_longlong_bytes_to_float", [v_longlong], resulttype=lltype.Float) diff --git a/pypy/rlib/test/test_longlong2float.py b/pypy/rlib/test/test_longlong2float.py --- a/pypy/rlib/test/test_longlong2float.py +++ b/pypy/rlib/test/test_longlong2float.py @@ -2,6 +2,7 @@ from pypy.rlib.longlong2float import longlong2float, float2longlong from pypy.rlib.longlong2float import uint2singlefloat, singlefloat2uint from pypy.rlib.rarithmetic import r_singlefloat +from pypy.rpython.test.test_llinterp import interpret def fn(f1): @@ -31,6 +32,18 @@ res = fn2(x) assert repr(res) == repr(x) +def test_interpreted(): + def f(f1): + try: + ll = float2longlong(f1) + return longlong2float(ll) + except Exception: + return 500 + + for x in enum_floats(): + res = interpret(f, [x]) + assert repr(res) == repr(x) + # ____________________________________________________________ def fnsingle(f1): diff --git a/pypy/rpython/lltypesystem/lloperation.py b/pypy/rpython/lltypesystem/lloperation.py --- a/pypy/rpython/lltypesystem/lloperation.py +++ b/pypy/rpython/lltypesystem/lloperation.py @@ -350,6 +350,7 @@ 'truncate_longlong_to_int':LLOp(canfold=True), 'force_cast': LLOp(sideeffects=False), # only for rffi.cast() 'convert_float_bytes_to_longlong': LLOp(canfold=True), + 'convert_longlong_bytes_to_float': LLOp(canfold=True), # __________ pointer operations __________ diff --git a/pypy/rpython/lltypesystem/opimpl.py b/pypy/rpython/lltypesystem/opimpl.py --- a/pypy/rpython/lltypesystem/opimpl.py +++ b/pypy/rpython/lltypesystem/opimpl.py @@ -431,6 +431,10 @@ from pypy.rlib.longlong2float import float2longlong return float2longlong(a) +def op_convert_longlong_bytes_to_float(a): + from pypy.rlib.longlong2float import longlong2float + return longlong2float(a) + def op_unichar_eq(x, y): assert isinstance(x, unicode) and len(x) == 1 diff --git a/pypy/rpython/lltypesystem/rstr.py b/pypy/rpython/lltypesystem/rstr.py --- a/pypy/rpython/lltypesystem/rstr.py +++ b/pypy/rpython/lltypesystem/rstr.py @@ -765,7 +765,11 @@ def _ll_stringslice(s1, start, stop): lgt = stop - start assert start >= 0 - assert lgt >= 0 + # If start > stop, return a empty string. This can happen if the start + # is greater than the length of the string. Use < instead of <= to avoid + # creating another path for the JIT when start == stop. + if lgt < 0: + return s1.empty() newstr = s1.malloc(lgt) s1.copy_contents(s1, newstr, start, 0, lgt) return newstr diff --git a/pypy/rpython/ootypesystem/rstr.py b/pypy/rpython/ootypesystem/rstr.py --- a/pypy/rpython/ootypesystem/rstr.py +++ b/pypy/rpython/ootypesystem/rstr.py @@ -222,6 +222,10 @@ length = s.ll_strlen() if stop > length: stop = length + # If start > stop, return a empty string. This can happen if the start + # is greater than the length of the string. + if start > stop: + start = stop return s.ll_substring(start, stop-start) def ll_stringslice_minusone(s): diff --git a/pypy/rpython/test/test_rstr.py b/pypy/rpython/test/test_rstr.py --- a/pypy/rpython/test/test_rstr.py +++ b/pypy/rpython/test/test_rstr.py @@ -477,7 +477,11 @@ s1 = s[:3] s2 = s[3:] s3 = s[3:10] - return s1+s2 == s and s2+s1 == const('lohel') and s1+s3 == s + s4 = s[42:44] + return (s1+s2 == s and + s2+s1 == const('lohel') and + s1+s3 == s and + s4 == const('')) res = self.interpret(fn, [0]) assert res diff --git a/pypy/tool/clean_old_branches.py b/pypy/tool/clean_old_branches.py --- a/pypy/tool/clean_old_branches.py +++ b/pypy/tool/clean_old_branches.py @@ -38,7 +38,7 @@ closed_heads.reverse() for head, branch in closed_heads: - print '\t', branch + print '\t', head, '\t', branch print print 'The branches listed above will be merged to "closed-branches".' print 'You need to run this script in a clean working copy where you' diff --git a/pypy/translator/c/src/float.h b/pypy/translator/c/src/float.h --- a/pypy/translator/c/src/float.h +++ b/pypy/translator/c/src/float.h @@ -43,5 +43,6 @@ #define OP_CAST_FLOAT_TO_LONGLONG(x,r) r = (long long)(x) #define OP_CAST_FLOAT_TO_ULONGLONG(x,r) r = (unsigned long long)(x) #define OP_CONVERT_FLOAT_BYTES_TO_LONGLONG(x,r) memcpy(&r, &x, sizeof(double)) +#define OP_CONVERT_LONGLONG_BYTES_TO_FLOAT(x,r) memcpy(&r, &x, sizeof(long long)) #endif diff --git a/pypy/translator/jvm/opcodes.py b/pypy/translator/jvm/opcodes.py --- a/pypy/translator/jvm/opcodes.py +++ b/pypy/translator/jvm/opcodes.py @@ -243,4 +243,5 @@ 'force_cast': [PushAllArgs, CastPrimitive, StoreResult], 'convert_float_bytes_to_longlong': jvm.PYPYDOUBLEBYTESTOLONG, + 'convert_longlong_bytes_to_float': jvm.PYPYLONGBYTESTODOUBLE, }) diff --git a/pypy/translator/jvm/typesystem.py b/pypy/translator/jvm/typesystem.py --- a/pypy/translator/jvm/typesystem.py +++ b/pypy/translator/jvm/typesystem.py @@ -942,6 +942,7 @@ PYPYULONGTODOUBLE = Method.s(jPyPy, 'ulong_to_double', (jLong,), jDouble) PYPYLONGBITWISENEGATE = Method.v(jPyPy, 'long_bitwise_negate', (jLong,), jLong) PYPYDOUBLEBYTESTOLONG = Method.v(jPyPy, 'pypy__float2longlong', (jDouble,), jLong) +PYPYLONGBYTESTODOUBLE = Method.v(jPyPy, 'pypy__longlong2float', (jLong,), jDouble) PYPYSTRTOINT = Method.v(jPyPy, 'str_to_int', (jString,), jInt) PYPYSTRTOUINT = Method.v(jPyPy, 'str_to_uint', (jString,), jInt) PYPYSTRTOLONG = Method.v(jPyPy, 'str_to_long', (jString,), jLong) _______________________________________________ pypy-commit mailing list [email protected] http://mail.python.org/mailman/listinfo/pypy-commit
