From c658843370906087ba4cf698e1bb7858234eada5 Mon Sep 17 00:00:00 2001 From: haswee Date: Mon, 18 May 2026 19:24:10 +0000 Subject: [PATCH 01/14] Fix extra blank line in pystack output Signed-off-by: haswee --- src/pystack/traceback_formatter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pystack/traceback_formatter.py b/src/pystack/traceback_formatter.py index 35637b97..12a510d7 100644 --- a/src/pystack/traceback_formatter.py +++ b/src/pystack/traceback_formatter.py @@ -35,7 +35,7 @@ def format_frame(frame: PyFrame) -> Iterable[str]: if line_end != line_start: col_end = len(source) a = source[:col_start] - b = source[col_start:col_end] + b = source[col_start:col_end].strip("\n") c = source[col_end:] final = f'{a}{colored(b, color="blue")}{c}' yield f" {final.strip()}" From d0418d02c4b9392a31391358708fb18370425b65 Mon Sep 17 00:00:00 2001 From: haswee Date: Tue, 19 May 2026 00:18:25 +0000 Subject: [PATCH 02/14] Add 3.15 CI support Signed-off-by: haswee --- .github/workflows/build_wheels.yml | 8 +- src/pystack/_pystack/cpython/code.h | 39 ++++ src/pystack/_pystack/cpython/frame.h | 34 ++++ src/pystack/_pystack/cpython/gc.h | 20 ++ src/pystack/_pystack/cpython/interpreter.h | 76 ++++++++ src/pystack/_pystack/cpython/runtime.h | 216 +++++++++++++++++++++ src/pystack/_pystack/cpython/string.h | 14 ++ src/pystack/_pystack/cpython/thread.h | 73 +++++++ 8 files changed, 476 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 8d7dc757..83ed1d9d 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -68,7 +68,7 @@ jobs: - name: Build wheels uses: pypa/cibuildwheel@v3.4.1 env: - CIBW_BUILD: "cp3{9..14}{t,}-${{ matrix.wheel_type }}" + CIBW_BUILD: "cp3{9..15}{t,}-${{ matrix.wheel_type }}" CIBW_ARCHS_LINUX: auto CIBW_ENABLE: cpython-prerelease cpython-freethreading - uses: actions/upload-artifact@v7 @@ -157,7 +157,7 @@ jobs: strategy: fail-fast: false matrix: - python_version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.13t", "3.14", "3.14t"] + python_version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.13t", "3.14", "3.14t", "3.15", "3.15t"] steps: - uses: actions/checkout@v6 - name: Set up Python @@ -192,7 +192,7 @@ jobs: strategy: fail-fast: false matrix: - python_version: ["3.9", "3.13", "3.14"] + python_version: ["3.9", "3.13", "3.14", "3.15"] steps: - uses: actions/checkout@v6 - name: Set up Python @@ -230,7 +230,7 @@ jobs: strategy: fail-fast: false matrix: - python_version: ["3.13", "3.14"] + python_version: ["3.13", "3.14", "3.15"] steps: - uses: actions/checkout@v6 - name: Set up Python diff --git a/src/pystack/_pystack/cpython/code.h b/src/pystack/_pystack/cpython/code.h index d14db7de..d837c66b 100644 --- a/src/pystack/_pystack/cpython/code.h +++ b/src/pystack/_pystack/cpython/code.h @@ -236,4 +236,43 @@ typedef struct } PyCodeObject; } // namespace Python3_14 +namespace Python3_15 { +typedef uint16_t _Py_CODEUNIT; + +typedef struct +{ + PyObject_VAR_HEAD PyObject* co_consts; + PyObject* co_names; + PyObject* co_exceptiontable; + int co_flags; + int co_argcount; + int co_posonlyargcount; + int co_kwonlyargcount; + int co_stacksize; + int co_firstlineno; + int co_nlocalsplus; + int co_framesize; + int co_nlocals; + int co_ncellvars; + int co_nfreevars; + uint32_t co_version; + PyObject* co_localsplusnames; + PyObject* co_localspluskinds; + PyObject* co_filename; + PyObject* co_name; + PyObject* co_qualname; + PyObject* co_linetable; + PyObject* co_weakreflist; + void* co_executors; + void* _co_cached; + uintptr_t _co_instrumentation_version; + void* _co_monitoring; + Py_ssize_t _co_unique_id; + int _co_firsttraceable; + void* co_extra; + /* deal with co_tlbc somehow */ + char co_code_adaptive[1]; +} PyCodeObject; +} // namespace Python3_15 + } // namespace pystack diff --git a/src/pystack/_pystack/cpython/frame.h b/src/pystack/_pystack/cpython/frame.h index a71f9dd8..3ec964ac 100644 --- a/src/pystack/_pystack/cpython/frame.h +++ b/src/pystack/_pystack/cpython/frame.h @@ -167,4 +167,38 @@ typedef struct _interpreter_frame } // namespace Python3_14 +namespace Python3_15 { + +enum _frameowner { + FRAME_OWNED_BY_THREAD = 0, + FRAME_OWNED_BY_GENERATOR = 1, + FRAME_OWNED_BY_FRAME_OBJECT = 2, + FRAME_OWNED_BY_INTERPRETER = 3, + FRAME_OWNED_BY_CSTACK = 4, +}; + +typedef union _PyStackRef { + uintptr_t bits; +} _PyStackRef; + +typedef struct _interpreter_frame +{ + _PyStackRef f_executable; + void* previous; + void* f_funcobj; + PyObject* f_globals; + PyObject* f_builtins; + PyObject* f_locals; + PyObject* frame_obj; + _Py_CODEUNIT* instr_ptr; + _PyStackRef stackpointer; + /* int32_t tlbc_index; */ + uint16_t return_offset; + char owner; + uint8_t visited; + void* localsplus[1]; +} PyFrameObject; + +} // namespace Python3_15 + } // namespace pystack diff --git a/src/pystack/_pystack/cpython/gc.h b/src/pystack/_pystack/cpython/gc.h index b64bd073..a564ae38 100644 --- a/src/pystack/_pystack/cpython/gc.h +++ b/src/pystack/_pystack/cpython/gc.h @@ -106,4 +106,24 @@ struct _gc_runtime_state } // namespace Python3_14 +namespace Python3_15 { + +struct _gc_runtime_state +{ + int enabled; + int debug; + struct Python3_8::gc_generation young; + struct Python3_8::gc_generation old[2]; + struct Python3_8::gc_generation permanent_generation; + struct gc_generation_stats generation_stats[NUM_GENERATIONS]; + int collecting; + PyObject* garbage; + PyObject* callbacks; + Py_ssize_t heap_size; + Py_ssize_t long_lived_total; + Py_ssize_t long_lived_pending; +}; + +} // namespace Python3_15 + } // namespace pystack diff --git a/src/pystack/_pystack/cpython/interpreter.h b/src/pystack/_pystack/cpython/interpreter.h index b35b26ba..bc2f1742 100644 --- a/src/pystack/_pystack/cpython/interpreter.h +++ b/src/pystack/_pystack/cpython/interpreter.h @@ -416,4 +416,80 @@ typedef struct _is } // namespace Python3_14 +namespace Python3_15 { + +struct _pythreadstate; + +typedef struct +{ + Python3_13::PyMutex mutex; + unsigned long long thread; + size_t level; +} _PyRecursiveMutex; + +struct _import_state +{ + PyObject* modules; + PyObject* modules_by_index; + PyObject* importlib; + int override_frozen_modules; + int override_multi_interp_extensions_check; + PyObject* import_func; + _PyRecursiveMutex lock; + /* diagnostic info in PyImport_ImportModuleLevelObject() */ + struct + { + int import_level; + int64_t accumulated; + int header; + } find_and_load; +}; + +struct _gil_runtime_state +{ + unsigned long interval; + struct _pythreadstate* last_holder; + int locked; + unsigned long switch_number; + pthread_cond_t cond; + pthread_cond_t mutex; +#ifdef FORCE_SWITCHING + pthread_cond_t switch_cond; + pthread_cond_t switch_mutex; +#endif +}; + +typedef struct _is +{ + struct _ceval_state ceval; + struct _is* next; + int64_t id; + Py_ssize_t id_refcount; + int requires_idref; + long _whence; + int _initialized; + int _ready; + int finalizing; + uintptr_t last_restart_version; + struct pythreads + { + uint64_t next_unique_id; + struct _pythreadstate* head; + struct _pythreadstate* preallocated; + struct _pythreadstate* main; + Py_ssize_t count; + size_t stacksize; + } threads; + void* runtime; + struct _pythreadstate* _finalizing; + unsigned long _finalizing_id; + struct _gc_runtime_state gc; + PyObject* sysdict; + PyObject* builtins; + struct _import_state imports; + struct _gil_runtime_state _gil; +} PyInterpreterState; + +} // namespace Python3_15 + } // namespace pystack diff --git a/src/pystack/_pystack/cpython/runtime.h b/src/pystack/_pystack/cpython/runtime.h index 5fa637bd..a5ddd112 100644 --- a/src/pystack/_pystack/cpython/runtime.h +++ b/src/pystack/_pystack/cpython/runtime.h @@ -638,6 +638,221 @@ typedef struct pyruntimestate } // namespace Python3_14 +namespace Python3_15 { + +typedef struct _Py_DebugOffsets +{ + char cookie[8]; + uint64_t version; + uint64_t free_threaded; + // Runtime state offset; + struct _runtime_state + { + uint64_t size; + uint64_t finalizing; + uint64_t interpreters_head; + } runtime_state; + + // Interpreter state offset; + struct _interpreter_state + { + uint64_t size; + uint64_t id; + uint64_t next; + uint64_t threads_head; + uint64_t threads_main; + uint64_t gc; + uint64_t imports_modules; + uint64_t sysdict; + uint64_t builtins; + uint64_t ceval_gil; + uint64_t gil_runtime_state; + uint64_t gil_runtime_state_enabled; + uint64_t gil_runtime_state_locked; + uint64_t gil_runtime_state_holder; + uint64_t code_object_generation; + uint64_t tlbc_generation; + } interpreter_state; + + // Thread state offset; + struct _thread_state + { + uint64_t size; + uint64_t prev; + uint64_t next; + uint64_t interp; + uint64_t current_frame; + uint64_t thread_id; + uint64_t native_thread_id; + uint64_t datastack_chunk; + uint64_t status; + } thread_state; + + // InterpreterFrame offset; + struct _interpreter_frame + { + uint64_t size; + uint64_t previous; + uint64_t executable; + uint64_t instr_ptr; + uint64_t localsplus; + uint64_t owner; + uint64_t stackpointer; + uint64_t tlbc_index; + } interpreter_frame; + + // Code object offset; + struct _code_object + { + uint64_t size; + uint64_t filename; + uint64_t name; + uint64_t qualname; + uint64_t linetable; + uint64_t firstlineno; + uint64_t argcount; + uint64_t localsplusnames; + uint64_t localspluskinds; + uint64_t co_code_adaptive; + uint64_t co_tlbc; + } code_object; + + // PyObject offset; + struct _pyobject + { + uint64_t size; + uint64_t ob_type; + } pyobject; + + // PyTypeObject object offset; + struct _type_object + { + uint64_t size; + uint64_t tp_name; + uint64_t tp_repr; + uint64_t tp_flags; + } type_object; + + // PyTuple object offset; + struct _tuple_object + { + uint64_t size; + uint64_t ob_item; + uint64_t ob_size; + } tuple_object; + + // PyList object offset; + struct _list_object + { + uint64_t size; + uint64_t ob_item; + uint64_t ob_size; + } list_object; + + // PySet object offset; + struct _set_object + { + uint64_t size; + uint64_t used; + uint64_t table; + uint64_t mask; + } set_object; + + // PyDict object offset; + struct _dict_object + { + uint64_t size; + uint64_t ma_keys; + uint64_t ma_values; + } dict_object; + + // PyFloat object offset; + struct _float_object + { + uint64_t size; + uint64_t ob_fval; + } float_object; + + // PyLong object offset; + struct _long_object + { + uint64_t size; + uint64_t lv_tag; + uint64_t ob_digit; + } long_object; + + // PyBytes object offset; + struct _bytes_object + { + uint64_t size; + uint64_t ob_size; + uint64_t ob_sval; + } bytes_object; + + // Unicode object offset; + struct _unicode_object + { + uint64_t size; + uint64_t state; + uint64_t length; + uint64_t asciiobject_size; + } unicode_object; + + // GC runtime state offset; + struct _gc + { + uint64_t size; + uint64_t collecting; + } gc; + + // Generator object offset; + struct _gen_object + { + uint64_t size; + uint64_t gi_name; + uint64_t gi_iframe; + uint64_t gi_frame_state; + } gen_object; + + struct _llist_node + { + uint64_t next; + uint64_t prev; + } llist_node; + + struct _debugger_support + { + uint64_t eval_breaker; + uint64_t remote_debugger_support; + uint64_t remote_debugging_enabled; + uint64_t debugger_pending_call; + uint64_t debugger_script_path; + uint64_t debugger_script_path_size; + } debugger_support; +} _Py_DebugOffsets; + +typedef struct pyruntimestate +{ + _Py_DebugOffsets debug_offsets; + int _initialized; + int preinitializing; + int preinitialized; + int core_initialized; + int initialized; + struct _pythreadstate* finalizing; + unsigned long _finalizing_id; + + struct pyinterpreters + { + Python3_13::PyMutex mutex; + PyInterpreterState* head; + PyInterpreterState* main; + int64_t next_id; + } interpreters; +} PyRuntimeState; + +} // namespace Python3_15 + typedef union { Python3_7::PyRuntimeState v3_7; Python3_8::PyRuntimeState v3_8; @@ -646,6 +861,7 @@ typedef union { Python3_12::PyRuntimeState v3_12; Python3_13::PyRuntimeState v3_13; Python3_14::PyRuntimeState v3_14; + Python3_15::PyRuntimeState v3_15; } PyRuntimeState; } // namespace pystack diff --git a/src/pystack/_pystack/cpython/string.h b/src/pystack/_pystack/cpython/string.h index b6618c3c..17d02fe0 100644 --- a/src/pystack/_pystack/cpython/string.h +++ b/src/pystack/_pystack/cpython/string.h @@ -122,9 +122,23 @@ struct _PyUnicode_State } // namespace Python3_14t +namespace Python3_15t { + +struct _PyUnicode_State +{ + unsigned char interned; + unsigned int kind : 3; + unsigned int compact : 1; + unsigned int ascii : 1; + unsigned int statically_allocated : 1; +}; + +} // namespace Python3_15t + union AnyPyUnicodeState { Python3::_PyUnicode_State python3; Python3_14t::_PyUnicode_State python3_14t; + Python3_15t::_PyUnicode_State python3_15t; }; } // namespace pystack diff --git a/src/pystack/_pystack/cpython/thread.h b/src/pystack/_pystack/cpython/thread.h index 977cd2ae..091481ec 100644 --- a/src/pystack/_pystack/cpython/thread.h +++ b/src/pystack/_pystack/cpython/thread.h @@ -362,4 +362,77 @@ typedef struct _pythreadstate } // namespace Python3_14 +namespace Python3_15 { + +typedef struct _remote_debugger_support +{ + int32_t debugger_pending_call; + char debugger_script_path[512]; +} _PyRemoteDebuggerSupport; + +typedef struct _pythreadstate +{ + struct _pythreadstate* prev; + struct _pythreadstate* next; + PyInterpreterState* interp; + uintptr_t eval_breaker; + struct + { + unsigned int initialized : 1; + unsigned int bound : 1; + unsigned int unbound : 1; + unsigned int bound_gilstate : 1; + unsigned int active : 1; + unsigned int finalizing : 1; + unsigned int cleared : 1; + unsigned int finalized : 1; + unsigned int : 24; + } _status; + int holds_gil; + int gil_requested; + int _whence; + int state; + int py_recursion_remaining; + int py_recursion_limit; + int recursion_headroom; + int tracing; + int what_event; + struct _PyInterpreterFrame* current_frame; + struct _PyInterpreterFrame* base_frame; + struct _PyInterpreterFrame* last_profiled_frame; + + Py_tracefunc c_profilefunc; + Py_tracefunc c_tracefunc; + PyObject* c_profileobj; + PyObject* c_traceobj; + PyObject* current_exception; + Python3_13::_PyErr_StackItem* exc_info; + PyObject* dict; + int gilstate_counter; + PyObject* async_exc; + unsigned long thread_id; + unsigned long native_thread_id; + PyObject* delete_later; + uintptr_t critical_section; + int coroutine_origin_tracking_depth; + PyObject* async_gen_firstiter; + PyObject* async_gen_finalizer; + PyObject* context; + uint64_t context_ver; + uint64_t id; + void* datastack_chunk; + PyObject** datastack_top; + PyObject** datastack_limit; + void* datastack_cached_chunk; + Python3_13::_PyErr_StackItem exc_state; + PyObject* current_executor; + uint64_t dict_global_version; + PyObject* threading_local_key; + PyObject* threading_local_sentinel; + _PyRemoteDebuggerSupport remote_debugger_support; + +} PyThreadState; + +} // namespace Python3_15 + } // namespace pystack From bb4c69f591a3a990d1c8b976aa4cc014a8d0500a Mon Sep 17 00:00:00 2001 From: haswee Date: Tue, 19 May 2026 19:01:29 +0000 Subject: [PATCH 03/14] Add missing core struct changes Signed-off-by: haswee --- src/pystack/_pystack/cpython/runtime.h | 9 +++++ src/pystack/_pystack/version.cpp | 49 ++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/pystack/_pystack/cpython/runtime.h b/src/pystack/_pystack/cpython/runtime.h index a5ddd112..6db44b07 100644 --- a/src/pystack/_pystack/cpython/runtime.h +++ b/src/pystack/_pystack/cpython/runtime.h @@ -682,10 +682,16 @@ typedef struct _Py_DebugOffsets uint64_t next; uint64_t interp; uint64_t current_frame; + uint64_t base_frame; + uint64_t last_profiled_frame; uint64_t thread_id; uint64_t native_thread_id; uint64_t datastack_chunk; uint64_t status; + uint64_t holds_gil; + uint64_t gil_requested; + uint64_t current_exception; + uint64_t exc_state; } thread_state; // InterpreterFrame offset; @@ -803,6 +809,9 @@ typedef struct _Py_DebugOffsets { uint64_t size; uint64_t collecting; + uint64_t frame; + uint64_t generation_stats_size; + uint64_t generation_stats; } gc; // Generator object offset; diff --git a/src/pystack/_pystack/version.cpp b/src/pystack/_pystack/version.cpp index f58ff878..25f1a962 100644 --- a/src/pystack/_pystack/version.cpp +++ b/src/pystack/_pystack/version.cpp @@ -105,6 +105,22 @@ py_framev314() }; } +template +constexpr py_frame_v +py_framev315() +{ + return { + sizeof(T), + {offsetof(T, previous)}, + {offsetof(T, f_executable)}, + {0}, + {offsetof(T, instr_ptr)}, + {offsetof(T, localsplus)}, + {0}, + {offsetof(T, owner)}, + }; +} + template constexpr py_thread_v py_thead_h() @@ -746,6 +762,32 @@ python_v python_v3_14 = { // ----------------------------------------------------------------------------- +// ---- Python 3.15 ------------------------------------------------------------ + +python_v python_v3_15 = { + py_tuple(), + py_list(), + py_dict(), + py_dictkeys(), + py_dictvalues(), + py_float(), + py_long<_PyLongObject>(), + py_bytes(), + py_unicode(), + py_object(), + py_type(), + py_codev311(), + py_framev314(), + py_threadv313(), + py_isv312(), + py_runtimev313(), + py_gc(), + py_cframe(), + py_gilruntimestate(), +}; + +// ----------------------------------------------------------------------------- + const python_v* getCPythonOffsets(int major, int minor) { @@ -811,11 +853,14 @@ getCPythonOffsets(int major, int minor) case 13: return &python_v3_13; break; + case 14: + return &python_v3_14; + break; default: warnAboutUnsuportedVersion(major, minor); // fallthrough to latest - case 14: - return &python_v3_14; + case 15: + return &python_v3_15; break; } break; From 96442f4ef1e5327b78f2a028a6b3455ae6883b29 Mon Sep 17 00:00:00 2001 From: haswee Date: Tue, 19 May 2026 19:01:52 +0000 Subject: [PATCH 04/14] Add testing changes Signed-off-by: haswee --- tests/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/utils.py b/tests/utils.py index dacb18fa..aa1173dc 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -23,6 +23,7 @@ PythonVersion = Tuple[Tuple[int, int], pathlib.Path] ALL_VERSIONS = [ + ((3, 15), "python3.15"), ((3, 14), "python3.14t"), ((3, 14), "python3.14"), ((3, 13), "python3.13t"), From 46dcae8d8d597ec392291d2aa1e476ef4cfdc351 Mon Sep 17 00:00:00 2001 From: haswee Date: Tue, 19 May 2026 19:08:48 +0000 Subject: [PATCH 05/14] Add version mapping core struct changes Signed-off-by: haswee --- src/pystack/_pystack/cpython/gc.h | 1 + src/pystack/_pystack/version.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pystack/_pystack/cpython/gc.h b/src/pystack/_pystack/cpython/gc.h index a564ae38..d1bb99e3 100644 --- a/src/pystack/_pystack/cpython/gc.h +++ b/src/pystack/_pystack/cpython/gc.h @@ -117,6 +117,7 @@ struct _gc_runtime_state struct Python3_8::gc_generation permanent_generation; struct gc_generation_stats generation_stats[NUM_GENERATIONS]; int collecting; + _PyInterpreterFrame* frame; PyObject* garbage; PyObject* callbacks; Py_ssize_t heap_size; diff --git a/src/pystack/_pystack/version.cpp b/src/pystack/_pystack/version.cpp index 25f1a962..c8f87bd4 100644 --- a/src/pystack/_pystack/version.cpp +++ b/src/pystack/_pystack/version.cpp @@ -781,7 +781,7 @@ python_v python_v3_15 = { py_threadv313(), py_isv312(), py_runtimev313(), - py_gc(), + py_gc(), py_cframe(), py_gilruntimestate(), }; From a8c346d211c6447403f7a3f2363bbf0aff8d39cd Mon Sep 17 00:00:00 2001 From: haswee Date: Tue, 19 May 2026 19:27:15 +0000 Subject: [PATCH 06/14] Add core struct changes Signed-off-by: haswee --- src/pystack/_pystack/cpython/gc.h | 2 +- src/pystack/_pystack/cpython/runtime.h | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/pystack/_pystack/cpython/gc.h b/src/pystack/_pystack/cpython/gc.h index d1bb99e3..5ac0c941 100644 --- a/src/pystack/_pystack/cpython/gc.h +++ b/src/pystack/_pystack/cpython/gc.h @@ -117,7 +117,7 @@ struct _gc_runtime_state struct Python3_8::gc_generation permanent_generation; struct gc_generation_stats generation_stats[NUM_GENERATIONS]; int collecting; - _PyInterpreterFrame* frame; + struct _PyInterpreterFrame* frame; PyObject* garbage; PyObject* callbacks; Py_ssize_t heap_size; diff --git a/src/pystack/_pystack/cpython/runtime.h b/src/pystack/_pystack/cpython/runtime.h index 6db44b07..9ba3153f 100644 --- a/src/pystack/_pystack/cpython/runtime.h +++ b/src/pystack/_pystack/cpython/runtime.h @@ -694,6 +694,12 @@ typedef struct _Py_DebugOffsets uint64_t exc_state; } thread_state; + // Exception stack item offset + struct + { + uint64_t exc_value; + } err_stackitem; + // InterpreterFrame offset; struct _interpreter_frame { @@ -737,8 +743,17 @@ typedef struct _Py_DebugOffsets uint64_t tp_name; uint64_t tp_repr; uint64_t tp_flags; + uint64_t tp_basicsize; + uint64_t tp_dictoffset; } type_object; + // PyHeapTypeObject offset; + struct _heap_type_object + { + uint64_t size; + uint64_t ht_cached_keys; + } heap_type_object; + // PyTuple object offset; struct _tuple_object { @@ -802,6 +817,7 @@ typedef struct _Py_DebugOffsets uint64_t state; uint64_t length; uint64_t asciiobject_size; + uint64_t compactunicodeobject_size; } unicode_object; // GC runtime state offset; From 72c7f0c4769ce8e67383815eb58fc88736ddabeb Mon Sep 17 00:00:00 2001 From: haswee Date: Tue, 19 May 2026 19:50:18 +0000 Subject: [PATCH 07/14] Add version mapping core struct changes Signed-off-by: haswee --- src/pystack/_pystack/version.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pystack/_pystack/version.cpp b/src/pystack/_pystack/version.cpp index c8f87bd4..997a2219 100644 --- a/src/pystack/_pystack/version.cpp +++ b/src/pystack/_pystack/version.cpp @@ -780,7 +780,7 @@ python_v python_v3_15 = { py_framev314(), py_threadv313(), py_isv312(), - py_runtimev313(), + py_runtimev313(), py_gc(), py_cframe(), py_gilruntimestate(), From 02a4f452e8b696ac0ffea7ef9340cfd21f8259cc Mon Sep 17 00:00:00 2001 From: haswee Date: Tue, 19 May 2026 20:03:32 +0000 Subject: [PATCH 08/14] Remove unused strucs Signed-off-by: haswee --- src/pystack/_pystack/cpython/frame.h | 34 ---------------------------- src/pystack/_pystack/version.cpp | 16 ------------- 2 files changed, 50 deletions(-) diff --git a/src/pystack/_pystack/cpython/frame.h b/src/pystack/_pystack/cpython/frame.h index 3ec964ac..a71f9dd8 100644 --- a/src/pystack/_pystack/cpython/frame.h +++ b/src/pystack/_pystack/cpython/frame.h @@ -167,38 +167,4 @@ typedef struct _interpreter_frame } // namespace Python3_14 -namespace Python3_15 { - -enum _frameowner { - FRAME_OWNED_BY_THREAD = 0, - FRAME_OWNED_BY_GENERATOR = 1, - FRAME_OWNED_BY_FRAME_OBJECT = 2, - FRAME_OWNED_BY_INTERPRETER = 3, - FRAME_OWNED_BY_CSTACK = 4, -}; - -typedef union _PyStackRef { - uintptr_t bits; -} _PyStackRef; - -typedef struct _interpreter_frame -{ - _PyStackRef f_executable; - void* previous; - void* f_funcobj; - PyObject* f_globals; - PyObject* f_builtins; - PyObject* f_locals; - PyObject* frame_obj; - _Py_CODEUNIT* instr_ptr; - _PyStackRef stackpointer; - /* int32_t tlbc_index; */ - uint16_t return_offset; - char owner; - uint8_t visited; - void* localsplus[1]; -} PyFrameObject; - -} // namespace Python3_15 - } // namespace pystack diff --git a/src/pystack/_pystack/version.cpp b/src/pystack/_pystack/version.cpp index 997a2219..975b5446 100644 --- a/src/pystack/_pystack/version.cpp +++ b/src/pystack/_pystack/version.cpp @@ -105,22 +105,6 @@ py_framev314() }; } -template -constexpr py_frame_v -py_framev315() -{ - return { - sizeof(T), - {offsetof(T, previous)}, - {offsetof(T, f_executable)}, - {0}, - {offsetof(T, instr_ptr)}, - {offsetof(T, localsplus)}, - {0}, - {offsetof(T, owner)}, - }; -} - template constexpr py_thread_v py_thead_h() From 4b15ecfe4ad48c722e4eef368c5be818b88c439c Mon Sep 17 00:00:00 2001 From: haswee Date: Tue, 19 May 2026 20:03:44 +0000 Subject: [PATCH 09/14] Add testing changes Signed-off-by: haswee --- tests/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/utils.py b/tests/utils.py index aa1173dc..cf26d411 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -23,6 +23,7 @@ PythonVersion = Tuple[Tuple[int, int], pathlib.Path] ALL_VERSIONS = [ + ((3, 15), "python3.15t"), ((3, 15), "python3.15"), ((3, 14), "python3.14t"), ((3, 14), "python3.14"), From 28b35a1b794becaf190e8219f96a9386785dd22a Mon Sep 17 00:00:00 2001 From: haswee Date: Tue, 19 May 2026 21:03:45 +0000 Subject: [PATCH 10/14] Add wheels changes Signed-off-by: haswee --- .github/workflows/build_wheels.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 83ed1d9d..9b3fd7bd 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -66,7 +66,7 @@ jobs: run: | tar zxvf *.tar.gz --strip-components=1 - name: Build wheels - uses: pypa/cibuildwheel@v3.4.1 + uses: pypa/cibuildwheel@54327ab9d35de03b359ac25c97de9417d94639c0 # v4.0.0rc1 env: CIBW_BUILD: "cp3{9..15}{t,}-${{ matrix.wheel_type }}" CIBW_ARCHS_LINUX: auto @@ -425,6 +425,10 @@ jobs: mv dist/*-wheels/*.whl dist/ rmdir dist/{sdist,*-wheels} ls -R dist + - name: Avoid publishing Python 3.15 wheels + run: | + rm -f dist/*cp315* + ls -R dist - uses: pypa/gh-action-pypi-publish@release/v1 with: skip_existing: true From 0bb28cc1c58fdf5b7ef25b28973d007ec5a51c59 Mon Sep 17 00:00:00 2001 From: haswee Date: Tue, 19 May 2026 21:42:16 +0000 Subject: [PATCH 11/14] Add wheels changes Signed-off-by: haswee --- .github/workflows/build_wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 9b3fd7bd..e49c6053 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -70,7 +70,7 @@ jobs: env: CIBW_BUILD: "cp3{9..15}{t,}-${{ matrix.wheel_type }}" CIBW_ARCHS_LINUX: auto - CIBW_ENABLE: cpython-prerelease cpython-freethreading + CIBW_ENABLE: cpython-prerelease - uses: actions/upload-artifact@v7 with: name: ${{ matrix.wheel_type }}-wheels From d6b58ef05c98d0a868eafe855166544460dd70ff Mon Sep 17 00:00:00 2001 From: haswee Date: Tue, 19 May 2026 21:43:23 +0000 Subject: [PATCH 12/14] fix test: changes for Python 3.15+ tail call interpreter drops the .llvm. suffix Signed-off-by: haswee --- src/pystack/types.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pystack/types.py b/src/pystack/types.py index fbd1eb13..c0f72011 100644 --- a/src/pystack/types.py +++ b/src/pystack/types.py @@ -52,6 +52,9 @@ def _is_eval_frame(symbol: str, python_version: Tuple[int, int]) -> bool: # Python 3.14 tail call interpreter uses LLVM-generated functions if symbol.startswith("_TAIL_CALL_") and ".llvm." in symbol: return True + # Python 3.15+ tail call interpreter drops the .llvm. suffix + if python_version >= (3, 15) and symbol.startswith("_TAIL_CALL_"): + return True return False From 594a04589b1dd47f707ac74f887731c6ab340536 Mon Sep 17 00:00:00 2001 From: haswee Date: Tue, 19 May 2026 21:50:03 +0000 Subject: [PATCH 13/14] Add news Signed-off-by: haswee --- news/289.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/289.feature.rst diff --git a/news/289.feature.rst b/news/289.feature.rst new file mode 100644 index 00000000..e65d5673 --- /dev/null +++ b/news/289.feature.rst @@ -0,0 +1 @@ +Python 3.15 is now supported. The wheels are not yet published because the ABI is not yet frozen. From 83afbe7e90b8d0204ea51f1bf57c6b6e1d6711c1 Mon Sep 17 00:00:00 2001 From: haswee Date: Tue, 19 May 2026 22:07:24 +0000 Subject: [PATCH 14/14] skip test for python3.15 due to PyInstaller support Signed-off-by: haswee --- tests/integration/test_relocatable_cores.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_relocatable_cores.py b/tests/integration/test_relocatable_cores.py index f19265ed..3c79111f 100644 --- a/tests/integration/test_relocatable_cores.py +++ b/tests/integration/test_relocatable_cores.py @@ -3,6 +3,7 @@ import sys from pathlib import Path +import pytest from pytest import LogCaptureFixture from pystack.engine import CoreFileAnalyzer @@ -17,6 +18,10 @@ TEST_MULTIPLE_THREADS_FILE = Path(__file__).parent / "multiple_thread_program.py" +@pytest.mark.skipif( + sys.version_info >= (3, 15), + reason="PyInstaller does not support Python 3.15 yet", +) def test_single_thread_stack_for_relocated_core( tmpdir: Path, caplog: LogCaptureFixture ) -> None: @@ -25,7 +30,6 @@ def test_single_thread_stack_for_relocated_core( that we can inspect and obtain all the information we need from the frame stack, including the native frames by using symbols only. """ - # GIVEN target_bundle = Path(tmpdir / "bundle")