-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Make makeSetValue rounding consistent with HEAP64 semantics #19239
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
Conversation
|
The test code for unaligned i64 writes was added in #17619... which seems to suggest that i64 can be unaligned.. but that is not the case AFAICT. |
ba2527d to
e9f917a
Compare
|
Reading that old PR, it sounds like that fix was necessary and the test I added failed before it. If you go back to the commit before the PR landed and just add the test but not the fix, does it fail? Specifically it sounds like that test was actually showing an unaligned i64, so understanding how that occurred might be helpful (maybe LLVM doesn't always align them?) |
I verified that the test in question (browser.test_wasmfs_fetch_backend_bigint) passes both before and after this PR. I will see if I can do back to the old version.. |
|
I tracked down the issue. The problem was in proxied_async_js_impl_backend.h: Here getSize was fixed to use |
This change does two things: 1. Require 8 byte alignment for makeSetValue of i64. Natural alignment is already required for all other types in `makeSetValue` and I don't know of good reason not to require this also for i64. The comment here implied that perhaps i64 was not always 8 bytes aligned, perhaps in the fastcomp days. But with wasm32 and wasm64 i64 should always be 8 bytes aligned. Folks with unaligned data already cannot use makeGetValue/makeSetValue. 2. Make splitI64 consistent with assignment to HEAPI64 in the way it handles numbers that are larger than the maximum i64 value.
e9f917a to
5baa86d
Compare
|
Good find! |
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.
Sorry, I wrote that other comment when I was still reading the code. I have two typos and a question.
| // bits, so HEAP64[ptr>>3] might be broken. | ||
| return '(tempI64 = [' + splitI64(value) + '],' + | ||
| if (type == 'i64' && !WASM_BIGINT) { | ||
| // If we lack either BigInt we must fall back to an reading a pair of I32 |
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.
| // If we lack either BigInt we must fall back to an reading a pair of I32 | |
| // If we lack BigInt we must fall back to an reading a pair of I32 |
| // $1$0 = ~~$d >>> 0; | ||
| // $1$1 = Math.abs($d) >= 1 ? ( | ||
| // $d > 0 ? Math.min(Math.floor(($d)/ 4294967296.0), 4294967295.0) | ||
| // $d > 0 ? Math.floor(($d)/ 4294967296.0) >>> 0, |
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.
| // $d > 0 ? Math.floor(($d)/ 4294967296.0) >>> 0, | |
| // $d > 0 ? Math.floor(($d)/ 4294967296.0) >>> 0 |
| asmEnsureFloat(4294967296, 'double') + ')', 'double') + ', ' + | ||
| asmEnsureFloat(4294967295, 'double') + ')', 'i32') + '>>>0' + | ||
| asmCoercion('Math.floor((VALUE)/' + | ||
| asmEnsureFloat(4294967296, 'double') + ')', 'double') + '>>>0' + |
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 is it correct to remove the Math.min here?
I think the old code was correct. We can do rounding for the lower bits because we've ensured they are in the right range. But we need to use saturation because the value might be outside of range - imagine that the value is 2^70 plus some small value, then doing >>>0 will do an odd wrapping operation (which can make it "randomly" lower or higher).
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 old code did saturation of the upper half produced this odd result:
0x1122334455667788AA -> 0xffffffff66780000
The new code truncates and produces the same result as BigInt64Array:
0x1122334455667788AA -> 0x2233445566780000
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.
"imagine that the value is 2^70 plus some small value" .. I'm not sure I understand doesn't the Math.floor(($d)/ 4294967296.0) take case of removing all the low bits? Basically the same as >> 32 in C/C++?
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 removes the low bits, by my concern is that without saturation the loss abs(x - int64(x)) can be high. Saturation keeps the value as close as possible to the original, minimizing the absolute error/difference.
But maybe I'm wrong and that is not the error to care about?
What do you mean by BigInt64Array's behavior here? How are you checking that?
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 run this test both with and without WASM_BIGINT:
@also_with_wasm_bigint
def test_parseTools(self):
self.do_other_test('test_parseTools.c', emcc_args=['--js-library', test_file('other/test_parseTools.js')])
The change in the expected output was initially generated because I changed WASM_BIGINT to always use BigInt64Array... then I updated splitI64 such that its output produces the same result.
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.
Do you know what code path that is on? I'm surprised splitI64 is even used in WASM_BIGINT mode as nothing is legalized. Probably I'm forgetting something...
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 example:
$ node
Welcome to Node.js v19.0.0.
Type ".help" for more information.
> const uint8 = new Uint8Array(2);
undefined
> uint8[0] = 300
300
> uint8[0]
44
>
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.
True. Maybe I'm confused as to where we use splitI64 then. I agree that when we store a bigger value into a small number of bits then we must naturally truncate. But splitI64, I thought, is used to approximate a number, not prepare it for storing.
If it is used to prepare for storing, then as I asked before, why is it even used in WASM_BIGINT mode? In that mode we can store directly.
Or is my confusion that it isn't used in that mode, and you fixed it for non-WASM_BIGINT mode?
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.
True. Maybe I'm confused as to where we use
splitI64then. I agree that when we store a bigger value into a small number of bits then we must naturally truncate. ButsplitI64, I thought, is used to approximate a number, not prepare it for storing.
Perhaps splitI64 had other callsites in the past but today it has exactly one usage in makeSetValue and it used to split the incoming number for storage as a pair of I32.
If it is used to prepare for storing, then as I asked before, why is it even used in WASM_BIGINT mode? In that mode we can store directly.
After this change is not longer used in WASM_BIGINT mode and we using BigIntArray directly.
Prior to this change we used splitI64 only because there was some confusion about whether the target address would be 8 byte aligned.
Or is my confusion that it isn't used in that mode, and you fixed it for non-WASM_BIGINT mode?
Right the changes to splitI64 are so that the non-WASM_BIGINT mode writes the same bits that WASM_BIGINT mode would write.
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 see. Thanks, that all makes sense then!
|
Oops, sorry I was too quick to land there. |
This change does two things:
Require 8 byte alignment for
makeSetValueof i64. Natural alignment is already required for all other types inmakeSetValueand I don't know of good reason not to require this also for i64. The comment here implied that perhaps i64 was not always 8 bytes aligned, perhaps in the fastcomp days. But with wasm32 and wasm64 i64 should always be 8 bytes aligned. Folks with unaligned data already cannot use makeGetValue/makeSetValue.Make
splitI64consistent withHEAP64in the way it handles numbers that are outside the i64 range. i.e. truncationWithout the second part of the change the
other.test_parseTools_bigintwill fail because it will useHEAP64.setto set the value rather thansplitI64, andHEAP64.setwrites0x2233445566780000rather than0xffffffff66780000.The existing behaviour of
splitI64to write0xffffffff66780000seems rather odd since it seems to use saturation, but only for the upper half.