-
-
Notifications
You must be signed in to change notification settings - Fork 686
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
Support more bit array options for ints and floats on JavaScript #3293
base: main
Are you sure you want to change the base?
Support more bit array options for ints and floats on JavaScript #3293
Conversation
b71d160
to
773c3d0
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.
Awesome! Thank you! I've left some notes inline.
let default_size = if segment.type_ == crate::type_::int() { | ||
8usize | ||
} else { | ||
64usize |
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.
Shouldn't this only be for floats?
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 ints have an 8-bit size by default?
IntFromSlice(usize, usize, bool, bool), | ||
FloatFromSlice(usize, usize, bool), |
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.
Convert these to struct variants IntFromSlice { a: ..., b: ...}
please as I don't understand what the 4 unnamed fields are here
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.
Done!
} | ||
|
||
fn push_float_at(&mut self, i: usize) { | ||
self.path.push(Index::FloatAt(i)); | ||
fn push_float_from_slice(&mut self, i: usize, end: usize, is_big_endian: bool) { |
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's use an enum for endianness
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.
Done! I wasn't totally sure where to put it, so it's in its own file. Please let me know if it belongs better somewhere else.
self.pop(); | ||
offset.increment(1); | ||
Ok(()) | ||
if segment.type_ == crate::type_::int() |
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.
Same here as above with doing it in two passes
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.
Done as per previous comment
@@ -50,6 +50,24 @@ fn float() { | |||
r#" | |||
fn go() { | |||
<<1.1:float>> | |||
<<1.1:float-big>> | |||
<<1.1:float-little>> |
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.
Each test should only test one thing, so make a new test for each of these new examples please 🙏
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.
Done!
const msg = `Bit arrays must be byte aligned on JavaScript, got size of ${size} bits`; | ||
throw new globalThis.Error(msg); | ||
} | ||
|
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.
Put back the byte alignment check please 🙏
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.
Done. I thought it was no longer needed since the check added in 6d168ef, but reading the commit more carefully I was 100% wrong about that.
return view.getFloat64(start, !isBigEndian) | ||
} else if (byteSize === 4) { | ||
return view.getFloat32(start, !isBigEndian) | ||
} |
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 function returns undefined
when byteSize is not 8 or 4. It must always return a valid value.
Rather than have an end we could have a second bool for whether it is 32 or 64 bits.
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'm considering adding 16-bit floats to JS as well in a separate PR, hence the usage of start
& end
here to match byteArrayToInt
and also allow for 16-bit floats.
Bit array pattern matching on JS doesn't yet allow dynamic sizing, i.e. the size must be a compile-time const, and existing checks ensure that it is 16, 32, or 64.
I've added a check to the JS side so there's a compile error if a float size of 16 is given, as this isn't currently supported.
Given those two points, byteArrayToFloat
can't ever be called with a byte size other than 4 or 8. For safety, I've added a check that throws an error to avoid undefined
being silently returned, but it shouldn't be possible to ever get there.
Thoughts?
export function float64Bits(float) { | ||
return new Uint8Array(Float64Array.from([float]).buffer).reverse(); | ||
export function sizedFloat(float, size, isBigEndian) { | ||
if (size !== 32 && size !== 64) { |
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's use a bool as that forces it to be one or the other.
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 don't think that's an option because the size can be specified dynamically:
let a = int.random(1000)
let b = <<1.0:float-size(a)>>
Both Erlang and JS throw an error if the size at runtime isn't supported for floats.
773c3d0
to
513f386
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.
Thanks for the review, all comments have been addressed or responded to, ready for another look 👍
let default_size = if segment.type_ == crate::type_::int() { | ||
8usize | ||
} else { | ||
64usize |
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 ints have an 8-bit size by default?
IntFromSlice(usize, usize, bool, bool), | ||
FloatFromSlice(usize, usize, bool), |
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.
Done!
} | ||
|
||
fn push_float_at(&mut self, i: usize) { | ||
self.path.push(Index::FloatAt(i)); | ||
fn push_float_from_slice(&mut self, i: usize, end: usize, is_big_endian: bool) { |
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.
Done! I wasn't totally sure where to put it, so it's in its own file. Please let me know if it belongs better somewhere else.
const msg = `Bit arrays must be byte aligned on JavaScript, got size of ${size} bits`; | ||
throw new globalThis.Error(msg); | ||
} | ||
|
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.
Done. I thought it was no longer needed since the check added in 6d168ef, but reading the commit more carefully I was 100% wrong about that.
return view.getFloat64(start, !isBigEndian) | ||
} else if (byteSize === 4) { | ||
return view.getFloat32(start, !isBigEndian) | ||
} |
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'm considering adding 16-bit floats to JS as well in a separate PR, hence the usage of start
& end
here to match byteArrayToInt
and also allow for 16-bit floats.
Bit array pattern matching on JS doesn't yet allow dynamic sizing, i.e. the size must be a compile-time const, and existing checks ensure that it is 16, 32, or 64.
I've added a check to the JS side so there's a compile error if a float size of 16 is given, as this isn't currently supported.
Given those two points, byteArrayToFloat
can't ever be called with a byte size other than 4 or 8. For safety, I've added a check that throws an error to avoid undefined
being silently returned, but it shouldn't be possible to ever get there.
Thoughts?
export function float64Bits(float) { | ||
return new Uint8Array(Float64Array.from([float]).buffer).reverse(); | ||
export function sizedFloat(float, size, isBigEndian) { | ||
if (size !== 32 && size !== 64) { |
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 don't think that's an option because the size can be specified dynamically:
let a = int.random(1000)
let b = <<1.0:float-size(a)>>
Both Erlang and JS throw an error if the size at runtime isn't supported for floats.
self.pop(); | ||
offset.increment(1); | ||
Ok(()) | ||
if segment.type_ == crate::type_::int() |
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.
Done as per previous comment
@@ -50,6 +50,24 @@ fn float() { | |||
r#" | |||
fn go() { | |||
<<1.1:float>> | |||
<<1.1:float-big>> | |||
<<1.1:float-little>> |
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.
Done!
544ad7b
to
751dee0
Compare
751dee0
to
55d0cd5
Compare
This PR adds support for the following options for floats and ints in bit array patterns & expressions on the JavaScript target:
big
andlittle
options.native
endianness hasn't been added, but potentially could be.big
remains the default.signed
andunsigned
.Implementation notes:
expression.rs
for bit arrays in constant expressions. This PR hasn't aimed to fix this, and there's now a bit more code duplication of a similar nature inexpression.rs
as a result.Float64Array
). Most clients these days are indeed little endian, so it wasn't much of a problem in practice, but I've changed the code to useDataView
instead.// @internal
so I assumed that it's ok to change these.