Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pyrona: Create RegionObject and RegionMetadata objects #9

Merged
merged 12 commits into from
Dec 2, 2024
14 changes: 13 additions & 1 deletion Include/internal/pycore_regions.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ extern "C" {
#define Py_CHECKWRITE(op) ((op) && _PyObject_CAST(op)->ob_region != _Py_IMMUTABLE)
#define Py_REQUIREWRITE(op, msg) {if (Py_CHECKWRITE(op)) { _PyObject_ASSERT_FAILED_MSG(op, msg); }}

/* This makes the given objects and all object reachable from the given
* object immutable. This will also move the objects into the immutable
* region.
*
* The argument is borrowed, meaning that it expects the calling context
* to handle the reference count.
*
* The function will return `Py_None` by default.
*/
PyObject* _Py_MakeImmutable(PyObject* obj);
#define Py_MakeImmutable(op) _Py_MakeImmutable(_PyObject_CAST(op))

Expand All @@ -26,6 +35,9 @@ PyObject* _Py_InvariantTgtFailure(void);
PyObject* _Py_EnableInvariant(void);
#define Py_EnableInvariant() _Py_EnableInvariant()

PyObject* _Py_ResetInvariant(void);
#define Py_ResetInvariant() _Py_ResetInvariant()

#ifdef NDEBUG
#define _Py_VPYDBG(fmt, ...)
#define _Py_VPYDBGPRINT(fmt, ...)
Expand All @@ -39,4 +51,4 @@ int _Py_CheckRegionInvariant(PyThreadState *tstate);
#ifdef __cplusplus
}
#endif
#endif /* !Py_INTERNAL_VERONAPY_H */
#endif /* !Py_INTERNAL_VERONAPY_H */
13 changes: 13 additions & 0 deletions Include/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ static inline Py_ALWAYS_INLINE int _Py_IsImmutable(PyObject *op)
}
#define _Py_IsImmutable(op) _Py_IsImmutable(_PyObject_CAST(op))

static inline Py_ALWAYS_INLINE int _Py_IsLocal(PyObject *op)
{
return op->ob_region == _Py_DEFAULT_REGION;
}
#define _Py_IsLocal(op) _Py_IsLocal(_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 Down Expand Up @@ -314,6 +320,13 @@ static inline void Py_SET_REGION(PyObject *ob, Py_uintptr_t region) {
# define Py_SET_REGION(ob, region) Py_SET_REGION(_PyObject_CAST(ob), (region))
#endif

static inline Py_uintptr_t Py_GET_REGION(PyObject *ob) {
return ob->ob_region;
}
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000
# define Py_GET_REGION(ob) Py_GET_REGION(_PyObject_CAST(ob))
#endif


/*
Type objects contain a string containing the type name (to help somewhat
Expand Down
122 changes: 122 additions & 0 deletions Lib/test/test_veronapy.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,128 @@ def test_weakref(self):
# self.assertTrue(c.val() is obj)
self.assertIsNone(c.val())

class TestRegionOwnership(unittest.TestCase):
class A:
pass

def setUp(self):
# This freezes A and super and meta types of A namely `type` and `object`
makeimmutable(self.A)
enableinvariant()

def test_default_ownership(self):
a = self.A()
r = Region()
self.assertFalse(r.owns_object(a))

def test_add_ownership(self):
a = self.A()
r = Region()
r.add_object(a)
self.assertTrue(r.owns_object(a))

def test_remove_ownership(self):
a = self.A()
r = Region()
r.add_object(a)
r.remove_object(a)
self.assertFalse(r.owns_object(a))

def test_add_ownership2(self):
a = self.A()
r1 = Region()
r2 = Region()
r1.add_object(a)
self.assertFalse(r2.owns_object(a))

def test_should_fail_add_ownership_twice_1(self):
a = self.A()
r = Region()
r.add_object(a)
self.assertRaises(RuntimeError, r.add_object, a)

def test_should_fail_add_ownership_twice_2(self):
a = self.A()
r = Region()
r.add_object(a)
r2 = Region()
self.assertRaises(RuntimeError, r2.add_object, a)

def test_init_with_name(self):
r1 = Region()
r2 = Region("Super-name")
self.assertTrue("Super-name" in repr(r2))

r3_name = "Trevligt-Name"
r3a = Region(r3_name)
r3b = Region(r3_name)
self.assertTrue(r3_name in repr(r3a))
self.assertTrue(r3_name in repr(r3b))
self.assertTrue(isimmutable(r3_name))

def test_init_invalid_name(self):
self.assertRaises(TypeError, Region, 42)

def test_init_same_name(self):
r1 = Region("Andy")
r2 = Region("Andy")
# Check that we reach the end of the test
self.assertTrue(True)

class TestRegionInvariance(unittest.TestCase):
class A:
pass

def setUp(self):
# This freezes A and super and meta types of A namely `type` and `object`
makeimmutable(self.A)
enableinvariant()

def test_invalid_point_to_local(self):
# Create linked objects (a) -> (b)
a = self.A()
b = self.A()
a.b = b

# Create a region and take ownership of a
r = Region()
# FIXME: Once the write barrier is implemented, this assert will fail.
# The code above should work without any errors.
self.assertRaises(RuntimeError, r.add_object, a)

# Check that the errors are on the appropriate objects
self.assertFalse(r.owns_object(b))
self.assertTrue(r.owns_object(a))
self.assertEqual(invariant_failure_src(), a)
self.assertEqual(invariant_failure_tgt(), b)

def test_allow_bridge_object_ref(self):
# Create linked objects (a) -> (b)
a = self.A()
b = Region()
a.b = b

# Create a region and take ownership of a
r = Region()
r.add_object(a)
self.assertFalse(r.owns_object(b))
self.assertTrue(r.owns_object(a))

def test_should_fail_external_uniqueness(self):
a = self.A()
r = Region()
a.f = r
a.g = r
r2 = Region()
try:
r2.add_object(a)
except RuntimeError:
# Check that the errors are on the appropriate objects
self.assertEqual(invariant_failure_src(), a)
self.assertEqual(invariant_failure_tgt(), r)
else:
self.fail("Should not reach here -- external uniqueness validated but not caught by invariant checker")

# This test will make the Python environment unusable.
# Should perhaps forbid making the frame immutable.
# class TestStackCapture(unittest.TestCase):
Expand Down
4 changes: 4 additions & 0 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -2041,6 +2041,7 @@ extern PyTypeObject _PyMemoryIter_Type;
extern PyTypeObject _PyLineIterator;
extern PyTypeObject _PyPositionsIterator;
extern PyTypeObject _PyLegacyEventHandler_Type;
extern PyTypeObject PyRegion_Type;

static PyTypeObject* static_types[] = {
// The two most important base types: must be initialized first and
Expand Down Expand Up @@ -2161,6 +2162,9 @@ static PyTypeObject* static_types[] = {
&PyODictKeys_Type, // base=&PyDictKeys_Type
&PyODictValues_Type, // base=&PyDictValues_Type
&PyODict_Type, // base=&PyDict_Type

// Pyrona Region:
&PyRegion_Type,
};


Expand Down
Loading