-
Notifications
You must be signed in to change notification settings - Fork 2
Adding Deep Immutability to 3.12 #51
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
base: v3.12.0
Are you sure you want to change the base?
Conversation
dd6a77c to
469ce9e
Compare
9d8efcd to
e676ca4
Compare
8ce6f94 to
295e983
Compare
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
xFrednet
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My comments can be quite nit-picky, so please don't hesitate to push back on them :)
I'm not done reviewing everything yet, but from what I've seen thus far: Excellent job! It's amazing that you managed to get all of this together, well done :D
This review is still incomplete. From all I've read, excellent job!
xFrednet
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nicely done! This is mainly a collection of questions, which probably come from me missing some context.
e269f3f to
1cdd289
Compare
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Python/immutability.c
Outdated
| }else if(PyDict_Contains(module_dict, name)){ | ||
| PyObject* value = PyDict_GetItem(module_dict, name); // value.rc = x | ||
|
|
||
| _PyDict_SetKeyImmutable((PyDictObject*)module_dict, name); | ||
|
|
||
| if(!_Py_IsImmutable(value)){ | ||
| if(PyList_Append(frontier, value)){ | ||
| goto nomemory; | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what this is doing? Could you explain an example that this is catching that is a problem?
Python/immutability.c
Outdated
| }else if(PyDict_Contains(module_dict, name)){ | ||
| PyObject* value = PyDict_GetItem(module_dict, name); | ||
|
|
||
| _PyDict_SetKeyImmutable((PyDictObject*)module_dict, name); | ||
|
|
||
| if(!_Py_IsImmutable(value)){ | ||
| if(push(frontier, value)){ | ||
| goto nomemory; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't work out how to exercise this code. It doesn't appear to be hit by test_freeze. Should we be able to get coverage for this? I had an experiment with the REPL and a few modules, but this code path didn't get exercised.
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
| struct _Py_async_gen_state async_gen; | ||
| struct _Py_context_state context; | ||
| struct _Py_exc_state exc_state; | ||
| struct _Py_immutability_state immutability; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean that different interpreters have different views on what is freezable?
| return; | ||
| } | ||
| ob->ob_refcnt = refcnt; | ||
| ob->ob_refcnt = (ob->ob_refcnt & _Py_IMMUTABLE_MASK) | (refcnt & _Py_REFCNT_MASK); |
There was a problem hiding this comment.
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?
|
@matajoh Do we want to change the title of the PR to be ready for outside viewers? Maybe something like "Adding Deep Immutability to 3.12" |
| if (!Py_CHECKWRITE(op)) { \ | ||
| PyErr_SetObject( \ | ||
| PyExc_TypeError, \ | ||
| (PyObject *)((op))); \ | ||
| return -1; \ | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this create an Error object and doesn't use PyErr_WriteToImmutable like the additions in the other files?
Also why is it called _VALIDATE_WRITABLE here and _CHECK_IS_WRITABLE above? The difference seems to be the return value, but they seem to be used in the same way.
| if (type == NULL) { \ | ||
| goto error; \ | ||
| } \ | ||
| #define CREATE_TYPE(module, type, spec, base) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this new base parameter still needed? It seems to be always NULL in the uses below
| if (_PyImmutability_RegisterFreezable((PyTypeObject *)state->PyStructType) < 0){ | ||
| return -1; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file Modules/_elementtree.c uses _PyImport_GetModuleAttrString("immutable", "register_freezable"); to get the register_freezable function or handle failure if the "immutable" module is not available. This one uses the builtin function _PyImmutability_RegisterFreezable from pycore. How is it decided which function is used?
| if(self->codec->encinit != NULL){ | ||
| if(!Py_CHECKWRITE(self)){ | ||
| return PyErr_WriteToImmutable(self); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file has new Py_CHECKWRITE but doesn't register the type as freezable. Is this declared somewhere else?
| #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) | ||
| # include "pycore_gc.h" // PyGC_Head | ||
| # include "pycore_runtime.h" // _Py_ID() | ||
| #endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two includes don't seem to be used in the header file, can we move them into the .c file instead?
| #include "clinic/immutablemodule.c.h" | ||
|
|
||
| typedef struct { | ||
| PyObject *not_freezable_error_obj; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this object still used? Looking at the code, it seems to only be initialized and cleared, but never used outsite of this
| static PyObject * | ||
| immutable_error(void) | ||
| { | ||
| PyThreadState *tstate = _PyThreadState_GET(); | ||
| if (!_PyErr_Occurred(tstate)) { | ||
| _PyErr_SetString(tstate, PyExc_TypeError, | ||
| "cannot modify immutable instance"); | ||
| } | ||
| return NULL; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this used a custom immutable_error function and not PyErr_WriteToImmutable(). The difference seems to be that this function doesn't take the object that caused the error. But looking at the Py_CHECKWRITE(o) we always seem to have an object
| if(PyDict_SetItemString(PyModule_GetDict(frozen_importlib), "_freezable_types", state->freezable_types)){ | ||
| Py_DECREF(frozen_importlib); | ||
| return -1; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding is, that this exposes the freezable_types set via the frozen_importlib module. Is there a reason for this?
| size = PySequence_Fast_GET_SIZE(f_code->co_consts); | ||
| for(Py_ssize_t i = 0; i < size; i++){ | ||
| PyObject* value = PySequence_Fast_GET_ITEM(f_code->co_consts, i); | ||
| if(check_globals && PyUnicode_Check(value)){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't we move the check_globals check outside the for loop?
| if(_Py_IsImmutable(item)){ | ||
| continue; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we move this check above check_freezable? It seems like it's way cheaper.
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
|
I've been playing with the current implementation. Seems that it’s sometimes trying to freeze type variables that are arguments to generic functions. And that can’t be done. A simple decorator example: from __future__ import annotations
from immutable import freeze
from typing import Callable
def foo[**P, R](fn: Callable[P, R]) -> Callable[P, R]:
def fn_wrap(*args: P.args, **kwargs: P.kwargs) -> R:
return fn(*args, **kwargs)
return fn_wrap
freeze(foo) # TypeError: Cannot freeze instance of type typing.ParamSpec due to custom functionality implemented in CI’m surprised that |
Thanks for raising this. I'll investigate what is happening, and get back to you. |
This draft PR is to give a chance to view the diff between 3.12 and just adding the deep immutability from veronapy. The key difference is the use of the lowest bit of
ob_refcntto indicate immutability (so as to not impact object header size).📚 Documentation preview 📚: https://cpython-previews--51.org.readthedocs.build/