mirror of https://github.com/python/cpython
gh-111545: Add Py_HashPointer() function (#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().
This commit is contained in:
parent
f8852634ed
commit
828451dfde
|
@ -49,3 +49,13 @@ See also the :c:member:`PyTypeObject.tp_hash` member.
|
||||||
:pep:`456` "Secure and interchangeable hash algorithm".
|
:pep:`456` "Secure and interchangeable hash algorithm".
|
||||||
|
|
||||||
.. versionadded:: 3.4
|
.. versionadded:: 3.4
|
||||||
|
|
||||||
|
|
||||||
|
.. c:function:: Py_hash_t Py_HashPointer(const void *ptr)
|
||||||
|
|
||||||
|
Hash a pointer value: process the pointer value as an integer (cast it to
|
||||||
|
``uintptr_t`` internally). The pointer is not dereferenced.
|
||||||
|
|
||||||
|
The function cannot fail: it cannot return ``-1``.
|
||||||
|
|
||||||
|
.. versionadded:: 3.13
|
||||||
|
|
|
@ -1249,6 +1249,9 @@ New Features
|
||||||
:exc:`KeyError` if the key missing.
|
:exc:`KeyError` if the key missing.
|
||||||
(Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.)
|
(Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.)
|
||||||
|
|
||||||
|
* Add :c:func:`Py_HashPointer` function to hash a pointer.
|
||||||
|
(Contributed by Victor Stinner in :gh:`111545`.)
|
||||||
|
|
||||||
|
|
||||||
Porting to Python 3.13
|
Porting to Python 3.13
|
||||||
----------------------
|
----------------------
|
||||||
|
|
|
@ -21,7 +21,9 @@
|
||||||
|
|
||||||
/* Helpers for hash functions */
|
/* Helpers for hash functions */
|
||||||
PyAPI_FUNC(Py_hash_t) _Py_HashDouble(PyObject *, double);
|
PyAPI_FUNC(Py_hash_t) _Py_HashDouble(PyObject *, double);
|
||||||
PyAPI_FUNC(Py_hash_t) _Py_HashPointer(const void*);
|
|
||||||
|
// Kept for backward compatibility
|
||||||
|
#define _Py_HashPointer Py_HashPointer
|
||||||
|
|
||||||
|
|
||||||
/* hash function definition */
|
/* hash function definition */
|
||||||
|
@ -33,3 +35,5 @@ typedef struct {
|
||||||
} PyHash_FuncDef;
|
} PyHash_FuncDef;
|
||||||
|
|
||||||
PyAPI_FUNC(PyHash_FuncDef*) PyHash_GetFuncDef(void);
|
PyAPI_FUNC(PyHash_FuncDef*) PyHash_GetFuncDef(void);
|
||||||
|
|
||||||
|
PyAPI_FUNC(Py_hash_t) Py_HashPointer(const void *ptr);
|
||||||
|
|
|
@ -5,8 +5,20 @@
|
||||||
# error "this header requires Py_BUILD_CORE define"
|
# error "this header requires Py_BUILD_CORE define"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Similar to _Py_HashPointer(), but don't replace -1 with -2
|
// Similar to Py_HashPointer(), but don't replace -1 with -2.
|
||||||
extern Py_hash_t _Py_HashPointerRaw(const void*);
|
static inline Py_hash_t
|
||||||
|
_Py_HashPointerRaw(const void *ptr)
|
||||||
|
{
|
||||||
|
uintptr_t x = (uintptr_t)ptr;
|
||||||
|
Py_BUILD_ASSERT(sizeof(x) == sizeof(ptr));
|
||||||
|
|
||||||
|
// Bottom 3 or 4 bits are likely to be 0; rotate x by 4 to the right
|
||||||
|
// to avoid excessive hash collisions for dicts and sets.
|
||||||
|
x = (x >> 4) | (x << (8 * sizeof(uintptr_t) - 4));
|
||||||
|
|
||||||
|
Py_BUILD_ASSERT(sizeof(x) == sizeof(Py_hash_t));
|
||||||
|
return (Py_hash_t)x;
|
||||||
|
}
|
||||||
|
|
||||||
// Export for '_datetime' shared extension
|
// Export for '_datetime' shared extension
|
||||||
PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void*, Py_ssize_t);
|
PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void*, Py_ssize_t);
|
||||||
|
|
|
@ -4,7 +4,8 @@ from test.support import import_helper
|
||||||
_testcapi = import_helper.import_module('_testcapi')
|
_testcapi = import_helper.import_module('_testcapi')
|
||||||
|
|
||||||
|
|
||||||
SIZEOF_PY_HASH_T = _testcapi.SIZEOF_VOID_P
|
SIZEOF_VOID_P = _testcapi.SIZEOF_VOID_P
|
||||||
|
SIZEOF_PY_HASH_T = SIZEOF_VOID_P
|
||||||
|
|
||||||
|
|
||||||
class CAPITest(unittest.TestCase):
|
class CAPITest(unittest.TestCase):
|
||||||
|
@ -31,3 +32,48 @@ class CAPITest(unittest.TestCase):
|
||||||
self.assertEqual(func_def.name, hash_info.algorithm)
|
self.assertEqual(func_def.name, hash_info.algorithm)
|
||||||
self.assertEqual(func_def.hash_bits, hash_info.hash_bits)
|
self.assertEqual(func_def.hash_bits, hash_info.hash_bits)
|
||||||
self.assertEqual(func_def.seed_bits, hash_info.seed_bits)
|
self.assertEqual(func_def.seed_bits, hash_info.seed_bits)
|
||||||
|
|
||||||
|
def test_hash_pointer(self):
|
||||||
|
# Test Py_HashPointer()
|
||||||
|
hash_pointer = _testcapi.hash_pointer
|
||||||
|
|
||||||
|
UHASH_T_MASK = ((2 ** (8 * SIZEOF_PY_HASH_T)) - 1)
|
||||||
|
HASH_T_MAX = (2 ** (8 * SIZEOF_PY_HASH_T - 1) - 1)
|
||||||
|
|
||||||
|
def python_hash_pointer(x):
|
||||||
|
# Py_HashPointer() rotates the pointer bits by 4 bits to the right
|
||||||
|
x = (x >> 4) | ((x & 15) << (8 * SIZEOF_VOID_P - 4))
|
||||||
|
|
||||||
|
# Convert unsigned uintptr_t (Py_uhash_t) to signed Py_hash_t
|
||||||
|
if HASH_T_MAX < x:
|
||||||
|
x = (~x) + 1
|
||||||
|
x &= UHASH_T_MASK
|
||||||
|
x = (~x) + 1
|
||||||
|
return x
|
||||||
|
|
||||||
|
if SIZEOF_VOID_P == 8:
|
||||||
|
values = (
|
||||||
|
0xABCDEF1234567890,
|
||||||
|
0x1234567890ABCDEF,
|
||||||
|
0xFEE4ABEDD1CECA5E,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
values = (
|
||||||
|
0x12345678,
|
||||||
|
0x1234ABCD,
|
||||||
|
0xDEADCAFE,
|
||||||
|
)
|
||||||
|
|
||||||
|
for value in values:
|
||||||
|
expected = python_hash_pointer(value)
|
||||||
|
with self.subTest(value=value):
|
||||||
|
self.assertEqual(hash_pointer(value), expected,
|
||||||
|
f"hash_pointer({value:x}) = "
|
||||||
|
f"{hash_pointer(value):x} != {expected:x}")
|
||||||
|
|
||||||
|
# Py_HashPointer(NULL) returns 0
|
||||||
|
self.assertEqual(hash_pointer(0), 0)
|
||||||
|
|
||||||
|
# Py_HashPointer((void*)(uintptr_t)-1) doesn't return -1 but -2
|
||||||
|
VOID_P_MAX = -1 & (2 ** (8 * SIZEOF_VOID_P) - 1)
|
||||||
|
self.assertEqual(hash_pointer(VOID_P_MAX), -2)
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add :c:func:`Py_HashPointer` function to hash a pointer. Patch by Victor
|
||||||
|
Stinner.
|
|
@ -44,8 +44,24 @@ hash_getfuncdef(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
hash_pointer(PyObject *Py_UNUSED(module), PyObject *arg)
|
||||||
|
{
|
||||||
|
void *ptr = PyLong_AsVoidPtr(arg);
|
||||||
|
if (ptr == NULL && PyErr_Occurred()) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_hash_t hash = Py_HashPointer(ptr);
|
||||||
|
Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash));
|
||||||
|
return PyLong_FromLongLong(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyMethodDef test_methods[] = {
|
static PyMethodDef test_methods[] = {
|
||||||
{"hash_getfuncdef", hash_getfuncdef, METH_NOARGS},
|
{"hash_getfuncdef", hash_getfuncdef, METH_NOARGS},
|
||||||
|
{"hash_pointer", hash_pointer, METH_O},
|
||||||
{NULL},
|
{NULL},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
#include "pycore_hashtable.h"
|
#include "pycore_hashtable.h" // export _Py_hashtable_new()
|
||||||
#include "pycore_pyhash.h" // _Py_HashPointerRaw()
|
#include "pycore_pyhash.h" // _Py_HashPointerRaw()
|
||||||
|
|
||||||
#define HASHTABLE_MIN_SIZE 16
|
#define HASHTABLE_MIN_SIZE 16
|
||||||
|
|
|
@ -83,8 +83,6 @@ static Py_ssize_t hashstats[Py_HASH_STATS_MAX + 1] = {0};
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Py_hash_t _Py_HashPointer(const void *);
|
|
||||||
|
|
||||||
Py_hash_t
|
Py_hash_t
|
||||||
_Py_HashDouble(PyObject *inst, double v)
|
_Py_HashDouble(PyObject *inst, double v)
|
||||||
{
|
{
|
||||||
|
@ -132,23 +130,13 @@ _Py_HashDouble(PyObject *inst, double v)
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_hash_t
|
Py_hash_t
|
||||||
_Py_HashPointerRaw(const void *p)
|
Py_HashPointer(const void *ptr)
|
||||||
{
|
{
|
||||||
size_t y = (size_t)p;
|
Py_hash_t hash = _Py_HashPointerRaw(ptr);
|
||||||
/* bottom 3 or 4 bits are likely to be 0; rotate y by 4 to avoid
|
if (hash == -1) {
|
||||||
excessive hash collisions for dicts and sets */
|
hash = -2;
|
||||||
y = (y >> 4) | (y << (8 * SIZEOF_VOID_P - 4));
|
|
||||||
return (Py_hash_t)y;
|
|
||||||
}
|
|
||||||
|
|
||||||
Py_hash_t
|
|
||||||
_Py_HashPointer(const void *p)
|
|
||||||
{
|
|
||||||
Py_hash_t x = _Py_HashPointerRaw(p);
|
|
||||||
if (x == -1) {
|
|
||||||
x = -2;
|
|
||||||
}
|
}
|
||||||
return x;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_hash_t
|
Py_hash_t
|
||||||
|
|
Loading…
Reference in New Issue