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 992c39e

Browse filesBrowse files
committed
gh-111545: Add PyHash_Pointer() function
* Keep _Py_HashPointer() function as an alias to PyHash_Pointer(). * Add PyHash_Pointer() tests to test_capi.test_hash. * Remove _Py_HashPointerRaw() function: inline code in _Py_hashtable_hash_ptr().
1 parent d4f83e1 commit 992c39e
Copy full SHA for 992c39e

File tree

Expand file treeCollapse file tree

10 files changed

+109
-23
lines changed
Filter options
Expand file treeCollapse file tree

10 files changed

+109
-23
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 PyHash_Pointer(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
@@ -1181,6 +1181,9 @@ 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_Pointer` function to hash a pointer.
1185+
(Contributed by Victor Stinner in :gh:`111545`.)
1186+
11841187

11851188
Porting to Python 3.13
11861189
----------------------

‎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
@@ -11,3 +11,5 @@ typedef struct {
1111
} PyHash_FuncDef;
1212

1313
PyAPI_FUNC(PyHash_FuncDef*) PyHash_GetFuncDef(void);
14+
15+
PyAPI_FUNC(Py_hash_t) PyHash_Pointer(const void *ptr);

‎Include/internal/pycore_pyhash.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_pyhash.h
+2-5Lines changed: 2 additions & 5 deletions
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

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 PyHash_Pointer()
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+
# PyHash_Pointer() 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 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+
# PyHash_Pointer(NULL) returns 0
75+
self.assertEqual(hash_pointer(0), 0)
76+
77+
# PyHash_Pointer((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:`PyHash_Pointer` 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 = PyHash_Pointer(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

‎PC/pyconfig.h

Copy file name to clipboardExpand all lines: PC/pyconfig.h
+2Lines changed: 2 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: Python/hashtable.c
+13-3Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@
4545
*/
4646

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

5150
#define HASHTABLE_MIN_SIZE 16
5251
#define HASHTABLE_HIGH 0.50
@@ -89,10 +88,21 @@ _Py_slist_remove(_Py_slist_t *list, _Py_slist_item_t *previous,
8988
}
9089

9190

91+
// Similar to PyHash_Pointer() but avoid "if (x == -1) x = -2;" for best
92+
// performance. The value (Py_uhash_t)-1 is not special for
93+
// _Py_hashtable_t.hash_func function, there is no need to replace it with -2.
9294
Py_uhash_t
9395
_Py_hashtable_hash_ptr(const void *key)
9496
{
95-
return (Py_uhash_t)_Py_HashPointerRaw(key);
97+
uintptr_t x = (uintptr_t)key;
98+
Py_BUILD_ASSERT(sizeof(x) == sizeof(key));
99+
100+
// Bottom 3 or 4 bits are likely to be 0; rotate x by 4 to the right
101+
// to avoid excessive hash collisions.
102+
x = (x >> 4) | (x << (8 * SIZEOF_UINTPTR_T - 4));
103+
104+
Py_BUILD_ASSERT(sizeof(x) == sizeof(Py_hash_t));
105+
return (Py_uhash_t)x;
96106
}
97107

98108

‎Python/pyhash.c

Copy file name to clipboardExpand all lines: Python/pyhash.c
+13-14Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -131,24 +131,23 @@ _Py_HashDouble(PyObject *inst, double v)
131131
return (Py_hash_t)x;
132132
}
133133

134+
// See also _Py_hashtable_hash_ptr() which is similar but can return -1.
134135
Py_hash_t
135-
_Py_HashPointerRaw(const void *p)
136+
PyHash_Pointer(const void *ptr)
136137
{
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-
}
138+
uintptr_t x = (uintptr_t)ptr;
139+
Py_BUILD_ASSERT(sizeof(x) == sizeof(ptr));
143140

144-
Py_hash_t
145-
_Py_HashPointer(const void *p)
146-
{
147-
Py_hash_t x = _Py_HashPointerRaw(p);
148-
if (x == -1) {
149-
x = -2;
141+
// Bottom 3 or 4 bits are likely to be 0; rotate x by 4 to the right
142+
// to avoid excessive hash collisions for dicts and sets.
143+
x = (x >> 4) | (x << (8 * SIZEOF_UINTPTR_T - 4));
144+
145+
Py_BUILD_ASSERT(sizeof(x) == sizeof(Py_hash_t));
146+
Py_hash_t result = (Py_hash_t)x;
147+
if (result == -1) {
148+
result = -2;
150149
}
151-
return x;
150+
return result;
152151
}
153152

154153
Py_hash_t

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.