Skip to content

Conversation

@TobiasWrigstad
Copy link
Collaborator

@TobiasWrigstad TobiasWrigstad commented Dec 14, 2024

  • Line 1176: if(Py_CHECKWRITE(v)){
  • Line 1193: if(Py_CHECKWRITE(v)){
  • Line 1639: if(!Py_CHECKWRITE(obj)){

The first two cases are in PyObject_SetAttr.

I believe that PyObject_SetAttr falls out for free with the default tp_setattro implementation which simply calls PyObjectGenericSetAttr which in turn always ends up in dictobject.c -- unless we hit line 1567 which uses tp_descr_set, overloading of __set__.

However, __set__ descriptors' behaviour should fall out for free as they will eventually hit a "normal" store which should be trapped by the write barrier.

The normal tp_setattro implementation is changed by __setattr__ being overridden by user. We should expect things to fall out for free in this case since that code will eventually hit a "normal" store.

Exceptions: unless the implementation of __setattr__ or __set__ is written in C. This is handled using the Py_TPFLAGS_REGION_AWARE from PR #33. If this flag is not set, a dirty flag is set in the object.

The third and last case is a call to PyObject_GenericSetDict which is the setter for o.__dict__ = v. In this case, we need an add ref on the v and a remove ref on the old value of o.__dict__. This has been implemented. However, I have so far been unable to trigger this code. o.__dict__ = v runs subtype_setdict which I have extended in a similar fashion.

@TobiasWrigstad
Copy link
Collaborator Author

"How to handle this case" has two parts:

  1. What to do when the set attr function is an "arbitrary C function" outside of the CPython standard libraries — the answer is "set the LRC dirty flag"
  2. How do we distinguish between OK C functions (such as those in the CPython standard libraries) and NOT OK C functions — here the answer is possibly check if the type is Pyrona-aware.

If the answer to 2. is Pyrona-awareness checking, then I believe we should discuss this further before I start making more changes. @mjp41 @xFrednet

@xFrednet
Copy link
Collaborator

2. How do we distinguish between OK C functions (such as those in the CPython standard libraries) and NOT OK C functions — here the answer is possibly check if the type is Pyrona-aware.

I assumed, that we would check if the type is pyrona aware.

If the answer to 2. is Pyrona-awareness checking, then I believe we should discuss this further before I start making more changes.

I'm free after tomorrow's daily, so we could expand it to discuss this further

@TobiasWrigstad
Copy link
Collaborator Author

@xFrednet This PR adds a strawman implementation of the LRC dirty flag and a function void _PyRegion_set_lrc_dirty(PyObject *op) on line 175 in regions.c to set the flag (and one more to read if ofc). I'd like to talk about how to integrate that flag with your trace-based checking.

@TobiasWrigstad TobiasWrigstad changed the title WIP Adding WB to object.c Adding WB to object.c Jan 15, 2025
@TobiasWrigstad TobiasWrigstad marked this pull request as ready for review January 15, 2025 15:04
@xFrednet
Copy link
Collaborator

Small comment regarding the PR description:

Right now, we don't do anything with this flag and never clear it -- that is the next step in this WIP PR.

#35 starts using the dirty flag to determine if we can close a region or not. This might cause some test failures once both are on the phase 3 branch

Copy link
Collaborator

@xFrednet xFrednet left a comment

Choose a reason for hiding this comment

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

Three comments, the rest looks good to me. I believe this actually discovered a new case in the WB that I didn't consider previously. FrankenScript has a MoveReference function which I believe we'll also need.

Comment on lines 2945 to 2947
if (value == NULL || _Pyrona_AddReference(obj, value)) {
if (*dictptr) {
_Pyrona_RemoveReference(obj, *dictptr);
}
Py_XSETREF(*dictptr, Py_XNewRef(value));
return 0;
Copy link
Collaborator

@xFrednet xFrednet Jan 15, 2025

Choose a reason for hiding this comment

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

I believe this can run into the problem described here: #35 (comment)

But the Py_REGIONCHANGEREFERENCE will sadly not be the solution here. Since we move a reference. I think we have to add another Py_REGIONMOVEREFERENCE util, which would handle cases where the owner of the same object changes.

I drafted up this implementation, but it doesn't handle cowns yet. I have to think a bit longer about this 🤔
int _Py_RegionMoveReference(PyObject* old_src, PyObject* src, PyObject* tgt) {
    result = 0;

    regionmetadata *parent = regionmetadata_get_parent(Py_REGION(tgt));
    bool was_clean = regionmetadata_is_dirty(parent);

    // Prevent the parent from closing by marking it as dirty
    regionmetadata_mark_as_dirty(parent);

    // First remove the reference to allow the move of bridges and cross
    // region references. Any parent regions should remain open since they've
    // been marked as dirty.
    _Pyrona_RemoveReference(old_src, tgt);

    // Attempt to move the reference
    if (!_Pyrona_AddReference(src, tgt)) {
        // Re-add the previous reference since the new owner is invalid
        _Pyrona_AddReference(old_src, tgt);
        result = -1
    }

    if (was_clean) {
        regionmetadata_mark_as_not_dirty(parent);
    }

    // TODO, this doesn't handle cases where `old_src` is a cown...

    return result;
}

This can be optimized further. We can add a fast track, if the old and new src are from the same region and the target is not bridge object.

EDIT: I now found an easier way based on the changes in #34 we can simply inc the LRC do the move and then DEC the LRC again, all while keeping the region open do to the virtual LRC we added :D

These are the problems that make my brain happy to think about :D

Copy link
Owner

Choose a reason for hiding this comment

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

I always thought of move between regions as "move to local" followed by "move from local". I think that might be what you end result might be?

Copy link
Owner

Choose a reason for hiding this comment

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

I do wonder if we should build a Py_XSETREF_WITH_REGION operation, like I have added for Py_CLEAR in my PR.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I do wonder if we should build a Py_XSETREF_WITH_REGION operation, like I have added for Py_CLEAR in my PR.

I like the idea, but feel like the error handling is too inconsistent, some require a jump, others just a simple return 0 or return -1.

I always thought of move between regions as "move to local" followed by "move from local". I think that might be what you end result might be?

Yes, implementing a change reference like that should work and keep the region open, as we expect.

@TobiasWrigstad
Copy link
Collaborator Author

Small comment regarding the PR description:

Right now, we don't do anything with this flag and never clear it -- that is the next step in this WIP PR.

#35 starts using the dirty flag to determine if we can close a region or not. This might cause some test failures once both are on the phase 3 branch

Right. I think the PR description is >1 month old by now :)

@TobiasWrigstad
Copy link
Collaborator Author

Three comments, the rest looks good to me. I believe this actually discovered a new case in the WB that I didn't consider previously. FrankenScript has a MoveReference function which I believe we'll also need.

What does that to? I thought move was just syntactic sugar for remove + add (in some order).

@xFrednet
Copy link
Collaborator

Three comments, the rest looks good to me. I believe this actually discovered a new case in the WB that I didn't consider previously. FrankenScript has a MoveReference function which I believe we'll also need.

What does that to? I thought move was just syntactic sugar for remove + add (in some order).

In FrankenScript it is just syntactic sugar, we can get away with it since every problematic add will stop execution and show the error. However, here we need to make sure that the objects are in a "decent" state when the exception is thrown.

The problem with removing and then adding a reference is, that the removal of the reference could close the region, which could intern release a cown. @mjp41 last change in #34 gave me an idea. We can just increase the LRC during this move. That will prevent the region from closing and then we can do the "remove reference, add reference dance" without accidentally closing any regions.

@TobiasWrigstad
Copy link
Collaborator Author

TobiasWrigstad commented Jan 15, 2025

Three comments, the rest looks good to me. I believe this actually discovered a new case in the WB that I didn't consider previously. FrankenScript has a MoveReference function which I believe we'll also need.

What does that to? I thought move was just syntactic sugar for remove + add (in some order).

In FrankenScript it is just syntactic sugar, we can get away with it since every problematic add will stop execution and show the error. However, here we need to make sure that the objects are in a "decent" state when the exception is thrown.

The problem with removing and then adding a reference is, that the removal of the reference could close the region, which could intern release a cown. @mjp41 last change in #34 gave me an idea. We can just increase the LRC during this move. That will prevent the region from closing and then we can do the "remove reference, add reference dance" without accidentally closing any regions.

I believe we always add before move. Does that prevent the premature closing?

@xFrednet
Copy link
Collaborator

I believe we always add before move. Does that prevent the premature closing?

Yes, it prevents the premature closing. But could result in errors for bridge objects.

@TobiasWrigstad
Copy link
Collaborator Author

I believe we always add before move. Does that prevent the premature closing?

Yes, it prevents the premature closing. But could result in errors for bridge objects.

Meaning the error in #35 (comment) ?

@xFrednet
Copy link
Collaborator

I believe we always add before move. Does that prevent the premature closing?

Yes, it prevents the premature closing. But could result in errors for bridge objects.

Meaning the error in #35 (comment) ?

Yes! I saw your answer in that PR, but first wanted to think a bit more about it before answering.

@TobiasWrigstad
Copy link
Collaborator Author

I believe we always add before move. Does that prevent the premature closing?

Yes, it prevents the premature closing. But could result in errors for bridge objects.

Meaning the error in #35 (comment) ?

Yes! I saw your answer in that PR, but first wanted to think a bit more about it before answering.

Great, thanks for clarifying! So we are ultimately dealing with a single case. Nice!

Comment on lines 1196 to 1214
if (Py_CHECKWRITE(v)) {
if (!Py_IS_REGION_AWARE(tp)) {
_PyObject_mark_region_as_dirty(v);
}
Copy link
Owner

Choose a reason for hiding this comment

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

Should we modify CHECKWRITE to do this as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah you mean Py-CHECKWRITE(obj) marks the region as dirty if !Py_IS_REGION_AWARE(obj->ob_type)?

Copy link
Owner

Choose a reason for hiding this comment

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

Yep

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That's excellent! And obvious in hindsight.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If we add that check to CHECKWRITE that will add that check to many more places that what I think we currently would need it. Maybe that's OK because branch prediction.

Comment on lines 2945 to 2947
if (value == NULL || _Pyrona_AddReference(obj, value)) {
if (*dictptr) {
_Pyrona_RemoveReference(obj, *dictptr);
}
Py_XSETREF(*dictptr, Py_XNewRef(value));
return 0;
Copy link
Owner

Choose a reason for hiding this comment

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

I always thought of move between regions as "move to local" followed by "move from local". I think that might be what you end result might be?

Comment on lines 2945 to 2947
if (value == NULL || _Pyrona_AddReference(obj, value)) {
if (*dictptr) {
_Pyrona_RemoveReference(obj, *dictptr);
}
Py_XSETREF(*dictptr, Py_XNewRef(value));
return 0;
Copy link
Owner

Choose a reason for hiding this comment

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

I do wonder if we should build a Py_XSETREF_WITH_REGION operation, like I have added for Py_CLEAR in my PR.

@TobiasWrigstad
Copy link
Collaborator Author

@mjp41 regarding move btw regions as reg1 --> local --> reg2 that seems logical to me. I'm not sure I understand your comment fully though. Are you suggesting what the REMOVE barrier should do?

@TobiasWrigstad
Copy link
Collaborator Author

@mjp41 I don't follow the Py_XSETREF_WITH_REGION comment. Sorry, maybe I'm in a completely different head space today... Can you unpack?

@mjp41
Copy link
Owner

mjp41 commented Jan 17, 2025

@mjp41 regarding move btw regions as reg1 --> local --> reg2 that seems logical to me. I'm not sure I understand your comment fully though. Are you suggesting what the REMOVE barrier should do?

I was more reacting that I didn't think a "MOVE" between regions was needed, but it could be more optimal.

@mjp41
Copy link
Owner

mjp41 commented Jan 19, 2025

@mjp41 I don't follow the Py_XSETREF_WITH_REGION comment. Sorry, maybe I'm in a completely different head space today... Can you unpack?

So the code here:

#define Py_XSETREF(dst, src) \
do { \
_Py_TYPEOF(dst)* _tmp_dst_ptr = &(dst); \
_Py_TYPEOF(dst) _tmp_old_dst = (*_tmp_dst_ptr); \
*_tmp_dst_ptr = (src); \
Py_XDECREF(_tmp_old_dst); \
} while (0)
#else
#define Py_XSETREF(dst, src) \
do { \
PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \
PyObject *_tmp_old_dst = (*_tmp_dst_ptr); \
PyObject *_tmp_src = _PyObject_CAST(src); \
memcpy(_tmp_dst_ptr, &_tmp_src, sizeof(PyObject*)); \
Py_XDECREF(_tmp_old_dst); \
} while (0)

Does the update and the decref. I wonder if we should have a related macro that does the update, region ref remove, and the decref.

@mjp41 mjp41 changed the title Adding WB to object.c Phase 3: Adding WB to object.c Jan 21, 2025
@TobiasWrigstad
Copy link
Collaborator Author

@mjp41 I've added the proposed SET_XREF_WITH_REGION now (and rebased on new Phase3).

Copy link
Collaborator

@xFrednet xFrednet left a comment

Choose a reason for hiding this comment

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

I only skimmed over the changes again, but this looks good to me. It probably makes sense to merge this and just do any fixes in followup PRs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants