Skip to content

Commit 3a0752a

Browse files
committed
pythongh-111545: Add PyHash_Pointer() function
* Keep _Py_HashPointer() function as an alias to PyHash_Pointer(). * Add _Py_rotateright_uintptr() function with tests. * Add PyHash_Pointer() tests to test_capi.test_hash. * Remove _Py_HashPointerRaw() function: inline code in _Py_hashtable_hash_ptr().
1 parent 46fef17 commit 3a0752a

File tree

12 files changed

+185
-27
lines changed

12 files changed

+185
-27
lines changed

Doc/c-api/hash.rst

+9
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,12 @@ Functions
6565
:pep:`456` "Secure and interchangeable hash algorithm".
6666
6767
.. versionadded:: 3.4
68+
69+
70+
.. c:function:: Py_hash_t PyHash_Pointer(const void *ptr)
71+
72+
Hash a pointer.
73+
74+
The function cannot return ``-1``.
75+
76+
.. versionadded:: 3.13

Doc/whatsnew/3.13.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -1181,7 +1181,8 @@ New Features
11811181
:exc:`KeyError` if the key missing.
11821182
(Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.)
11831183

1184-
* Add :c:func:`PyHash_Double` function to hash a C double number.
1184+
* Add :c:func:`PyHash_Double` and :c:func:`PyHash_Pointer` functions to hash a
1185+
C double number and hash a pointer.
11851186
(Contributed by Victor Stinner in :gh:`111545`.)
11861187

11871188

Include/cpython/pyhash.h

+1
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ typedef struct {
1313
PyAPI_FUNC(PyHash_FuncDef*) PyHash_GetFuncDef(void);
1414

1515
PyAPI_FUNC(Py_hash_t) PyHash_Double(double value);
16+
PyAPI_FUNC(Py_hash_t) PyHash_Pointer(const void *ptr);

Include/internal/pycore_bitutils.h

+16
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,22 @@ _Py_bit_length(unsigned long x)
180180
}
181181

182182

183+
// Rotate x bits to the right.
184+
// Function used by Py_HashPointer().
185+
static inline uintptr_t
186+
_Py_rotateright_uintptr(uintptr_t x, const unsigned int bits)
187+
{
188+
assert(bits < (8 * SIZEOF_UINTPTR_T));
189+
#if _Py__has_builtin(__builtin_rotateright64) && SIZEOF_UINTPTR_T == 8
190+
return __builtin_rotateright64(x, bits);
191+
#elif _Py__has_builtin(__builtin_rotateright32) && SIZEOF_UINTPTR_T == 4
192+
return __builtin_rotateright32(x, bits);
193+
#else
194+
return (x >> bits) | (x << (8 * SIZEOF_UINTPTR_T - bits));
195+
#endif
196+
}
197+
198+
183199
#ifdef __cplusplus
184200
}
185201
#endif

Include/internal/pycore_pyhash.h

+2-5
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,8 @@
88
/* Helpers for hash functions */
99
extern Py_hash_t _Py_HashDouble(PyObject *, double);
1010

11-
// Export for '_decimal' shared extension
12-
PyAPI_FUNC(Py_hash_t) _Py_HashPointer(const void*);
13-
14-
// Similar to _Py_HashPointer(), but don't replace -1 with -2
15-
extern Py_hash_t _Py_HashPointerRaw(const void*);
11+
// Kept for backward compatibility
12+
#define _Py_HashPointer PyHash_Pointer
1613

1714
// Export for '_datetime' shared extension
1815
PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void*, Py_ssize_t);

Lib/test/test_capi/test_hash.py

+27
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,30 @@ def python_hash_double(x):
7878
with self.subTest(x=x):
7979
self.assertEqual(hash_double(x), python_hash_double(x))
8080
self.assertEqual(hash_double(-x), python_hash_double(-x))
81+
82+
def test_hash_pointer(self):
83+
# Test PyHash_Pointer()
84+
hash_pointer = _testcapi.hash_pointer
85+
86+
HASH_BITS = 8 * _testcapi.SIZEOF_VOID_P
87+
UHASH_T_MASK = ((2 ** HASH_BITS) - 1)
88+
HASH_T_MAX = (2 ** (HASH_BITS - 1) - 1)
89+
MAX_PTR = UHASH_T_MASK
90+
91+
def uhash_to_hash(x):
92+
# Convert unsigned Py_uhash_t to signed Py_hash_t
93+
if HASH_T_MAX < x:
94+
x = (~x) + 1
95+
x &= UHASH_T_MASK
96+
x = (~x) + 1
97+
return x
98+
99+
# Known values
100+
self.assertEqual(hash_pointer(0), 0)
101+
self.assertEqual(hash_pointer(MAX_PTR), -2)
102+
self.assertEqual(hash_pointer(0xABCDEF1234567890),
103+
0x0ABCDEF123456789)
104+
self.assertEqual(hash_pointer(0x1234567890ABCDEF),
105+
uhash_to_hash(0xF1234567890ABCDE))
106+
self.assertEqual(hash_pointer(0xFEE4ABEDD1CECA5E),
107+
uhash_to_hash(0xEFEE4ABEDD1CECA5))
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
Add :c:func:`PyHash_Double` function to hash a C double number. Patch by
2-
Victor Stinner.
1+
Add :c:func:`PyHash_Double` and :c:func:`PyHash_Pointer` functions to hash a C
2+
double number and hash a pointer. Patch by Victor Stinner.

Modules/_testcapi/hash.c

+22-2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ hash_getfuncdef(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
4646
}
4747

4848

49+
static PyObject*
50+
long_from_hash(Py_hash_t hash)
51+
{
52+
Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash));
53+
return PyLong_FromLongLong(hash);
54+
}
55+
56+
4957
static PyObject *
5058
hash_double(PyObject *Py_UNUSED(module), PyObject *args)
5159
{
@@ -54,14 +62,26 @@ hash_double(PyObject *Py_UNUSED(module), PyObject *args)
5462
return NULL;
5563
}
5664
Py_hash_t hash = PyHash_Double(value);
57-
Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash));
58-
return PyLong_FromLongLong(hash);
65+
return long_from_hash(hash);
66+
}
67+
68+
69+
static PyObject *
70+
hash_pointer(PyObject *Py_UNUSED(module), PyObject *arg)
71+
{
72+
void *ptr = PyLong_AsVoidPtr(arg);
73+
if (ptr == NULL && PyErr_Occurred()) {
74+
return NULL;
75+
}
76+
Py_hash_t hash = PyHash_Pointer(ptr);
77+
return long_from_hash(hash);
5978
}
6079

6180

6281
static PyMethodDef test_methods[] = {
6382
{"hash_getfuncdef", hash_getfuncdef, METH_NOARGS},
6483
{"hash_double", hash_double, METH_VARARGS},
84+
{"hash_pointer", hash_pointer, METH_O},
6585
{NULL},
6686
};
6787

Modules/_testinternalcapi.c

+75
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,80 @@ test_bit_length(PyObject *self, PyObject *Py_UNUSED(args))
216216
}
217217

218218

219+
static int
220+
check_rotateright_uintptr(uintptr_t ptr, unsigned int bits, uintptr_t expected)
221+
{
222+
#if SIZEOF_UINTPTR_T == 8
223+
# define FMT "0x%llx"
224+
#else
225+
# define FMT "0x%lx"
226+
#endif
227+
228+
// Use volatile to prevent the compiler to optimize out the whole test
229+
volatile uintptr_t x = ptr;
230+
uintptr_t y = _Py_rotateright_uintptr(x, bits);
231+
if (y != expected) {
232+
PyErr_Format(PyExc_AssertionError,
233+
"_Py_rotateright_uintptr(" FMT ", %u) returns " FMT ", expected " FMT,
234+
x, bits, y, expected);
235+
return -1;
236+
}
237+
return 0;
238+
239+
#undef FMT
240+
}
241+
242+
243+
static PyObject*
244+
test_rotateright_uintptr(PyObject *self, PyObject *Py_UNUSED(args))
245+
{
246+
#define CHECK(X, BITS, EXPECTED) \
247+
do { \
248+
if (check_rotateright_uintptr(X, BITS, EXPECTED) < 0) { \
249+
return NULL; \
250+
} \
251+
} while (0)
252+
253+
// Test _Py_rotateright_uintptr()
254+
#if SIZEOF_UINTPTR_T == 8
255+
CHECK(UINT64_C(0x1234567890ABCDEF), 4, UINT64_C(0xF1234567890ABCDE));
256+
CHECK(UINT64_C(0x1234567890ABCDEF), 8, UINT64_C(0xEF1234567890ABCD));
257+
CHECK(UINT64_C(0x1234567890ABCDEF), 12, UINT64_C(0xDEF1234567890ABC));
258+
CHECK(UINT64_C(0x1234567890ABCDEF), 16, UINT64_C(0xCDEF1234567890AB));
259+
CHECK(UINT64_C(0x1234567890ABCDEF), 20, UINT64_C(0xBCDEF1234567890A));
260+
CHECK(UINT64_C(0x1234567890ABCDEF), 24, UINT64_C(0xABCDEF1234567890));
261+
CHECK(UINT64_C(0x1234567890ABCDEF), 28, UINT64_C(0x0ABCDEF123456789));
262+
CHECK(UINT64_C(0x1234567890ABCDEF), 32, UINT64_C(0x90ABCDEF12345678));
263+
CHECK(UINT64_C(0x1234567890ABCDEF), 36, UINT64_C(0x890ABCDEF1234567));
264+
CHECK(UINT64_C(0x1234567890ABCDEF), 40, UINT64_C(0x7890ABCDEF123456));
265+
CHECK(UINT64_C(0x1234567890ABCDEF), 44, UINT64_C(0x67890ABCDEF12345));
266+
CHECK(UINT64_C(0x1234567890ABCDEF), 48, UINT64_C(0x567890ABCDEF1234));
267+
CHECK(UINT64_C(0x1234567890ABCDEF), 52, UINT64_C(0x4567890ABCDEF123));
268+
CHECK(UINT64_C(0x1234567890ABCDEF), 56, UINT64_C(0x34567890ABCDEF12));
269+
CHECK(UINT64_C(0x1234567890ABCDEF), 60, UINT64_C(0x234567890ABCDEF1));
270+
271+
CHECK(UINT64_C(0xFEE4ABEDD1CECA5E), 4, UINT64_C(0xEFEE4ABEDD1CECA5));
272+
CHECK(UINT64_C(0xFEE4ABEDD1CECA5E), 32, UINT64_C(0xD1CECA5EFEE4ABED));
273+
#elif SIZEOF_UINTPTR_T == 4
274+
CHECK(UINT32_C(0x12345678), 4, UINT32_C(0x81234567));
275+
CHECK(UINT32_C(0x12345678), 8, UINT32_C(0x78123456));
276+
CHECK(UINT32_C(0x12345678), 12, UINT32_C(0x67812345));
277+
CHECK(UINT32_C(0x12345678), 16, UINT32_C(0x56781234));
278+
CHECK(UINT32_C(0x12345678), 20, UINT32_C(0x45678123));
279+
CHECK(UINT32_C(0x12345678), 24, UINT32_C(0x34567812));
280+
CHECK(UINT32_C(0x12345678), 28, UINT32_C(0x23456781));
281+
282+
CHECK(UINT32_C(0xDEADCAFE), 4, UINT32_C(0xEDEADCAF));
283+
CHECK(UINT32_C(0xDEADCAFE), 16, UINT32_C(0xCAFEDEAD));
284+
#else
285+
# error "unsupported uintptr_t size"
286+
#endif
287+
Py_RETURN_NONE;
288+
289+
#undef CHECK
290+
}
291+
292+
219293
#define TO_PTR(ch) ((void*)(uintptr_t)ch)
220294
#define FROM_PTR(ptr) ((uintptr_t)ptr)
221295
#define VALUE(key) (1 + ((int)(key) - 'a'))
@@ -1614,6 +1688,7 @@ static PyMethodDef module_functions[] = {
16141688
{"test_bswap", test_bswap, METH_NOARGS},
16151689
{"test_popcount", test_popcount, METH_NOARGS},
16161690
{"test_bit_length", test_bit_length, METH_NOARGS},
1691+
{"test_rotateright_uintptr", test_rotateright_uintptr, METH_NOARGS},
16171692
{"test_hashtable", test_hashtable, METH_NOARGS},
16181693
{"get_config", test_get_config, METH_NOARGS},
16191694
{"set_config", test_set_config, METH_O},

PC/pyconfig.h

+2
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */
355355
# define ALIGNOF_MAX_ALIGN_T 8
356356
#endif
357357

358+
#define SIZEOF_UINTPTR_T SIZEOF_VOID_P
359+
358360
#ifdef _DEBUG
359361
# define Py_DEBUG
360362
#endif

Python/hashtable.c

+14-3
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@
4545
*/
4646

4747
#include "Python.h"
48-
#include "pycore_hashtable.h"
49-
#include "pycore_pyhash.h" // _Py_HashPointerRaw()
48+
#include "pycore_bitutils.h" // _Py_rotateright_uintptr()
49+
#include "pycore_hashtable.h" // export _Py_hashtable_new()
5050

5151
#define HASHTABLE_MIN_SIZE 16
5252
#define HASHTABLE_HIGH 0.50
@@ -89,10 +89,21 @@ _Py_slist_remove(_Py_slist_t *list, _Py_slist_item_t *previous,
8989
}
9090

9191

92+
// Similar to PyHash_Pointer() but avoid "if (x == -1) x = -2;" for best
93+
// performance. The value (Py_uhash_t)-1 is not special for
94+
// _Py_hashtable_t.hash_func function, there is no need to replace it with -2.
9295
Py_uhash_t
9396
_Py_hashtable_hash_ptr(const void *key)
9497
{
95-
return (Py_uhash_t)_Py_HashPointerRaw(key);
98+
uintptr_t x = (uintptr_t)key;
99+
Py_BUILD_ASSERT(sizeof(x) == sizeof(key));
100+
101+
// Bottom 3 or 4 bits are likely to be 0; rotate x by 4 to the right
102+
// to avoid excessive hash collisions.
103+
x = _Py_rotateright_uintptr(x, 4);
104+
105+
Py_BUILD_ASSERT(sizeof(x) == sizeof(Py_hash_t));
106+
return (Py_uhash_t)x;
96107
}
97108

98109

Python/pyhash.c

+13-14
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
All the utility functions (_Py_Hash*()) return "-1" to signify an error.
55
*/
66
#include "Python.h"
7+
#include "pycore_bitutils.h" // _Py_rotateright_uintptr()
78
#include "pycore_pyhash.h" // _Py_HashSecret_t
89

910
#ifdef __APPLE__
@@ -154,23 +155,21 @@ _Py_HashDouble(PyObject *inst, double v)
154155
}
155156

156157
Py_hash_t
157-
_Py_HashPointerRaw(const void *p)
158+
PyHash_Pointer(const void *ptr)
158159
{
159-
size_t y = (size_t)p;
160-
/* bottom 3 or 4 bits are likely to be 0; rotate y by 4 to avoid
161-
excessive hash collisions for dicts and sets */
162-
y = (y >> 4) | (y << (8 * SIZEOF_VOID_P - 4));
163-
return (Py_hash_t)y;
164-
}
160+
uintptr_t x = (uintptr_t)ptr;
161+
Py_BUILD_ASSERT(sizeof(x) == sizeof(ptr));
165162

166-
Py_hash_t
167-
_Py_HashPointer(const void *p)
168-
{
169-
Py_hash_t x = _Py_HashPointerRaw(p);
170-
if (x == -1) {
171-
x = -2;
163+
// Bottom 3 or 4 bits are likely to be 0; rotate x by 4 to the right
164+
// to avoid excessive hash collisions for dicts and sets.
165+
x = _Py_rotateright_uintptr(x, 4);
166+
167+
Py_BUILD_ASSERT(sizeof(x) == sizeof(Py_hash_t));
168+
Py_hash_t result = (Py_hash_t)x;
169+
if (result == -1) {
170+
result = -2;
172171
}
173-
return x;
172+
return result;
174173
}
175174

176175
Py_hash_t

0 commit comments

Comments
 (0)