diff --git a/Doc/c-api/hash.rst b/Doc/c-api/hash.rst
new file mode 100644
index 00000000000000..4dc121d7fbaa9b
--- /dev/null
+++ b/Doc/c-api/hash.rst
@@ -0,0 +1,48 @@
+.. highlight:: c
+
+PyHash API
+----------
+
+See also the :c:member:`PyTypeObject.tp_hash` member.
+
+.. c:type:: Py_hash_t
+
+ Hash value type: signed integer.
+
+ .. versionadded:: 3.2
+
+.. c:type:: Py_uhash_t
+
+ Hash value type: unsigned integer.
+
+ .. versionadded:: 3.2
+
+
+.. c:type:: PyHash_FuncDef
+
+ Hash function definition used by :c:func:`PyHash_GetFuncDef`.
+
+ .. c::member:: Py_hash_t (*const hash)(const void *, Py_ssize_t)
+
+ Hash function.
+
+ .. c:member:: const char *name
+
+ Hash function name (UTF-8 encoded string).
+
+ .. c:member:: const int hash_bits
+
+ Internal size of the hash value in bits.
+
+ .. c:member:: const int seed_bits
+
+ Size of seed input in bits.
+
+ .. versionadded:: 3.4
+
+
+.. c:function:: PyHash_FuncDef* PyHash_GetFuncDef(void)
+
+ Get the hash function definition.
+
+ .. versionadded:: 3.4
diff --git a/Doc/c-api/utilities.rst b/Doc/c-api/utilities.rst
index ccbf14e1850f68..48ae54acebe887 100644
--- a/Doc/c-api/utilities.rst
+++ b/Doc/c-api/utilities.rst
@@ -17,6 +17,7 @@ and parsing function arguments and constructing Python values from C values.
marshal.rst
arg.rst
conversion.rst
+ hash.rst
reflection.rst
codec.rst
perfmaps.rst
diff --git a/Lib/test/test_capi/test_hash.py b/Lib/test/test_capi/test_hash.py
new file mode 100644
index 00000000000000..59dec15bc21445
--- /dev/null
+++ b/Lib/test/test_capi/test_hash.py
@@ -0,0 +1,33 @@
+import sys
+import unittest
+from test.support import import_helper
+_testcapi = import_helper.import_module('_testcapi')
+
+
+SIZEOF_PY_HASH_T = _testcapi.SIZEOF_VOID_P
+
+
+class CAPITest(unittest.TestCase):
+ def test_hash_getfuncdef(self):
+ # Test PyHash_GetFuncDef()
+ hash_getfuncdef = _testcapi.hash_getfuncdef
+ func_def = hash_getfuncdef()
+
+ match func_def.name:
+ case "fnv":
+ self.assertEqual(func_def.hash_bits, 8 * SIZEOF_PY_HASH_T)
+ self.assertEqual(func_def.seed_bits, 16 * SIZEOF_PY_HASH_T)
+ case "siphash13":
+ self.assertEqual(func_def.hash_bits, 64)
+ self.assertEqual(func_def.seed_bits, 128)
+ case "siphash24":
+ self.assertEqual(func_def.hash_bits, 64)
+ self.assertEqual(func_def.seed_bits, 128)
+ case _:
+ self.fail(f"unknown function name: {func_def.name!r}")
+
+ # compare with sys.hash_info
+ hash_info = sys.hash_info
+ self.assertEqual(func_def.name, hash_info.algorithm)
+ self.assertEqual(func_def.hash_bits, hash_info.hash_bits)
+ self.assertEqual(func_def.seed_bits, hash_info.seed_bits)
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index d144ad312ef560..54650ea9c1d4ac 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -159,7 +159,7 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
-@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c
+@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c _testcapi/hash.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
diff --git a/Modules/_testcapi/hash.c b/Modules/_testcapi/hash.c
new file mode 100644
index 00000000000000..d0b8127020c5c1
--- /dev/null
+++ b/Modules/_testcapi/hash.c
@@ -0,0 +1,56 @@
+#include "parts.h"
+#include "util.h"
+
+static PyObject *
+hash_getfuncdef(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ // bind PyHash_GetFuncDef()
+ PyHash_FuncDef *def = PyHash_GetFuncDef();
+
+ PyObject *types = PyImport_ImportModule("types");
+ if (types == NULL) {
+ return NULL;
+ }
+
+ PyObject *result = PyObject_CallMethod(types, "SimpleNamespace", NULL);
+ Py_DECREF(types);
+ if (result == NULL) {
+ return NULL;
+ }
+
+ // ignore PyHash_FuncDef.hash
+
+ PyObject *value = PyUnicode_FromString(def->name);
+ int res = PyObject_SetAttrString(result, "name", value);
+ Py_DECREF(value);
+ if (res < 0) {
+ return NULL;
+ }
+
+ value = PyLong_FromLong(def->hash_bits);
+ res = PyObject_SetAttrString(result, "hash_bits", value);
+ Py_DECREF(value);
+ if (res < 0) {
+ return NULL;
+ }
+
+ value = PyLong_FromLong(def->seed_bits);
+ res = PyObject_SetAttrString(result, "seed_bits", value);
+ Py_DECREF(value);
+ if (res < 0) {
+ return NULL;
+ }
+
+ return result;
+}
+
+static PyMethodDef test_methods[] = {
+ {"hash_getfuncdef", hash_getfuncdef, METH_NOARGS},
+ {NULL},
+};
+
+int
+_PyTestCapi_Init_Hash(PyObject *m)
+{
+ return PyModule_AddFunctions(m, test_methods);
+}
diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h
index 9af414e10d0160..29817edd69b134 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -58,6 +58,7 @@ int _PyTestCapi_Init_Codec(PyObject *module);
int _PyTestCapi_Init_Immortal(PyObject *module);
int _PyTestCapi_Init_GC(PyObject *module);
int _PyTestCapi_Init_Sys(PyObject *module);
+int _PyTestCapi_Init_Hash(PyObject *module);
int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
int _PyTestCapi_Init_HeaptypeRelative(PyObject *module);
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 300025ce3705fe..999bd866f14814 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -3995,6 +3995,9 @@ PyInit__testcapi(void)
if (_PyTestCapi_Init_HeaptypeRelative(m) < 0) {
return NULL;
}
+ if (_PyTestCapi_Init_Hash(m) < 0) {
+ return NULL;
+ }
PyState_AddModule(m, &_testcapimodule);
return m;
diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj
index 2d8a652a84b92d..1c15541d3ec735 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -124,6 +124,7 @@
+
diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters
index 573e54d56a2020..6059959bb9a040 100644
--- a/PCbuild/_testcapi.vcxproj.filters
+++ b/PCbuild/_testcapi.vcxproj.filters
@@ -102,6 +102,9 @@
Source Files
+
+ Source Files
+
Source Files