blob: 91caa9e327f12a431831a67895f392a8329bbe5f [file]
// Collects code that was copied in from cpython, for a couple of different reasons:
// * We wanted to modify it to produce a more efficient version for our uses
// * We needed to call it and it was static :(
// * We wanted to call it and needed to backport it
#ifndef CPY_PYTHONSUPPORT_H
#define CPY_PYTHONSUPPORT_H
#include <Python.h>
#include <stdbool.h>
#include "pythoncapi_compat.h"
#include <frameobject.h>
#include <assert.h>
#include "static_data.h"
#include "mypyc_util.h"
#if CPY_3_13_FEATURES
#ifndef Py_BUILD_CORE
#define Py_BUILD_CORE
#endif
#include "internal/pycore_genobject.h" // _PyGen_FetchStopIterationValue
#include "internal/pycore_pyerrors.h" // _PyErr_FormatFromCause, _PyErr_SetKeyError
#include "internal/pycore_setobject.h" // _PySet_Update
#endif
#ifdef Py_GIL_DISABLED
#include "internal/pycore_object.h" // _Py_TryIncrefFast, _Py_TryIncRefShared
#endif
#if CPY_3_12_FEATURES
#include "internal/pycore_frame.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if 0
} // why isn't emacs smart enough to not indent this
#endif
#ifdef Py_GIL_DISABLED
// Read a native attribute that is a single reference-counted 'PyObject *' field,
// returning a new reference (or NULL if the field is NULL/undefined).
//
// On free-threaded builds a plain load followed by an incref races with a
// concurrent setter that may decref the old value to zero and free it before the
// incref runs (use-after-free). We avoid that with an optimistic try-incref,
// validating the field when taking a shared reference. If no reference can be
// secured that way, the fallback takes the same per-object lock as CPy_SetAttrRef
// and reloads the field before taking a reference. This mirrors CPython's own
// instance attribute read (in CPython 3.14: _Py_TryIncrefCompare, then a locked
// reload -- see _PyObject_TryGetInstanceAttribute in Objects/dictobject.c).
//
// CPython's QSBR-aware object heaps keep a stale pointer safe to inspect as a
// PyObject header. The try-incref protocol ensures that stale memory is never returned
// as the attribute value. These guarantees depend on CPython internals and must be
// rechecked when adding support for a new Python version.
//
// The hot path is lock-free and intentionally small: _Py_TryIncrefFast handles
// values owned by this thread and immortal values. Everything colder -- the
// shared-refcount CAS, field validation, and per-object lock fallback -- is in
// CPy_GetAttrRefSlow. The cold annotation also prevents those arguments and
// branches from bloating the caller's hot path. An unflagged cross-thread value
// takes the locked slow path once; _Py_XNewRefWithLock sets maybe-weakref lazily,
// so subsequent reads generally succeed through the lock-free shared-refcount
// path. The default (GIL) build keeps the plain load + incref generated inline by
// mypyc.
//
// Note that observing a non-NULL 'v' does not guarantee a non-NULL result: 'v' is
// only a provisional read, and if the attribute is deleted before a reference to it
// can be secured, this returns NULL and the caller raises AttributeError. That is a
// legal outcome for a racing read, since it is ordered after the delete.
CPy_COLD PyObject *CPy_GetAttrRefSlow(PyObject *v, PyObject *owner, PyObject **field);
static inline PyObject *CPy_GetAttrRef(PyObject *owner, PyObject **field) {
PyObject *v = (PyObject *)_Py_atomic_load_ptr_acquire(field);
if (v == NULL) {
return NULL;
}
if (likely(_Py_TryIncrefFast(v))) {
return v;
}
return CPy_GetAttrRefSlow(v, owner, field);
}
// Read a native attribute that is a single reference-counted 'PyObject *' field
// AND is Final (assigned once during construction, never rebound -- mypyc emits no
// setter for it), returning a new reference (or NULL if undefined).
//
// A Final attribute has no concurrent writer after 'self' is published, so the
// use-after-free race that CPy_GetAttrRef guards against cannot happen: the field
// holds a strong reference for the object's whole lifetime, and any thread reading
// it necessarily holds 'self', which keeps the value alive. So the try-incref +
// _Py_XNewRefWithLock fallback are unnecessary here -- a plain load + Py_INCREF is
// safe. A cross-thread Py_INCREF is an unconditional atomic add on ob_ref_shared
// (CPython 3.14), so (unlike CPy_GetAttrRef's try-incref) it needs no maybe-weakref
// and has no slow path. The load is relaxed rather than acquire: the
// reader reached 'self' through a synchronization edge (self's own publication)
// that already ordered the construction stores before it, exactly as with
// CPy_InitAttrRef's relaxed store. Relaxed keeps it TSan-clean at zero cost (plain
// mov/ldr).
static inline PyObject *CPy_GetAttrRefFinal(PyObject **field) {
PyObject *v = (PyObject *)_Py_atomic_load_ptr_relaxed(field);
if (v != NULL) {
Py_INCREF(v);
}
return v;
}
// Set a native attribute that is a single reference-counted 'PyObject *' field,
// stealing the reference to 'value' (which may be NULL to delete the attribute)
// and safely reclaiming the previous value.
//
// The owner's critical section serializes writers and synchronizes them with
// CPy_GetAttrRef's fallback path. A lock-free reader either secures a local or
// immortal reference directly, or secures a shared reference and validates the
// field. Otherwise it reloads the field under this lock. Thus once the replacement
// is published, the old field reference can be decrefed immediately, even if a
// reader is still dereferencing the old value (see CPy_GetAttrRef for why touching
// a freed value there is safe). Decref outside the critical section so an
// arbitrary destructor does not run while the owner is locked -- the equivalent in
// CPython 3.14 (store_instance_attr_lock_held) decrefs with the lock still held.
static inline void CPy_SetAttrRef(PyObject *owner, PyObject **field, PyObject *value) {
PyObject *old;
Py_BEGIN_CRITICAL_SECTION(owner);
old = (PyObject *)_Py_atomic_load_ptr_relaxed(field);
_Py_atomic_store_ptr_release(field, value);
Py_END_CRITICAL_SECTION();
Py_XDECREF(old);
}
// Publish a lazily created attribute once. Concurrent initializers may create
// separate values; only the first is installed, and the others are discarded.
// Decref outside the owner's critical section in case it runs Python code.
static inline void CPy_InitAttrRefIfNull(PyObject *owner, PyObject **field, PyObject *value) {
Py_BEGIN_CRITICAL_SECTION(owner);
if (_Py_atomic_load_ptr_relaxed(field) == NULL) {
_Py_atomic_store_ptr_release(field, value);
value = NULL;
}
Py_END_CRITICAL_SECTION();
Py_XDECREF(value);
}
// Initialize a native attribute that is known to be previously undefined (NULL),
// stealing the reference to 'value'.
//
// Initializer stores only happen while 'self' is still thread-local (the
// attribute-definedness analysis marks a SetAttr as an initializer only before
// 'self' can leak -- see mypyc/analysis/attrdefined.py). So there is no old value
// to reclaim, no competing writer, and the field store is not itself the
// publication point: 'self' is published later (when it escapes __init__ or is
// returned), and that publication carries the release barrier making all the
// construction stores visible. A relaxed store therefore suffices.
//
// This deliberately does NOT call SetMaybeWeakref (its CAS is pure overhead here,
// ~+2.6ns per fresh store, and construction-heavy code pays it on every attribute
// of every new object). CPy_SetAttrRef likewise leaves the flag unset. The cost is
// moved onto CPy_GetAttrRef's cold slow path, which sets maybe-weakref lazily on
// the first cross-thread read that needs it.
static inline void CPy_InitAttrRef(PyObject **field, PyObject *value) {
_Py_atomic_store_ptr_relaxed(field, value);
}
#endif
// Generated generator and coroutine helpers claim this flag while executing,
// rejecting reentrant or concurrent resumes.
//
// On free-threaded builds, the atomic exchange provides mutual exclusion and acquire
// ordering; the release store publishes body writes to the next resume. This also
// permits plain access to private generator attributes. Claiming must be a single
// atomic operation, or two threads could both observe a clear flag and enter. Under
// the GIL, plain accesses suffice.
#ifdef Py_GIL_DISABLED
static inline int CPyGen_TryEnter(uint32_t *running) {
return _Py_atomic_exchange_uint32(running, 1) == 0;
}
static inline void CPyGen_Exit(uint32_t *running) {
_Py_atomic_store_uint32_release(running, 0);
}
#else
static inline int CPyGen_TryEnter(uint32_t *running) {
if (*running) {
return 0;
}
*running = 1;
return 1;
}
static inline void CPyGen_Exit(uint32_t *running) {
*running = 0;
}
#endif
PyObject* update_bases(PyObject *bases);
int init_subclass(PyTypeObject *type, PyObject *kwds);
Py_ssize_t
CPyLong_AsSsize_tAndOverflow_(PyObject *vv, int *overflow);
#if CPY_3_12_FEATURES
static inline Py_ssize_t
CPyLong_AsSsize_tAndOverflow(PyObject *vv, int *overflow)
{
/* This version by Tim Peters */
PyLongObject *v = (PyLongObject *)vv;
Py_ssize_t res;
Py_ssize_t i;
*overflow = 0;
res = -1;
i = CPY_LONG_TAG(v);
// TODO: Combine zero and non-zero cases helow?
if (likely(i == (1 << CPY_NON_SIZE_BITS))) {
res = CPY_LONG_DIGIT(v, 0);
} else if (likely(i == CPY_SIGN_ZERO)) {
res = 0;
} else if (i == ((1 << CPY_NON_SIZE_BITS) | CPY_SIGN_NEGATIVE)) {
res = -(sdigit)CPY_LONG_DIGIT(v, 0);
} else {
// Slow path is moved to a non-inline helper function to
// limit size of generated code
int overflow_local;
res = CPyLong_AsSsize_tAndOverflow_(vv, &overflow_local);
*overflow = overflow_local;
}
return res;
}
#else
// Adapted from longobject.c in Python 3.7.0
/* This function adapted from PyLong_AsLongLongAndOverflow, but with
* some safety checks removed and specialized to only work for objects
* that are already longs.
* About half of the win this provides, though, just comes from being
* able to inline the function, which in addition to saving function call
* overhead allows the out-parameter overflow flag to be collapsed into
* control flow.
* Additionally, we check against the possible range of CPyTagged, not of
* Py_ssize_t. */
static inline Py_ssize_t
CPyLong_AsSsize_tAndOverflow(PyObject *vv, int *overflow)
{
/* This version by Tim Peters */
PyLongObject *v = (PyLongObject *)vv;
Py_ssize_t res;
Py_ssize_t i;
*overflow = 0;
res = -1;
i = Py_SIZE(v);
if (likely(i == 1)) {
res = CPY_LONG_DIGIT(v, 0);
} else if (likely(i == 0)) {
res = 0;
} else if (i == -1) {
res = -(sdigit)CPY_LONG_DIGIT(v, 0);
} else {
// Slow path is moved to a non-inline helper function to
// limit size of generated code
int overflow_local;
res = CPyLong_AsSsize_tAndOverflow_(vv, &overflow_local);
*overflow = overflow_local;
}
return res;
}
#endif
// Adapted from listobject.c in Python 3.7.0
static int
list_resize(PyListObject *self, Py_ssize_t newsize)
{
PyObject **items;
size_t new_allocated, num_allocated_bytes;
Py_ssize_t allocated = self->allocated;
/* Bypass realloc() when a previous overallocation is large enough
to accommodate the newsize. If the newsize falls lower than half
the allocated size, then proceed with the realloc() to shrink the list.
*/
if (allocated >= newsize && newsize >= (allocated >> 1)) {
assert(self->ob_item != NULL || newsize == 0);
Py_SET_SIZE(self, newsize);
return 0;
}
/* This over-allocates proportional to the list size, making room
* for additional growth. The over-allocation is mild, but is
* enough to give linear-time amortized behavior over a long
* sequence of appends() in the presence of a poorly-performing
* system realloc().
* The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
* Note: new_allocated won't overflow because the largest possible value
* is PY_SSIZE_T_MAX * (9 / 8) + 6 which always fits in a size_t.
*/
new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6);
if (new_allocated > (size_t)PY_SSIZE_T_MAX / sizeof(PyObject *)) {
PyErr_NoMemory();
return -1;
}
if (newsize == 0)
new_allocated = 0;
num_allocated_bytes = new_allocated * sizeof(PyObject *);
items = (PyObject **)PyMem_Realloc(self->ob_item, num_allocated_bytes);
if (items == NULL) {
PyErr_NoMemory();
return -1;
}
self->ob_item = items;
Py_SET_SIZE(self, newsize);
self->allocated = new_allocated;
return 0;
}
// Changed to use PyList_SetSlice instead of the internal list_ass_slice
static PyObject *
list_pop_impl(PyListObject *self, Py_ssize_t index)
{
PyObject *v;
int status;
if (Py_SIZE(self) == 0) {
/* Special-case most common failure cause */
PyErr_SetString(PyExc_IndexError, "pop from empty list");
return NULL;
}
if (index < 0)
index += Py_SIZE(self);
if (index < 0 || index >= Py_SIZE(self)) {
PyErr_SetString(PyExc_IndexError, "pop index out of range");
return NULL;
}
v = self->ob_item[index];
if (index == Py_SIZE(self) - 1) {
status = list_resize(self, Py_SIZE(self) - 1);
if (status >= 0)
return v; /* and v now owns the reference the list had */
else
return NULL;
}
Py_INCREF(v);
status = PyList_SetSlice((PyObject *)self, index, index+1, (PyObject *)NULL);
if (status < 0) {
Py_DECREF(v);
return NULL;
}
return v;
}
// Tweaked to directly use CPyTagged
static CPyTagged
list_count(PyListObject *self, PyObject *value)
{
Py_ssize_t count = 0;
Py_ssize_t i;
#ifdef Py_GIL_DISABLED
for (i = 0; i < PyList_GET_SIZE(self); i++) {
PyObject *item = PyList_GetItemRef((PyObject *)self, i);
if (unlikely(item == NULL)) {
// Race condition: list shrank between size read and get item
if (PyErr_ExceptionMatches(PyExc_IndexError)) {
PyErr_Clear();
break;
}
return CPY_INT_TAG;
}
int cmp = PyObject_RichCompareBool(item, value, Py_EQ);
Py_DECREF(item);
if (cmp > 0)
count++;
else if (cmp < 0)
return CPY_INT_TAG;
}
#else
for (i = 0; i < Py_SIZE(self); i++) {
int cmp = PyObject_RichCompareBool(self->ob_item[i], value, Py_EQ);
if (cmp > 0)
count++;
else if (cmp < 0)
return CPY_INT_TAG;
}
#endif
return CPyTagged_ShortFromSsize_t(count);
}
// Adapted from genobject.c in Python 3.7.2
// Copied because it wasn't in 3.5.2 and it is undocumented anyways.
/*
* Set StopIteration with specified value. Value can be arbitrary object
* or NULL.
*
* Returns 0 if StopIteration is set and -1 if any other exception is set.
*/
static int
CPyGen_SetStopIterationValue(PyObject *value)
{
PyObject *e;
if (value == NULL ||
(!PyTuple_Check(value) && !PyExceptionInstance_Check(value)))
{
/* Delay exception instantiation if we can */
PyErr_SetObject(PyExc_StopIteration, value);
return 0;
}
/* Construct an exception instance manually with
* PyObject_CallOneArg and pass it to PyErr_SetObject.
*
* We do this to handle a situation when "value" is a tuple, in which
* case PyErr_SetObject would set the value of StopIteration to
* the first element of the tuple.
*
* (See PyErr_SetObject/_PyErr_CreateException code for details.)
*/
e = PyObject_CallOneArg(PyExc_StopIteration, value);
if (e == NULL) {
return -1;
}
PyErr_SetObject(PyExc_StopIteration, e);
Py_DECREF(e);
return 0;
}
// Copied from dictobject.c and dictobject.h, these are not Public before
// Python 3.8. Also remove some error checks that we do in the callers.
typedef struct {
PyObject_HEAD
PyDictObject *dv_dict;
} _CPyDictViewObject;
static PyObject *
_CPyDictView_New(PyObject *dict, PyTypeObject *type)
{
_CPyDictViewObject *dv = PyObject_GC_New(_CPyDictViewObject, type);
if (dv == NULL)
return NULL;
Py_INCREF(dict);
dv->dv_dict = (PyDictObject *)dict;
PyObject_GC_Track(dv);
return (PyObject *)dv;
}
#ifdef __cplusplus
}
#endif
#if CPY_3_12_FEATURES
// These are copied from genobject.c in Python 3.12
static int
gen_is_coroutine(PyObject *o)
{
if (PyGen_CheckExact(o)) {
PyCodeObject *code = PyGen_GetCode((PyGenObject*)o);
if (code->co_flags & CO_ITERABLE_COROUTINE) {
return 1;
}
}
return 0;
}
#else
// Copied from genobject.c in Python 3.10
static int
gen_is_coroutine(PyObject *o)
{
if (PyGen_CheckExact(o)) {
PyCodeObject *code = (PyCodeObject *)((PyGenObject*)o)->gi_code;
if (code->co_flags & CO_ITERABLE_COROUTINE) {
return 1;
}
}
return 0;
}
#endif
/*
* This helper function returns an awaitable for `o`:
* - `o` if `o` is a coroutine-object;
* - `type(o)->tp_as_async->am_await(o)`
*
* Raises a TypeError if it's not possible to return
* an awaitable and returns NULL.
*/
static PyObject *
CPyCoro_GetAwaitableIter(PyObject *o)
{
unaryfunc getter = NULL;
PyTypeObject *ot;
if (PyCoro_CheckExact(o) || gen_is_coroutine(o)) {
/* 'o' is a coroutine. */
Py_INCREF(o);
return o;
}
ot = Py_TYPE(o);
if (ot->tp_as_async != NULL) {
getter = ot->tp_as_async->am_await;
}
if (getter != NULL) {
PyObject *res = (*getter)(o);
if (res != NULL) {
if (PyCoro_CheckExact(res) || gen_is_coroutine(res)) {
/* __await__ must return an *iterator*, not
a coroutine or another awaitable (see PEP 492) */
PyErr_SetString(PyExc_TypeError,
"__await__() returned a coroutine");
Py_CLEAR(res);
} else if (!PyIter_Check(res)) {
PyErr_Format(PyExc_TypeError,
"__await__() returned non-iterator "
"of type '%.100s'",
Py_TYPE(res)->tp_name);
Py_CLEAR(res);
}
}
return res;
}
PyErr_Format(PyExc_TypeError,
"object %.100s can't be used in 'await' expression",
ot->tp_name);
return NULL;
}
#endif