[pypy-commit] pypy default: hg merge int-float-list-strategy

arigo noreply at buildbot.pypy.org
Sat Jul 4 11:19:34 CEST 2015


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r78425:d369501b8f6d
Date: 2015-07-04 11:19 +0200
http://bitbucket.org/pypy/pypy/changeset/d369501b8f6d/

Log:	hg merge int-float-list-strategy

	Add a list strategy for lists that store both floats and 32-bit
	integers. The latter are encoded as nonstandard NaNs. Benchmarks
	show that the speed of such lists is now very close to the speed of
	purely-int or purely-float lists.

diff --git a/pypy/objspace/std/listobject.py b/pypy/objspace/std/listobject.py
--- a/pypy/objspace/std/listobject.py
+++ b/pypy/objspace/std/listobject.py
@@ -14,6 +14,7 @@
 from rpython.rlib.listsort import make_timsort_class
 from rpython.rlib.objectmodel import (
     import_from_mixin, instantiate, newlist_hint, resizelist_hint, specialize)
+from rpython.rlib import longlong2float
 from rpython.tool.sourcetools import func_with_new_name
 
 from pypy.interpreter.baseobjspace import W_Root
@@ -73,33 +74,56 @@
             return SizeListStrategy(space, sizehint)
         return space.fromcache(EmptyListStrategy)
 
-    # check for ints
-    for w_obj in list_w:
-        if not type(w_obj) is W_IntObject:
+    w_firstobj = list_w[0]
+    check_int_or_float = False
+
+    if type(w_firstobj) is W_IntObject:
+        # check for all-ints
+        for i in range(1, len(list_w)):
+            w_obj = list_w[i]
+            if type(w_obj) is not W_IntObject:
+                check_int_or_float = (type(w_obj) is W_FloatObject)
+                break
+        else:
+            return space.fromcache(IntegerListStrategy)
+
+    elif type(w_firstobj) is W_BytesObject:
+        # check for all-strings
+        for i in range(1, len(list_w)):
+            if type(list_w[i]) is not W_BytesObject:
+                break
+        else:
+            return space.fromcache(BytesListStrategy)
+
+    elif type(w_firstobj) is W_UnicodeObject:
+        # check for all-unicodes
+        for i in range(1, len(list_w)):
+            if type(list_w[i]) is not W_UnicodeObject:
+                break
+        else:
+            return space.fromcache(UnicodeListStrategy)
+
+    elif type(w_firstobj) is W_FloatObject:
+        # check for all-floats
+        for i in range(1, len(list_w)):
+            w_obj = list_w[i]
+            if type(w_obj) is not W_FloatObject:
+                check_int_or_float = (type(w_obj) is W_IntObject)
+                break
+        else:
+            return space.fromcache(FloatListStrategy)
+
+    if check_int_or_float:
+        for w_obj in list_w:
+            if type(w_obj) is W_IntObject:
+                if longlong2float.can_encode_int32(space.int_w(w_obj)):
+                    continue    # ok
+            elif type(w_obj) is W_FloatObject:
+                if longlong2float.can_encode_float(space.float_w(w_obj)):
+                    continue    # ok
             break
-    else:
-        return space.fromcache(IntegerListStrategy)
-
-    # check for strings
-    for w_obj in list_w:
-        if not type(w_obj) is W_BytesObject:
-            break
-    else:
-        return space.fromcache(BytesListStrategy)
-
-    # check for unicode
-    for w_obj in list_w:
-        if not type(w_obj) is W_UnicodeObject:
-            break
-    else:
-        return space.fromcache(UnicodeListStrategy)
-
-    # check for floats
-    for w_obj in list_w:
-        if not type(w_obj) is W_FloatObject:
-            break
-    else:
-        return space.fromcache(FloatListStrategy)
+        else:
+            return space.fromcache(IntOrFloatListStrategy)
 
     return space.fromcache(ObjectListStrategy)
 
@@ -1382,12 +1406,15 @@
             return W_ListObject.from_storage_and_strategy(
                     self.space, storage, self)
 
+    def switch_to_next_strategy(self, w_list, w_sample_item):
+        w_list.switch_to_object_strategy()
+
     def append(self, w_list, w_item):
         if self.is_correct_type(w_item):
             self.unerase(w_list.lstorage).append(self.unwrap(w_item))
             return
 
-        w_list.switch_to_object_strategy()
+        self.switch_to_next_strategy(w_list, w_item)
         w_list.append(w_item)
 
     def insert(self, w_list, index, w_item):
@@ -1397,7 +1424,7 @@
             l.insert(index, self.unwrap(w_item))
             return
 
-        w_list.switch_to_object_strategy()
+        self.switch_to_next_strategy(w_list, w_item)
         w_list.insert(index, w_item)
 
     def _extend_from_list(self, w_list, w_other):
@@ -1421,7 +1448,7 @@
             except IndexError:
                 raise
         else:
-            w_list.switch_to_object_strategy()
+            self.switch_to_next_strategy(w_list, w_item)
             w_list.setitem(index, w_item)
 
     def setslice(self, w_list, start, step, slicelength, w_other):
@@ -1586,6 +1613,9 @@
     def getitems(self, w_list):
         return self.unerase(w_list.lstorage)
 
+    # no sort() method here: W_ListObject.descr_sort() handles this
+    # case explicitly
+
 
 class IntegerListStrategy(ListStrategy):
     import_from_mixin(AbstractUnwrappedStrategy)
@@ -1628,6 +1658,11 @@
             assert other is not None
             l += other
             return
+        if (w_other.strategy is self.space.fromcache(FloatListStrategy) or
+            w_other.strategy is self.space.fromcache(IntOrFloatListStrategy)):
+            if self.switch_to_int_or_float_strategy(w_list):
+                w_list.extend(w_other)
+                return
         return self._base_extend_from_list(w_list, w_other)
 
 
@@ -1638,8 +1673,46 @@
             storage = self.erase(w_other.getitems_int())
             w_other = W_ListObject.from_storage_and_strategy(
                     self.space, storage, self)
+        if (w_other.strategy is self.space.fromcache(FloatListStrategy) or
+            w_other.strategy is self.space.fromcache(IntOrFloatListStrategy)):
+            if self.switch_to_int_or_float_strategy(w_list):
+                w_list.setslice(start, step, slicelength, w_other)
+                return
         return self._base_setslice(w_list, start, step, slicelength, w_other)
 
+
+    @staticmethod
+    def int_2_float_or_int(w_list):
+        l = IntegerListStrategy.unerase(w_list.lstorage)
+        if not longlong2float.CAN_ALWAYS_ENCODE_INT32:
+            for intval in l:
+                if not longlong2float.can_encode_int32(intval):
+                    raise ValueError
+        return [longlong2float.encode_int32_into_longlong_nan(intval)
+                for intval in l]
+
+    def switch_to_int_or_float_strategy(self, w_list):
+        try:
+            generalized_list = self.int_2_float_or_int(w_list)
+        except ValueError:
+            return False
+        strategy = self.space.fromcache(IntOrFloatListStrategy)
+        w_list.strategy = strategy
+        w_list.lstorage = strategy.erase(generalized_list)
+        return True
+
+    def switch_to_next_strategy(self, w_list, w_sample_item):
+        if type(w_sample_item) is W_FloatObject:
+            if self.switch_to_int_or_float_strategy(w_list):
+                # yes, we can switch to IntOrFloatListStrategy
+                # (ignore here the extremely unlikely case where
+                # w_sample_item is just the wrong nonstandard NaN float;
+                # it will caught later and yet another switch will occur)
+                return
+        # no, fall back to ObjectListStrategy
+        w_list.switch_to_object_strategy()
+
+
 class FloatListStrategy(ListStrategy):
     import_from_mixin(AbstractUnwrappedStrategy)
 
@@ -1671,9 +1744,34 @@
     def getitems_float(self, w_list):
         return self.unerase(w_list.lstorage)
 
+
+    _base_extend_from_list = _extend_from_list
+
+    def _extend_from_list(self, w_list, w_other):
+        if (w_other.strategy is self.space.fromcache(IntegerListStrategy) or
+            w_other.strategy is self.space.fromcache(IntOrFloatListStrategy)):
+            # xxx a case that we don't optimize: [3.4].extend([9999999999999])
+            # will cause a switch to int-or-float, followed by another
+            # switch to object
+            if self.switch_to_int_or_float_strategy(w_list):
+                w_list.extend(w_other)
+                return
+        return self._base_extend_from_list(w_list, w_other)
+
+
+    _base_setslice = setslice
+
+    def setslice(self, w_list, start, step, slicelength, w_other):
+        if (w_other.strategy is self.space.fromcache(IntegerListStrategy) or
+            w_other.strategy is self.space.fromcache(IntOrFloatListStrategy)):
+            if self.switch_to_int_or_float_strategy(w_list):
+                w_list.setslice(start, step, slicelength, w_other)
+                return
+        return self._base_setslice(w_list, start, step, slicelength, w_other)
+
+
     def _safe_find(self, w_list, obj, start, stop):
         from rpython.rlib.rfloat import isnan
-        from rpython.rlib.longlong2float import float2longlong
         #
         l = self.unerase(w_list.lstorage)
         stop = min(stop, len(l))
@@ -1683,13 +1781,156 @@
                 if val == obj:
                     return i
         else:
-            search = float2longlong(obj)
+            search = longlong2float.float2longlong(obj)
             for i in range(start, stop):
                 val = l[i]
-                if float2longlong(val) == search:
+                if longlong2float.float2longlong(val) == search:
                     return i
         raise ValueError
 
+    @staticmethod
+    def float_2_float_or_int(w_list):
+        l = FloatListStrategy.unerase(w_list.lstorage)
+        generalized_list = []
+        for floatval in l:
+            if not longlong2float.can_encode_float(floatval):
+                raise ValueError
+            generalized_list.append(
+                longlong2float.float2longlong(floatval))
+        return generalized_list
+
+    def switch_to_int_or_float_strategy(self, w_list):
+        # xxx we should be able to use the same lstorage, but
+        # there is a typing issue (float vs longlong)...
+        try:
+            generalized_list = self.float_2_float_or_int(w_list)
+        except ValueError:
+            return False
+        strategy = self.space.fromcache(IntOrFloatListStrategy)
+        w_list.strategy = strategy
+        w_list.lstorage = strategy.erase(generalized_list)
+        return True
+
+    def switch_to_next_strategy(self, w_list, w_sample_item):
+        if type(w_sample_item) is W_IntObject:
+            sample_intval = self.space.int_w(w_sample_item)
+            if longlong2float.can_encode_int32(sample_intval):
+                if self.switch_to_int_or_float_strategy(w_list):
+                    # yes, we can switch to IntOrFloatListStrategy
+                    return
+        # no, fall back to ObjectListStrategy
+        w_list.switch_to_object_strategy()
+
+
+class IntOrFloatListStrategy(ListStrategy):
+    import_from_mixin(AbstractUnwrappedStrategy)
+
+    _none_value = longlong2float.float2longlong(0.0)
+
+    def wrap(self, llval):
+        if longlong2float.is_int32_from_longlong_nan(llval):
+            intval = longlong2float.decode_int32_from_longlong_nan(llval)
+            return self.space.wrap(intval)
+        else:
+            floatval = longlong2float.longlong2float(llval)
+            return self.space.wrap(floatval)
+
+    def unwrap(self, w_int_or_float):
+        if type(w_int_or_float) is W_IntObject:
+            intval = self.space.int_w(w_int_or_float)
+            return longlong2float.encode_int32_into_longlong_nan(intval)
+        else:
+            floatval = self.space.float_w(w_int_or_float)
+            return longlong2float.float2longlong(floatval)
+
+    erase, unerase = rerased.new_erasing_pair("longlong")
+    erase = staticmethod(erase)
+    unerase = staticmethod(unerase)
+
+    def is_correct_type(self, w_obj):
+        if type(w_obj) is W_IntObject:
+            intval = self.space.int_w(w_obj)
+            return longlong2float.can_encode_int32(intval)
+        elif type(w_obj) is W_FloatObject:
+            floatval = self.space.float_w(w_obj)
+            return longlong2float.can_encode_float(floatval)
+        else:
+            return False
+
+    def list_is_correct_type(self, w_list):
+        return w_list.strategy is self.space.fromcache(IntOrFloatListStrategy)
+
+    def sort(self, w_list, reverse):
+        l = self.unerase(w_list.lstorage)
+        sorter = IntOrFloatSort(l, len(l))
+        # Reverse sort stability achieved by initially reversing the list,
+        # applying a stable forward sort, then reversing the final result.
+        if reverse:
+            l.reverse()
+        sorter.sort()
+        if reverse:
+            l.reverse()
+
+    _base_extend_from_list = _extend_from_list
+
+    def _extend_longlong(self, w_list, longlong_list):
+        l = self.unerase(w_list.lstorage)
+        l += longlong_list
+
+    def _extend_from_list(self, w_list, w_other):
+        if w_other.strategy is self.space.fromcache(IntegerListStrategy):
+            try:
+                longlong_list = IntegerListStrategy.int_2_float_or_int(w_other)
+            except ValueError:
+                pass
+            else:
+                return self._extend_longlong(w_list, longlong_list)
+        if w_other.strategy is self.space.fromcache(FloatListStrategy):
+            try:
+                longlong_list = FloatListStrategy.float_2_float_or_int(w_other)
+            except ValueError:
+                pass
+            else:
+                return self._extend_longlong(w_list, longlong_list)
+        return self._base_extend_from_list(w_list, w_other)
+
+    _base_setslice = setslice
+
+    def _temporary_longlong_list(self, longlong_list):
+        storage = self.erase(longlong_list)
+        return W_ListObject.from_storage_and_strategy(self.space, storage, self)
+
+    def setslice(self, w_list, start, step, slicelength, w_other):
+        if w_other.strategy is self.space.fromcache(IntegerListStrategy):
+            try:
+                longlong_list = IntegerListStrategy.int_2_float_or_int(w_other)
+            except ValueError:
+                pass
+            else:
+                w_other = self._temporary_longlong_list(longlong_list)
+        elif w_other.strategy is self.space.fromcache(FloatListStrategy):
+            try:
+                longlong_list = FloatListStrategy.float_2_float_or_int(w_other)
+            except ValueError:
+                pass
+            else:
+                w_other = self._temporary_longlong_list(longlong_list)
+        return self._base_setslice(w_list, start, step, slicelength, w_other)
+
+    def _safe_find(self, w_list, obj, start, stop):
+        l = self.unerase(w_list.lstorage)
+        # careful: we must consider that 0.0 == -0.0 == 0, but also
+        # NaN == NaN if they have the same bit pattern.
+        fobj = longlong2float.maybe_decode_longlong_as_float(obj)
+        for i in range(start, min(stop, len(l))):
+            llval = l[i]
+            if llval == obj:     # equal as longlongs: includes NaN == NaN
+                return i
+            fval = longlong2float.maybe_decode_longlong_as_float(llval)
+            if fval == fobj:     # cases like 0.0 == -0.0 or 42 == 42.0
+                return i
+        raise ValueError
+
 
 class BytesListStrategy(ListStrategy):
     import_from_mixin(AbstractUnwrappedStrategy)
@@ -1786,6 +2027,7 @@
 TimSort = make_timsort_class()
 IntBaseTimSort = make_timsort_class()
 FloatBaseTimSort = make_timsort_class()
+IntOrFloatBaseTimSort = make_timsort_class()
 StringBaseTimSort = make_timsort_class()
 UnicodeBaseTimSort = make_timsort_class()
 
@@ -1816,6 +2058,13 @@
         return a < b
 
 
+class IntOrFloatSort(IntOrFloatBaseTimSort):
+    def lt(self, a, b):
+        fa = longlong2float.maybe_decode_longlong_as_float(a)
+        fb = longlong2float.maybe_decode_longlong_as_float(b)
+        return fa < fb
+
+
 class StringSort(StringBaseTimSort):
     def lt(self, a, b):
         return a < b
diff --git a/pypy/objspace/std/test/test_listobject.py b/pypy/objspace/std/test/test_listobject.py
--- a/pypy/objspace/std/test/test_listobject.py
+++ b/pypy/objspace/std/test/test_listobject.py
@@ -1575,7 +1575,19 @@
         L2 = [N, 0.0]         # float strategy
         assert N in L2
         assert L2.index(N) == 0
+        assert L2.index(-0.0) == 1
         assert L2 == [N, -0.0]
+        # same with the int-or-float list strategy
+        L3 = [N, 0.0, -0.0, 0]
+        assert N in L3
+        assert L3.index(N) == 0
+        for i in [1, 2, 3]:
+            assert L3[i] == 0
+            assert L3[i] == 0.0
+            assert L3[i] == -0.0
+            assert L3.index(0, i) == i
+            assert L3.index(0.0, i) == i
+            assert L3.index(-0.0, i) == i
 
 
 class AppTestListObjectWithRangeList(AppTestListObject):
diff --git a/pypy/objspace/std/test/test_liststrategies.py b/pypy/objspace/std/test/test_liststrategies.py
--- a/pypy/objspace/std/test/test_liststrategies.py
+++ b/pypy/objspace/std/test/test_liststrategies.py
@@ -1,8 +1,10 @@
 import sys
+import py
 from pypy.objspace.std.listobject import (
     W_ListObject, EmptyListStrategy, ObjectListStrategy, IntegerListStrategy,
     FloatListStrategy, BytesListStrategy, RangeListStrategy,
-    SimpleRangeListStrategy, make_range_list, UnicodeListStrategy)
+    SimpleRangeListStrategy, make_range_list, UnicodeListStrategy,
+    IntOrFloatListStrategy)
 from pypy.objspace.std import listobject
 from pypy.objspace.std.test.test_listobject import TestW_ListObject
 
@@ -166,7 +168,6 @@
         assert isinstance(l.strategy, IntegerListStrategy)
 
     def test_list_empty_after_delete(self):
-        import py
         py.test.skip("return to emptyliststrategy is not supported anymore")
         l = W_ListObject(self.space, [self.space.wrap(3)])
         assert isinstance(l.strategy, IntegerListStrategy)
@@ -317,7 +318,7 @@
 
         l = W_ListObject(space, [w(1.1), w(2.2), w(3.3)])
         assert isinstance(l.strategy, FloatListStrategy)
-        l.extend(W_ListObject(space, [w(4), w(5), w(6)]))
+        l.extend(W_ListObject(space, [w("abc"), w("def"), w("ghi")]))
         assert isinstance(l.strategy, ObjectListStrategy)
 
     def test_empty_extend_with_any(self):
@@ -733,6 +734,306 @@
         list_copy[0] = 42
         assert list_orig == [1, 2, 3]
 
+    def test_int_or_float_special_nan(self):
+        from rpython.rlib import longlong2float, rarithmetic
+        space = self.space
+        ll = rarithmetic.r_longlong(0xfffffffe12345678 - 2**64)
+        specialnan = longlong2float.longlong2float(ll)
+        w_l = W_ListObject(space, [space.wrap(1), space.wrap(specialnan)])
+        assert isinstance(w_l.strategy, ObjectListStrategy)
+
+    def test_int_or_float_int_overflow(self):
+        if sys.maxint == 2147483647:
+            py.test.skip("only on 64-bit")
+        space = self.space
+        ok1 = 2**31 - 1
+        ok2 = -2**31
+        ovf1 = ok1 + 1
+        ovf2 = ok2 - 1
+        w_l = W_ListObject(space, [space.wrap(1.2), space.wrap(ovf1)])
+        assert isinstance(w_l.strategy, ObjectListStrategy)
+        w_l = W_ListObject(space, [space.wrap(1.2), space.wrap(ovf2)])
+        assert isinstance(w_l.strategy, ObjectListStrategy)
+        w_l = W_ListObject(space, [space.wrap(1.2), space.wrap(ok1)])
+        assert isinstance(w_l.strategy, IntOrFloatListStrategy)
+        w_l = W_ListObject(space, [space.wrap(1.2), space.wrap(ok2)])
+        assert isinstance(w_l.strategy, IntOrFloatListStrategy)
+
+    def test_int_or_float_base(self):
+        from rpython.rlib.rfloat import INFINITY, NAN
+        space = self.space
+        w = space.wrap
+        w_l = W_ListObject(space, [space.wrap(1), space.wrap(2.3)])
+        assert isinstance(w_l.strategy, IntOrFloatListStrategy)
+        w_l.append(w(int(2**31-1)))
+        assert isinstance(w_l.strategy, IntOrFloatListStrategy)
+        w_l.append(w(-5.1))
+        assert isinstance(w_l.strategy, IntOrFloatListStrategy)
+        assert space.int_w(w_l.getitem(2)) == 2**31-1
+        assert space.float_w(w_l.getitem(3)) == -5.1
+        w_l.append(w(INFINITY))
+        assert isinstance(w_l.strategy, IntOrFloatListStrategy)
+        w_l.append(w(NAN))
+        assert isinstance(w_l.strategy, IntOrFloatListStrategy)
+        w_l.append(w(-NAN))
+        assert isinstance(w_l.strategy, IntOrFloatListStrategy)
+        w_l.append(space.newlist([]))
+        assert isinstance(w_l.strategy, ObjectListStrategy)
+
+    def test_int_or_float_from_integer(self):
+        space = self.space
+        w = space.wrap
+        w_l = W_ListObject(space, [space.wrap(int(-2**31))])
+        assert isinstance(w_l.strategy, IntegerListStrategy)
+        w_l.append(w(-5.1))
+        assert isinstance(w_l.strategy, IntOrFloatListStrategy)
+        assert space.int_w(w_l.getitem(0)) == -2**31
+        assert space.float_w(w_l.getitem(1)) == -5.1
+        assert space.len_w(w_l) == 2
+
+    def test_int_or_float_from_integer_overflow(self):
+        if sys.maxint == 2147483647:
+            py.test.skip("only on 64-bit")
+        space = self.space
+        w = space.wrap
+        ovf1 = -2**31 - 1
+        w_l = W_ListObject(space, [space.wrap(ovf1)])
+        assert isinstance(w_l.strategy, IntegerListStrategy)
+        w_l.append(w(-5.1))
+        assert isinstance(w_l.strategy, ObjectListStrategy)
+        assert space.int_w(w_l.getitem(0)) == ovf1
+        assert space.float_w(w_l.getitem(1)) == -5.1
+        assert space.len_w(w_l) == 2
+
+    def test_int_or_float_from_integer_special_nan(self):
+        from rpython.rlib import longlong2float, rarithmetic
+        space = self.space
+        w = space.wrap
+        w_l = W_ListObject(space, [space.wrap(int(-2**31))])
+        assert isinstance(w_l.strategy, IntegerListStrategy)
+        ll = rarithmetic.r_longlong(0xfffffffe12345678 - 2**64)
+        specialnan = longlong2float.longlong2float(ll)
+        w_l.append(w(specialnan))
+        assert isinstance(w_l.strategy, ObjectListStrategy)
+        assert space.int_w(w_l.getitem(0)) == -2**31
+        assert space.len_w(w_l) == 2
+
+    def test_int_or_float_from_float(self):
+        space = self.space
+        w = space.wrap
+        w_l = W_ListObject(space, [space.wrap(-42.5)])
+        assert isinstance(w_l.strategy, FloatListStrategy)
+        w_l.append(w(-15))
+        assert isinstance(w_l.strategy, IntOrFloatListStrategy)
+        assert space.float_w(w_l.getitem(0)) == -42.5
+        assert space.int_w(w_l.getitem(1)) == -15
+        assert space.len_w(w_l) == 2
+
+    def test_int_or_float_from_float_int_overflow(self):
+        if sys.maxint == 2147483647:
+            py.test.skip("only on 64-bit")
+        space = self.space
+        w = space.wrap
+        ovf1 = 2 ** 31
+        w_l = W_ListObject(space, [space.wrap(1.2)])
+        assert isinstance(w_l.strategy, FloatListStrategy)
+        w_l.append(w(ovf1))
+        assert isinstance(w_l.strategy, ObjectListStrategy)
+        assert space.float_w(w_l.getitem(0)) == 1.2
+        assert space.int_w(w_l.getitem(1)) == ovf1
+        assert space.len_w(w_l) == 2
+
+    def test_int_or_float_from_float_special_nan(self):
+        from rpython.rlib import longlong2float, rarithmetic
+        space = self.space
+        w = space.wrap
+        ll = rarithmetic.r_longlong(0xfffffffe12345678 - 2**64)
+        specialnan = longlong2float.longlong2float(ll)
+        w_l = W_ListObject(space, [space.wrap(specialnan)])
+        assert isinstance(w_l.strategy, FloatListStrategy)
+        w_l.append(w(42))
+        assert isinstance(w_l.strategy, ObjectListStrategy)
+        assert space.int_w(w_l.getitem(1)) == 42
+        assert space.len_w(w_l) == 2
+
+    def test_int_or_float_extend(self):
+        space = self.space
+        w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(1.2)])
+        w_l2 = W_ListObject(space, [space.wrap(3), space.wrap(4.5)])
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert isinstance(w_l2.strategy, IntOrFloatListStrategy)
+        w_l1.extend(w_l2)
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert space.unwrap(w_l1) == [0, 1.2, 3, 4.5]
+
+    def test_int_or_float_extend_mixed_1(self):
+        space = self.space
+        w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(1.2)])
+        w_l2 = W_ListObject(space, [space.wrap(3)])
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert isinstance(w_l2.strategy, IntegerListStrategy)
+        w_l1.extend(w_l2)
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert [(type(x), x) for x in space.unwrap(w_l1)] == [
+            (int, 0), (float, 1.2), (int, 3)]
+
+    def test_int_or_float_extend_mixed_2(self):
+        space = self.space
+        w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(1.2)])
+        w_l2 = W_ListObject(space, [space.wrap(3.4)])
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert isinstance(w_l2.strategy, FloatListStrategy)
+        w_l1.extend(w_l2)
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert space.unwrap(w_l1) == [0, 1.2, 3.4]
+
+    def test_int_or_float_extend_mixed_3(self):
+        space = self.space
+        w_l1 = W_ListObject(space, [space.wrap(0)])
+        w_l2 = W_ListObject(space, [space.wrap(3.4)])
+        assert isinstance(w_l1.strategy, IntegerListStrategy)
+        assert isinstance(w_l2.strategy, FloatListStrategy)
+        w_l1.extend(w_l2)
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert space.unwrap(w_l1) == [0, 3.4]
+
+    def test_int_or_float_extend_mixed_4(self):
+        space = self.space
+        w_l1 = W_ListObject(space, [space.wrap(0)])
+        w_l2 = W_ListObject(space, [space.wrap(3.4), space.wrap(-2)])
+        assert isinstance(w_l1.strategy, IntegerListStrategy)
+        assert isinstance(w_l2.strategy, IntOrFloatListStrategy)
+        w_l1.extend(w_l2)
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert space.unwrap(w_l1) == [0, 3.4, -2]
+
+    def test_int_or_float_extend_mixed_5(self):
+        space = self.space
+        w_l1 = W_ListObject(space, [space.wrap(-2.5)])
+        w_l2 = W_ListObject(space, [space.wrap(42)])
+        assert isinstance(w_l1.strategy, FloatListStrategy)
+        assert isinstance(w_l2.strategy, IntegerListStrategy)
+        w_l1.extend(w_l2)
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert space.unwrap(w_l1) == [-2.5, 42]
+
+    def test_int_or_float_extend_mixed_5_overflow(self):
+        if sys.maxint == 2147483647:
+            py.test.skip("only on 64-bit")
+        space = self.space
+        ovf1 = 2 ** 35
+        w_l1 = W_ListObject(space, [space.wrap(-2.5)])
+        w_l2 = W_ListObject(space, [space.wrap(ovf1)])
+        assert isinstance(w_l1.strategy, FloatListStrategy)
+        assert isinstance(w_l2.strategy, IntegerListStrategy)
+        w_l1.extend(w_l2)
+        assert isinstance(w_l1.strategy, ObjectListStrategy)
+        assert space.unwrap(w_l1) == [-2.5, ovf1]
+
+    def test_int_or_float_extend_mixed_6(self):
+        space = self.space
+        w_l1 = W_ListObject(space, [space.wrap(-2.5)])
+        w_l2 = W_ListObject(space, [space.wrap(3.4), space.wrap(-2)])
+        assert isinstance(w_l1.strategy, FloatListStrategy)
+        assert isinstance(w_l2.strategy, IntOrFloatListStrategy)
+        w_l1.extend(w_l2)
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert space.unwrap(w_l1) == [-2.5, 3.4, -2]
+
+    def test_int_or_float_setslice(self):
+        space = self.space
+        w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(1.2)])
+        w_l2 = W_ListObject(space, [space.wrap(3), space.wrap(4.5)])
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert isinstance(w_l2.strategy, IntOrFloatListStrategy)
+        w_l1.setslice(0, 1, 1, w_l2)
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert space.unwrap(w_l1) == [3, 4.5, 1.2]
+
+    def test_int_or_float_setslice_mixed_1(self):
+        space = self.space
+        w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(12)])
+        w_l2 = W_ListObject(space, [space.wrap(3.2), space.wrap(4.5)])
+        assert isinstance(w_l1.strategy, IntegerListStrategy)
+        assert isinstance(w_l2.strategy, FloatListStrategy)
+        w_l1.setslice(0, 1, 1, w_l2)
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert space.unwrap(w_l1) == [3.2, 4.5, 12]
+
+    def test_int_or_float_setslice_mixed_2(self):
+        space = self.space
+        w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(12)])
+        w_l2 = W_ListObject(space, [space.wrap(3.2), space.wrap(45)])
+        assert isinstance(w_l1.strategy, IntegerListStrategy)
+        assert isinstance(w_l2.strategy, IntOrFloatListStrategy)
+        w_l1.setslice(0, 1, 1, w_l2)
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert space.unwrap(w_l1) == [3.2, 45, 12]
+
+    def test_int_or_float_setslice_mixed_3(self):
+        space = self.space
+        w_l1 = W_ListObject(space, [space.wrap(0.1), space.wrap(1.2)])
+        w_l2 = W_ListObject(space, [space.wrap(32), space.wrap(45)])
+        assert isinstance(w_l1.strategy, FloatListStrategy)
+        assert isinstance(w_l2.strategy, IntegerListStrategy)
+        w_l1.setslice(0, 1, 1, w_l2)
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert space.unwrap(w_l1) == [32, 45, 1.2]
+
+    def test_int_or_float_setslice_mixed_4(self):
+        space = self.space
+        w_l1 = W_ListObject(space, [space.wrap(0.1), space.wrap(1.2)])
+        w_l2 = W_ListObject(space, [space.wrap(3.2), space.wrap(45)])
+        assert isinstance(w_l1.strategy, FloatListStrategy)
+        assert isinstance(w_l2.strategy, IntOrFloatListStrategy)
+        w_l1.setslice(0, 1, 1, w_l2)
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert space.unwrap(w_l1) == [3.2, 45, 1.2]
+
+    def test_int_or_float_setslice_mixed_5(self):
+        space = self.space
+        w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(1.2)])
+        w_l2 = W_ListObject(space, [space.wrap(32), space.wrap(45)])
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert isinstance(w_l2.strategy, IntegerListStrategy)
+        w_l1.setslice(0, 1, 1, w_l2)
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert space.unwrap(w_l1) == [32, 45, 1.2]
+
+    def test_int_or_float_setslice_mixed_5_overflow(self):
+        if sys.maxint == 2147483647:
+            py.test.skip("only on 64-bit")
+        space = self.space
+        ovf1 = 2 ** 35
+        w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(1.2)])
+        w_l2 = W_ListObject(space, [space.wrap(32), space.wrap(ovf1)])
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert isinstance(w_l2.strategy, IntegerListStrategy)
+        w_l1.setslice(0, 1, 1, w_l2)
+        assert isinstance(w_l1.strategy, ObjectListStrategy)
+        assert space.unwrap(w_l1) == [32, ovf1, 1.2]
+
+    def test_int_or_float_setslice_mixed_6(self):
+        space = self.space
+        w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(1.2)])
+        w_l2 = W_ListObject(space, [space.wrap(3.2), space.wrap(4.5)])
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert isinstance(w_l2.strategy, FloatListStrategy)
+        w_l1.setslice(0, 1, 1, w_l2)
+        assert isinstance(w_l1.strategy, IntOrFloatListStrategy)
+        assert space.unwrap(w_l1) == [3.2, 4.5, 1.2]
+
+    def test_int_or_float_sort(self):
+        space = self.space
+        w_l = W_ListObject(space, [space.wrap(1.2), space.wrap(1),
+                                   space.wrap(1.0), space.wrap(5)])
+        w_l.sort(False)
+        assert [(type(x), x) for x in space.unwrap(w_l)] == [
+            (int, 1), (float, 1.0), (float, 1.2), (int, 5)]
+        w_l.sort(True)
+        assert [(type(x), x) for x in space.unwrap(w_l)] == [
+            (int, 5), (float, 1.2), (int, 1), (float, 1.0)]
+
 
 class TestW_ListStrategiesDisabled:
     spaceconfig = {"objspace.std.withliststrategies": False}
diff --git a/rpython/rlib/longlong2float.py b/rpython/rlib/longlong2float.py
--- a/rpython/rlib/longlong2float.py
+++ b/rpython/rlib/longlong2float.py
@@ -7,8 +7,9 @@
 """
 
 from __future__ import with_statement
+import sys
 from rpython.annotator import model as annmodel
-from rpython.rlib.rarithmetic import r_int64
+from rpython.rlib.rarithmetic import r_int64, intmask
 from rpython.rtyper.lltypesystem import lltype, rffi
 from rpython.rtyper.extregistry import ExtRegistryEntry
 from rpython.translator.tool.cbuild import ExternalCompilationInfo
@@ -99,3 +100,46 @@
         [v_longlong] = hop.inputargs(lltype.SignedLongLong)
         hop.exception_cannot_occur()
         return hop.genop("convert_longlong_bytes_to_float", [v_longlong], resulttype=lltype.Float)
+
+# ____________________________________________________________
+
+
+# For encoding integers inside nonstandard NaN bit patterns.
+#   ff ff ff fe xx xx xx xx    (signed 32-bit int)
+nan_high_word_int32 = -2       # -2 == (int)0xfffffffe
+nan_encoded_zero = r_int64(nan_high_word_int32 << 32)
+
+def encode_int32_into_longlong_nan(value):
+    return (nan_encoded_zero +
+            rffi.cast(rffi.LONGLONG, rffi.cast(rffi.UINT, value)))
+
+def decode_int32_from_longlong_nan(value):
+    return rffi.cast(lltype.Signed, rffi.cast(rffi.INT, value))
+
+def is_int32_from_longlong_nan(value):
+    return intmask(value >> 32) == nan_high_word_int32
+
+CAN_ALWAYS_ENCODE_INT32 = (sys.maxint == 2147483647)
+
+def can_encode_int32(value):
+    if CAN_ALWAYS_ENCODE_INT32:
+        return True
+    return value == rffi.cast(lltype.Signed, rffi.cast(rffi.INT, value))
+
+def can_encode_float(value):
+    return intmask(float2longlong(value) >> 32) != nan_high_word_int32
+
+def maybe_decode_longlong_as_float(value):
+    # Decode a longlong value.  If a float, just return it as a float.
+    # If an encoded integer, return a float that has the (exact) same
+    # value.  This relies on the fact that casting from a decoded int
+    # to a float is an exact operation.  If we had full 64-bit
+    # integers, this cast would loose precision.  But this works
+    # because the integers are only 32-bit.  This would also work even
+    # if we encoded larger integers: as long as they are encoded
+    # inside a subset of the mantissa of a float, then the
+    # cast-to-float will be exact.
+    if is_int32_from_longlong_nan(value):
+        return float(decode_int32_from_longlong_nan(value))
+    else:
+        return longlong2float(value)
diff --git a/rpython/rlib/test/test_longlong2float.py b/rpython/rlib/test/test_longlong2float.py
--- a/rpython/rlib/test/test_longlong2float.py
+++ b/rpython/rlib/test/test_longlong2float.py
@@ -1,7 +1,7 @@
 from rpython.translator.c.test.test_genc import compile
 from rpython.rlib.longlong2float import longlong2float, float2longlong
 from rpython.rlib.longlong2float import uint2singlefloat, singlefloat2uint
-from rpython.rlib.rarithmetic import r_singlefloat
+from rpython.rlib.rarithmetic import r_singlefloat, r_longlong
 from rpython.rtyper.test.test_llinterp import interpret
 
 
@@ -21,6 +21,9 @@
     yield -inf
     yield inf / inf     # nan
 
+def test_float2longlong():
+    assert float2longlong(0.0) == r_longlong(0)
+
 def test_longlong_as_float():
     for x in enum_floats():
         res = fn(x)
@@ -63,3 +66,32 @@
     for x in enum_floats():
         res = fn2(x)
         assert repr(res) == repr(float(r_singlefloat(x)))
+
+# ____________________________________________________________
+
+def fn_encode_nan(f1, i2):
+    from rpython.rlib.longlong2float import can_encode_float, can_encode_int32
+    from rpython.rlib.longlong2float import encode_int32_into_longlong_nan
+    from rpython.rlib.longlong2float import decode_int32_from_longlong_nan
+    from rpython.rlib.longlong2float import is_int32_from_longlong_nan
+    from rpython.rlib.rfloat import isnan
+    assert can_encode_float(f1)
+    assert can_encode_int32(i2)
+    l1 = float2longlong(f1)
+    l2 = encode_int32_into_longlong_nan(i2)
+    assert not is_int32_from_longlong_nan(l1)
+    assert is_int32_from_longlong_nan(l2)
+    f1b = longlong2float(l1)
+    assert f1b == f1 or (isnan(f1b) and isnan(f1))
+    assert decode_int32_from_longlong_nan(l2) == i2
+    return 42
+
+def test_compiled_encode_nan():
+    fn2 = compile(fn_encode_nan, [float, int])
+    ints = [-2**31, 2**31-1, 42]
+    for x in enum_floats():
+        y = ints.pop()
+        ints.insert(0, y)
+        fn_encode_nan(x, y)
+        res = fn2(x, y)
+        assert res == 42


More information about the pypy-commit mailing list