Make clamp_to compliant with the WebIDL specification#694
Make clamp_to compliant with the WebIDL specification#694mrobinson merged 5 commits intoservo:mainfrom
clamp_to compliant with the WebIDL specification#694Conversation
Signed-off-by: Taym Haddadi <[email protected]>
mrobinson
left a comment
There was a problem hiding this comment.
Please restore the PR template and also explain what the fix is that you made in the PR title and description.
Apologies, |
The underlying issue is already fully discussed in the referenced issue, which is linked from this PR. This mozjs PR is scoped to the mozjs change, I opened a corresponding Servo PR respecting the PR template and additional context. I’ve also added more detail to this PR description. |
mrobinson
left a comment
There was a problem hiding this comment.
Oh, I think it also makes sense to link to the specification in the rustdoc as well. Another option would be to use specification text, but whatever you like is fine. There just needs to be an explanation of what's going on here as this is quite tricky code.
Signed-off-by: Taym Haddadi <[email protected]>
mrobinson
left a comment
There was a problem hiding this comment.
Sorry. I have a doubt about the code itself that I didn't notice before. Otherwise this seems good as it is passing tests.
mozjs/src/conversions.rs
Outdated
| /* | ||
| * It was a tie (since moving away from 0 by 0.5 gave us the exact integer | ||
| * we want). Since we rounded away from 0, we either already have an even | ||
| * number or we have an odd number but the number we want is one closer to | ||
| * 0. So just unconditionally masking out the ones bit should do the trick | ||
| * to get us the value we want. | ||
| */ |
There was a problem hiding this comment.
Let's keep the comment style consistent in the function by switching to //.
There was a problem hiding this comment.
I followed Gecko and copied the comments as is, I will switch them to // for consistency.
mozjs/src/conversions.rs
Outdated
| /// This function is ported from Gecko’s `PrimitiveConversionTraits_Clamp`. | ||
| /// [Gecko source]: https://searchfox.org/firefox-main/rev/aee7c0f24f488cd7f5a835803b48dd0c0cb2fd5f/dom/bindings/PrimitiveConversions.h#226 |
There was a problem hiding this comment.
The markdown link from my suggestion is now broken as the text inside [...] has to match some from a link in the preceding paragraph. You can fix it with:
| /// This function is ported from Gecko’s `PrimitiveConversionTraits_Clamp`. | |
| /// [Gecko source]: https://searchfox.org/firefox-main/rev/aee7c0f24f488cd7f5a835803b48dd0c0cb2fd5f/dom/bindings/PrimitiveConversions.h#226 | |
| /// This function is ported from Gecko’s [`PrimitiveConversionTraits_Clamp`]. | |
| /// | |
| /// [`PrimitiveConversionTraits_Clamp`]: https://searchfox.org/firefox-main/rev/aee7c0f24f488cd7f5a835803b48dd0c0cb2fd5f/dom/bindings/PrimitiveConversions.h#226 |
By the way if you use an IDE like Visual Studio Code or Zed, if you hover over the function name it should give you a formatted view of the rustdoc and let you test clicking on links.
mozjs/src/conversions.rs
Outdated
| * 0. So just unconditionally masking out the ones bit should do the trick | ||
| * to get us the value we want. | ||
| */ | ||
| truncated = (((to_truncate as i64) & !1) as f64).cast(); |
There was a problem hiding this comment.
Hrm. Shouldn't you be truncating truncate here instead of to_truncate? to_truncate has been rounded, but not truncated via the cast.
| truncated = (((to_truncate as i64) & !1) as f64).cast(); | |
| truncated = truncated &= !1; |
With that change does Servo still pass the tests?
If that doesn't work I also wonder about the as f64 here. Theoretically this function should only be called if D is an integral type. Gecko ensures this with a static cast, but that doesn't seem to be possible in Rust unfortunately. If the above doesn't work, does the following?
| truncated = (((to_truncate as i64) & !1) as f64).cast(); | |
| truncated = ((to_truncate as i64) & !1).cast(); |
There was a problem hiding this comment.
No — to_truncate is intentional here and matches the Gecko implementation,the tie check must compare against the value after the ±0.5 adjustment and truncation step, using the already cast value would lose the information needed to detect exact .5 ties.
I ran the Servo tests before committing, and they are passing with this change.
There was a problem hiding this comment.
Here is the Gecko code:
T truncated = static_cast<T>(toTruncate);
if (truncated == toTruncate) {
/*
* It was a tie (since moving away from 0 by 0.5 gave us the exact integer
* we want). Since we rounded away from 0, we either already have an even
* number or we have an odd number but the number we want is one closer to
* 0. So just unconditionally masking out the ones bit should do the trick
* to get us the value we want.
*/
truncated &= ~1;
}The value that is modified here is truncated which has already been cast to the final type. I believe that Gecko code is trying to compare the rounded value to the rounded+cast value and then modify the rounded+cast value if they are equal. In your case you are comparing the rounded value to the rounded+cast value and then modifying the rounded (but not cast value). Instead you cast to i64, which I think is redoing the rounding cast but to a type of a potentially different size than D.
There was a problem hiding this comment.
FWIW, I think this might work fine when doing this operation from f64 to i64, but I suspect it will not work exactly the same was as Gecko in the general case. That could potentially be why this still passes tests.
There was a problem hiding this comment.
I think I see your concern, In Gecko the low bit is cleared on the already cast value, while in the Rust version the even adjustment is computed via an i64 intermediate and then cast back to D, right?
given the WebIDL bounds, this produces the same result today, but I agree the difference can be confusing since the comments are copied from Gecko. I can adjust the code to operate directly on truncated if that’s clearer.
There was a problem hiding this comment.
Yes, I think we should just use the truncated value here and then maybe put a warning in the rustdoc saying that the function should only be used when the target is an integer type. Thank you!
I don’t think there is any remaining doubt about the code. It follows the Gecko implementation and the WPT tests are passing. |
Signed-off-by: Taym Haddadi <[email protected]>
Signed-off-by: Taym Haddadi <[email protected]>
mozjs/src/conversions.rs
Outdated
| // number or we have an odd number but the number we want is one closer to | ||
| // 0. So just unconditionally masking out the ones bit should do the trick | ||
| // to get us the value we want. | ||
| truncated = (((truncated.cast() as i64) & !1) as f64).cast(); |
There was a problem hiding this comment.
So this is:
- Converting an integer to a float (I think this is what
truncated.cast()does) - Converting the float back to an integer. (
as i64) - Doing a bitwise operation on the integer. (
& !1) - Converting the integer back to a float. (
as f64) - Converting that float back to an integer. (
.cast())
In the Gecko code it directly does the bitwise operation on the integer. 😦 I wish we could do the same here. Two options:
- Do the truncation and bit flipping in i64 and then convert at the end. We cannot even do this easily as the various integer types (such as
u8) do not implementAs<i64>This could be done withTryIntobut I don't know the performance implications or how performance sensitive this code is. - Ensure that
Dimplements the necessary bit operations as well asnum-traitscrate'sOnetrait. This might work, but I have not tried it.
Maybe someone with a better grasp of Rust generics can help here.
There was a problem hiding this comment.
In the meantime I've dismissed my review so it doesn't block this change.
There was a problem hiding this comment.
Yes truncated = truncated & !D::one(); works here.
I am not entirely sure what the best approach is here.
Signed-off-by: Taym Haddadi <[email protected]>
0c7bfff to
a2edddb
Compare
clamp_to compliant with the WebIDL specification
clamp_to compliant with the WebIDL specificationclamp_to compliant with the WebIDL specification
|
@Taym95 It is useful to link companion servo PR in this PR description. |
|
@sagudev That's probably my fault. There was a link to the original issue in the description, but I was worried that it would close the issue so I removed it. |
|
I meant companion PR in servo, not the issue (issue is usually linked in the companion PR). |
|
It seems there isn’t a PR template or a defined process here yet, Having one similar to what we use in the Servo repo, could help set expectations and avoid this kind of back and forth. |
Incorporates #694 Signed-off-by: Taym Haddadi <[email protected]>
This PR fixes the implementation of WebIDL clamp numeric conversions to match the behavior required by the WebIDL specification and [Firefox](https://searchfox.org/firefox-main/rev/aee7c0f24f488cd7f5a835803b48dd0c0cb2fd5f/dom/bindings/PrimitiveConversions.h#225-269). Testing: more test in tests/wpt/tests/FileAPI/blob/Blob-slice.any should pass. Fixes: #41638 mozjs PR: servo/mozjs#694 --------- Signed-off-by: Taym Haddadi <[email protected]>
I've opened #696 |
This PR fixes the implementation of WebIDL clamp numeric conversions to match the behavior required by the WebIDL specification and Firefox.
Companion PR: servo/servo#41640