From a3648df7db145bcc5f720baa79723f6e0603032e Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Wed, 12 Feb 2025 16:09:07 +0000 Subject: [PATCH 1/2] gh-130030: Fix crash on 32-bit Linux with free threading The `gc_get_refs` assertion needs to be after we check the alive and unreachable bits. Otherwise, `ob_tid` may store the actual thread id instead of the computed `gc_refs`, which may trigger the assertion if the `ob_tid` looks like a negative value. Also fix a few type warnings on 32-bit systems. --- Objects/obmalloc.c | 12 ++++++------ Python/gc_free_threading.c | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 5688049b024696..6341251007aa8e 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -3297,12 +3297,12 @@ static bool _collect_alloc_stats( static void py_mimalloc_print_stats(FILE *out) { - fprintf(out, "Small block threshold = %zd, in %u size classes.\n", - MI_SMALL_OBJ_SIZE_MAX, MI_BIN_HUGE); - fprintf(out, "Medium block threshold = %zd\n", - MI_MEDIUM_OBJ_SIZE_MAX); - fprintf(out, "Large object max size = %zd\n", - MI_LARGE_OBJ_SIZE_MAX); + fprintf(out, "Small block threshold = %zu, in %u size classes.\n", + (size_t)MI_SMALL_OBJ_SIZE_MAX, MI_BIN_HUGE); + fprintf(out, "Medium block threshold = %zu\n", + (size_t)MI_MEDIUM_OBJ_SIZE_MAX); + fprintf(out, "Large object max size = %zu\n", + (size_t)MI_LARGE_OBJ_SIZE_MAX); mi_heap_t *heap = mi_heap_get_default(); struct _alloc_stats stats; diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 9e459da3a44370..7a8565ecb15449 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -564,8 +564,8 @@ typedef struct { } gc_span_stack_t; typedef struct { - unsigned int in; - unsigned int out; + Py_ssize_t in; + Py_ssize_t out; _PyObjectStack stack; gc_span_stack_t spans; PyObject *buffer[BUFFER_SIZE]; @@ -574,14 +574,14 @@ typedef struct { // Returns number of entries in buffer -static inline unsigned int +static inline Py_ssize_t gc_mark_buffer_len(gc_mark_args_t *args) { return args->in - args->out; } // Returns number of free entry slots in buffer -static inline unsigned int +static inline Py_ssize_t gc_mark_buffer_avail(gc_mark_args_t *args) { return BUFFER_SIZE - gc_mark_buffer_len(args); @@ -1074,14 +1074,14 @@ mark_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area, return true; } - _PyObject_ASSERT_WITH_MSG(op, gc_get_refs(op) >= 0, - "refcount is too small"); - if (gc_is_alive(op) || !gc_is_unreachable(op)) { // Object was already marked as reachable. return true; } + _PyObject_ASSERT_WITH_MSG(op, gc_get_refs(op) >= 0, + "refcount is too small"); + // GH-129236: If we've seen an active frame without a valid stack pointer, // then we can't collect objects with deferred references because we may // have missed some reference to the object on the stack. In that case, From a79d4a6f4c1dd0ce45d458c33bdbfaafc80fb3f2 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Wed, 12 Feb 2025 21:12:20 +0000 Subject: [PATCH 2/2] Switch back to unsigned int --- Python/gc_free_threading.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 7a8565ecb15449..0d6fddb5705b4a 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -564,8 +564,8 @@ typedef struct { } gc_span_stack_t; typedef struct { - Py_ssize_t in; - Py_ssize_t out; + unsigned int in; + unsigned int out; _PyObjectStack stack; gc_span_stack_t spans; PyObject *buffer[BUFFER_SIZE]; @@ -574,18 +574,20 @@ typedef struct { // Returns number of entries in buffer -static inline Py_ssize_t +static inline unsigned int gc_mark_buffer_len(gc_mark_args_t *args) { return args->in - args->out; } // Returns number of free entry slots in buffer -static inline Py_ssize_t +#ifndef NDEBUG +static inline unsigned int gc_mark_buffer_avail(gc_mark_args_t *args) { return BUFFER_SIZE - gc_mark_buffer_len(args); } +#endif static inline bool gc_mark_buffer_is_empty(gc_mark_args_t *args) @@ -1178,10 +1180,10 @@ move_legacy_finalizer_reachable(struct collection_state *state); static void gc_prime_from_spans(gc_mark_args_t *args) { - Py_ssize_t space = BUFFER_HI - gc_mark_buffer_len(args); + unsigned int space = BUFFER_HI - gc_mark_buffer_len(args); // there should always be at least this amount of space assert(space <= gc_mark_buffer_avail(args)); - assert(space > 0); + assert(space <= BUFFER_HI); gc_span_t entry = args->spans.stack[--args->spans.size]; // spans on the stack should always have one or more elements assert(entry.start < entry.end);