Skip to content

Commit 8bb71fe

Browse files
committed
gh-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 d4f83e1 commit 8bb71fe

File tree

12 files changed

+181
-22
lines changed

12 files changed

+181
-22
lines changed

Doc/c-api/hash.rst

Lines 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 (cannot return ``-1``).
56+
57+
.. versionadded:: 3.13

Doc/whatsnew/3.13.rst

Lines 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

Lines 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_bitutils.h

Lines changed: 16 additions & 0 deletions
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

Lines 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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,30 @@ def test_hash_getfuncdef(self):
3131
self.assertEqual(func_def.name, hash_info.algorithm)
3232
self.assertEqual(func_def.hash_bits, hash_info.hash_bits)
3333
self.assertEqual(func_def.seed_bits, hash_info.seed_bits)
34+
35+
def test_hash_pointer(self):
36+
# Test PyHash_Pointer()
37+
hash_pointer = _testcapi.hash_pointer
38+
39+
HASH_BITS = 8 * _testcapi.SIZEOF_VOID_P
40+
UHASH_T_MASK = ((2 ** HASH_BITS) - 1)
41+
HASH_T_MAX = (2 ** (HASH_BITS - 1) - 1)
42+
MAX_PTR = UHASH_T_MASK
43+
44+
def uhash_to_hash(x):
45+
# Convert unsigned Py_uhash_t to signed Py_hash_t
46+
if HASH_T_MAX < x:
47+
x = (~x) + 1
48+
x &= UHASH_T_MASK
49+
x = (~x) + 1
50+
return x
51+
52+
# Known values
53+
self.assertEqual(hash_pointer(0), 0)
54+
self.assertEqual(hash_pointer(MAX_PTR), -2)
55+
self.assertEqual(hash_pointer(0xABCDEF1234567890),
56+
0x0ABCDEF123456789)
57+
self.assertEqual(hash_pointer(0x1234567890ABCDEF),
58+
uhash_to_hash(0xF1234567890ABCDE))
59+
self.assertEqual(hash_pointer(0xFEE4ABEDD1CECA5E),
60+
uhash_to_hash(0xEFEE4ABEDD1CECA5))
Lines 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

Lines 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

Modules/_testinternalcapi.c

Lines changed: 75 additions & 0 deletions
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

Lines 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

0 commit comments

Comments
 (0)