-
-
Notifications
You must be signed in to change notification settings - Fork 405
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
Introduce NonMaxU32 for array index #2131
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
//! This module implements `NonMaxU32` | ||
//! which is known not to equal `u32::MAX`. | ||
//! | ||
//! This would be useful for integers like `https://tc39.es/ecma262/#array-index`. | ||
|
||
use boa_gc::{unsafe_empty_trace, Finalize, Trace}; | ||
use std::fmt; | ||
use std::ops::{BitOr, BitOrAssign}; | ||
|
||
/// An integer that is known not to equal `u32::MAX`. | ||
/// | ||
/// This enables some memory layout optimization. | ||
/// For example, `Option<NonMaxU32>` is the same size as `u32`: | ||
/// | ||
/// ```rust | ||
/// use std::mem::size_of; | ||
/// use boa_engine::nonmaxu32::NonMaxU32; | ||
/// assert_eq!(size_of::<Option<NonMaxU32>>(), size_of::<u32>()); | ||
/// ``` | ||
#[derive(Copy, Clone, Eq, Finalize, PartialEq, Ord, PartialOrd, Hash)] | ||
#[repr(transparent)] | ||
pub struct NonMaxU32(u32); | ||
|
||
// Safety: `NonMaxU32` does not contain any objects which needs to be traced, | ||
// so this is safe. | ||
unsafe impl Trace for NonMaxU32 { | ||
unsafe_empty_trace!(); | ||
} | ||
Comment on lines
+24
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would suggest to just not implement this, and use |
||
|
||
/// An error type returned when a checked integral type conversion fails (mimics [`std::num::TryFromIntError`]) | ||
#[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
pub struct TryFromIntError(()); | ||
|
||
impl fmt::Display for TryFromIntError { | ||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
"out of range integral type conversion attempted".fmt(fmt) | ||
} | ||
} | ||
|
||
impl From<core::num::TryFromIntError> for TryFromIntError { | ||
fn from(_: core::num::TryFromIntError) -> Self { | ||
Self(()) | ||
} | ||
} | ||
|
||
impl From<core::convert::Infallible> for TryFromIntError { | ||
fn from(never: core::convert::Infallible) -> Self { | ||
match never {} | ||
} | ||
} | ||
|
||
/// An error type returned when an integer cannot be parsed (mimics [`std::num::ParseIntError`]) | ||
#[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
pub struct ParseIntError(()); | ||
|
||
impl fmt::Display for ParseIntError { | ||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
"unable to parse integer".fmt(fmt) | ||
} | ||
} | ||
|
||
impl From<core::num::ParseIntError> for ParseIntError { | ||
fn from(_: core::num::ParseIntError) -> Self { | ||
Self(()) | ||
} | ||
} | ||
|
||
impl NonMaxU32 { | ||
/// Creates a non-u32-max without checking the value. | ||
/// | ||
/// # Safety | ||
/// | ||
/// The value must not be `u32::MAX`. | ||
#[inline] | ||
pub const unsafe fn new_unchecked(n: u32) -> Self { | ||
// SAFETY: this is guaranteed to be safe by the caller. | ||
Self(n) | ||
} | ||
|
||
/// Creates a non-u32-max if the given value is not `u32::MAX`. | ||
#[inline] | ||
pub const fn new(n: u32) -> Option<Self> { | ||
if n == u32::MAX { | ||
None | ||
} else { | ||
// SAFETY: we just checked that there's no `u32::MAX` | ||
Some(Self(n)) | ||
} | ||
} | ||
|
||
/// Returns the value as a primitive type. | ||
#[inline] | ||
pub const fn get(self) -> u32 { | ||
self.0 | ||
} | ||
Comment on lines
+93
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we also include setters? And operations such as add, sub, mul and so on? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm good with adding these methods though, I wonder it also depends on how we want to use it. Do we want to always do calculation with On the other hand, I wonder it might be good to at least add methods like WDYT? |
||
} | ||
|
||
impl TryFrom<u32> for NonMaxU32 { | ||
type Error = TryFromIntError; | ||
|
||
fn try_from(value: u32) -> Result<Self, Self::Error> { | ||
Self::new(value).ok_or(TryFromIntError(())) | ||
} | ||
} | ||
|
||
impl From<NonMaxU32> for u32 { | ||
/// Converts a `NonMaxU32` into an `u32` | ||
fn from(nonzero: NonMaxU32) -> Self { | ||
nonzero.0 | ||
} | ||
} | ||
|
||
impl core::str::FromStr for NonMaxU32 { | ||
type Err = ParseIntError; | ||
fn from_str(value: &str) -> Result<Self, Self::Err> { | ||
Self::new(u32::from_str(value)?).ok_or(ParseIntError(())) | ||
} | ||
} | ||
|
||
impl BitOr for NonMaxU32 { | ||
type Output = Self; | ||
#[inline] | ||
fn bitor(self, rhs: Self) -> Self::Output { | ||
// Safety: since `self` and `rhs` are both nonzero, the | ||
// result of the bitwise-or will be nonzero. | ||
unsafe { NonMaxU32::new_unchecked(self.get() | rhs.get()) } | ||
} | ||
} | ||
|
||
impl BitOr<u32> for NonMaxU32 { | ||
type Output = Self; | ||
|
||
#[inline] | ||
fn bitor(self, rhs: u32) -> Self::Output { | ||
// Safety: since `self` is nonzero, the result of the | ||
// bitwise-or will be nonzero regardless of the value of | ||
// `rhs`. | ||
unsafe { NonMaxU32::new_unchecked(self.get() | rhs) } | ||
} | ||
} | ||
|
||
impl BitOr<NonMaxU32> for u32 { | ||
type Output = NonMaxU32; | ||
|
||
#[inline] | ||
fn bitor(self, rhs: NonMaxU32) -> Self::Output { | ||
// Safety: since `rhs` is nonzero, the result of the | ||
// bitwise-or will be nonzero regardless of the value of | ||
// `self`. | ||
unsafe { NonMaxU32::new_unchecked(self | rhs.get()) } | ||
} | ||
} | ||
Comment on lines
+120
to
+152
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The "Safety" argument and code seems to be copied from the nonzero implementaion and is not correct. (At least the comment is wrong) |
||
|
||
impl BitOrAssign for NonMaxU32 { | ||
#[inline] | ||
fn bitor_assign(&mut self, rhs: Self) { | ||
*self = *self | rhs; | ||
} | ||
} | ||
|
||
impl BitOrAssign<u32> for NonMaxU32 { | ||
#[inline] | ||
fn bitor_assign(&mut self, rhs: u32) { | ||
*self = *self | rhs; | ||
} | ||
} | ||
|
||
impl fmt::Debug for NonMaxU32 { | ||
#[inline] | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
self.get().fmt(f) | ||
} | ||
} | ||
Comment on lines
+168
to
+173
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might be a good implementation for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to follow There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree with Razican |
||
|
||
impl fmt::Display for NonMaxU32 { | ||
#[inline] | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
self.get().fmt(f) | ||
} | ||
} |
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 would suggest the internal type to be not an enum type and be a structure type:
This would disallow direct editing of the internal structure (having to use getters and setters), and changing the internal type would be API compatible.
Talking about changing the internal type, it might be useful to use a
NonZeroU32
, which is better optimized for space. On the other hand, this would require using a+1
when storing, and a-1
when retrieving, which could cause a small performance penalty. We should test both options.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 for late reply 🙏 Thank you for your suggestion! I will try to work on a fix.
This sounds a good idea to me. I experimented a bit using XOR to save with
NonZeroU32
like the nonmax crate does last week but I realized we would need torev()
the iterator because the xor'ed value would be in reversed order. So, I wonder just +1 and -1 would be easier.I will try it more and share more information here or on Discord 🙏 Thank you!
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 I'm ok with either approach (-1/+1 or XOR).