Commit 5195f89f authored by Carl Friedrich Bolz-Tereick's avatar Carl Friedrich Bolz-Tereick
Browse files

follow CPython's new tuple hash

--HG--
branch : py3.8
parent 182657acf90b
Pipeline #23142 failed with stage
in 8 minutes and 11 seconds
from pypy.interpreter.error import oefmt
from pypy.objspace.std.tupleobject import W_AbstractTupleObject
from pypy.objspace.std.tupleobject import (W_AbstractTupleObject,
XXPRIME_1, XXPRIME_2, XXPRIME_5, xxrotate, uhash_type)
from pypy.objspace.std.util import negate
from rpython.rlib.objectmodel import specialize
from rpython.rlib.rarithmetic import intmask
......@@ -64,31 +65,32 @@ def make_specialised_class(typetuple):
getitems_copy = func_with_new_name(tolist, 'getitems_copy')
def descr_hash(self, space):
mult = 1000003
x = 0x345678
z = typelen
acc = XXPRIME_5
for i in iter_n:
value = getattr(self, 'value%s' % i)
if typetuple[i] == object:
y = space.int_w(space.hash(value))
lane = uhash_type(space.int_w(space.hash(value)))
elif typetuple[i] == float:
# get the correct hash for float which is an
# integer & other less frequent cases
from pypy.objspace.std.floatobject import _hash_float
y = _hash_float(space, value)
lane = uhash_type(_hash_float(space, value))
elif typetuple[i] == int:
# hash for int which is different from the hash
# given by rpython
from pypy.objspace.std.intobject import _hash_int
y = _hash_int(value)
lane = uhash_type(_hash_int(value))
else:
raise NotImplementedError
x = (x ^ y) * mult
z -= 1
mult += 82520 + z + z
x += 97531
return space.newint(intmask(x))
acc += lane * XXPRIME_2
acc = xxrotate(acc)
acc *= XXPRIME_1
acc += typelen ^ (XXPRIME_5 ^ uhash_type(3527539))
acc += (acc == uhash_type(-1)) * uhash_type(1546275796 + 1)
return space.newint(intmask(acc))
def descr_eq(self, space, w_other):
if not isinstance(w_other, W_AbstractTupleObject):
......
......@@ -234,6 +234,16 @@ class TestW_TupleObject:
assert self.space.eq_w(self.space.le(w_tuple4, w_tuple3),
self.space.w_True)
def test_hash_consistency(self):
# make sure that the two copies of the hash implementation are the same
w = self.space.wrap
w_tuple1 = W_TupleObject([w(5), w(3), w(99)])
w_tuple2 = W_TupleObject([w(5), w(3), w(99)])
w_tuple3 = W_TupleObject([w(5), w(3), w(99), w(-1)])
w_tuple4 = W_TupleObject([w(5), w(3), w(9), w(-1)])
for w_tup in w_tuple1, w_tuple2, w_tuple3, w_tuple4:
assert w_tup._descr_hash_unroll(self.space) == w_tup._descr_hash_jitdriver(self.space)
class AppTestW_TupleObject:
def test_is_true(self):
......@@ -347,10 +357,22 @@ class AppTestW_TupleObject:
assert t2 <= t3
def test_hash(self):
# check that hash behaves as in 2.4 for at least 31 bits
assert hash(()) & 0x7fffffff == 0x35d373
assert hash((12,)) & 0x7fffffff == 0x1cca0557
assert hash((12, 34)) & 0x7fffffff == 0x153e2a41
# check that hash behaves as in 3.8
import sys
is_32 = sys.maxsize == 2 ** 31 - 1
def check_one_exact(t, h32, h64):
h = hash(t)
if is_32:
assert h == h32
else:
assert h == h64
check_one_exact((), 750394483, 5740354900026072187)
check_one_exact((0,), 1214856301, -8753497827991233192)
check_one_exact((0, 0), -168982784, -8458139203682520985)
check_one_exact((0.5,), 2077348973, -408149959306781352)
check_one_exact((0.5, (), (-2, 3, (4, 6))), 714642271,
-1845940830829704396)
def test_getnewargs(self):
assert () .__getnewargs__() == ((),)
......
......@@ -12,7 +12,7 @@ from pypy.objspace.std.sliceobject import (W_SliceObject, unwrap_start_stop,
from pypy.objspace.std.util import negate, IDTAG_SPECIAL, IDTAG_SHIFT
from rpython.rlib import jit
from rpython.rlib.debug import make_sure_not_resized
from rpython.rlib.rarithmetic import intmask
from rpython.rlib.rarithmetic import intmask, r_ulonglong, r_uint
UNROLL_CUTOFF = 10
......@@ -286,6 +286,18 @@ If the argument is a tuple, the return value is the same object.""",
)
W_AbstractTupleObject.typedef.flag_sequence_bug_compat = True
if sys.maxint == 2 ** 31 - 1:
XXPRIME_1 = r_uint(2654435761)
XXPRIME_2 = r_uint(2246822519)
XXPRIME_5 = r_uint(374761393)
xxrotate = lambda x: ((x << 13) | (x >> 19)) # rotate left 13 bits
uhash_type = r_uint
else:
XXPRIME_1 = r_ulonglong(11400714785074694791)
XXPRIME_2 = r_ulonglong(14029467366897019727)
XXPRIME_5 = r_ulonglong(2870177450012600261)
xxrotate = lambda x: ((x << 31) | (x >> 33)) # Rotate left 31 bits
uhash_type = r_ulonglong
class W_TupleObject(W_AbstractTupleObject):
_immutable_fields_ = ['wrappeditems[*]']
......@@ -305,41 +317,54 @@ class W_TupleObject(W_AbstractTupleObject):
def descr_hash(self, space):
if _unroll_condition(self):
res = self._descr_hash_unroll(space)
acc = self._descr_hash_unroll(space)
else:
res = self._descr_hash_jitdriver(space)
return space.newint(res)
acc = self._descr_hash_jitdriver(space)
# Add input length, mangled to keep the historical value of hash(())
acc += len(self.wrappeditems) ^ (XXPRIME_5 ^ uhash_type(3527539))
acc += (acc == uhash_type(-1)) * uhash_type(1546275796 + 1)
return space.newint(intmask(acc))
@jit.unroll_safe
def _descr_hash_unroll(self, space):
mult = 1000003
x = 0x345678
z = len(self.wrappeditems)
# Hash for tuples. This is a slightly simplified version of the xxHash
# non-cryptographic hash:
# - we do not use any parallellism, there is only 1 accumulator.
# - we drop the final mixing since this is just a permutation of the
# output space: it does not help against collisions.
# - at the end, we mangle the length with a single constant.
# For the xxHash specification, see
# https://github.com/Cyan4973/xxHash/blob/master/doc/xxhash_spec.md
# Below are the official constants from the xxHash specification. Optimizing
# compilers should emit a single "rotate" instruction for the
# _PyHASH_XXROTATE() expansion. If that doesn't happen for some important
# platform, the macro could be changed to expand to a platform-specific rotate
# spelling instead.
acc = XXPRIME_5
for w_item in self.wrappeditems:
y = space.hash_w(w_item)
x = (x ^ y) * mult
z -= 1
mult += 82520 + z + z
x += 97531
return intmask(x)
lane = uhash_type(space.hash_w(w_item))
acc += lane * XXPRIME_2
acc = xxrotate(acc)
acc *= XXPRIME_1
return acc
def _descr_hash_jitdriver(self, space):
mult = 1000003
x = 0x345678
z = len(self.wrappeditems)
# see comments above
acc = XXPRIME_5
w_type = space.type(self.wrappeditems[0])
wrappeditems = self.wrappeditems
i = 0
while i < len(wrappeditems):
hash_driver.jit_merge_point(w_type=w_type)
w_item = wrappeditems[i]
y = space.hash_w(w_item)
x = (x ^ y) * mult
z -= 1
mult += 82520 + z + z
lane = uhash_type(space.hash_w(w_item))
acc += lane * XXPRIME_2
acc = xxrotate(acc)
acc *= XXPRIME_1
i += 1
x += 97531
return intmask(x)
return acc
def descr_eq(self, space, w_other):
if not isinstance(w_other, W_AbstractTupleObject):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment