Skip to content

Commit

Permalink
gh-116417: Add _testlimitedcapi C extension (#116419)
Browse files Browse the repository at this point in the history
Add a new C extension "_testlimitedcapi" which is only built with the
limited C API.

Move heaptype_relative.c and vectorcall_limited.c from
Modules/_testcapi/ to Modules/_testlimitedcapi/.

* configure: add _testlimitedcapi test extension.
* Update generate_stdlib_module_names.py.
* Update make check-c-globals.

Co-authored-by: Erlend E. Aasland <[email protected]>
  • Loading branch information
vstinner and erlend-aasland authored Mar 7, 2024
1 parent d9ccde2 commit d9bcdda
Show file tree
Hide file tree
Showing 23 changed files with 394 additions and 109 deletions.
3 changes: 2 additions & 1 deletion Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1153,8 +1153,9 @@ def refcount_test(test):
def requires_limited_api(test):
try:
import _testcapi
import _testlimitedcapi
except ImportError:
return unittest.skip('needs _testcapi module')(test)
return unittest.skip('needs _testcapi and _testlimitedcapi modules')(test)
return test

def requires_specialization(test):
Expand Down
10 changes: 7 additions & 3 deletions Lib/test/test_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
import _testcapi
except ImportError:
_testcapi = None
try:
import _testlimitedcapi
except ImportError:
_testlimitedcapi = None
import struct
import collections
import itertools
Expand Down Expand Up @@ -837,12 +841,12 @@ def get_a(x):
@requires_limited_api
def test_vectorcall_limited_incoming(self):
from _testcapi import pyobject_vectorcall
obj = _testcapi.LimitedVectorCallClass()
obj = _testlimitedcapi.LimitedVectorCallClass()
self.assertEqual(pyobject_vectorcall(obj, (), ()), "vectorcall called")

@requires_limited_api
def test_vectorcall_limited_outgoing(self):
from _testcapi import call_vectorcall
from _testlimitedcapi import call_vectorcall

args_captured = []
kwargs_captured = []
Expand All @@ -858,7 +862,7 @@ def f(*args, **kwargs):

@requires_limited_api
def test_vectorcall_limited_outgoing_method(self):
from _testcapi import call_vectorcall_method
from _testlimitedcapi import call_vectorcall_method

args_captured = []
kwargs_captured = []
Expand Down
54 changes: 33 additions & 21 deletions Lib/test/test_capi/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
# Skip this test if the _testcapi module isn't available.
_testcapi = import_helper.import_module('_testcapi')

import _testlimitedcapi
import _testinternalcapi


Expand Down Expand Up @@ -1124,7 +1125,7 @@ def test_heaptype_relative_sizes(self):
# Test subclassing using "relative" basicsize, see PEP 697
def check(extra_base_size, extra_size):
Base, Sub, instance, data_ptr, data_offset, data_size = (
_testcapi.make_sized_heaptypes(
_testlimitedcapi.make_sized_heaptypes(
extra_base_size, -extra_size))

# no alignment shenanigans when inheriting directly
Expand Down Expand Up @@ -1152,11 +1153,11 @@ def check(extra_base_size, extra_size):

# we don't reserve (requested + alignment) or more data
self.assertLess(data_size - extra_size,
_testcapi.ALIGNOF_MAX_ALIGN_T)
_testlimitedcapi.ALIGNOF_MAX_ALIGN_T)

# The offsets/sizes we calculated should be aligned.
self.assertEqual(data_offset % _testcapi.ALIGNOF_MAX_ALIGN_T, 0)
self.assertEqual(data_size % _testcapi.ALIGNOF_MAX_ALIGN_T, 0)
self.assertEqual(data_offset % _testlimitedcapi.ALIGNOF_MAX_ALIGN_T, 0)
self.assertEqual(data_size % _testlimitedcapi.ALIGNOF_MAX_ALIGN_T, 0)

sizes = sorted({0, 1, 2, 3, 4, 7, 8, 123,
object.__basicsize__,
Expand All @@ -1182,7 +1183,7 @@ def test_heaptype_inherit_itemsize(self):
object.__basicsize__+1})
for extra_size in sizes:
with self.subTest(extra_size=extra_size):
Sub = _testcapi.subclass_var_heaptype(
Sub = _testlimitedcapi.subclass_var_heaptype(
_testcapi.HeapCCollection, -extra_size, 0, 0)
collection = Sub(1, 2, 3)
collection.set_data_to_3s()
Expand All @@ -1196,7 +1197,7 @@ def test_heaptype_invalid_inheritance(self):
with self.assertRaises(SystemError,
msg="Cannot extend variable-size class without "
+ "Py_TPFLAGS_ITEMS_AT_END"):
_testcapi.subclass_heaptype(int, -8, 0)
_testlimitedcapi.subclass_heaptype(int, -8, 0)

def test_heaptype_relative_members(self):
"""Test HeapCCollection subclasses work properly"""
Expand All @@ -1209,7 +1210,7 @@ def test_heaptype_relative_members(self):
for offset in sizes:
with self.subTest(extra_base_size=extra_base_size, extra_size=extra_size, offset=offset):
if offset < extra_size:
Sub = _testcapi.make_heaptype_with_member(
Sub = _testlimitedcapi.make_heaptype_with_member(
extra_base_size, -extra_size, offset, True)
Base = Sub.mro()[1]
instance = Sub()
Expand All @@ -1228,29 +1229,29 @@ def test_heaptype_relative_members(self):
instance.set_memb_relative(0)
else:
with self.assertRaises(SystemError):
Sub = _testcapi.make_heaptype_with_member(
Sub = _testlimitedcapi.make_heaptype_with_member(
extra_base_size, -extra_size, offset, True)
with self.assertRaises(SystemError):
Sub = _testcapi.make_heaptype_with_member(
Sub = _testlimitedcapi.make_heaptype_with_member(
extra_base_size, extra_size, offset, True)
with self.subTest(extra_base_size=extra_base_size, extra_size=extra_size):
with self.assertRaises(SystemError):
Sub = _testcapi.make_heaptype_with_member(
Sub = _testlimitedcapi.make_heaptype_with_member(
extra_base_size, -extra_size, -1, True)

def test_heaptype_relative_members_errors(self):
with self.assertRaisesRegex(
SystemError,
r"With Py_RELATIVE_OFFSET, basicsize must be negative"):
_testcapi.make_heaptype_with_member(0, 1234, 0, True)
_testlimitedcapi.make_heaptype_with_member(0, 1234, 0, True)
with self.assertRaisesRegex(
SystemError, r"Member offset out of range \(0\.\.-basicsize\)"):
_testcapi.make_heaptype_with_member(0, -8, 1234, True)
_testlimitedcapi.make_heaptype_with_member(0, -8, 1234, True)
with self.assertRaisesRegex(
SystemError, r"Member offset out of range \(0\.\.-basicsize\)"):
_testcapi.make_heaptype_with_member(0, -8, -1, True)
_testlimitedcapi.make_heaptype_with_member(0, -8, -1, True)

Sub = _testcapi.make_heaptype_with_member(0, -8, 0, True)
Sub = _testlimitedcapi.make_heaptype_with_member(0, -8, 0, True)
instance = Sub()
with self.assertRaisesRegex(
SystemError, r"PyMember_GetOne used with Py_RELATIVE_OFFSET"):
Expand Down Expand Up @@ -2264,10 +2265,19 @@ def test_gilstate_matches_current(self):
_testcapi.test_current_tstate_matches()


def get_test_funcs(mod, exclude_prefix=None):
funcs = {}
for name in dir(mod):
if not name.startswith('test_'):
continue
if exclude_prefix is not None and name.startswith(exclude_prefix):
continue
funcs[name] = getattr(mod, name)
return funcs


class Test_testcapi(unittest.TestCase):
locals().update((name, getattr(_testcapi, name))
for name in dir(_testcapi)
if name.startswith('test_'))
locals().update(get_test_funcs(_testcapi))

# Suppress warning from PyUnicode_FromUnicode().
@warnings_helper.ignore_warnings(category=DeprecationWarning)
Expand All @@ -2278,11 +2288,13 @@ def test_version_api_data(self):
self.assertEqual(_testcapi.Py_Version, sys.hexversion)


class Test_testlimitedcapi(unittest.TestCase):
locals().update(get_test_funcs(_testlimitedcapi))


class Test_testinternalcapi(unittest.TestCase):
locals().update((name, getattr(_testinternalcapi, name))
for name in dir(_testinternalcapi)
if name.startswith('test_')
and not name.startswith('test_lock_'))
locals().update(get_test_funcs(_testinternalcapi,
exclude_prefix='test_lock_'))


@threading_helper.requires_working_threading()
Expand Down
3 changes: 2 additions & 1 deletion Modules/Setup.stdlib.in
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@
@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 _testcapi/hash.c _testcapi/time.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.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/gc.c _testcapi/sys.c _testcapi/hash.c _testcapi/time.c
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/heaptype_relative.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c

Expand Down
20 changes: 0 additions & 20 deletions Modules/_testcapi/clinic/vectorcall_limited.c.h

This file was deleted.

3 changes: 0 additions & 3 deletions Modules/_testcapi/parts.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,4 @@ int _PyTestCapi_Init_Sys(PyObject *module);
int _PyTestCapi_Init_Hash(PyObject *module);
int _PyTestCapi_Init_Time(PyObject *module);

int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
int _PyTestCapi_Init_HeaptypeRelative(PyObject *module);

#endif // Py_TESTCAPI_PARTS_H
6 changes: 0 additions & 6 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -4098,12 +4098,6 @@ PyInit__testcapi(void)
if (_PyTestCapi_Init_PyAtomic(m) < 0) {
return NULL;
}
if (_PyTestCapi_Init_VectorcallLimited(m) < 0) {
return NULL;
}
if (_PyTestCapi_Init_HeaptypeRelative(m) < 0) {
return NULL;
}
if (_PyTestCapi_Init_Hash(m) < 0) {
return NULL;
}
Expand Down
36 changes: 36 additions & 0 deletions Modules/_testlimitedcapi.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Test the limited C API.
*
* The 'test_*' functions exported by this module are run as part of the
* standard Python regression test, via Lib/test/test_capi.py.
*/

#include "_testlimitedcapi/parts.h"

static PyMethodDef TestMethods[] = {
{NULL, NULL} /* sentinel */
};

static struct PyModuleDef _testlimitedcapimodule = {
PyModuleDef_HEAD_INIT,
.m_name = "_testlimitedcapi",
.m_size = 0,
.m_methods = TestMethods,
};

PyMODINIT_FUNC
PyInit__testlimitedcapi(void)
{
PyObject *mod = PyModule_Create(&_testlimitedcapimodule);
if (mod == NULL) {
return NULL;
}

if (_PyTestCapi_Init_VectorcallLimited(mod) < 0) {
return NULL;
}
if (_PyTestCapi_Init_HeaptypeRelative(mod) < 0) {
return NULL;
}
return mod;
}
20 changes: 20 additions & 0 deletions Modules/_testlimitedcapi/clinic/vectorcall_limited.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
#include "pyconfig.h" // Py_GIL_DISABLED

#ifndef Py_GIL_DISABLED
#define Py_LIMITED_API 0x030c0000 // 3.12
#endif

#include "parts.h"
#include <stddef.h> // max_align_t
#include <string.h> // memset
Expand Down
27 changes: 27 additions & 0 deletions Modules/_testlimitedcapi/parts.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#ifndef Py_TESTLIMITEDCAPI_PARTS_H
#define Py_TESTLIMITEDCAPI_PARTS_H

// Always enable assertions
#undef NDEBUG

#include "pyconfig.h" // Py_GIL_DISABLED

// Use the limited C API
#ifndef Py_GIL_DISABLED
# define Py_LIMITED_API 0x030c0000 // 3.12
#endif

// Make sure that the internal C API cannot be used.
#undef Py_BUILD_CORE_MODULE
#undef Py_BUILD_CORE_BUILTIN

#include "Python.h"

#ifdef Py_BUILD_CORE
# error "Py_BUILD_CORE macro must not be defined"
#endif

int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
int _PyTestCapi_Init_HeaptypeRelative(PyObject *module);

#endif // Py_TESTLIMITEDCAPI_PARTS_H
Loading

0 comments on commit d9bcdda

Please sign in to comment.