Skip to content

Commit adc1319

Browse files
mjp41matajoh
authored andcommitted
Make mutable during finalisation. (#6)
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
1 parent a31e962 commit adc1319

5 files changed

Lines changed: 80 additions & 7 deletions

File tree

Include/internal/pycore_object.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,14 @@ static inline void _Py_SetImmutable(PyObject *op)
103103
static inline void
104104
_Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct)
105105
{
106-
if (_Py_IsImmortal(op)) {
106+
if (_Py_IsImmortalOrImmutable(op)) {
107+
if (_Py_IsImmortal(op)) {
108+
return;
109+
}
110+
assert(_Py_IsImmutable(op));
111+
if (_Py_DecRef_Immutable(op)) {
112+
destruct(op);
113+
}
107114
return;
108115
}
109116
_Py_DECREF_STAT_INC();
@@ -125,7 +132,11 @@ _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct)
125132
static inline void
126133
_Py_DECREF_NO_DEALLOC(PyObject *op)
127134
{
128-
if (_Py_IsImmortal(op)) {
135+
if (_Py_IsImmortalOrImmutable(op)) {
136+
if (_Py_IsImmortal(op)) {
137+
return;
138+
}
139+
_Py_DecRef_Immutable(op);
129140
return;
130141
}
131142
_Py_DECREF_STAT_INC();

Include/object.h

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -288,12 +288,30 @@ static inline Py_ALWAYS_INLINE int _Py_IsImmutable(PyObject *op)
288288
#endif
289289
#define Py_REQUIREWRITE(op, msg) {if (Py_CHECKWRITE(op)) { _PyObject_ASSERT_FAILED_MSG(op, msg); }}
290290

291+
static inline Py_ALWAYS_INLINE int _Py_IsImmortalOrImmutable(PyObject *op)
292+
{
293+
// TODO(Immutable): Does this work for both 32 and 64bit?
294+
return op->ob_refcnt >= _Py_IMMORTAL_REFCNT;
295+
}
296+
#define _Py_IsImmortalOrImmutable(op) _Py_IsImmortalOrImmutable(_PyObject_CAST(op))
297+
291298
static inline void Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) {
292299
// This immortal check is for code that is unaware of immortal objects.
293300
// The runtime tracks these objects and we should avoid as much
294301
// as possible having extensions inadvertently change the refcnt
295302
// of an immortalized object.
296-
if (_Py_IsImmortal(ob)) {
303+
if (_Py_IsImmortalOrImmutable(ob))
304+
{
305+
if (_Py_IsImmortal(ob)) {
306+
return;
307+
}
308+
assert(_Py_IsImmutable(ob));
309+
// TODO(Immutable): It is dangerous to set the reference count of an
310+
// immutable object. The majority of calls appear to be where the rc
311+
// has reached 0 and a finalizer is running. This seems a reasonable
312+
// place to allow the refcnt to be set to 1, and clear the immutable flag.
313+
assert((ob->ob_refcnt & _Py_REFCNT_MASK) == 0);
314+
ob->ob_refcnt = refcnt;
297315
return;
298316
}
299317
ob->ob_refcnt = (ob->ob_refcnt & _Py_IMMUTABLE_MASK) | (refcnt & _Py_REFCNT_MASK);
@@ -655,6 +673,8 @@ PyAPI_FUNC(void) Py_DecRef(PyObject *);
655673
PyAPI_FUNC(void) _Py_IncRef(PyObject *);
656674
PyAPI_FUNC(void) _Py_DecRef(PyObject *);
657675

676+
PyAPI_FUNC(int) _Py_DecRef_Immutable(PyObject *op);
677+
658678
static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
659679
{
660680
#if defined(Py_LIMITED_API) && (Py_LIMITED_API+0 >= 0x030c0000 || defined(Py_REF_DEBUG))
@@ -715,7 +735,15 @@ static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
715735
if (op->ob_refcnt <= 0) {
716736
_Py_NegativeRefcount(filename, lineno, op);
717737
}
718-
if (_Py_IsImmortal(op)) {
738+
739+
if (_Py_IsImmortalOrImmutable(op))
740+
{
741+
if (_Py_IsImmortal(op)) {
742+
return;
743+
}
744+
assert(_Py_IsImmutable(op));
745+
if (_Py_DecRef_Immutable(op))
746+
_Py_Dealloc(op);
719747
return;
720748
}
721749
_Py_DECREF_STAT_INC();
@@ -732,7 +760,14 @@ static inline Py_ALWAYS_INLINE void Py_DECREF(PyObject *op)
732760
{
733761
// Non-limited C API and limited C API for Python 3.9 and older access
734762
// directly PyObject.ob_refcnt.
735-
if (_Py_IsImmortal(op)) {
763+
if (_Py_IsImmortalOrImmutable(op))
764+
{
765+
if (_Py_IsImmortal(op)) {
766+
return;
767+
}
768+
assert(_Py_IsImmutable(op));
769+
if (_Py_DecRef_Immutable(op))
770+
_Py_Dealloc(op);
736771
return;
737772
}
738773
_Py_DECREF_STAT_INC();

Lib/test/test_freeze/test_ctypes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import ctypes
22
import unittest
3-
from immutable import freeze, isfrozen, isfrozen
3+
from immutable import isfrozen
44

55
from .test_common import BaseObjectTest
66

Python/ceval.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,14 @@
8484
#define _Py_DECREF_SPECIALIZED(arg, dealloc) \
8585
do { \
8686
PyObject *op = _PyObject_CAST(arg); \
87-
if (_Py_IsImmortal(op)) { \
87+
if (_Py_IsImmortalOrImmutable(op)) { \
88+
if (_Py_IsImmortal(op)) { \
89+
break; \
90+
} \
91+
if (_Py_DecRef_Immutable(op)) { \
92+
destructor d = (destructor)(dealloc); \
93+
d(op); \
94+
} \
8895
break; \
8996
} \
9097
_Py_DECREF_STAT_INC(); \

Python/immutability.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,26 @@ int _PyImmutability_RegisterFreezable(PyTypeObject* tp)
453453
return result;
454454
}
455455

456+
// Perform a decref on an immutable object
457+
// returns true if the object should be deallocated.
458+
int _Py_DecRef_Immutable(PyObject *op)
459+
{
460+
// Decrement the reference count of an immutable object without
461+
// deallocating it.
462+
assert(_Py_IsImmutable(op));
463+
464+
// TODO(Immutable): This needs to be atomic.
465+
op->ob_refcnt -= 1;
466+
if ((op->ob_refcnt & _Py_REFCNT_MASK) != 0)
467+
// Context does not to dealloc this object.
468+
return false;
469+
470+
// Clear the immutable flag so that finalisers can run correctly.
471+
assert((op->ob_refcnt & _Py_REFCNT_MASK) == 0);
472+
op->ob_refcnt = 0;
473+
return true;
474+
}
475+
456476

457477
int _PyImmutability_Freeze(PyObject* obj)
458478
{

0 commit comments

Comments
 (0)