Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
7ad9131
Proposed code changes for [PEP XXX](link).
matajoh Mar 28, 2025
3b832e6
Addressing PR comments
matajoh Apr 3, 2025
d52b222
Cleaning up the immutability code
matajoh Apr 3, 2025
80b648c
deque working
matajoh Apr 4, 2025
697d3b1
Adding write barriers for array
matajoh Apr 4, 2025
4024677
Cleaning up function freezing + more tests
matajoh Apr 9, 2025
9136246
Immutability for blake2 + test_freeze module
matajoh Apr 9, 2025
61a0400
Fixing some holes in object/typeobject
matajoh Apr 9, 2025
6a6b04b
Remove immutable keys from dictionary as no longer required
mjp41 Apr 10, 2025
0caa788
Dealing with buffer immutability
matajoh Apr 11, 2025
a5ceb51
_ctypes immutability
matajoh Apr 11, 2025
2e54f32
_decimal
matajoh Apr 11, 2025
32d3fe7
Adding a NotFreezable type
matajoh Apr 14, 2025
37f2d76
_io
matajoh Apr 14, 2025
fe5de34
Fixing some broken tests
matajoh Apr 14, 2025
fef1fdb
Fixing the name of NotWritableError
matajoh Apr 14, 2025
ab10e21
_multiprocessing _sqlite3
matajoh Apr 14, 2025
a7dbd1a
cjkcodecs
matajoh Apr 14, 2025
dd611e4
_abc
matajoh Apr 16, 2025
47fab24
_asyncio
matajoh Apr 16, 2025
99f259c
Fixing import error
matajoh Apr 16, 2025
4f7816a
bz2
matajoh Apr 17, 2025
09fb9a7
_collections _csv
matajoh Apr 17, 2025
11119d0
Moving Py_Freeze to public API
matajoh Apr 17, 2025
baff5f8
xxlimited, xxsubtype, zlib
xFrednet Apr 17, 2025
93eca6d
_dbm etree
matajoh Apr 22, 2025
2ab77d3
immutable module
matajoh Apr 28, 2025
a4d42da
All tests passing again
matajoh Apr 29, 2025
5a0b5b7
_datetime
matajoh May 1, 2025
2fab5cd
_struct
matajoh May 1, 2025
f621a0c
Making freezable types a weakset
matajoh May 1, 2025
a31e962
These don't need to be freezable
matajoh May 1, 2025
adc1319
Make mutable during finalisation. (#6)
mjp41 May 1, 2025
ccb893c
Dealing with more finalisation bugs
matajoh May 2, 2025
30dd83f
Shadow cell
matajoh May 2, 2025
57df4db
Removing isfreezable and adding some TODOs
matajoh May 2, 2025
4866d04
Using import helper
matajoh May 2, 2025
0387c12
Adding the bases tuple as discussed
matajoh May 12, 2025
c867271
Initial simple Python module.
Jul 10, 2025
81aebf3
Add .clang-format file.
Jul 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,6 @@ Python/frozen_modules/MANIFEST
# Ignore ./python binary on Unix but still look into ./Python/ directory.
/python
!/Python/

# Ignore the build directory.
build*
1 change: 1 addition & 0 deletions Doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@
('c:data', 'PyExc_UnicodeError'),
('c:data', 'PyExc_UnicodeTranslateError'),
('c:data', 'PyExc_ValueError'),
('c:data', 'PyExc_NotWriteableError'),
('c:data', 'PyExc_ZeroDivisionError'),
# C API: Standard Python warning classes
('c:data', 'PyExc_BytesWarning'),
Expand Down
1 change: 1 addition & 0 deletions Doc/data/stable_abi.dat

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

7 changes: 7 additions & 0 deletions Include/cpython/pyerrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,13 @@ PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalErrorFormat(
const char *format,
...);

PyAPI_FUNC(PyObject *) _PyErr_WriteToImmutable(const char* file, int line, PyObject *obj);
#define PyErr_WriteToImmutable(obj) _PyErr_WriteToImmutable(__FILE__, __LINE__, _PyObject_CAST(obj))

PyAPI_FUNC(PyObject *) _PyErr_WriteToImmutableKey(const char* file, int line, PyObject *key);
#define PyErr_WriteToImmutableKey(key) _PyErr_WriteToImmutableKey(__FILE__, __LINE__, _PyObject_CAST(key))


extern PyObject *_PyErr_SetImportErrorWithNameFrom(
PyObject *,
PyObject *,
Expand Down
15 changes: 13 additions & 2 deletions Include/internal/pycore_dict.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,24 @@ typedef struct {
/* Cached hash code of me_key. */
Py_hash_t me_hash;
PyObject *me_key;
PyObject *me_value; /* This field is only meaningful for combined tables */
PyObject *_me_value; /* This field is only meaningful for combined tables */
} PyDictKeyEntry;

typedef struct {
PyObject *me_key; /* The key must be Unicode and have hash. */
PyObject *me_value; /* This field is only meaningful for combined tables */
PyObject *_me_value; /* This field is only meaningful for combined tables */
} PyDictUnicodeEntry;

#define _PyDictEntry_IsImmutable(entry) (((uintptr_t)((entry)->_me_value)) & 0x1)
#define _PyDictEntry_SetImmutable(entry) ((entry)->_me_value = (PyObject*)((uintptr_t)(entry)->_me_value | 0x1))
#define _PyDictEntry_Hash(entry) ((entry)->me_hash)
#define _PyDictEntry_Key(entry) ((entry)->me_key)
#define _PyDictEntry_Value(entry) ((PyObject*)((((uintptr_t)((entry)->_me_value)) >> 1) << 1))
#define _PyDictEntry_SetValue(entry, value) ((entry)->_me_value = value)
#define _PyDictEntry_IsEmpty(entry) ((entry)->_me_value == NULL)

extern PyObject *_PyDict_IsKeyImmutable(PyObject* op, PyObject* key);

extern PyDictKeysObject *_PyDict_NewKeysForClass(void);
extern PyObject *_PyDict_FromKeys(PyObject *, PyObject *, PyObject *);

Expand All @@ -50,6 +60,7 @@ extern Py_ssize_t _Py_dict_lookup(PyDictObject *mp, PyObject *key, Py_hash_t has
extern Py_ssize_t _PyDict_LookupIndex(PyDictObject *, PyObject *);
extern Py_ssize_t _PyDictKeys_StringLookup(PyDictKeysObject* dictkeys, PyObject *key);
extern PyObject *_PyDict_LoadGlobal(PyDictObject *, PyDictObject *, PyObject *);
extern PyObject *_PyDict_SetKeyImmutable(PyDictObject *mp, PyObject *key);

/* Consumes references to key and value */
extern int _PyDict_SetItem_Take2(PyDictObject *op, PyObject *key, PyObject *value);
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ _PyFrame_MakeAndSetFrameObject(_PyInterpreterFrame *frame);

/* Gets the PyFrameObject for this frame, lazily
* creating it if necessary.
* Returns a borrowed referennce */
* Returns a borrowed reference */
static inline PyFrameObject *
_PyFrame_GetFrameObject(_PyInterpreterFrame *frame)
{
Expand Down
2 changes: 2 additions & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

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

2 changes: 2 additions & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ struct _Py_global_strings {
STRUCT_FOR_STR(json_decoder, "json.decoder")
STRUCT_FOR_STR(kwdefaults, ".kwdefaults")
STRUCT_FOR_STR(list_err, "list index out of range")
STRUCT_FOR_STR(namedtuple, "namedtuple")
STRUCT_FOR_STR(newline, "\n")
STRUCT_FOR_STR(open_br, "{")
STRUCT_FOR_STR(percent, "%")
STRUCT_FOR_STR(shim_name, "<shim>")
STRUCT_FOR_STR(startswith, "startswith")
STRUCT_FOR_STR(type_params, ".type_params")
STRUCT_FOR_STR(utf_8, "utf-8")
} literals;
Expand Down
18 changes: 18 additions & 0 deletions Include/internal/pycore_immutability.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef Py_FREEZE_H
#define Py_FREEZE_H

#ifdef __cplusplus
extern "C" {
#endif

#ifndef Py_BUILD_CORE
# error "this header requires Py_BUILD_CORE define"
#endif

PyObject* _Py_Freeze(PyObject*);
#define Py_Freeze(op) _Py_Freeze(_PyObject_CAST(op))

#ifdef __cplusplus
}
#endif
#endif /* !Py_FREEZE_H */
19 changes: 16 additions & 3 deletions Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ static inline void _Py_RefcntAdd(PyObject* op, Py_ssize_t n)
static inline void _Py_SetImmortal(PyObject *op)
{
if (op) {
op->ob_refcnt = _Py_IMMORTAL_REFCNT;
op->ob_refcnt = (op->ob_refcnt & _Py_IMMUTABLE_MASK) | _Py_IMMORTAL_REFCNT;
}
}
#define _Py_SetImmortal(op) _Py_SetImmortal(_PyObject_CAST(op))
Expand All @@ -80,7 +80,8 @@ static inline void _Py_SetImmortal(PyObject *op)
static inline void _Py_ClearImmortal(PyObject *op)
{
if (op) {
assert(op->ob_refcnt == _Py_IMMORTAL_REFCNT);
assert((op->ob_refcnt & _Py_REFCNT_MASK) == _Py_IMMORTAL_REFCNT);
// note this also clears the _Py_IMMUTABLE_FLAG, if set
op->ob_refcnt = 1;
Py_DECREF(op);
}
Expand All @@ -91,6 +92,17 @@ static inline void _Py_ClearImmortal(PyObject *op)
op = NULL; \
} while (0)

static inline void _Py_SetImmutable(PyObject *op)
{
if(op) {
op->ob_refcnt |= _Py_IMMUTABLE_FLAG;
}
}
#define _Py_SetImmutable(op) _Py_SetImmutable(_PyObject_CAST(op))

#define Py_CHECKWRITE(op) ((op) && (!_Py_IsImmutable(op) || _Py_IsFinalizing()))
#define Py_REQUIREWRITE(op, msg) {if (Py_CHECKWRITE(op)) { _PyObject_ASSERT_FAILED_MSG(op, msg); }}

static inline void
_Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct)
{
Expand All @@ -101,7 +113,8 @@ _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct)
#ifdef Py_REF_DEBUG
_Py_DEC_REFTOTAL(_PyInterpreterState_GET());
#endif
if (--op->ob_refcnt != 0) {
op->ob_refcnt -= 1;
if ((op->ob_refcnt & _Py_REFCNT_MASK) != 0) {
assert(op->ob_refcnt > 0);
}
else {
Expand Down
2 changes: 2 additions & 0 deletions Include/internal/pycore_runtime_init_generated.h

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

42 changes: 34 additions & 8 deletions Include/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,25 @@ whose size is determined when the object is allocated.
/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD PyObject ob_base;

/*
Immutability:

Immutability is tracked in the top bit of the reference count. The immutability
system also uses the second-to-top bit for managing immutable graphs.
*/

#if SIZEOF_VOID_P > 4
#define _Py_REFCNT_MASK 0xFFFFFFFF
#define _Py_IMMUTABLE_MASK 0xC000000000
#define _Py_IMMUTABLE_FLAG 0x4000000000
#define _Py_IMMUTABLE_SCC_FLAG 0x8000000000
#else
#define _Py_REFCNT_MASK 0x3FFFFFFF
#define _Py_IMMUTABLE_MASK 0xC0000000
#define _Py_IMMUTABLE_FLAG 0x40000000
#define _Py_IMMUTABLE_SCC_FLAG 0x80000000
#endif

/*
Immortalization:

Expand Down Expand Up @@ -112,17 +131,17 @@ be done by checking the bit sign flag in the lower 32 bits.
#else
/*
In 32 bit systems, an object will be marked as immortal by setting all of the
lower 30 bits of the reference count field, which is equal to: 0x3FFFFFFF
lower 28 bits of the reference count field, which is equal to: 0x0FFFFFFF.

Using the lower 30 bits makes the value backwards compatible by allowing
Using the lower 28 bits makes the value backwards compatible by allowing
C-Extensions without the updated checks in Py_INCREF and Py_DECREF to safely
increase and decrease the objects reference count. The object would lose its
immortality, but the execution would still be correct.

Reference count increases and decreases will first go through an immortality
check by comparing the reference count field to the immortality reference count.
*/
#define _Py_IMMORTAL_REFCNT (UINT_MAX >> 2)
#define _Py_IMMORTAL_REFCNT (UINT_MAX >> 4)
#endif

// Make all internal uses of PyObject_HEAD_INIT immortal while preserving the
Expand Down Expand Up @@ -208,7 +227,7 @@ PyAPI_FUNC(int) Py_Is(PyObject *x, PyObject *y);


static inline Py_ssize_t Py_REFCNT(PyObject *ob) {
return ob->ob_refcnt;
return ob->ob_refcnt & _Py_REFCNT_MASK;
}
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000
# define Py_REFCNT(ob) Py_REFCNT(_PyObject_CAST(ob))
Expand Down Expand Up @@ -242,7 +261,7 @@ static inline Py_ALWAYS_INLINE int _Py_IsImmortal(PyObject *op)
#if SIZEOF_VOID_P > 4
return _Py_CAST(PY_INT32_T, op->ob_refcnt) < 0;
#else
return op->ob_refcnt == _Py_IMMORTAL_REFCNT;
return (op->ob_refcnt & _Py_REFCNT_MASK) == _Py_IMMORTAL_REFCNT;
#endif
}
#define _Py_IsImmortal(op) _Py_IsImmortal(_PyObject_CAST(op))
Expand All @@ -254,6 +273,11 @@ static inline int Py_IS_TYPE(PyObject *ob, PyTypeObject *type) {
# define Py_IS_TYPE(ob, type) Py_IS_TYPE(_PyObject_CAST(ob), (type))
#endif

static inline Py_ALWAYS_INLINE int _Py_IsImmutable(PyObject *op)
{
return (op->ob_refcnt & _Py_IMMUTABLE_FLAG) > 0;
}
#define _Py_IsImmutable(op) _Py_IsImmutable(_PyObject_CAST(op))

static inline void Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) {
// This immortal check is for code that is unaware of immortal objects.
Expand All @@ -263,7 +287,7 @@ static inline void Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) {
if (_Py_IsImmortal(ob)) {
return;
}
ob->ob_refcnt = refcnt;
ob->ob_refcnt = (ob->ob_refcnt & _Py_IMMUTABLE_MASK) | (refcnt & _Py_REFCNT_MASK);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the immutability flag always be 0 here due to the if statement above?

}
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000
# define Py_SET_REFCNT(ob, refcnt) Py_SET_REFCNT(_PyObject_CAST(ob), (refcnt))
Expand Down Expand Up @@ -687,7 +711,8 @@ static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
}
_Py_DECREF_STAT_INC();
_Py_DECREF_DecRefTotal();
if (--op->ob_refcnt == 0) {
op->ob_refcnt -= 1;
if ((op->ob_refcnt & _Py_REFCNT_MASK) == 0) {
_Py_Dealloc(op);
}
}
Expand All @@ -702,7 +727,8 @@ static inline Py_ALWAYS_INLINE void Py_DECREF(PyObject *op)
return;
}
_Py_DECREF_STAT_INC();
if (--op->ob_refcnt == 0) {
op->ob_refcnt -= 1;
if ((op->ob_refcnt & _Py_REFCNT_MASK) == 0) {
_Py_Dealloc(op);
}
}
Expand Down
1 change: 1 addition & 0 deletions Include/pyerrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ PyAPI_DATA(PyObject *) PyExc_UnicodeDecodeError;
PyAPI_DATA(PyObject *) PyExc_UnicodeTranslateError;
PyAPI_DATA(PyObject *) PyExc_ValueError;
PyAPI_DATA(PyObject *) PyExc_ZeroDivisionError;
PyAPI_DATA(PyObject *) PyExc_NotWriteableError;

#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03030000
PyAPI_DATA(PyObject *) PyExc_BlockingIOError;
Expand Down
1 change: 1 addition & 0 deletions Lib/_compat_pickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
"UnicodeWarning",
"UserWarning",
"ValueError",
"NotWriteableError",
"Warning",
"ZeroDivisionError",
)
Expand Down
1 change: 1 addition & 0 deletions Lib/test/exception_hierarchy.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ BaseException
│ ├── UnicodeDecodeError
│ ├── UnicodeEncodeError
│ └── UnicodeTranslateError
├── NotWriteableError
└── Warning
├── BytesWarning
├── DeprecationWarning
Expand Down
4 changes: 3 additions & 1 deletion Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -3282,13 +3282,15 @@ class F(D, E): pass
def cant(x, C):
try:
x.__class__ = C
except NotWriteableError:
pass
except TypeError:
pass
else:
self.fail("shouldn't allow %r.__class__ = %r" % (x, C))
try:
delattr(x, "__class__")
except (TypeError, AttributeError):
except (TypeError, AttributeError, NotWriteableError):
pass
else:
self.fail("shouldn't allow del %r.__class__" % x)
Expand Down
Loading