Skip to content

Commit c305506

Browse files
vstinnerGlyphack
authored andcommitted
pythongh-111545: Add Py_HashPointer() function (python#112096)
* Implement _Py_HashPointerRaw() as a static inline function. * Add Py_HashPointer() tests to test_capi.test_hash. * Keep _Py_HashPointer() function as an alias to Py_HashPointer().
1 parent 66501b7 commit c305506

File tree

9 files changed

+103
-22
lines changed

9 files changed

+103
-22
lines changed

Doc/c-api/hash.rst

+10
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,13 @@ See also the :c:member:`PyTypeObject.tp_hash` member.
4949
:pep:`456` "Secure and interchangeable hash algorithm".
5050
5151
.. versionadded:: 3.4
52+
53+
54+
.. c:function:: Py_hash_t Py_HashPointer(const void *ptr)
55+
56+
Hash a pointer value: process the pointer value as an integer (cast it to
57+
``uintptr_t`` internally). The pointer is not dereferenced.
58+
59+
The function cannot fail: it cannot return ``-1``.
60+
61+
.. versionadded:: 3.13

Doc/whatsnew/3.13.rst

+3
Original file line numberDiff line numberDiff line change
@@ -1249,6 +1249,9 @@ New Features
12491249
:exc:`KeyError` if the key missing.
12501250
(Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.)
12511251

1252+
* Add :c:func:`Py_HashPointer` function to hash a pointer.
1253+
(Contributed by Victor Stinner in :gh:`111545`.)
1254+
12521255

12531256
Porting to Python 3.13
12541257
----------------------

Include/cpython/pyhash.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121

2222
/* Helpers for hash functions */
2323
PyAPI_FUNC(Py_hash_t) _Py_HashDouble(PyObject *, double);
24-
PyAPI_FUNC(Py_hash_t) _Py_HashPointer(const void*);
24+
25+
// Kept for backward compatibility
26+
#define _Py_HashPointer Py_HashPointer
2527

2628

2729
/* hash function definition */
@@ -33,3 +35,5 @@ typedef struct {
3335
} PyHash_FuncDef;
3436

3537
PyAPI_FUNC(PyHash_FuncDef*) PyHash_GetFuncDef(void);
38+
39+
PyAPI_FUNC(Py_hash_t) Py_HashPointer(const void *ptr);

Include/internal/pycore_pyhash.h

+14-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,20 @@
55
# error "this header requires Py_BUILD_CORE define"
66
#endif
77

8-
// Similar to _Py_HashPointer(), but don't replace -1 with -2
9-
extern Py_hash_t _Py_HashPointerRaw(const void*);
8+
// Similar to Py_HashPointer(), but don't replace -1 with -2.
9+
static inline Py_hash_t
10+
_Py_HashPointerRaw(const void *ptr)
11+
{
12+
uintptr_t x = (uintptr_t)ptr;
13+
Py_BUILD_ASSERT(sizeof(x) == sizeof(ptr));
14+
15+
// Bottom 3 or 4 bits are likely to be 0; rotate x by 4 to the right
16+
// to avoid excessive hash collisions for dicts and sets.
17+
x = (x >> 4) | (x << (8 * sizeof(uintptr_t) - 4));
18+
19+
Py_BUILD_ASSERT(sizeof(x) == sizeof(Py_hash_t));
20+
return (Py_hash_t)x;
21+
}
1022

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

Lib/test/test_capi/test_hash.py

+47-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
_testcapi = import_helper.import_module('_testcapi')
55

66

7-
SIZEOF_PY_HASH_T = _testcapi.SIZEOF_VOID_P
7+
SIZEOF_VOID_P = _testcapi.SIZEOF_VOID_P
8+
SIZEOF_PY_HASH_T = SIZEOF_VOID_P
89

910

1011
class CAPITest(unittest.TestCase):
@@ -31,3 +32,48 @@ def test_hash_getfuncdef(self):
3132
self.assertEqual(func_def.name, hash_info.algorithm)
3233
self.assertEqual(func_def.hash_bits, hash_info.hash_bits)
3334
self.assertEqual(func_def.seed_bits, hash_info.seed_bits)
35+
36+
def test_hash_pointer(self):
37+
# Test Py_HashPointer()
38+
hash_pointer = _testcapi.hash_pointer
39+
40+
UHASH_T_MASK = ((2 ** (8 * SIZEOF_PY_HASH_T)) - 1)
41+
HASH_T_MAX = (2 ** (8 * SIZEOF_PY_HASH_T - 1) - 1)
42+
43+
def python_hash_pointer(x):
44+
# Py_HashPointer() rotates the pointer bits by 4 bits to the right
45+
x = (x >> 4) | ((x & 15) << (8 * SIZEOF_VOID_P - 4))
46+
47+
# Convert unsigned uintptr_t (Py_uhash_t) to signed Py_hash_t
48+
if HASH_T_MAX < x:
49+
x = (~x) + 1
50+
x &= UHASH_T_MASK
51+
x = (~x) + 1
52+
return x
53+
54+
if SIZEOF_VOID_P == 8:
55+
values = (
56+
0xABCDEF1234567890,
57+
0x1234567890ABCDEF,
58+
0xFEE4ABEDD1CECA5E,
59+
)
60+
else:
61+
values = (
62+
0x12345678,
63+
0x1234ABCD,
64+
0xDEADCAFE,
65+
)
66+
67+
for value in values:
68+
expected = python_hash_pointer(value)
69+
with self.subTest(value=value):
70+
self.assertEqual(hash_pointer(value), expected,
71+
f"hash_pointer({value:x}) = "
72+
f"{hash_pointer(value):x} != {expected:x}")
73+
74+
# Py_HashPointer(NULL) returns 0
75+
self.assertEqual(hash_pointer(0), 0)
76+
77+
# Py_HashPointer((void*)(uintptr_t)-1) doesn't return -1 but -2
78+
VOID_P_MAX = -1 & (2 ** (8 * SIZEOF_VOID_P) - 1)
79+
self.assertEqual(hash_pointer(VOID_P_MAX), -2)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :c:func:`Py_HashPointer` function to hash a pointer. Patch by Victor
2+
Stinner.

Modules/_testcapi/hash.c

+16
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,24 @@ hash_getfuncdef(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
4444
return result;
4545
}
4646

47+
48+
static PyObject *
49+
hash_pointer(PyObject *Py_UNUSED(module), PyObject *arg)
50+
{
51+
void *ptr = PyLong_AsVoidPtr(arg);
52+
if (ptr == NULL && PyErr_Occurred()) {
53+
return NULL;
54+
}
55+
56+
Py_hash_t hash = Py_HashPointer(ptr);
57+
Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash));
58+
return PyLong_FromLongLong(hash);
59+
}
60+
61+
4762
static PyMethodDef test_methods[] = {
4863
{"hash_getfuncdef", hash_getfuncdef, METH_NOARGS},
64+
{"hash_pointer", hash_pointer, METH_O},
4965
{NULL},
5066
};
5167

Python/hashtable.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
*/
4646

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

5151
#define HASHTABLE_MIN_SIZE 16

Python/pyhash.c

+5-17
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,6 @@ static Py_ssize_t hashstats[Py_HASH_STATS_MAX + 1] = {0};
8383
8484
*/
8585

86-
Py_hash_t _Py_HashPointer(const void *);
87-
8886
Py_hash_t
8987
_Py_HashDouble(PyObject *inst, double v)
9088
{
@@ -132,23 +130,13 @@ _Py_HashDouble(PyObject *inst, double v)
132130
}
133131

134132
Py_hash_t
135-
_Py_HashPointerRaw(const void *p)
136-
{
137-
size_t y = (size_t)p;
138-
/* bottom 3 or 4 bits are likely to be 0; rotate y by 4 to avoid
139-
excessive hash collisions for dicts and sets */
140-
y = (y >> 4) | (y << (8 * SIZEOF_VOID_P - 4));
141-
return (Py_hash_t)y;
142-
}
143-
144-
Py_hash_t
145-
_Py_HashPointer(const void *p)
133+
Py_HashPointer(const void *ptr)
146134
{
147-
Py_hash_t x = _Py_HashPointerRaw(p);
148-
if (x == -1) {
149-
x = -2;
135+
Py_hash_t hash = _Py_HashPointerRaw(ptr);
136+
if (hash == -1) {
137+
hash = -2;
150138
}
151-
return x;
139+
return hash;
152140
}
153141

154142
Py_hash_t

0 commit comments

Comments
 (0)