-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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.
@@ -238,12 +306,12 @@ impl PyNumber<'_> { | |||
|
|||
// 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
AtomicCell
fromPyNumberMethods
(for bothBinaryFunc
andUnaryFunc
) - changed
update_slot
to save user defined binary ops from ext_funcnumber_methods.op
to 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
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
@@ -214,6 +336,18 @@ fn float_wrapper(num: PyNumber, vm: &VirtualMachine) -> PyResult<PyRef<PyFloat>> | |||
}) | |||
} | |||
|
|||
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
|
@@ -85,6 +84,7 @@ pub struct PyTypeSlots { | |||
|
|||
// 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
AsNumber
trait,extend_slots
will not be executed, and theirPyNumberSlots
fields 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 ofPyNumberSlots
for the type -
when trying to call certain Number Protocol method for a type, fields of
PyNumberSlots
will be looked up, without considering whether the type has implementedAsNumber
trait 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