[mpypc] Promotion to Value Type optimization
This PR introduce to mypyc the ability to generate efficient classes passed by value unleashing deeper optimizations in the C generated code. With this change mypyc will be finding immutable classes to be promoted as Value Types. Value types are unboxed representation of classes that can be passed by value avoiding dynamic memory allocations and replacing those with stack based values.
Should be reviewed and rebased after #17934
For example:
For this class
@final
@mypyc_attr(value_type=True)
class Vector2:
def __init__(self, x: i32, y: i32) -> None:
self.x: Final = x
self.y: Final = y
def __add__(self, other: "Vector2") -> "Vector2"
return Vector2(self.x + other.x, self.y + other.y)
The add method will produce the code:
Vector2Object CPyDef_Vector2_____add__(Vector2Object cpy_r_self, Vector2Object cpy_r_other) {
int32_t cpy_r_r0;
int32_t cpy_r_r1;
int32_t cpy_r_r2;
int32_t cpy_r_r3;
int32_t cpy_r_r4;
int32_t cpy_r_r5;
Vector2Object cpy_r_r6;
Vector2Object cpy_r_r7;
cpy_r_r0 = cpy_r_self._x;
cpy_r_r1 = cpy_r_other._x;
cpy_r_r2 = cpy_r_r0 + cpy_r_r1;
cpy_r_r3 = cpy_r_self._y;
cpy_r_r4 = cpy_r_other._y;
cpy_r_r5 = cpy_r_r3 + cpy_r_r4;
cpy_r_r6 = CPyDef_Vector2(cpy_r_r2, cpy_r_r5);
if (unlikely(cpy_r_r6.vtable == NULL)) {
CPy_AddTraceback("engine/vectors2.py", "__add__", 12, CPyStatic_globals);
goto CPyL2;
}
return cpy_r_r6;
CPyL2: ;
cpy_r_r7 = ((Vector2Object){0});
return cpy_r_r7;
}
Note the values passed as structs directly and returning it directly too. It has an impact similar to the RTuple but should work transparently with more flexible definitions due the class syntax. It works similar to Value Types in C# (CLR) or structures in Swift. It will allow the language be a more suitable option for performance centric tasks like video game engines or real time simulation computation.
Implementation details
It uses the vtable field to signal if any exception happen similar to the empty flag in RTuple. A new RType called RInstanceValue is created to represent value types. All semantics of boxed and unboxed values are preserved where (RInstanceValue is unboxed) and RInstance continues being (boxed). In order to easily box values using the existing infrastructure, the type's setup method has been used and promoted to exported function.
Value types are a great feature, thanks! This seems to address mypyc/mypyc#841 -- what do you think?
Since using a value type can change semantics (at least is / object identity), and sometimes a heap type is preferred even if all attributes are final (e.g. there are dozens of attributes -> copying values would be slow), it may be better to require value classes to be annotated explicitly. We could start with @mypyc_attr(<something>=True) and come up with a better decorator name in mypy_extensions. See the referred issue for more about this.
@JukkaL
Value types are a great feature, thanks! This seems to address mypyc/mypyc#841 -- what do you think?
Yes, it goes in that direction, however the implementation presented here do not cover the @record for your use case.
Additionally, I think that functionality is already provided by python using NamedTuple, where a NamedTuple._replace() method (maybe ugly named) can be used for modifying the object keeping the immutability (thats the way I use today to avoid extra deps in some contexts). Not sure if frozen dataclasses have something similar. We could adapt it to namedtuple as next step to avoid additional syntatic sugar
Since using a value type can change semantics (at least
is/ object identity), and sometimes a heap type is preferred even if all attributes are final (e.g. there are dozens of attributes -> copying values would be slow), it may be better to require value classes to be annotated explicitly. We could start with@mypyc_attr(<something>=True)and come up with a better decorator name inmypy_extensions. See the referred issue for more about this.
For sure makes sense, I already updated the PR for using @mypyc_attr(value_type=True)
Additionally, I think that functionality is already provided by python using NamedTuple
I don't think NamedTuple can be a value type, since it supports subclassing and subclasses can add settable attributes. The idea with @record (or whatever syntax we'd use for this) is that it's both a value type and makes it easy to modify values in-place.
Not sure if frozen dataclasses have something similar.
I don't think they anything like that, and besides dataclasses have reference semantics, even if they are frozen, and support subclassing.
makes sense, I didnt saw that NamedTuples can be subclassed and add new mutable members
@JukkaL can you review at this point. I think is ready due the tests I have made, let me know if you consider any other test.
Can you merge master? It now includes the dunder changes. This is a big PR so it will take some time to review and test. I'll start looking into this early next week probably.
Done
These features may not have tests (and some are probably not implemented):
- Fixed-length tuple items with value types
- Undefined attribute/local variable with value type (e.g. conditionally assign value)
- Module-level attribute with value type (may involve boxing, unfortunately)
- Final module-level attribute with value type (shouldn't involve boxing)
- Default argument value with value type
- Reference counting when value type attribute uses reference counting (similar to tuples)
If some of these are unclear, I can help. Also, implementing all of these in a single PR may make the PR too big for comfortable reviewing. It's okay to implement these in multiple PRs, if the functionality is behind a feature flag until complete. We have way of hiding features behind feature flags (--enable-incomplete-feature, see INCOMPLETE_FEATURES in mypy.options).
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅
@JukkaL Hey, I have made some modifications there. Including hiding it behind a feature flag. I can make the additional verification for the cases you mentioned in a new PR to avoid making this grow more
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅
@JukkaL any other idea of aspect to test? I have covered:
- ~~Fixed-length tuple items with value types~~
- ~~Module-level attribute with value type (may involve boxing, unfortunately)~~
- ~~Final module-level attribute with value type (shouldn't involve boxing)~~
- ~~Default argument value with value type~~
- ~~Reference counting when value type attribute uses reference counting (similar to tuples)~~
any comment?