Skip to content

Commit c107495

Browse files
committed
Clear TLBC when other caches are cleared
1 parent 176b24e commit c107495

File tree

6 files changed

+158
-8
lines changed

6 files changed

+158
-8
lines changed

Include/internal/pycore_code.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,11 @@ extern int32_t _Py_ReserveTLBCIndex(PyInterpreterState *interp);
634634

635635
// Release the current thread's index into thread-local bytecode arrays
636636
extern void _Py_ClearTLBCIndex(_PyThreadStateImpl *tstate);
637+
638+
// Free all TLBC copies not associated with live threads.
639+
//
640+
// Returns 0 on success or -1 on error.
641+
extern int _Py_ClearUnusedTLBC(PyInterpreterState *interp);
637642
#endif
638643

639644
#ifdef __cplusplus

Include/internal/pycore_gc.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,10 @@ extern int _PyGC_VisitStackRef(union _PyStackRef *ref, visitproc visit, void *ar
397397
} \
398398
} while (0)
399399

400+
#ifdef Py_GIL_DISABLED
401+
extern void _PyGC_VisitObjectsWorldStopped(PyInterpreterState *interp,
402+
gcvisitobjects_t callback, void *arg);
403+
#endif
400404

401405
#ifdef __cplusplus
402406
}

Lib/test/libregrtest/refleak.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,6 @@ def get_pooled_int(value):
145145
# Use an internal-only keyword argument that mypy doesn't know yet
146146
_only_immortal=True) # type: ignore[call-arg]
147147
alloc_after = getallocatedblocks() - interned_immortal_after
148-
if _get_tlbc_blocks := getattr(sys, "_get_tlbc_blocks", None):
149-
# Ignore any thread-local bytecode that was allocated. These will be
150-
# released when the code object is destroyed, typically at runtime
151-
# shutdown
152-
alloc_after -= _get_tlbc_blocks()
153148
rc_after = gettotalrefcount()
154149
fd_after = fd_count()
155150

Objects/codeobject.c

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "pycore_initconfig.h" // _PyStatus_OK()
1111
#include "pycore_interp.h" // PyInterpreterState.co_extra_freefuncs
1212
#include "pycore_object.h" // _PyObject_SetDeferredRefcount
13+
#include "pycore_object_stack.h"
1314
#include "pycore_opcode_metadata.h" // _PyOpcode_Deopt, _PyOpcode_Caches
1415
#include "pycore_opcode_utils.h" // RESUME_AT_FUNC_START
1516
#include "pycore_pymem.h" // _PyMem_FreeDelayed
@@ -2824,4 +2825,138 @@ _PyCode_GetTLBC(PyCodeObject *co)
28242825
return result;
28252826
}
28262827

2828+
// My kingdom for a bitset
2829+
struct flag_set {
2830+
uint8_t *flags;
2831+
Py_ssize_t size;
2832+
};
2833+
2834+
static inline int
2835+
flag_is_set(struct flag_set *flags, Py_ssize_t idx)
2836+
{
2837+
assert(idx >= 0);
2838+
return (idx < flags->size) && flags->flags[idx];
2839+
}
2840+
2841+
// Set the flag for each tlbc index in use
2842+
static int
2843+
get_indices_in_use(PyInterpreterState *interp, struct flag_set *in_use)
2844+
{
2845+
assert(interp->stoptheworld.world_stopped);
2846+
assert(in_use->flags == NULL);
2847+
int32_t max_index = 0;
2848+
for (PyThreadState *p = interp->threads.head; p != NULL; p = p->next) {
2849+
int32_t idx = ((_PyThreadStateImpl *) p)->tlbc_index;
2850+
if (idx > max_index) {
2851+
max_index = idx;
2852+
}
2853+
}
2854+
in_use->size = (size_t) max_index + 1;
2855+
in_use->flags = PyMem_Calloc(in_use->size, sizeof(*in_use->flags));
2856+
if (in_use->flags == NULL) {
2857+
return -1;
2858+
}
2859+
for (PyThreadState *p = interp->threads.head; p != NULL; p = p->next) {
2860+
in_use->flags[((_PyThreadStateImpl *) p)->tlbc_index] = 1;
2861+
}
2862+
return 0;
2863+
}
2864+
2865+
struct get_code_args {
2866+
_PyObjectStack code_objs;
2867+
struct flag_set indices_in_use;
2868+
int err;
2869+
};
2870+
2871+
static void
2872+
clear_get_code_args(struct get_code_args *args)
2873+
{
2874+
if (args->indices_in_use.flags != NULL) {
2875+
PyMem_Free(args->indices_in_use.flags);
2876+
args->indices_in_use.flags = NULL;
2877+
}
2878+
_PyObjectStack_Clear(&args->code_objs);
2879+
}
2880+
2881+
static inline int
2882+
is_bytecode_unused(_PyCodeArray *tlbc, Py_ssize_t idx,
2883+
struct flag_set *indices_in_use)
2884+
{
2885+
assert(idx > 0 && idx < tlbc->size);
2886+
return tlbc->entries[idx] != NULL && !flag_is_set(indices_in_use, idx);
2887+
}
2888+
2889+
static int
2890+
get_code_with_unused_tlbc(PyObject *obj, struct get_code_args *args)
2891+
{
2892+
if (!PyCode_Check(obj)) {
2893+
return 1;
2894+
}
2895+
PyCodeObject *co = (PyCodeObject *) obj;
2896+
_PyCodeArray *tlbc = co->co_tlbc;
2897+
// The first index always points at the main copy of the bytecode embedded
2898+
// in the code object.
2899+
for (Py_ssize_t i = 1; i < tlbc->size; i++) {
2900+
if (is_bytecode_unused(tlbc, i, &args->indices_in_use)) {
2901+
if (_PyObjectStack_Push(&args->code_objs, obj) < 0) {
2902+
args->err = -1;
2903+
return 0;
2904+
}
2905+
return 1;
2906+
}
2907+
}
2908+
return 1;
2909+
}
2910+
2911+
static void
2912+
free_unused_bytecode(PyCodeObject *co, struct flag_set *indices_in_use)
2913+
{
2914+
_PyCodeArray *tlbc = co->co_tlbc;
2915+
// The first index always points at the main copy of the bytecode embedded
2916+
// in the code object.
2917+
for (Py_ssize_t i = 1; i < tlbc->size; i++) {
2918+
if (is_bytecode_unused(tlbc, i, indices_in_use)) {
2919+
PyMem_Free(tlbc->entries[i]);
2920+
tlbc->entries[i] = NULL;
2921+
}
2922+
}
2923+
}
2924+
2925+
int
2926+
_Py_ClearUnusedTLBC(PyInterpreterState *interp)
2927+
{
2928+
struct get_code_args args = {
2929+
.code_objs = {NULL},
2930+
.indices_in_use = {NULL, 0},
2931+
.err = 0,
2932+
};
2933+
_PyEval_StopTheWorld(interp);
2934+
// Collect in-use tlbc indices
2935+
if (get_indices_in_use(interp, &args.indices_in_use) < 0) {
2936+
goto err;
2937+
}
2938+
// Collect code objects that have bytecode not in use by any thread
2939+
_PyGC_VisitObjectsWorldStopped(
2940+
interp, (gcvisitobjects_t)get_code_with_unused_tlbc, &args);
2941+
if (args.err < 0) {
2942+
goto err;
2943+
}
2944+
// Free unused bytecode. This must happen outside of gc_visit_heaps; it is
2945+
// unsafe to allocate or free any mimalloc managed memory when it's
2946+
// running.
2947+
PyObject *obj;
2948+
while ((obj = _PyObjectStack_Pop(&args.code_objs)) != NULL) {
2949+
free_unused_bytecode((PyCodeObject*) obj, &args.indices_in_use);
2950+
}
2951+
_PyEval_StartTheWorld(interp);
2952+
clear_get_code_args(&args);
2953+
return 0;
2954+
2955+
err:
2956+
_PyEval_StartTheWorld(interp);
2957+
clear_get_code_args(&args);
2958+
PyErr_NoMemory();
2959+
return -1;
2960+
}
2961+
28272962
#endif

Python/gc_free_threading.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1962,16 +1962,22 @@ custom_visitor_wrapper(const mi_heap_t *heap, const mi_heap_area_t *area,
19621962
}
19631963

19641964
void
1965-
PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void *arg)
1965+
_PyGC_VisitObjectsWorldStopped(PyInterpreterState *interp,
1966+
gcvisitobjects_t callback, void *arg)
19661967
{
1967-
PyInterpreterState *interp = _PyInterpreterState_GET();
19681968
struct custom_visitor_args wrapper = {
19691969
.callback = callback,
19701970
.arg = arg,
19711971
};
1972+
gc_visit_heaps(interp, &custom_visitor_wrapper, &wrapper.base);
1973+
}
19721974

1975+
void
1976+
PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void *arg)
1977+
{
1978+
PyInterpreterState *interp = _PyInterpreterState_GET();
19731979
_PyEval_StopTheWorld(interp);
1974-
gc_visit_heaps(interp, &custom_visitor_wrapper, &wrapper.base);
1980+
_PyGC_VisitObjectsWorldStopped(interp, callback, arg);
19751981
_PyEval_StartTheWorld(interp);
19761982
}
19771983

Python/sysmodule.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2175,6 +2175,11 @@ sys__clear_internal_caches_impl(PyObject *module)
21752175
#ifdef _Py_TIER2
21762176
PyInterpreterState *interp = _PyInterpreterState_GET();
21772177
_Py_Executors_InvalidateAll(interp, 0);
2178+
#endif
2179+
#ifdef Py_GIL_DISABLED
2180+
if (_Py_ClearUnusedTLBC(_PyInterpreterState_GET()) < 0) {
2181+
return NULL;
2182+
}
21782183
#endif
21792184
PyType_ClearCache();
21802185
Py_RETURN_NONE;

0 commit comments

Comments
 (0)