Skip to content
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

Magic zeroslice! macros #3454

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions components/datetime/src/fields/length.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ pub enum FieldLength {

impl FieldLength {
#[inline]
pub(crate) fn idx(&self) -> u8 {
pub(crate) const fn idx(&self) -> u8 {
match self {
FieldLength::One => 1,
FieldLength::TwoDigit => 2,
FieldLength::Abbreviated => 3,
FieldLength::Wide => 4,
FieldLength::Narrow => 5,
FieldLength::Six => 6,
FieldLength::Fixed(p) => 128 + p.min(&127), /* truncate to at most 127 digits to avoid overflow */
FieldLength::Fixed(p) => 128 + if *p >= 127 { 127 } else { *p }, /* truncate to at most 127 digits to avoid overflow */
}
}

Expand Down Expand Up @@ -125,6 +125,12 @@ impl FieldLengthULE {
.map(|_| ())
.map_err(|_| ZeroVecError::parse::<FieldLength>())
}

/// The same as [`AsULE::to_unaligned`].
#[inline]
pub const fn from_aligned(fl: FieldLength) -> Self {
Self(fl.idx())
}
}

// Safety checklist for ULE:
Expand Down
10 changes: 8 additions & 2 deletions components/datetime/src/fields/symbols.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ impl FieldSymbol {
/// This model limits the available number of possible types and symbols to 16 each.

#[inline]
pub(crate) fn idx(&self) -> u8 {
pub(crate) const fn idx(&self) -> u8 {
let (high, low) = match self {
FieldSymbol::Era => (0, 0),
FieldSymbol::Year(year) => (1, year.idx()),
Expand Down Expand Up @@ -186,6 +186,12 @@ impl FieldSymbolULE {
.map(|_| ())
.map_err(|_| ZeroVecError::parse::<FieldSymbol>())
}

/// The same as [`AsULE::to_unaligned`].
#[inline]
pub const fn from_aligned(fs: FieldSymbol) -> Self {
Self(fs.idx())
}
}

// Safety checklist for ULE:
Expand Down Expand Up @@ -378,7 +384,7 @@ macro_rules! field_type {
/// and does not guarantee index stability between ICU4X
/// versions.
#[inline]
pub(crate) fn idx(self) -> u8 {
pub(crate) const fn idx(self) -> u8 {
self as u8
}

Expand Down
10 changes: 10 additions & 0 deletions components/properties/src/expandme.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// expand me by cd'ing into properties, then `cargo expand expandme`

#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: eventually should be removed, yes?

#[allow(clippy::exhaustive_structs)]
#[zerovec::make_ule(AlignedULE)]
pub struct Aligned {
field_one: u8,
field_two: char,
field_three: i32,
}
4 changes: 2 additions & 2 deletions components/properties/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ pub mod maps;
// of the `CanonicalCombiningClass` struct inside the `props`
// module. Please do not change the crate-module-qualified
// name of that struct without coordination.
mod props;

pub mod bidi_data;
pub mod exemplar_chars;
mod expandme;
mod props;
pub mod provider;
pub(crate) mod runtime;
#[allow(clippy::exhaustive_structs)] // TODO
Expand Down
45 changes: 45 additions & 0 deletions utils/zerovec/derive/src/make_ule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ fn make_ule_enum_impl(

let doc = format!("[`ULE`](zerovec::ule::ULE) type for {name}");

let from_aligned_doc = format!("Converts a [`{name}`] to a [`{ule_name}`]. This is equivalent to calling [`AsULE::to_unaligned`].");

// Safety (based on the safety checklist on the ULE trait):
// 1. ULE type does not include any uninitialized or padding bytes.
// (achieved by `#[repr(transparent)]` on a type that satisfies this invariant
Expand All @@ -177,6 +179,24 @@ fn make_ule_enum_impl(
#[doc = #doc]
#vis struct #ule_name(u8);

impl #ule_name {
#[doc = #from_aligned_doc]
#[inline]
pub const fn from_aligned(a: #name) -> #ule_name {
// safety: the enum is repr(u8) and can be cast to a u8
unsafe {
::core::mem::transmute(a)
}
}

zerovec::impl_ule_from_array!(
#name,
#ule_name,
// safety: the enum is repr(u8), can be cast to a u8 and contains at least one variant
unsafe { ::core::mem::transmute(0u8) }
);
}

unsafe impl zerovec::ule::ULE for #ule_name {
#[inline]
fn validate_byte_slice(bytes: &[u8]) -> Result<(), zerovec::ZeroVecError> {
Expand Down Expand Up @@ -268,6 +288,7 @@ fn make_ule_struct_impl(

let mut as_ule_conversions = vec![];
let mut from_ule_conversions = vec![];
let mut const_from_aligned_conversions = vec![];

for (i, field) in struc.fields.iter().enumerate() {
let ty = &field.ty;
Expand All @@ -278,10 +299,15 @@ fn make_ule_struct_impl(
from_ule_conversions.push(
quote!(#ident: <#ty as zerovec::ule::AsULE>::from_unaligned(unaligned.#ident)),
);
// TODO: need to match here and below on #ty, and then call the appropriate function, as in zeroslice! macro.
const_from_aligned_conversions
.push(quote!(#ident: zerovec::const_ule_conversion_fn!(#ty)(aligned.#ident)));
} else {
as_ule_conversions.push(quote!(<#ty as zerovec::ule::AsULE>::to_unaligned(self.#i)));
from_ule_conversions
.push(quote!(<#ty as zerovec::ule::AsULE>::from_unaligned(unaligned.#i)));
const_from_aligned_conversions
.push(quote!(zerovec::const_ule_conversion_fn!(#ty)(aligned.#i)));
};
}

Expand Down Expand Up @@ -334,6 +360,23 @@ fn make_ule_struct_impl(
quote!()
};

let ule_doc = "The same as [`AsULE::to_unaligned`].";

let const_from_aligned_conversions =
utils::wrap_field_inits(&const_from_aligned_conversions, &struc.fields);
let ule_impl = quote!(
impl #ule_name {
#[doc = #ule_doc]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Probably just directly include the doc comment here? There's no interpolation

#[inline]
pub const fn from_aligned(aligned: #name) -> Self {
Self #const_from_aligned_conversions
}

// TODO: need to come up with a safe zero expression for the array initializer.
// zerovec::impl_ule_from_array!(#name, #ule_name, unsafe { core::mem::transmute([0; core::mem::size_of::<#ule_name>()]) })
}
);

quote!(
#asule_impl

Expand All @@ -344,5 +387,7 @@ fn make_ule_struct_impl(
#maybe_ord_impls

#maybe_hash

#ule_impl
)
}
2 changes: 1 addition & 1 deletion utils/zerovec/src/ule/chars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ impl AsULE for char {

#[inline]
fn to_unaligned(self) -> Self::ULE {
CharULE::from_aligned(self)
<Self as AsULE>::ULE::from_aligned(self)
}

#[inline]
Expand Down
66 changes: 64 additions & 2 deletions utils/zerovec/src/ule/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,31 @@
/// Pass any (cheap to construct) value.
#[macro_export]
macro_rules! impl_ule_from_array {
($aligned:ty, $unaligned:ty, $default:expr, $single:path, $fn_name:ident) => {
#[doc = concat!("Convert an array of `", stringify!($aligned), "` to an array of `", stringify!($unaligned), "`.")]
pub const fn $fn_name<const N: usize>(arr: [$aligned; N]) -> [Self; N] {
let mut result = [$default; N];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: could we potentially use MaybeUninit to avoid the need for $default? arrays of MaybeUninit are transmutable to regular arrays

let mut i = 0;
// Won't panic because i < N and arr has length N
#[allow(clippy::indexing_slicing)]
while i < N {
result[i] = $single(arr[i]);
i += 1;
}
result
}
};
($aligned:ty, $unaligned:ty, $default:expr) => {
$crate::impl_ule_from_array!($aligned, $unaligned, $default, Self::from_aligned, from_array);
};
}

#[macro_export]
macro_rules! impl_const_as_ule_array {
($aligned:ty, $unaligned:ty, $default:expr, $single:path) => {
#[doc = concat!("Convert an array of `", stringify!($aligned), "` to an array of `", stringify!($unaligned), "`.")]
pub const fn from_array<const N: usize>(arr: [$aligned; N]) -> [Self; N] {
#[allow(dead_code)]
pub const fn aligned_to_unaligned_array<const N: usize>(arr: [$aligned; N]) -> [$unaligned; N] {
let mut result = [$default; N];
let mut i = 0;
// Won't panic because i < N and arr has length N
Expand All @@ -24,6 +46,46 @@ macro_rules! impl_ule_from_array {
}
};
($aligned:ty, $unaligned:ty, $default:expr) => {
impl_ule_from_array!($aligned, $unaligned, $default, Self::from_aligned);
$crate::impl_const_as_ule_array!($aligned, $unaligned, $default, Self::aligned_to_unaligned);
};
}

#[macro_export]
macro_rules! const_ule_conversion_fn {
(u8) => {
core::convert::identity
};
(i8) => {
core::convert::identity
};
(u16) => {
<u16 as $crate::ule::AsULE>::ULE::from_unsigned
};
(i16) => {
<i16 as $crate::ule::AsULE>::ULE::from_signed
};
(u32) => {
<u32 as $crate::ule::AsULE>::ULE::from_unsigned
};
(i32) => {
<i32 as $crate::ule::AsULE>::ULE::from_signed
};
(u64) => {
<u64 as $crate::ule::AsULE>::ULE::from_unsigned
};
(i64) => {
<i64 as $crate::ule::AsULE>::ULE::from_signed
};
(u128) => {
<u128 as $crate::ule::AsULE>::ULE::from_unsigned
};
(i128) => {
<i128 as $crate::ule::AsULE>::ULE::from_signed
};
(UnvalidatedChar) => {
<UnvalidatedChar as $crate::ule::AsULE>::ULE::from_unvalidated_char
};
($aligned:ty) => {
<$aligned as $crate::ule::AsULE>::ULE::from_aligned
};
}
31 changes: 31 additions & 0 deletions utils/zerovec/src/ule/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,37 @@ where
}
}

/// A trait for any type that can be const-mapped to its ULE type. It is particularly
/// useful in combination with [`zeroslice!`](crate::zeroslice).
///
/// This trait serves only as a redirection to a type that contains the required associated const
/// functions for conversion. This is currently necessary due to the lack of const traits in Rust.
///
/// # Implementation
///
/// Using the [`#[make_ule]`](crate::make_ule) will implement this trait for you and you do not
/// have to read further.
///
/// If you implement `ConstAsULE` manually for your type `T`, the associated type `ConstConvert`
/// must have the following associated `const` functions:
/// * `ConstConvert::aligned_to_unaligned(T) -> T::ULE`
/// * `ConstConvert::aligned_to_unaligned_array<const N: usize>([T; N]) -> [T::ULE; N]`
///
/// For `T::ULE` types that only have one canonical aligned type, `T`, the associated type
/// `ConstConvert` can safely be set to `T::ULE` and the required functions can be implemented
/// there.
///
/// Issues arise when `T::ULE` has multiple or no canonical aligned types, as is the case with e.g.
/// `RawBytesULE<4>` and `i32`/`u32`. In this case, `ConstConvert` must be set to a different type
/// for each `impl ConstAsULE for T`. This crate does that by providing associated functions on a
/// custom type for each pair of `T` and `T::ULE`, namely
/// `crate::ule::constconvert::ConstConvert<T, T::ULE>`. Unfortunately, you will not be able
/// to reuse this type for your own `ConstAsULE` implementations, since you cannot provide
/// implementations for types outside of your crate.
pub trait ConstAsULE: AsULE {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: doesn't look like we're using it anymore

(I am also in general against adding such a trait)

type ConstConvert;
}

/// Variable-width, byte-aligned data that can be cast to and from a little-endian byte slice.
///
/// If you need to implement this trait, consider using [`#[make_varule]`](crate::make_varule) or
Expand Down
44 changes: 35 additions & 9 deletions utils/zerovec/src/ule/plain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ macro_rules! impl_const_constructors {
}

macro_rules! impl_byte_slice_type {
($type:ty, $size:literal) => {
($single_fn:ident, $array_fn_name:ident, $type:ty, $size:literal) => {
impl From<$type> for RawBytesULE<$size> {
#[inline]
fn from(value: $type) -> Self {
Expand All @@ -132,6 +132,32 @@ macro_rules! impl_byte_slice_type {
// EqULE is true because $type and RawBytesULE<$size>
// have the same byte sequence on little-endian
unsafe impl EqULE for $type {}

impl RawBytesULE<$size> {
pub const fn $single_fn(v: $type) -> Self {
RawBytesULE(v.to_le_bytes())
}

impl_ule_from_array!(
$type,
RawBytesULE<$size>,
RawBytesULE([0; $size]),
Self::$single_fn,
$array_fn_name
);
}
};
}

macro_rules! impl_byte_slice_unsigned_type {
($type:ty, $size:literal) => {
impl_byte_slice_type!(from_unsigned, from_unsigned_array, $type, $size);
};
}

macro_rules! impl_byte_slice_signed_type {
($type:ty, $size:literal) => {
impl_byte_slice_type!(from_signed, from_signed_array, $type, $size);
};
}

Expand All @@ -140,15 +166,15 @@ impl_byte_slice_size!(u32, 4);
impl_byte_slice_size!(u64, 8);
impl_byte_slice_size!(u128, 16);

impl_byte_slice_type!(u16, 2);
impl_byte_slice_type!(u32, 4);
impl_byte_slice_type!(u64, 8);
impl_byte_slice_type!(u128, 16);
impl_byte_slice_unsigned_type!(u16, 2);
impl_byte_slice_unsigned_type!(u32, 4);
impl_byte_slice_unsigned_type!(u64, 8);
impl_byte_slice_unsigned_type!(u128, 16);

impl_byte_slice_type!(i16, 2);
impl_byte_slice_type!(i32, 4);
impl_byte_slice_type!(i64, 8);
impl_byte_slice_type!(i128, 16);
impl_byte_slice_signed_type!(i16, 2);
impl_byte_slice_signed_type!(i32, 4);
impl_byte_slice_signed_type!(i64, 8);
impl_byte_slice_signed_type!(i128, 16);

impl_const_constructors!(u8, 1);
impl_const_constructors!(u16, 2);
Expand Down
Loading