Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ffbdae1

Browse filesBrowse files
committed
gh-111545: Add Py_HashPointer() function
* 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 c298238 commit ffbdae1
Copy full SHA for ffbdae1

File tree

Expand file treeCollapse file tree

9 files changed

+103
-21
lines changed
Filter options
Expand file treeCollapse file tree

9 files changed

+103
-21
lines changed

‎Doc/c-api/hash.rst

Copy file name to clipboardExpand all lines: Doc/c-api/hash.rst
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,12 @@ See also the :c:member:`PyTypeObject.tp_hash` member.
4646
Get the hash function definition.
4747
4848
.. versionadded:: 3.4
49+
50+
51+
.. c:function:: Py_hash_t Py_HashPointer(const void *ptr)
52+
53+
Hash a pointer.
54+
55+
The function cannot fail: it cannot return ``-1``.
56+
57+
.. versionadded:: 3.13

‎Doc/whatsnew/3.13.rst

Copy file name to clipboardExpand all lines: Doc/whatsnew/3.13.rst
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,6 +1230,9 @@ New Features
12301230
:exc:`KeyError` if the key missing.
12311231
(Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.)
12321232

1233+
* Add :c:func:`Py_HashPointer` function to hash a pointer.
1234+
(Contributed by Victor Stinner in :gh:`111545`.)
1235+
12331236

12341237
Porting to Python 3.13
12351238
----------------------

‎Include/cpython/pyhash.h

Copy file name to clipboardExpand all lines: Include/cpython/pyhash.h
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,5 @@ typedef struct {
3333
} PyHash_FuncDef;
3434

3535
PyAPI_FUNC(PyHash_FuncDef*) PyHash_GetFuncDef(void);
36+
37+
PyAPI_FUNC(Py_hash_t) Py_HashPointer(const void *ptr);

‎Include/internal/pycore_pyhash.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_pyhash.h
+18-2Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,24 @@
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 return -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+
}
22+
23+
24+
// Kept for backward compatibility
25+
#define _Py_HashPointer Py_HashPointer
1026

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

‎Lib/test/test_capi/test_hash.py

Copy file name to clipboardExpand all lines: Lib/test/test_capi/test_hash.py
+47-1Lines changed: 47 additions & 1 deletion
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)
+2Lines changed: 2 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: Modules/_testcapi/hash.c
+16Lines changed: 16 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: Python/hashtable.c
+1-1Lines changed: 1 addition & 1 deletion
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

Copy file name to clipboardExpand all lines: Python/pyhash.c
+5-17Lines changed: 5 additions & 17 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.