Skip to content

Commit 2df0060

Browse files
Fix build on Python 3.14 by isolating internal C headers and removing inline specifiers (#412)
* Fix build on Python 3.14 by isolating internal C headers Python 3.14 introduces C++ templates in `dynamic_annotations.h` (included via `internal/pycore_interp.h`). This caused compilation errors ("template with C linkage") because these headers were included inside `extern "C"` blocks in `c_trace_callbacks.h`, which is required for Cython/C++ compatibility. This commit refactors the code to separate the interface from the implementation details: - Moved internal Python header includes and compatibility macros (for `_PyGC_FINALIZED`, `HAVE_STD_ATOMIC`, etc.) from `c_trace_callbacks.h` to `c_trace_callbacks.c`. - `c_trace_callbacks.c` is now compiled asFix build on Python 3.14 by isolating internal C headers Python 3.14 introduces C++ templates in `dynamic_annotations.h` (included via `internal/pycore_interp.h`). This caused compilation errors ("template with C linkage") because these headers were included inside `extern "C"` blocks in `c_trace_callbacks.h`, which is required for Cython/C++ compatibility. This commit refactors the code to separate the interface from the implementation details: - Moved internal Python header includes and compatibility macros (for `_PyGC_FINALIZED`, `HAVE_STD_ATOMIC`, etc.) from `c_trace_callbacks.h` to `c_trace_callbacks.c`. - `c_trace_callbacks.c` is now compiled as Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org> * Fix inline specifiers --------- Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org> Co-authored-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
1 parent 5eb4a00 commit 2df0060

3 files changed

Lines changed: 77 additions & 69 deletions

File tree

line_profiler/_line_profiler.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ ctypedef PyCodeObject *PyCodeObjectPtr
109109
#ctypedef unordered_map[int64, LastTime] LastTimeMap
110110
#ctypedef unordered_map[int64, LineTime] LineTimeMap
111111

112-
cdef extern from "c_trace_callbacks.c": # Legacy tracing
112+
cdef extern from "c_trace_callbacks.h": # Legacy tracing
113113
ctypedef unsigned long long Py_uintptr_t
114114

115115
ctypedef struct TraceCallback:

line_profiler/c_trace_callbacks.c

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,72 @@
11
#include "c_trace_callbacks.h"
22

3+
/*
4+
* XXX: would make better sense to declare `PyInterpreterState` in
5+
* "Python_wrapper.h", but the file declaring it causes all sorts of
6+
* trouble across various platforms and Python versions... so we handle it here.
7+
*
8+
* Note that we don't actually use `PyInterpreterState` directly -- we just
9+
* need its memory layout so that we can refer to its `.last_restart_version` member.
10+
*/
11+
12+
// _is -> PyInterpreterState
13+
#if PY_VERSION_HEX >= 0x030c00b1 // 3.12.0b6
14+
# ifndef Py_BUILD_CORE
15+
# define Py_BUILD_CORE 1
16+
# endif
17+
# if PY_VERSION_HEX < 0x030d0000 // 3.13
18+
/*
19+
* - Undefine the `_PyGC_FINALIZED()` macro which is removed in 3.13+
20+
* and causes problems in 3.12 (see CPython #105268, #105350, #107348)
21+
* - Undefine the `HAVE_STD_ATOMIC` macro, which causes problems on
22+
* Linux in 3.12 (see CPython #108216)
23+
* - Set `Py_ATOMIC_H` to true to circumvent the #include of
24+
* `include/pycore_atomic.h` (in `include/pycore_interp.h`, so that
25+
* problematic function definitions therein are replaced with dummy
26+
* ones (see #390); note that we still need to vendor in parts
27+
* therefrom which are used by `pycore_interp.h`, and its dependencies
28+
* `pycore_ceval_state.h` and `pycore_gil.h` (or at least mock them)
29+
*/
30+
# undef _PyGC_FINALIZED
31+
# ifdef __linux__
32+
# undef HAVE_STD_ATOMIC
33+
# endif
34+
# if (defined(_M_ARM) || defined(_M_ARM64)) && (! defined(Py_ATOMIC_H))
35+
# define Py_ATOMIC_H
36+
// Used in `pycore_interp.h`
37+
typedef struct _Py_atomic_address {
38+
volatile uintptr_t _value;
39+
} _Py_atomic_address;
40+
// Used in `pycore_gil.h` and `pycore_ceval_state.h`
41+
typedef struct _Py_atomic_int {
42+
volatile int _value;
43+
} _Py_atomic_int;
44+
/* Stub out macros in `pycore_atomic.h` used in macros in
45+
* `pycore_interp.h` (which aren't related to the
46+
* `struct _is` we need).
47+
* If any stub is referenced, fail the build with an
48+
* unresolved external.
49+
* This ensures we never ship wheels that "use" these
50+
* placeholders. */
51+
# ifdef _MSC_VER
52+
__declspec(dllimport) void lp_link_error__stubbed_cpython_atomic_LOAD_relaxed_was_used_this_is_a_bug(void);
53+
__declspec(dllimport) void lp_link_error__stubbed_cpython_atomic_STORE_relaxed_was_used_this_is_a_bug(void);
54+
# else
55+
extern void lp_link_error__stubbed_cpython_atomic_LOAD_relaxed_was_used_this_is_a_bug(void);
56+
extern void lp_link_error__stubbed_cpython_atomic_STORE_relaxed_was_used_this_is_a_bug(void);
57+
# endif
58+
# define _LP_ATOMIC_PANIC_LOAD_EXPR() (lp_link_error__stubbed_cpython_atomic_LOAD_relaxed_was_used_this_is_a_bug(), 0)
59+
# define _LP_ATOMIC_PANIC_STORE_STMT() do { lp_link_error__stubbed_cpython_atomic_STORE_relaxed_was_used_this_is_a_bug(); } while (0)
60+
// Panic-on-use shims (expression/statement forms)
61+
# undef _Py_atomic_load_relaxed
62+
# undef _Py_atomic_store_relaxed
63+
# define _Py_atomic_load_relaxed(obj) ((void)(obj), _LP_ATOMIC_PANIC_LOAD_EXPR())
64+
# define _Py_atomic_store_relaxed(obj, val) do { (void)(obj); (void)(val); _LP_ATOMIC_PANIC_STORE_STMT(); } while (0)
65+
# endif
66+
# endif
67+
# include "internal/pycore_interp.h"
68+
#endif
69+
370
#define CYTHON_MODULE "line_profiler._line_profiler"
471
#define DISABLE_CALLBACK "disable_line_events"
572
#define RAISE_IN_CALL(func_name, xc, const_msg) \
@@ -69,7 +136,7 @@ void restore_callback(TraceCallback *callback)
69136
return;
70137
}
71138

72-
inline int is_null_callback(TraceCallback *callback)
139+
static inline int is_null_callback(TraceCallback *callback)
73140
{
74141
return (
75142
callback == NULL
@@ -169,7 +236,7 @@ int call_callback(
169236
return result;
170237
}
171238

172-
inline void set_local_trace(PyObject *manager, PyFrameObject *py_frame)
239+
void set_local_trace(PyObject *manager, PyFrameObject *py_frame)
173240
{
174241
/* Set the frame-local trace callable:
175242
* - If there isn't one already, set it to `manager`;
@@ -205,7 +272,7 @@ inline void set_local_trace(PyObject *manager, PyFrameObject *py_frame)
205272
return;
206273
}
207274

208-
inline Py_uintptr_t monitoring_restart_version()
275+
Py_uintptr_t monitoring_restart_version()
209276
#if PY_VERSION_HEX >= 0x030c00b1 // 3.12.0b1
210277
{
211278
/* Get the `.last_restart_version` of the interpretor state.

line_profiler/c_trace_callbacks.h

Lines changed: 6 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,71 +4,8 @@
44
#include "Python_wrapper.h"
55
#include "frameobject.h"
66

7-
/*
8-
* XXX: would make better sense to declare `PyInterpreterState` in
9-
* "Python_wrapper.h", but the file declaring it causes all sorts of
10-
* trouble across various platforms and Python versions... so
11-
* - Only include the file if we are actually using it here, i.e. in
12-
* 3.12+, and
13-
* - Undefine the `_PyGC_FINALIZED()` macro which is removed in 3.13+
14-
* and causes problems in 3.12 (see CPython #105268, #105350, #107348)
15-
* - Undefine the `HAVE_STD_ATOMIC` macro, which causes problems on
16-
* Linux in 3.12 (see CPython #108216)
17-
* - Set `Py_ATOMIC_H` to true to circumvent the #include of
18-
* `include/pycore_atomic.h` (in `include/pycore_interp.h`, so that
19-
* problematic function definitions therein are replaced with dummy
20-
* ones (see #390); note that we still need to vendor in parts
21-
* therefrom which are used by `pycore_interp.h`, and its dependencies
22-
* `pycore_ceval_state.h` and `pycore_gil.h` (or at least mock them)
23-
* Note in any case that we don't actually use `PyInterpreterState`
24-
* directly -- we just need its memory layout so that we can refer to
25-
* its `.last_restart_version` member
26-
*/
27-
28-
// _is -> PyInterpreterState
29-
#if PY_VERSION_HEX >= 0x030c00b1 // 3.12.0b6
30-
# ifndef Py_BUILD_CORE
31-
# define Py_BUILD_CORE 1
32-
# endif
33-
# if PY_VERSION_HEX < 0x030d0000 // 3.13
34-
# undef _PyGC_FINALIZED
35-
# ifdef __linux__
36-
# undef HAVE_STD_ATOMIC
37-
# endif
38-
# if (defined(_M_ARM) || defined(_M_ARM64)) && (! defined(Py_ATOMIC_H))
39-
# define Py_ATOMIC_H
40-
// Used in `pycore_interp.h`
41-
typedef struct _Py_atomic_address {
42-
volatile uintptr_t _value;
43-
} _Py_atomic_address;
44-
// Used in `pycore_gil.h` and `pycore_ceval_state.h`
45-
typedef struct _Py_atomic_int {
46-
volatile int _value;
47-
} _Py_atomic_int;
48-
/* Stub out macros in `pycore_atomic.h` used in macros in
49-
* `pycore_interp.h` (which aren't related to the
50-
* `struct _is` we need).
51-
* If any stub is referenced, fail the build with an
52-
* unresolved external.
53-
* This ensures we never ship wheels that "use" these
54-
* placeholders. */
55-
# ifdef _MSC_VER
56-
__declspec(dllimport) void lp_link_error__stubbed_cpython_atomic_LOAD_relaxed_was_used_this_is_a_bug(void);
57-
__declspec(dllimport) void lp_link_error__stubbed_cpython_atomic_STORE_relaxed_was_used_this_is_a_bug(void);
58-
# else
59-
extern void lp_link_error__stubbed_cpython_atomic_LOAD_relaxed_was_used_this_is_a_bug(void);
60-
extern void lp_link_error__stubbed_cpython_atomic_STORE_relaxed_was_used_this_is_a_bug(void);
61-
# endif
62-
# define _LP_ATOMIC_PANIC_LOAD_EXPR() (lp_link_error__stubbed_cpython_atomic_LOAD_relaxed_was_used_this_is_a_bug(), 0)
63-
# define _LP_ATOMIC_PANIC_STORE_STMT() do { lp_link_error__stubbed_cpython_atomic_STORE_relaxed_was_used_this_is_a_bug(); } while (0)
64-
// Panic-on-use shims (expression/statement forms)
65-
# undef _Py_atomic_load_relaxed
66-
# undef _Py_atomic_store_relaxed
67-
# define _Py_atomic_load_relaxed(obj) ((void)(obj), _LP_ATOMIC_PANIC_LOAD_EXPR())
68-
# define _Py_atomic_store_relaxed(obj, val) do { (void)(obj); (void)(val); _LP_ATOMIC_PANIC_STORE_STMT(); } while (0)
69-
# endif
70-
# endif
71-
# include "internal/pycore_interp.h"
7+
#ifdef __cplusplus
8+
extern "C" {
729
#endif
7310

7411
typedef struct TraceCallback
@@ -103,4 +40,8 @@ int call_callback(
10340
void set_local_trace(PyObject *manager, PyFrameObject *py_frame);
10441
Py_uintptr_t monitoring_restart_version();
10542

43+
#ifdef __cplusplus
44+
}
45+
#endif
46+
10647
#endif // LINE_PROFILER_C_TRACE_CALLBACKS_H

0 commit comments

Comments
 (0)