Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
2ec65dc
Initial implementation of tagged stackrefs for GIL build. Requires fi…
markshannon Oct 9, 2024
9a5c3a6
Merge branch 'main' into use-stackrefs
markshannon Oct 11, 2024
ee7f3a6
Streamline PyStackRef_XCLOSE and PyStackRef_CLEAR.
markshannon Oct 11, 2024
21d7e87
Fix error handling with not stackref outputs in code generator
markshannon Oct 11, 2024
370e77b
Merge branch 'main' into use-stackrefs
markshannon Oct 14, 2024
2636c04
Attempt to fix _BINARY_OP_INPLACE_ADD_UNICODE and use tag references …
markshannon Oct 14, 2024
5a38e03
Merge branch 'main' into use-stackrefs
markshannon Oct 14, 2024
a79aa47
Show frame locals in lltrace
markshannon Oct 14, 2024
a90e644
Fix _PyFrame_Copy and add some asserts to make reference heap safe
markshannon Oct 15, 2024
83e0323
Improve efficiency of _PyStackRef_FromPyObjectNew a bit
markshannon Oct 16, 2024
7fb6a48
Merge branch 'main' into use-stackrefs
markshannon Oct 22, 2024
7e6deef
Merge branch 'main' into use-stackrefs
markshannon Oct 29, 2024
f03f745
Make making heap safe more efficient
markshannon Oct 29, 2024
67670f0
Merge branch 'main' into use-stackrefs
markshannon Dec 2, 2024
ff9d044
Fix up after merge
markshannon Dec 2, 2024
3334c14
Merge branch 'main' into use-stackrefs
markshannon Dec 4, 2024
b7a8b5d
Restore immortality bit
markshannon Dec 4, 2024
49d0d42
Fix validity check
markshannon Dec 4, 2024
3a12d18
Add PyStackRef_IsMortal
markshannon Dec 5, 2024
497fb9f
Reduce overhead of stackrefs a tiny bit for mortal objects
markshannon Dec 5, 2024
4768a38
Merge branch 'main' into use-stackrefs
markshannon Dec 5, 2024
806c28a
Attempt to use tagged RC for a bit of extra speed
markshannon Dec 5, 2024
c540f28
Merge branch 'main' into use-stackrefs
markshannon Dec 6, 2024
0c20416
Revert addition of LOAD_FAST_WITH_COUNT
markshannon Dec 6, 2024
c88bcbc
Avoid INCREF + Steal pairs by using New or return stack ref directly
markshannon Dec 6, 2024
f5dec96
Avoid masking when comparing to True and False
markshannon Dec 6, 2024
bf2dca5
Merge branch 'main' into use-stackrefs
markshannon Dec 10, 2024
1069d98
Revert not-quite-true assert
markshannon Dec 10, 2024
2e752fc
Merge branch 'main' into use-stackrefs
markshannon Dec 12, 2024
d23ae47
Fix up after merge
markshannon Dec 12, 2024
382d101
Merge branch 'main' into use-stackrefs
markshannon Dec 13, 2024
d230f68
Fix compiler errors and warnings
markshannon Dec 13, 2024
1de6add
Merge branch 'main' into use-stackrefs
markshannon Dec 18, 2024
ad7e1e6
Merge branch 'main' into use-stackrefs
markshannon Dec 20, 2024
c96387a
Merge branch 'main' into use-stackrefs
markshannon Feb 4, 2025
69e993c
Post merge fix up
markshannon Feb 4, 2025
2a361c9
Merge branch 'main' into use-stackrefs
markshannon Feb 17, 2025
2efa0bd
Post merge fixups
markshannon Feb 17, 2025
036ffc5
Post merge fixup 2
markshannon Feb 17, 2025
ea43b6d
Revert changes to _PyLong_Add as not relevant to this PR
markshannon Feb 17, 2025
db7e049
Fix up a couple of comments
markshannon Feb 17, 2025
26e7868
Add news
markshannon Feb 17, 2025
d3da41c
Clarifications
markshannon Feb 17, 2025
9711f62
Fixup comments
markshannon Feb 17, 2025
d61e0bf
Tidy up frame ownership changes
markshannon Feb 17, 2025
2bc9ace
Get Py_STACKREF_DEBUG working again
markshannon Feb 18, 2025
12b961b
Turn off Py_STACKREF_DEBUG
markshannon Feb 18, 2025
00cd065
Reduce diff
markshannon Feb 18, 2025
0bea1f1
Reduce diff
markshannon Feb 18, 2025
0bae25d
Reduce diff
markshannon Feb 18, 2025
327f5c3
Merge branch 'main' into use-stackrefs
markshannon Feb 18, 2025
a527aaf
Fix immortality test
markshannon Feb 18, 2025
375bd16
Merge remote-tracking branch 'faster/use-stackrefs' into use-stackrefs
markshannon Feb 18, 2025
3b25dc3
Merge branch 'main' into use-stackrefs
markshannon Feb 19, 2025
56509f4
Add some asserts
markshannon Feb 19, 2025
d270553
Fix refcount accounting
markshannon Feb 20, 2025
98e132b
Use Sam's suggestion to avoid branching
markshannon Feb 20, 2025
522c0e7
Merge branch 'main' into use-stackrefs
markshannon Feb 25, 2025
b1edb96
Post merge fixup
markshannon Feb 25, 2025
4b0ef08
Revert changes to nogil flags
markshannon Feb 25, 2025
c501175
Revert erroneous change to FT refcount initialization
markshannon Feb 27, 2025
4702d58
Use macros on Windows
markshannon Feb 25, 2025
3e64d74
Fix typo
markshannon Feb 26, 2025
0b7a544
Fix macro
markshannon Feb 27, 2025
17b97c6
Make sure PyStackRef_DUP returns a reference
markshannon Feb 27, 2025
bf863cd
Avoid branch for known mortal objects
markshannon Feb 27, 2025
b7b9c6a
Merge branch 'main' into use-stackrefs
markshannon Feb 28, 2025
1e66e50
Don't evaluate macro argument twice
markshannon Feb 28, 2025
3e929d7
Add missing backslash
markshannon Feb 28, 2025
3ef8571
Merge branch 'main' into use-stackrefs
markshannon Mar 3, 2025
e2f1387
Update Misc/NEWS.d/next/Core_and_Builtins/2025-02-17-12-48-00.gh-issu…
markshannon Mar 3, 2025
36f6034
Use consistent return types for PyStackRef_IsHeapSafe
markshannon Mar 5, 2025
0f9c865
Drop 'immortal' bit. Only use 'counted' bit.
markshannon Mar 7, 2025
37f1e18
Remove use of Py_TAG_IMMORTAL
markshannon Mar 7, 2025
d744179
Rename functions and add comment
markshannon Mar 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Initial implementation of tagged stackrefs for GIL build. Requires fi…
…xes to immortal objects.
  • Loading branch information
markshannon committed Oct 10, 2024
commit 2ec65dc331054cf68f7a3b740c19db7f64533ff5
97 changes: 83 additions & 14 deletions Include/internal/pycore_stackref.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,13 @@ typedef union _PyStackRef {
} _PyStackRef;


#ifdef Py_GIL_DISABLED

#define Py_TAG_DEFERRED (1)

#define Py_TAG_PTR ((uintptr_t)0)
#define Py_TAG_BITS ((uintptr_t)1)

#ifdef Py_GIL_DISABLED

static const _PyStackRef PyStackRef_NULL = { .bits = Py_TAG_DEFERRED};
#define PyStackRef_IsNull(stackref) ((stackref).bits == PyStackRef_NULL.bits)
Expand Down Expand Up @@ -153,36 +154,104 @@ PyStackRef_AsStrongReference(_PyStackRef stackref)
return PyStackRef_FromPyObjectSteal(PyStackRef_AsPyObjectSteal(stackref));
}


#else // Py_GIL_DISABLED

// With GIL

#define Py_TAG_BITS 1
#define Py_TAG_REFCNT 1
#define BITS_TO_PTR(REF) ((PyObject *)((REF).bits))
#define BITS_TO_PTR_MASKED(REF) ((PyObject *)(((REF).bits) & (~Py_TAG_BITS)))

static const _PyStackRef PyStackRef_NULL = { .bits = 0 };
#define PyStackRef_IsNull(stackref) ((stackref).bits == 0)
#define PyStackRef_True ((_PyStackRef){.bits = (uintptr_t)&_Py_TrueStruct })
#define PyStackRef_False ((_PyStackRef){.bits = ((uintptr_t)&_Py_FalseStruct) })
#define PyStackRef_None ((_PyStackRef){.bits = ((uintptr_t)&_Py_NoneStruct) })

#define PyStackRef_AsPyObjectBorrow(stackref) ((PyObject *)(stackref).bits)
#define PyStackRef_IsNull(ref) ((ref).bits == 0)
#define PyStackRef_True ((_PyStackRef){.bits = ((uintptr_t)&_Py_TrueStruct) | Py_TAG_REFCNT })
#define PyStackRef_False ((_PyStackRef){.bits = ((uintptr_t)&_Py_FalseStruct) | Py_TAG_REFCNT })
#define PyStackRef_None ((_PyStackRef){.bits = ((uintptr_t)&_Py_NoneStruct) | Py_TAG_REFCNT })

#define PyStackRef_AsPyObjectSteal(stackref) PyStackRef_AsPyObjectBorrow(stackref)
static inline int
PyStackRef_HasCount(_PyStackRef ref)
{
return ref.bits & Py_TAG_REFCNT;
}

#define PyStackRef_FromPyObjectSteal(obj) ((_PyStackRef){.bits = ((uintptr_t)(obj))})
static inline PyObject *
PyStackRef_AsPyObjectBorrow(_PyStackRef ref)
{
return BITS_TO_PTR_MASKED(ref);
}

#define PyStackRef_FromPyObjectNew(obj) ((_PyStackRef){ .bits = (uintptr_t)(Py_NewRef(obj)) })
static inline PyObject *
PyStackRef_AsPyObjectSteal(_PyStackRef ref)
{
if (PyStackRef_HasCount(ref)) {
return Py_NewRef(BITS_TO_PTR_MASKED(ref));
}
else {
return BITS_TO_PTR(ref);
}
}

#define PyStackRef_FromPyObjectImmortal(obj) ((_PyStackRef){ .bits = (uintptr_t)(obj) })
/* We will want to extend this to a larger set of objects in the future */
#define _Py_IsDeferrable _Py_IsImmortal

#define PyStackRef_CLOSE(stackref) Py_DECREF(PyStackRef_AsPyObjectBorrow(stackref))
static inline _PyStackRef
PyStackRef_FromPyObjectSteal(PyObject *obj)
{
assert(obj != NULL);
unsigned int tag = _Py_IsDeferrable(obj) ? Py_TAG_REFCNT : 0;
_PyStackRef ref = ((_PyStackRef){.bits = ((uintptr_t)(obj)) | tag});
return ref;
}

static inline _PyStackRef
_PyStackRef_FromPyObjectNew(PyObject *obj)
{
if (_Py_IsDeferrable(obj)) {
return (_PyStackRef){ .bits = ((uintptr_t)obj) | Py_TAG_REFCNT};
}
Py_INCREF(obj);
_PyStackRef ref = (_PyStackRef){ .bits = (uintptr_t)obj };
return ref;
}
#define PyStackRef_FromPyObjectNew(obj) _PyStackRef_FromPyObjectNew(_PyObject_CAST(obj))

/* Create a new reference from an object with an embedded reference count */
static inline _PyStackRef
_PyStackRef_FromPyObjectWithCount(PyObject *obj)
{
return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_REFCNT};
}
#define PyStackRef_FromPyObjectWithCount(obj) _PyStackRef_FromPyObjectWithCount(_PyObject_CAST(obj))

#define PyStackRef_DUP(stackref) PyStackRef_FromPyObjectSteal(Py_NewRef(PyStackRef_AsPyObjectBorrow(stackref)))
#define PyStackRef_FromPyObjectImmortal PyStackRef_FromPyObjectWithCount


static inline _PyStackRef
PyStackRef_DUP(_PyStackRef ref)
{
assert(!PyStackRef_IsNull(ref));
if (!PyStackRef_HasCount(ref)) {
Py_INCREF_MORTAL(BITS_TO_PTR(ref));
}
return ref;
}

static inline void
PyStackRef_CLOSE(_PyStackRef ref)
{
assert(!PyStackRef_IsNull(ref));
if (!PyStackRef_HasCount(ref)) {
Py_DECREF_MORTAL(BITS_TO_PTR(ref));
}
}

#endif // Py_GIL_DISABLED

// Note: this is a macro because MSVC (Windows) has trouble inlining it.

#define PyStackRef_Is(a, b) ((a).bits == (b).bits)
#define PyStackRef_Is(a, b) (((a).bits & (~Py_TAG_BITS)) == ((b).bits & (~Py_TAG_BITS)))

// Converts a PyStackRef back to a PyObject *, converting the
// stackref to a new reference.
Expand Down
93 changes: 84 additions & 9 deletions Include/refcount.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ having all the lower 32 bits set, which will avoid the reference count to go
beyond the refcount limit. Immortality checks for reference count decreases will
be done by checking the bit sign flag in the lower 32 bits.
*/
#define _Py_IMMORTAL_REFCNT _Py_CAST(Py_ssize_t, UINT_MAX)
#define _Py_IMMORTAL_REFCNT ((Py_ssize_t)0xb0000000)

#else
/*
Expand Down Expand Up @@ -274,6 +274,32 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
# define Py_INCREF(op) Py_INCREF(_PyObject_CAST(op))
#endif

static inline Py_ALWAYS_INLINE void Py_INCREF_MORTAL(PyObject *op)
{
#if defined(Py_GIL_DISABLED)
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
uint32_t new_local = local + 1;
assert (new_local != 0);
if (_Py_IsOwnedByCurrentThread(op)) {
_Py_atomic_store_uint32_relaxed(&op->ob_ref_local, new_local);
}
else {
_Py_atomic_add_ssize(&op->ob_ref_shared, (1 << _Py_REF_SHARED_SHIFT));
}
#elif SIZEOF_VOID_P > 4
PY_UINT32_T cur_refcnt = op->ob_refcnt_split[PY_BIG_ENDIAN];
PY_UINT32_T new_refcnt = cur_refcnt + 1;
op->ob_refcnt_split[PY_BIG_ENDIAN] = new_refcnt;
#else
op->ob_refcnt++;
#endif
_Py_INCREF_STAT_INC();
#if defined(Py_REF_DEBUG) && !defined(Py_LIMITED_API)
if (!_Py_IsImmortal(op)) {
_Py_INCREF_IncRefTotal();
}
#endif
}

#if !defined(Py_LIMITED_API) && defined(Py_GIL_DISABLED)
// Implements Py_DECREF on objects not owned by the current thread.
Expand Down Expand Up @@ -302,6 +328,27 @@ static inline void Py_DECREF(PyObject *op) {
#define Py_DECREF(op) Py_DECREF(_PyObject_CAST(op))

#elif defined(Py_GIL_DISABLED) && defined(Py_REF_DEBUG)
static inline void Py_DECREF_MORTAL(const char *filename, int lineno, PyObject *op)
{
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
_Py_DECREF_STAT_INC();
_Py_DECREF_DecRefTotal();
if (_Py_IsOwnedByCurrentThread(op)) {
if (local == 0) {
_Py_NegativeRefcount(filename, lineno, op);
}
local--;
_Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local);
if (local == 0) {
_Py_MergeZeroLocalRefcount(op);
}
}
else {
_Py_DecRefSharedDebug(op, filename, lineno);
}
}
#define Py_DECREF_MORTAL(op) Py_DECREF_MORTAL(__FILE__, __LINE__, _PyObject_CAST(op))

static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
{
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
Expand All @@ -328,6 +375,22 @@ static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
#define Py_DECREF(op) Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op))

#elif defined(Py_GIL_DISABLED)
static inline void Py_DECREF_MORTAL(PyObject *op)
{
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
_Py_DECREF_STAT_INC();
if (_Py_IsOwnedByCurrentThread(op)) {
local--;
_Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local);
if (local == 0) {
_Py_MergeZeroLocalRefcount(op);
}
}
else {
_Py_DecRefShared(op);
}
}

static inline void Py_DECREF(PyObject *op)
{
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
Expand All @@ -350,6 +413,15 @@ static inline void Py_DECREF(PyObject *op)
#define Py_DECREF(op) Py_DECREF(_PyObject_CAST(op))

#elif defined(Py_REF_DEBUG)
static inline Py_ALWAYS_INLINE void Py_DECREF_MORTAL(PyObject *op)
{
_Py_DECREF_STAT_INC();
_Py_DECREF_DecRefTotal();
if (--op->ob_refcnt == 0) {
_Py_Dealloc(op);
}
}

static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
{
if (op->ob_refcnt <= 0) {
Expand All @@ -359,15 +431,21 @@ static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
_Py_DECREF_IMMORTAL_STAT_INC();
return;
}
_Py_DECREF_STAT_INC();
_Py_DECREF_DecRefTotal();
Py_DECREF_MORTAL(op);
}
#define Py_DECREF(op) Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op))

#else
static inline Py_ALWAYS_INLINE void Py_DECREF_MORTAL(PyObject *op)
{
if (!_Py_IsImmortal(op)) {
_Py_DECREF_STAT_INC();
}
if (--op->ob_refcnt == 0) {
_Py_Dealloc(op);
}
}
#define Py_DECREF(op) Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op))

#else
static inline Py_ALWAYS_INLINE void Py_DECREF(PyObject *op)
{
// Non-limited C API and limited C API for Python 3.9 and older access
Expand All @@ -376,10 +454,7 @@ static inline Py_ALWAYS_INLINE void Py_DECREF(PyObject *op)
_Py_DECREF_IMMORTAL_STAT_INC();
return;
}
_Py_DECREF_STAT_INC();
if (--op->ob_refcnt == 0) {
_Py_Dealloc(op);
}
Py_DECREF_MORTAL(op);
}
#define Py_DECREF(op) Py_DECREF(_PyObject_CAST(op))
#endif
Expand Down
10 changes: 6 additions & 4 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -3627,11 +3627,12 @@ dummy_func(
DEOPT_IF(!PyStackRef_IsNull(null));
DEOPT_IF(callable_o != (PyObject *)&PyUnicode_Type);
STAT_INC(CALL, hit);
res = PyStackRef_FromPyObjectSteal(PyObject_Str(arg_o));
PyObject *str = PyObject_Str(arg_o);
DEAD(null);
DEAD(callable);
PyStackRef_CLOSE(arg);
ERROR_IF(PyStackRef_IsNull(res), error);
ERROR_IF(str == NULL, error);
res = PyStackRef_FromPyObjectSteal(str);
}

macro(CALL_STR_1) =
Expand All @@ -3648,11 +3649,12 @@ dummy_func(
DEOPT_IF(!PyStackRef_IsNull(null));
DEOPT_IF(callable_o != (PyObject *)&PyTuple_Type);
STAT_INC(CALL, hit);
res = PyStackRef_FromPyObjectSteal(PySequence_Tuple(arg_o));
PyObject *tuple = PySequence_Tuple(arg_o);
DEAD(null);
DEAD(callable);
PyStackRef_CLOSE(arg);
ERROR_IF(PyStackRef_IsNull(res), error);
ERROR_IF(tuple == NULL, error);
res = PyStackRef_FromPyObjectSteal(tuple);
}

macro(CALL_TUPLE_1) =
Expand Down
4 changes: 0 additions & 4 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -1765,7 +1765,6 @@ _PyEvalFramePushAndInit_UnTagged(PyThreadState *tstate, _PyStackRef func,
PyObject *locals, PyObject *const* args,
size_t argcount, PyObject *kwnames, _PyInterpreterFrame *previous)
{
#if defined(Py_GIL_DISABLED)
size_t kw_count = kwnames == NULL ? 0 : PyTuple_GET_SIZE(kwnames);
size_t total_argcount = argcount + kw_count;
_PyStackRef *tagged_args_buffer = PyMem_Malloc(sizeof(_PyStackRef) * total_argcount);
Expand All @@ -1782,9 +1781,6 @@ _PyEvalFramePushAndInit_UnTagged(PyThreadState *tstate, _PyStackRef func,
_PyInterpreterFrame *res = _PyEvalFramePushAndInit(tstate, func, locals, (_PyStackRef const *)tagged_args_buffer, argcount, kwnames, previous);
PyMem_Free(tagged_args_buffer);
return res;
#else
return _PyEvalFramePushAndInit(tstate, func, locals, (_PyStackRef const *)args, argcount, kwnames, previous);
#endif
}

/* Same as _PyEvalFramePushAndInit but takes an args tuple and kwargs dict.
Expand Down
11 changes: 0 additions & 11 deletions Python/ceval_macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -442,25 +442,14 @@ do { \
/* How much scratch space to give stackref to PyObject* conversion. */
#define MAX_STACKREF_SCRATCH 10

#ifdef Py_GIL_DISABLED
#define STACKREFS_TO_PYOBJECTS(ARGS, ARG_COUNT, NAME) \
/* +1 because vectorcall might use -1 to write self */ \
PyObject *NAME##_temp[MAX_STACKREF_SCRATCH+1]; \
PyObject **NAME = _PyObjectArray_FromStackRefArray(ARGS, ARG_COUNT, NAME##_temp + 1);
#else
#define STACKREFS_TO_PYOBJECTS(ARGS, ARG_COUNT, NAME) \
PyObject **NAME = (PyObject **)ARGS; \
assert(NAME != NULL);
#endif

#ifdef Py_GIL_DISABLED
#define STACKREFS_TO_PYOBJECTS_CLEANUP(NAME) \
/* +1 because we +1 previously */ \
_PyObjectArray_Free(NAME - 1, NAME##_temp);
#else
#define STACKREFS_TO_PYOBJECTS_CLEANUP(NAME) \
(void)(NAME);
#endif

#ifdef Py_GIL_DISABLED
#define CONVERSION_FAILED(NAME) ((NAME) == NULL)
Expand Down
10 changes: 6 additions & 4 deletions Python/executor_cases.c.h

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

Loading