Skip to content

Commit 3b6aa96

Browse files
committed
Add PyABIInfo
1 parent 72349ba commit 3b6aa96

6 files changed

Lines changed: 406 additions & 48 deletions

File tree

Include/modsupport.h

Lines changed: 72 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -56,55 +56,14 @@ PyAPI_FUNC(int) PyModule_ExecDef(PyObject *module, PyModuleDef *def);
5656

5757
#define Py_CLEANUP_SUPPORTED 0x20000
5858

59+
/* The API and ABI versions are left for backwards compatibility.
60+
They've not been updated since 2006 and 2010, respectively.
61+
API/ABI versioning is now tied to the CPython version.
62+
The *_VERSION and *_STRING symbols should define the same value; as
63+
number and string literal respectively. Make sure the definitions match.
64+
*/
5965
#define PYTHON_API_VERSION 1013
6066
#define PYTHON_API_STRING "1013"
61-
/* The API version is maintained (independently from the Python version)
62-
so we can detect mismatches between the interpreter and dynamically
63-
loaded modules. These are diagnosed by an error message but
64-
the module is still loaded (because the mismatch can only be tested
65-
after loading the module). The error message is intended to
66-
explain the core dump a few seconds later.
67-
68-
The symbol PYTHON_API_STRING defines the same value as a string
69-
literal. *** PLEASE MAKE SURE THE DEFINITIONS MATCH. ***
70-
71-
Please add a line or two to the top of this log for each API
72-
version change:
73-
74-
22-Feb-2006 MvL 1013 PEP 353 - long indices for sequence lengths
75-
76-
19-Aug-2002 GvR 1012 Changes to string object struct for
77-
interning changes, saving 3 bytes.
78-
79-
17-Jul-2001 GvR 1011 Descr-branch, just to be on the safe side
80-
81-
25-Jan-2001 FLD 1010 Parameters added to PyCode_New() and
82-
PyFrame_New(); Python 2.1a2
83-
84-
14-Mar-2000 GvR 1009 Unicode API added
85-
86-
3-Jan-1999 GvR 1007 Decided to change back! (Don't reuse 1008!)
87-
88-
3-Dec-1998 GvR 1008 Python 1.5.2b1
89-
90-
18-Jan-1997 GvR 1007 string interning and other speedups
91-
92-
11-Oct-1996 GvR renamed Py_Ellipses to Py_Ellipsis :-(
93-
94-
30-Jul-1996 GvR Slice and ellipses syntax added
95-
96-
23-Jul-1996 GvR For 1.4 -- better safe than sorry this time :-)
97-
98-
7-Nov-1995 GvR Keyword arguments (should've been done at 1.3 :-( )
99-
100-
10-Jan-1995 GvR Renamed globals to new naming scheme
101-
102-
9-Jan-1995 GvR Initial version (incompatible with older API)
103-
*/
104-
105-
/* The PYTHON_ABI_VERSION is introduced in PEP 384. For the lifetime of
106-
Python 3, it will stay at the value of 3; changes to the limited API
107-
must be performed in a strictly backwards-compatible manner. */
10867
#define PYTHON_ABI_VERSION 3
10968
#define PYTHON_ABI_STRING "3"
11069

@@ -134,6 +93,72 @@ PyAPI_FUNC(PyObject *) PyModule_FromDefAndSpec2(PyModuleDef *def,
13493

13594
#endif /* New in 3.5 */
13695

96+
/* ABI info & checking (new in 3.15) */
97+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15)
98+
typedef struct PyABIInfo {
99+
uint8_t abiinfo_major_version;
100+
uint8_t abiinfo_minor_version;
101+
uint16_t flags;
102+
uint32_t build_version;
103+
uint32_t abi_version;
104+
} PyABIInfo;
105+
#define PyABIInfo_STABLE 0x0001
106+
#define PyABIInfo_GIL 0x0002
107+
#define PyABIInfo_FREETHREADED 0x0004
108+
#define PyABIInfo_INTERNAL 0x0008
109+
110+
#define PyABIInfo_FREETHREADING_AGNOSTIC (PyABIInfo_GIL|PyABIInfo_FREETHREADED)
111+
112+
PyAPI_FUNC(int) PyABIInfo_Check(PyABIInfo *info, const char *module_name);
113+
114+
// Define the defaults
115+
#ifdef Py_LIMITED_API
116+
#define _PyABIInfo_DEFAULT_FLAG_STABLE PyABIInfo_STABLE
117+
#if Py_LIMITED_API == 3
118+
#define PyABIInfo_DEFAULT_ABI_VERSION _Py_PACK_VERSION(3, 2)
119+
#else
120+
#define PyABIInfo_DEFAULT_ABI_VERSION Py_LIMITED_API
121+
#endif
122+
#else
123+
#define _PyABIInfo_DEFAULT_FLAG_STABLE 0
124+
#define PyABIInfo_DEFAULT_ABI_VERSION PY_VERSION_HEX
125+
#endif
126+
#if defined(Py_LIMITED_API) && defined(_Py_OPAQUE_PYOBJECT)
127+
#define _PyABIInfo_DEFAULT_FLAG_FT PyABIInfo_FREETHREADING_AGNOSTIC
128+
#elif defined(Py_GIL_DISABLED)
129+
#define _PyABIInfo_DEFAULT_FLAG_FT PyABIInfo_FREETHREADED
130+
#else
131+
#define _PyABIInfo_DEFAULT_FLAG_FT PyABIInfo_GIL
132+
#endif
133+
#if defined(Py_BUILD_CORE)
134+
#define _PyABIInfo_DEFAULT_FLAG_INTERNAL PyABIInfo_INTERNAL
135+
#else
136+
#define _PyABIInfo_DEFAULT_FLAG_INTERNAL 0
137+
#endif
138+
139+
#define PyABIInfo_DEFAULT_FLAGS ( \
140+
_PyABIInfo_DEFAULT_FLAG_STABLE \
141+
| _PyABIInfo_DEFAULT_FLAG_FT \
142+
| _PyABIInfo_DEFAULT_FLAG_INTERNAL \
143+
) \
144+
/////////////////////////////////////////////////////////
145+
146+
#define _PyABIInfo_DEFAULT() { \
147+
1, 0, \
148+
PyABIInfo_DEFAULT_FLAGS, \
149+
PY_VERSION_HEX, \
150+
PyABIInfo_DEFAULT_ABI_VERSION } \
151+
/////////////////////////////////////////////////////////
152+
153+
#define PyABIInfo_VAR(NAME) \
154+
static PyABIInfo NAME = _PyABIInfo_DEFAULT;
155+
156+
#undef _PyABIInfo_DEFAULT_STABLE
157+
#undef _PyABIInfo_DEFAULT_FT
158+
#undef _PyABIInfo_DEFAULT_INTERNAL
159+
160+
#endif /* ABI info (new in 3.15) */
161+
137162
#ifndef Py_LIMITED_API
138163
# define Py_CPYTHON_MODSUPPORT_H
139164
# include "cpython/modsupport.h"

Include/moduleobject.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,13 @@ struct PyModuleDef_Slot {
8181
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000
8282
# define Py_mod_gil 4
8383
#endif
84+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15)
85+
# define Py_mod_abi 5
86+
#endif
8487

8588

8689
#ifndef Py_LIMITED_API
87-
#define _Py_mod_LAST_SLOT 4
90+
#define _Py_mod_LAST_SLOT 5
8891
#endif
8992

9093
#endif /* New in 3.5 */
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import sys
2+
import unittest
3+
import sysconfig
4+
5+
from test.support import subTests
6+
from test.support import import_helper
7+
8+
_testcapi = import_helper.import_module('_testcapi')
9+
10+
11+
class Test_ABIInfo_Check(unittest.TestCase):
12+
@subTests('modname', (None, 'test_mod'))
13+
def test_zero(self, modname):
14+
_testcapi.pyabiinfo_check(modname, 0, 0, 0, 0, 0)
15+
_testcapi.pyabiinfo_check(modname, 1, 0, 0, 0, 0)
16+
17+
def test_large_major_version(self):
18+
with self.assertRaisesRegex(ImportError,
19+
'^PyABIInfo version too high$'):
20+
_testcapi.pyabiinfo_check(None, 2, 0, 0, 0, 0)
21+
with self.assertRaisesRegex(ImportError,
22+
'^test_mod: PyABIInfo version too high$'):
23+
_testcapi.pyabiinfo_check("test_mod", 2, 0, 0, 0, 0)
24+
25+
@subTests('modname', (None, 'test_mod'))
26+
def test_large_minor_version(self, modname):
27+
_testcapi.pyabiinfo_check(modname, 1, 2, 0, 0, 0)
28+
29+
@subTests('modname', (None, 'test_mod'))
30+
@subTests('major', (0, 1))
31+
@subTests('minor', (0, 1, 9))
32+
@subTests('build', (0, sys.hexversion))
33+
def test_positive_regular(self, modname, major, minor, build):
34+
ver = sys.hexversion
35+
truncated = ver & 0xffff0000
36+
filled = truncated | 0x12b8
37+
maxed = truncated | 0xffff
38+
for abi_version in (0, ver, truncated, filled, maxed):
39+
with self.subTest(abi_version=abi_version):
40+
_testcapi.pyabiinfo_check(modname, major, minor, 0,
41+
build, abi_version)
42+
43+
@subTests('modname', (None, 'test_mod'))
44+
@subTests('minor', (0, 1, 9))
45+
@subTests('build', (0, sys.hexversion))
46+
@subTests('offset', (+0x00010000, -0x00010000))
47+
def test_negative_regular(self, modname, minor, build, offset):
48+
ver = sys.hexversion + offset
49+
truncated = ver & 0xffff0000
50+
filled = truncated | 0x12b8
51+
maxed = truncated | 0xffff
52+
for abi_version in (ver, truncated, filled, maxed):
53+
with self.subTest(abi_version=abi_version):
54+
with self.assertRaisesRegex(
55+
ImportError,
56+
r'incompatible ABI version \(3\.\d+\)$'):
57+
_testcapi.pyabiinfo_check(modname, 1, minor, 0,
58+
build,
59+
abi_version)
60+
61+
@subTests('modname', (None, 'test_mod'))
62+
@subTests('major', (0, 1))
63+
@subTests('minor', (0, 1, 9))
64+
@subTests('build', (0, sys.hexversion))
65+
@subTests('abi_version', (
66+
0,
67+
0x03020000,
68+
sys.hexversion,
69+
sys.hexversion & 0xffff0000,
70+
sys.hexversion - 0x00010000,
71+
))
72+
def test_positive_stable(self, modname, major, minor, build, abi_version):
73+
_testcapi.pyabiinfo_check(modname, major, minor,
74+
_testcapi.PyABIInfo_STABLE,
75+
build,
76+
abi_version)
77+
78+
@subTests('modname', (None, 'test_mod'))
79+
@subTests('minor', (0, 1, 9))
80+
@subTests('build', (0, sys.hexversion))
81+
@subTests('abi_version_and_msg', (
82+
(1, 'invalid'),
83+
(3, 'invalid'),
84+
(0x0301ffff, 'invalid'),
85+
((sys.hexversion & 0xffff0000) + 0x00010000, 'incompatible future'),
86+
(sys.hexversion + 0x00010000, 'incompatible future'),
87+
(0x04000000, 'incompatible future'),
88+
))
89+
def test_negative_stable(self, modname, minor, build, abi_version_and_msg):
90+
abi_version, msg = abi_version_and_msg
91+
with self.assertRaisesRegex(
92+
ImportError,
93+
rf'{msg} stable ABI version \(\d+\.\d+\)$'):
94+
_testcapi.pyabiinfo_check(modname, 1, minor,
95+
_testcapi.PyABIInfo_STABLE,
96+
build,
97+
abi_version)
98+
99+
@subTests('modname', (None, 'test_mod'))
100+
@subTests('major', (0, 1))
101+
@subTests('minor', (0, 1, 9))
102+
@subTests('build', (0, sys.hexversion))
103+
@subTests('abi_version', (0, sys.hexversion))
104+
def test_positive_internal(self, modname, major, minor, build, abi_version):
105+
_testcapi.pyabiinfo_check(modname, major, minor,
106+
_testcapi.PyABIInfo_INTERNAL,
107+
build,
108+
abi_version)
109+
110+
@subTests('modname', (None, 'test_mod'))
111+
@subTests('minor', (0, 1, 9))
112+
@subTests('build', (0, sys.hexversion))
113+
@subTests('abi_version', (
114+
sys.hexversion - 0x00010000,
115+
sys.hexversion - 1,
116+
sys.hexversion + 1,
117+
sys.hexversion + 0x00010000,
118+
))
119+
def test_negative_internal(self, modname, minor, build, abi_version):
120+
with self.assertRaisesRegex(
121+
ImportError,
122+
r'incompatible internal ABI \(0x[\da-f]+ != 0x[\da-f]+\)$'):
123+
_testcapi.pyabiinfo_check(modname, 1, minor,
124+
_testcapi.PyABIInfo_INTERNAL,
125+
build,
126+
abi_version)
127+
128+
@subTests('modname', (None, 'test_mod'))
129+
@subTests('minor', (0, 1, 9))
130+
@subTests('build', (0, sys.hexversion))
131+
@subTests('abi_version', (
132+
sys.hexversion - 0x00010000,
133+
sys.hexversion - 1,
134+
sys.hexversion + 1,
135+
sys.hexversion + 0x00010000,
136+
))
137+
def test_negative_internal(self, modname, minor, build, abi_version):
138+
with self.assertRaisesRegex(
139+
ImportError,
140+
r'incompatible internal ABI \(0x[\da-f]+ != 0x[\da-f]+\)$'):
141+
_testcapi.pyabiinfo_check(modname, 1, minor,
142+
_testcapi.PyABIInfo_INTERNAL,
143+
build,
144+
abi_version)
145+
146+
@subTests('modname', (None, 'test_mod'))
147+
@subTests('minor', (0, 1, 9))
148+
@subTests('build', (0, sys.hexversion))
149+
@subTests('ft_flag', (
150+
0,
151+
(_testcapi.PyABIInfo_FREETHREADED
152+
if sysconfig.get_config_var("Py_GIL_DISABLED")
153+
else _testcapi.PyABIInfo_GIL),
154+
_testcapi.PyABIInfo_FREETHREADING_AGNOSTIC,
155+
))
156+
def test_positive_freethreading(self, modname, minor, build, ft_flag):
157+
self.assertEqual(ft_flag & _testcapi.PyABIInfo_FREETHREADING_AGNOSTIC,
158+
ft_flag)
159+
_testcapi.pyabiinfo_check(modname, 1, minor, ft_flag, build, 0)
160+
161+
@subTests('modname', (None, 'test_mod'))
162+
@subTests('minor', (0, 1, 9))
163+
@subTests('build', (0, sys.hexversion))
164+
def test_negative_freethreading(self, modname, minor, build):
165+
if sysconfig.get_config_var("Py_GIL_DISABLED"):
166+
ft_flag = _testcapi.PyABIInfo_GIL
167+
msg = "incompatible with free-threaded CPython"
168+
else:
169+
ft_flag = _testcapi.PyABIInfo_FREETHREADED
170+
msg = "only compatible with free-threaded CPython"
171+
with self.assertRaisesRegex(ImportError, msg):
172+
_testcapi.pyabiinfo_check(modname, 1, minor, ft_flag, build, 0)

Modules/_testcapi/modsupport.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,49 @@
22

33

44

5+
static PyObject *
6+
pyabiinfo_check(PyObject *Py_UNUSED(module), PyObject *args)
7+
{
8+
const char *modname;
9+
unsigned long maj, min, flags, buildver, abiver;
10+
11+
if (!PyArg_ParseTuple(args, "zkkkkk",
12+
&modname, &maj, &min, &flags, &buildver, &abiver))
13+
{
14+
return NULL;
15+
}
16+
PyABIInfo info = {maj, min, flags, buildver, abiver};
17+
if (PyABIInfo_Check(&info, modname) < 0) {
18+
return NULL;
19+
}
20+
Py_RETURN_NONE;
21+
}
22+
23+
static PyMethodDef TestMethods[] = {
24+
{"pyabiinfo_check", pyabiinfo_check, METH_VARARGS},
25+
{NULL},
26+
};
27+
528
int
629
_PyTestCapi_Init_Modsupport(PyObject *m)
730
{
31+
if (PyModule_AddIntMacro(m, PyABIInfo_STABLE) < 0) {
32+
return -1;
33+
}
34+
if (PyModule_AddIntMacro(m, PyABIInfo_INTERNAL) < 0) {
35+
return -1;
36+
}
37+
if (PyModule_AddIntMacro(m, PyABIInfo_GIL) < 0) {
38+
return -1;
39+
}
40+
if (PyModule_AddIntMacro(m, PyABIInfo_FREETHREADED) < 0) {
41+
return -1;
42+
}
43+
if (PyModule_AddIntMacro(m, PyABIInfo_FREETHREADING_AGNOSTIC) < 0) {
44+
return -1;
45+
}
46+
if (PyModule_AddFunctions(m, TestMethods) < 0) {
47+
return -1;
48+
}
849
return 0;
950
}

Objects/moduleobject.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,11 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
340340
gil_slot = cur_slot->value;
341341
has_gil_slot = 1;
342342
break;
343+
case Py_mod_abi:
344+
if (PyABIInfo_Check((PyABIInfo *)cur_slot->value, name) < 0) {
345+
goto error;
346+
}
347+
break;
343348
default:
344349
assert(cur_slot->slot < 0 || cur_slot->slot > _Py_mod_LAST_SLOT);
345350
PyErr_Format(
@@ -514,6 +519,7 @@ PyModule_ExecDef(PyObject *module, PyModuleDef *def)
514519
break;
515520
case Py_mod_multiple_interpreters:
516521
case Py_mod_gil:
522+
case Py_mod_abi:
517523
/* handled in PyModule_FromDefAndSpec2 */
518524
break;
519525
default:

0 commit comments

Comments
 (0)