-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Improve: binary ops with Number Protocol #4615
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
Improve: binary ops with Number Protocol #4615
Conversation
|
when rustpython crashes on initialization step, debugger is very useful https://github.com/RustPython/RustPython/wiki/Debugger-with-VSCode |
7c7f4de to
9bf7481
Compare
|
The next step is update_slot in slot.rs. In current version, this is not working: >>>>> class A:
..... def __add__(self, a):
..... print(a)
.....
>>>>> a = A()
>>>>> a + 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '+' not supported between instances of 'A' and 'int'
>>>>> Adding update_slot! will enable it. |
|
To support simple binary operators, looking for |
vm/src/protocol/number.rs
Outdated
| pub enum PyNumberBinaryOpSlot { | ||
| Add, | ||
| Subtract, | ||
| Multiply, | ||
| Remainder, | ||
| Divmod, | ||
| Power, | ||
| Lshift, | ||
| Rshift, | ||
| And, | ||
| Xor, | ||
| Or, | ||
| InplaceAdd, | ||
| InplaceSubtract, | ||
| InplaceMultiply, | ||
| InplaceRemainder, | ||
| InplacePower, | ||
| InplaceLshift, | ||
| InplaceRshift, | ||
| InplaceAnd, | ||
| InplaceXor, | ||
| InplaceOr, | ||
| FloorDivide, | ||
| TrueDivide, | ||
| InplaceFloorDivide, | ||
| InplaceTrueDivide, | ||
| MatrixMultiply, | ||
| InplaceMatrixMultiply, | ||
| } |
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 is ok but let the op function to take a lambda should also work
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.
How the lambda is used? I thought python allows only whitelisted list of operators.
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.
Oh, I mean closure in rust. Instead having a matching table from enum to methods(get_binary_op), we could make binary_ops generic that would take a FnOnce to specifiy which op will be use.
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.
It sounds like function pointer vs pointer offset design decision. I think either design works well enough to run the features.
Probably we can discuss better once this is fully done and glance at the design overview.
9bf7481 to
4ae4521
Compare
This comment was marked as resolved.
This comment was marked as resolved.
81d51f4 to
efa0535
Compare
0bef17e to
7f23449
Compare
7f23449 to
7f767ac
Compare
|
The following is the behavior in CPython |
7f767ac to
e2b82e0
Compare
|
I finally understand the trick. instead of |
8fe4b42 to
42e6107
Compare
|
@youknowone In commit 1b9a5f6 , I extended I also changed the comment of |
|
@xiaozhiyan I made a rough sketch of the cpython way |
|
Ok, I made its first version working. f01901c using #4645 by using this structure, PyNumberSlots take responsibility of order of operands and PyNumberMethods take responsibility of operating itself. |
|
one of the difference between my version and cpython is slot implementation. |
This comment was marked as outdated.
This comment was marked as outdated.
| // PyIndex_Check | ||
| pub fn is_index(&self) -> bool { | ||
| self.methods().index.load().is_some() | ||
| self.obj.class().slots.number.index.load().is_some() |
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.
binary ops operands can be swapped, but is this also need to access obj.class() again?
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.
Since we
- removed
AtomicCellfromPyNumberMethods(for bothBinaryFuncandUnaryFunc) - changed
update_slotto save user defined binary ops from ext_funcnumber_methods.opto subslotnumber.op
=> user defined unary ops in update_slot have to be changed to number.op as well
=> then user defined unary ops will be saved in PyNumberSlots in the same way as binary ops
Since self.methods() only returns the static methods predefined in AsNumber implementations, we need to change to self.obj.class().slots.number to load the latest methods from PyNumberSlots.
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.
ah, got it. we didn't handle unary operator yet.
| let lop = lhs.get_class_attr(reflection); | ||
| let rop = rhs.get_class_attr(reflection); | ||
| if let Some((lop, rop)) = lop.zip(rop) { | ||
| if !lop.is(&rop) { |
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.
we may lost this check. it seems to related to test_collections failure
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 think the purpose of the old code is to follow the CPython logic to check whether lop and rop are referring to the same op, in which case rop should not be used.
The new implementation uses the following part when loading rop, i.e. slot_b.
let mut slot_b = if b.class().is(a.class()) {
None
} else {
match b.class().slots.number.get_right_binary_op(op_slot)? {
Some(slot_b)
if slot_b as usize == slot_a.map(|s| s as usize).unwrap_or_default() =>
{
None
}
slot_b => slot_b,
}
};
vm/src/vm/vm_ops.rs
Outdated
| a: &PyObject, | ||
| b: &PyObject, | ||
| op_slot: &PyNumberBinaryOpSlot, | ||
| ) -> PyResult { |
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.
Which format config should we use please? When I run cargo fmt, it will just change back to one line as before.
Perhaps it has to do with Rust 1.68.0 being released. |
885eba9 to
3c4ac0e
Compare
youknowone
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.
let me handle 1.68 things
| }) | ||
| } | ||
|
|
||
| macro_rules! number_binary_op_wrapper { |
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 macro can generate function rather than lambda
Please help to check my comments in #4673 in case they're useful. |
|
Thanks! |
|
Though we have remaining issues, let's move forward. @xiaozhiyan Thank you so much for the huge effort! |
|
Sorry for late review, as long as the ci passed, we can accept this pr. But there is something I want to clarify:
|
|
@qingshi163 All other parts sounds good, but for No. 5, for each binary operator, having 2 slots and 1 number method is the CPython design. |
https://github.com/python/cpython/blob/main/Include/cpython/object.h cpython do not store the right_* functions in the type slots, it fallback the slots and than call it opposite direction |
|
we have a little bit different design now, so the wrapper looks different. but isn't |
|
Measure performance enhancement of this PR
|
|
|
||
| // The count of tp_members. | ||
| pub member_count: usize, | ||
| pub number: PyNumberSlots, |
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.
@xiaozhiyan Sorry for late comment 😂Could I ask you the reason why not to use Option for PyNumberSlots? If we want to know which type implements the Number protocol, shouldn't we verify all the fields in the Number methods?
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.
For a type to implement Number Protocol, basically it means the type implements a subset of Number Protocol methods, (while leaving the remained as None).
I'm not sure what the scenario is to check whether a type has implemented "at least one Number Protocol method", instead of checking whether a type has implemented "certain specified method".
If it's really needed in such scenarios, I think we may be able to change this implementation to use Option without side effect. Otherwise, it may be better to keep it as is to avoid an additional layer of Option wrapping.
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.
due to difference of AsNumber/PyNumberSlots implementation strategy, i guess PyNumberSlots always exists while AsNumber is not.
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.
According to current implementation,
-
for embedded types, if they don't implement
AsNumbertrait,extend_slotswill not be executed, and theirPyNumberSlotsfields will contain default method implementation, i.e.None -
for user defined types, if they implement any Number Protocol method, say,
__radd__, the function will be saved in corresponding field ofPyNumberSlotsfor the type -
when trying to call certain Number Protocol method for a type, fields of
PyNumberSlotswill be looked up, without considering whether the type has implementedAsNumbertrait or not
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.
Ah I see. Thanks for detail explanation 😊
Closes #4614
Based on #4139
Related to #3244
This PR will not pass some tests at this moment, since Number Protocol is missing for some types.
Let me create separate PRs to implement (part of) missing Number Protocols, and then rebase this PR once those are merged.
closes #4638
closes #4639
closes #4672