From 3a8160a053d72b20187fbbc112f445e6bf25c4cc Mon Sep 17 00:00:00 2001 From: Juniper Tyree <50025784+juntyr@users.noreply.github.com> Date: Wed, 13 Nov 2024 21:37:26 +0000 Subject: [PATCH 1/5] Switch to negabinary discriminant implementation --- const-type-layout-derive/src/lib.rs | 2 +- src/discriminant.rs | 86 +++++++++++++++++++++ src/impls/core/cmp.rs | 12 ++- src/impls/core/ops.rs | 12 +-- src/impls/core/option.rs | 6 +- src/impls/core/result.rs | 4 +- src/lib.rs | 115 +--------------------------- src/ser.rs | 53 +------------ 8 files changed, 114 insertions(+), 176 deletions(-) create mode 100644 src/discriminant.rs diff --git a/const-type-layout-derive/src/lib.rs b/const-type-layout-derive/src/lib.rs index db59873..b3a2fe8 100644 --- a/const-type-layout-derive/src/lib.rs +++ b/const-type-layout-derive/src/lib.rs @@ -532,7 +532,7 @@ fn quote_enum_variants( let discriminant = quote! { #variant_inhabited.map( - #crate_path::Discriminant::new(#discriminant) + #crate_path::discriminant::discriminant!(#discriminant) ) }; diff --git a/src/discriminant.rs b/src/discriminant.rs new file mode 100644 index 0000000..e2c9c8b --- /dev/null +++ b/src/discriminant.rs @@ -0,0 +1,86 @@ +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +/// Discriminant of an enum variant. +pub struct Discriminant<'a> { + /// The numeric value of the discriminant. + /// + /// The value is stored in [negabinary] representation in little-endian + /// order as a byte array. + /// + /// [negabinary]: https://oeis.org/wiki/Negabinary + #[cfg_attr(feature = "serde", serde(borrow))] + pub value: &'a [u8], +} + +#[macro_export] +#[doc(hidden)] +macro_rules! discriminant { + // https://oeis.org/wiki/Negabinary + // https://en.wikipedia.org/wiki/Negative_base#To_negabinary + ($x:expr) => {{ + const NBYTES: usize = { + let mut x = $x; + let mut nbits = 0; + + let mut flipped_sign = false; + + while x != 0 { + nbits += 1; + + let floor_x_2 = if (x % 2) < 0 { (x / 2) - 1 } else { x / 2 }; + + if (x % 2) == 0 { + x = floor_x_2; + } else if flipped_sign { + x = floor_x_2 + 1; + } else { + x = floor_x_2; + } + + flipped_sign = !flipped_sign; + } + + ((nbits + 7) / 8) + 1 + }; + + const NEGABINARY: [u8; NBYTES] = { + let mut x = $x; + let mut bits = [0; NBYTES]; + + let mut i = 0; + + let mut bit = 0x1_u8; + let mut flipped_sign = false; + + while x != 0 { + if (x % 2) != 0 { + bits[i] |= bit; + } + bit = bit.rotate_left(1); + + if bit == 0x1_u8 { + i += 1; + } + + let floor_x_2 = if (x % 2) < 0 { (x / 2) - 1 } else { x / 2 }; + + if (x % 2) == 0 { + x = floor_x_2; + } else if flipped_sign { + x = floor_x_2 + 1; + } else { + x = floor_x_2; + } + + flipped_sign = !flipped_sign; + } + + bits + }; + + $crate::discriminant::Discriminant { value: &NEGABINARY } + }}; +} + +#[doc(inline)] +pub use discriminant; diff --git a/src/impls/core/cmp.rs b/src/impls/core/cmp.rs index b3d4c46..eb63993 100644 --- a/src/impls/core/cmp.rs +++ b/src/impls/core/cmp.rs @@ -35,17 +35,23 @@ unsafe impl TypeLayout for core::cmp::Ordering { variants: &[ Variant { name: "Less", - discriminant: MaybeUninhabited::Inhabited(crate::Discriminant::I8(-1)), + discriminant: MaybeUninhabited::Inhabited(crate::discriminant::discriminant!( + -1 + )), fields: &[], }, Variant { name: "Equal", - discriminant: MaybeUninhabited::Inhabited(crate::Discriminant::I8(0)), + discriminant: MaybeUninhabited::Inhabited(crate::discriminant::discriminant!( + 0 + )), fields: &[], }, Variant { name: "Greater", - discriminant: MaybeUninhabited::Inhabited(crate::Discriminant::I8(1)), + discriminant: MaybeUninhabited::Inhabited(crate::discriminant::discriminant!( + 1 + )), fields: &[], }, ], diff --git a/src/impls/core/ops.rs b/src/impls/core/ops.rs index ffd3085..ce8c040 100644 --- a/src/impls/core/ops.rs +++ b/src/impls/core/ops.rs @@ -122,7 +122,7 @@ unsafe impl TypeLayout for core::ops::Bound { variants: &[ Variant { name: "Included", - discriminant: MaybeUninhabited::new::(crate::Discriminant::Isize(0)), + discriminant: MaybeUninhabited::new::(crate::discriminant::discriminant!(0)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!( @@ -133,7 +133,7 @@ unsafe impl TypeLayout for core::ops::Bound { }, Variant { name: "Excluded", - discriminant: MaybeUninhabited::new::(crate::Discriminant::Isize(1)), + discriminant: MaybeUninhabited::new::(crate::discriminant::discriminant!(1)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!( @@ -144,7 +144,9 @@ unsafe impl TypeLayout for core::ops::Bound { }, Variant { name: "Unbounded", - discriminant: MaybeUninhabited::Inhabited(crate::Discriminant::Isize(2)), + discriminant: MaybeUninhabited::Inhabited(crate::discriminant::discriminant!( + 2 + )), fields: &[], }, ], @@ -169,7 +171,7 @@ unsafe impl TypeLayout for core::ops::ControlFlow< variants: &[ Variant { name: "Continue", - discriminant: MaybeUninhabited::new::(crate::Discriminant::Isize(0)), + discriminant: MaybeUninhabited::new::(crate::discriminant::discriminant!(0)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!( @@ -180,7 +182,7 @@ unsafe impl TypeLayout for core::ops::ControlFlow< }, Variant { name: "Break", - discriminant: MaybeUninhabited::new::(crate::Discriminant::Isize(1)), + discriminant: MaybeUninhabited::new::(crate::discriminant::discriminant!(1)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!(Self, Break.0)), diff --git a/src/impls/core/option.rs b/src/impls/core/option.rs index 82307f6..4b26aaa 100644 --- a/src/impls/core/option.rs +++ b/src/impls/core/option.rs @@ -14,12 +14,14 @@ unsafe impl TypeLayout for core::option::Option { variants: &[ Variant { name: "None", - discriminant: MaybeUninhabited::Inhabited(crate::Discriminant::Isize(0)), + discriminant: MaybeUninhabited::Inhabited(crate::discriminant::discriminant!( + 0 + )), fields: &[], }, Variant { name: "Some", - discriminant: MaybeUninhabited::new::(crate::Discriminant::Isize(1)), + discriminant: MaybeUninhabited::new::(crate::discriminant::discriminant!(1)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!(Self, Some.0)), diff --git a/src/impls/core/result.rs b/src/impls/core/result.rs index 364a976..675dcd4 100644 --- a/src/impls/core/result.rs +++ b/src/impls/core/result.rs @@ -14,7 +14,7 @@ unsafe impl TypeLayout for core::result::Result(crate::Discriminant::Isize(0)), + discriminant: MaybeUninhabited::new::(crate::discriminant::discriminant!(0)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!(Self, Ok.0)), @@ -23,7 +23,7 @@ unsafe impl TypeLayout for core::result::Result(crate::Discriminant::Isize(1)), + discriminant: MaybeUninhabited::new::(crate::discriminant::discriminant!(1)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!(Self, Err.0)), diff --git a/src/lib.rs b/src/lib.rs index e5fd6fe..d8be6e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,6 +158,7 @@ use core::ops::Deref; #[cfg(feature = "derive")] pub use const_type_layout_derive::TypeLayout; +pub mod discriminant; mod impls; pub mod inhabited; mod ser; @@ -419,122 +420,12 @@ pub struct Variant<'a, F: Deref]> = &'a [Field<'a>]> { pub name: &'a str, /// The variant's descriminant, iff the variant is /// [inhabited](https://doc.rust-lang.org/reference/glossary.html#inhabited). - pub discriminant: MaybeUninhabited, + #[cfg_attr(feature = "serde", serde(borrow))] + pub discriminant: MaybeUninhabited>, /// The variant's fields. pub fields: F, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", allow(clippy::unsafe_derive_deserialize))] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -/// Discriminant value of a type. -pub enum Discriminant { - /// `#[repr(i8)]` discriminant. - I8(i8), - /// `#[repr(i16)]` discriminant. - I16(i16), - /// `#[repr(i32)]` discriminant. - I32(i32), - /// `#[repr(i64)]` discriminant. - I64(i64), - /// `#[repr(i128)]` discriminant. - I128(i128), - /// `#[repr(isize)]` discriminant (default). - Isize(isize), - /// `#[repr(u8)]` discriminant. - U8(u8), - /// `#[repr(u16)]` discriminant. - U16(u16), - /// `#[repr(u32)]` discriminant. - U32(u32), - /// `#[repr(u64)]` discriminant. - U64(u64), - /// `#[repr(u128)]` discriminant. - U128(u128), - /// `#[repr(usize)]` discriminant. - Usize(usize), -} - -impl Discriminant { - #[allow(clippy::missing_panics_doc)] - #[must_use] - /// Constructs a [`Discriminant`] value with the given value `v`. - /// - /// `T` must be a valid discriminant kind type, i.e. one of the variants - /// that [`Discriminant`] can represent. - pub const fn new(v: T) -> Self { - // TODO: can this constructor be written without panic or specialisation - #[repr(C)] - union Transmute { - v: T, - i8: i8, - i16: i16, - i32: i32, - i64: i64, - i128: i128, - isize: isize, - u8: u8, - u16: u16, - u32: u32, - u64: u64, - u128: u128, - usize: usize, - } - - if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to i8 - return Self::I8(unsafe { Transmute { v }.i8 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to i16 - return Self::I16(unsafe { Transmute { v }.i16 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to i32 - return Self::I32(unsafe { Transmute { v }.i32 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to i64 - return Self::I64(unsafe { Transmute { v }.i64 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to i128 - return Self::I128(unsafe { Transmute { v }.i128 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to isize - return Self::Isize(unsafe { Transmute { v }.isize }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to u8 - return Self::U8(unsafe { Transmute { v }.u8 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to u16 - return Self::U16(unsafe { Transmute { v }.u16 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to u32 - return Self::U32(unsafe { Transmute { v }.u32 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to u64 - return Self::U64(unsafe { Transmute { v }.u64 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to u128 - return Self::U128(unsafe { Transmute { v }.u128 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to usize - return Self::Usize(unsafe { Transmute { v }.usize }); - } - - panic!("bug: unknown discriminant kind") - } -} - -trait Same { - const EQ: bool; -} - -impl Same for T { - default const EQ: bool = false; -} - -impl Same for T { - const EQ: bool = true; -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] /// Descriptor of the shallow layout of a field. diff --git a/src/ser.rs b/src/ser.rs index 244779e..82b8cce 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -1,5 +1,5 @@ use crate::{ - Discriminant, Field, MaybeUninhabited, TypeLayout, TypeLayoutGraph, TypeLayoutInfo, + discriminant::Discriminant, Field, MaybeUninhabited, TypeLayoutGraph, TypeLayoutInfo, TypeStructure, Variant, }; @@ -130,57 +130,8 @@ impl Serialiser<'_> { }); } - const fn serialise_discriminant_bytes(&mut self, value_bytes: &[u8]) { - let mut leading_zeroes = 0; - - while leading_zeroes < value_bytes.len() { - if value_bytes[leading_zeroes] != 0_u8 { - break; - } - - leading_zeroes += 1; - } - - self.serialise_usize(value_bytes.len() - leading_zeroes); - - let mut i = leading_zeroes; - - while i < value_bytes.len() { - self.write_byte(value_bytes[i]); - i += 1; - } - } - pub const fn serialise_discriminant(&mut self, value: &Discriminant) { - match value { - Discriminant::I8(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::I16(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::I32(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::I64(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::I128(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::Isize(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::U8(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::U16(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::U32(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::U64(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::U128(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::Usize(_) => self.serialise_str(::TYPE_LAYOUT.name), - }; - - match value { - Discriminant::I8(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::I16(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::I32(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::I64(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::I128(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::Isize(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::U8(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::U16(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::U32(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::U64(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::U128(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::Usize(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - }; + self.write_bytes(value.value); } pub const fn serialise_field(&mut self, value: &Field) { From 4a881a59051187d729cf3697c2c90dcc974a7a6d Mon Sep 17 00:00:00 2001 From: Juniper Tyree <50025784+juntyr@users.noreply.github.com> Date: Wed, 13 Nov 2024 23:26:30 +0000 Subject: [PATCH 2/5] Fix prefix codec for discriminants --- src/discriminant.rs | 2 +- src/ser.rs | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/discriminant.rs b/src/discriminant.rs index e2c9c8b..e7df16c 100644 --- a/src/discriminant.rs +++ b/src/discriminant.rs @@ -40,7 +40,7 @@ macro_rules! discriminant { flipped_sign = !flipped_sign; } - ((nbits + 7) / 8) + 1 + (nbits + 7) / 8 }; const NEGABINARY: [u8; NBYTES] = { diff --git a/src/ser.rs b/src/ser.rs index 82b8cce..fceacfb 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -130,8 +130,29 @@ impl Serialiser<'_> { }); } + const fn serialise_discriminant_bytes(&mut self, value_bytes: &[u8]) { + let mut trailing_zeroes = 0; + + while trailing_zeroes < value_bytes.len() { + if value_bytes[value_bytes.len() - 1 - trailing_zeroes] != 0_u8 { + break; + } + + trailing_zeroes += 1; + } + + self.serialise_usize(value_bytes.len() - trailing_zeroes); + + let mut i = 0; + + while i < (value_bytes.len() - trailing_zeroes) { + self.write_byte(value_bytes[i]); + i += 1; + } + } + pub const fn serialise_discriminant(&mut self, value: &Discriminant) { - self.write_bytes(value.value); + self.serialise_discriminant_bytes(value.value); } pub const fn serialise_field(&mut self, value: &Field) { From 0cd8e407593eb5a138936b0482cb75c4f2b59ad7 Mon Sep 17 00:00:00 2001 From: Juniper Tyree <50025784+juntyr@users.noreply.github.com> Date: Thu, 14 Nov 2024 08:47:03 +0000 Subject: [PATCH 3/5] Small cleanup + add docs --- const-type-layout-derive/src/lib.rs | 2 +- src/discriminant.rs | 53 +++++++++++++++++++++++++---- src/impls/core/cmp.rs | 12 ++----- src/impls/core/ops.rs | 12 +++---- src/impls/core/option.rs | 6 ++-- src/impls/core/result.rs | 4 +-- src/lib.rs | 6 ++-- src/ser.rs | 3 +- src/typeset.rs | 2 +- 9 files changed, 66 insertions(+), 34 deletions(-) diff --git a/const-type-layout-derive/src/lib.rs b/const-type-layout-derive/src/lib.rs index b3a2fe8..53858bf 100644 --- a/const-type-layout-derive/src/lib.rs +++ b/const-type-layout-derive/src/lib.rs @@ -532,7 +532,7 @@ fn quote_enum_variants( let discriminant = quote! { #variant_inhabited.map( - #crate_path::discriminant::discriminant!(#discriminant) + #crate_path::discriminant!(#discriminant) ) }; diff --git a/src/discriminant.rs b/src/discriminant.rs index e7df16c..abb9d08 100644 --- a/src/discriminant.rs +++ b/src/discriminant.rs @@ -1,19 +1,63 @@ #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] /// Discriminant of an enum variant. +/// +/// The [`Discriminant`] value can be constructed using the [`discriminant!`] +/// macro. +/// +/// [`discriminant!`]: crate::discriminant! pub struct Discriminant<'a> { /// The numeric value of the discriminant. /// /// The value is stored in [negabinary] representation in little-endian - /// order as a byte array. + /// order as a byte array. Unlike the two's complement representation, + /// [negabinary] has a unique representation for signed and unsigned + /// numbers that is independent of the size of the value type (e.g. [`i16`] + /// vs [`u64`]). + /// + /// Specifically, + /// - the `(-2)^0`th digit is stored in the least significant bit of + /// `value[0]` + /// - the `(-2)^7`th digit is stored in the most significant bit of + /// `value[0]` + /// - the `(-2)^8`th digit is stored in the least significant bit of + /// `value[1]` + /// - for all `i >= value.len()`, `value[i]` is implicitly all-zero /// /// [negabinary]: https://oeis.org/wiki/Negabinary #[cfg_attr(feature = "serde", serde(borrow))] pub value: &'a [u8], } +/// Helper macro to construct a [`Discriminant`] from its constant value. +/// +/// The [`discriminant!`] macro is invoked with the constant expression value +/// of the discriminant, e.g. +/// +/// [`discriminant!`]: crate::discriminant! +/// +/// ``` +/// # use const_type_layout::discriminant::{discriminant, Discriminant}; +/// // unsigned literal with inferred type +/// const D1: Discriminant = discriminant!(4); +/// +/// // signed literal with inferred type +/// const D2: Discriminant = discriminant!(-2); +/// +/// // unsigned literal with explicit type +/// const D3: Discriminant = discriminant!(2_u8); +/// +/// // signed literal with explicit type +/// const D4: Discriminant = discriminant!(-4_i128); +/// +/// // constant expression with inferred type +/// const D5: Discriminant = discriminant!(-4 + 7); +/// +/// // constant value +/// const VALUE: isize = 42; +/// const D6: Discriminant = discriminant!(VALUE); +/// ``` #[macro_export] -#[doc(hidden)] macro_rules! discriminant { // https://oeis.org/wiki/Negabinary // https://en.wikipedia.org/wiki/Negative_base#To_negabinary @@ -78,9 +122,6 @@ macro_rules! discriminant { bits }; - $crate::discriminant::Discriminant { value: &NEGABINARY } + $crate::Discriminant { value: &NEGABINARY } }}; } - -#[doc(inline)] -pub use discriminant; diff --git a/src/impls/core/cmp.rs b/src/impls/core/cmp.rs index eb63993..dd35acf 100644 --- a/src/impls/core/cmp.rs +++ b/src/impls/core/cmp.rs @@ -35,23 +35,17 @@ unsafe impl TypeLayout for core::cmp::Ordering { variants: &[ Variant { name: "Less", - discriminant: MaybeUninhabited::Inhabited(crate::discriminant::discriminant!( - -1 - )), + discriminant: MaybeUninhabited::Inhabited(crate::discriminant!(-1)), fields: &[], }, Variant { name: "Equal", - discriminant: MaybeUninhabited::Inhabited(crate::discriminant::discriminant!( - 0 - )), + discriminant: MaybeUninhabited::Inhabited(crate::discriminant!(0)), fields: &[], }, Variant { name: "Greater", - discriminant: MaybeUninhabited::Inhabited(crate::discriminant::discriminant!( - 1 - )), + discriminant: MaybeUninhabited::Inhabited(crate::discriminant!(1)), fields: &[], }, ], diff --git a/src/impls/core/ops.rs b/src/impls/core/ops.rs index ce8c040..5038ec8 100644 --- a/src/impls/core/ops.rs +++ b/src/impls/core/ops.rs @@ -122,7 +122,7 @@ unsafe impl TypeLayout for core::ops::Bound { variants: &[ Variant { name: "Included", - discriminant: MaybeUninhabited::new::(crate::discriminant::discriminant!(0)), + discriminant: MaybeUninhabited::new::(crate::discriminant!(0)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!( @@ -133,7 +133,7 @@ unsafe impl TypeLayout for core::ops::Bound { }, Variant { name: "Excluded", - discriminant: MaybeUninhabited::new::(crate::discriminant::discriminant!(1)), + discriminant: MaybeUninhabited::new::(crate::discriminant!(1)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!( @@ -144,9 +144,7 @@ unsafe impl TypeLayout for core::ops::Bound { }, Variant { name: "Unbounded", - discriminant: MaybeUninhabited::Inhabited(crate::discriminant::discriminant!( - 2 - )), + discriminant: MaybeUninhabited::Inhabited(crate::discriminant!(2)), fields: &[], }, ], @@ -171,7 +169,7 @@ unsafe impl TypeLayout for core::ops::ControlFlow< variants: &[ Variant { name: "Continue", - discriminant: MaybeUninhabited::new::(crate::discriminant::discriminant!(0)), + discriminant: MaybeUninhabited::new::(crate::discriminant!(0)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!( @@ -182,7 +180,7 @@ unsafe impl TypeLayout for core::ops::ControlFlow< }, Variant { name: "Break", - discriminant: MaybeUninhabited::new::(crate::discriminant::discriminant!(1)), + discriminant: MaybeUninhabited::new::(crate::discriminant!(1)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!(Self, Break.0)), diff --git a/src/impls/core/option.rs b/src/impls/core/option.rs index 4b26aaa..bb47a2e 100644 --- a/src/impls/core/option.rs +++ b/src/impls/core/option.rs @@ -14,14 +14,12 @@ unsafe impl TypeLayout for core::option::Option { variants: &[ Variant { name: "None", - discriminant: MaybeUninhabited::Inhabited(crate::discriminant::discriminant!( - 0 - )), + discriminant: MaybeUninhabited::Inhabited(crate::discriminant!(0)), fields: &[], }, Variant { name: "Some", - discriminant: MaybeUninhabited::new::(crate::discriminant::discriminant!(1)), + discriminant: MaybeUninhabited::new::(crate::discriminant!(1)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!(Self, Some.0)), diff --git a/src/impls/core/result.rs b/src/impls/core/result.rs index 675dcd4..3df460a 100644 --- a/src/impls/core/result.rs +++ b/src/impls/core/result.rs @@ -14,7 +14,7 @@ unsafe impl TypeLayout for core::result::Result(crate::discriminant::discriminant!(0)), + discriminant: MaybeUninhabited::new::(crate::discriminant!(0)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!(Self, Ok.0)), @@ -23,7 +23,7 @@ unsafe impl TypeLayout for core::result::Result(crate::discriminant::discriminant!(1)), + discriminant: MaybeUninhabited::new::(crate::discriminant!(1)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!(Self, Err.0)), diff --git a/src/lib.rs b/src/lib.rs index d8be6e8..cb2b177 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,12 +158,14 @@ use core::ops::Deref; #[cfg(feature = "derive")] pub use const_type_layout_derive::TypeLayout; -pub mod discriminant; +mod discriminant; mod impls; pub mod inhabited; mod ser; pub mod typeset; +pub use discriminant::Discriminant; + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(::serde::Serialize))] #[cfg_attr(feature = "serde", derive(::serde::Deserialize))] @@ -421,7 +423,7 @@ pub struct Variant<'a, F: Deref]> = &'a [Field<'a>]> { /// The variant's descriminant, iff the variant is /// [inhabited](https://doc.rust-lang.org/reference/glossary.html#inhabited). #[cfg_attr(feature = "serde", serde(borrow))] - pub discriminant: MaybeUninhabited>, + pub discriminant: MaybeUninhabited>, /// The variant's fields. pub fields: F, } diff --git a/src/ser.rs b/src/ser.rs index fceacfb..b325a1c 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -1,6 +1,5 @@ use crate::{ - discriminant::Discriminant, Field, MaybeUninhabited, TypeLayoutGraph, TypeLayoutInfo, - TypeStructure, Variant, + Discriminant, Field, MaybeUninhabited, TypeLayoutGraph, TypeLayoutInfo, TypeStructure, Variant, }; pub enum Serialiser<'a> { diff --git a/src/typeset.rs b/src/typeset.rs index 5c4b9e6..6becc0e 100644 --- a/src/typeset.rs +++ b/src/typeset.rs @@ -82,7 +82,7 @@ pub unsafe trait ComputeTypeSet: crate::TypeLayout { /// type links to. /// /// Enums implementing [`crate::TypeLayout`] and [`ComputeTypeSet`] - /// manually should include [`crate::ExtractDiscriminant::Discriminant`] in + /// manually should include [`core::mem::Discriminant`] in /// their [`ComputeTypeSet::Output`] using the [`tset`] helper macro. type Output: ExpandTypeSet; } From c9485b3be9cfc406d1c019b068dc06f9f962cba2 Mon Sep 17 00:00:00 2001 From: Juniper Tyree <50025784+juntyr@users.noreply.github.com> Date: Thu, 14 Nov 2024 09:14:40 +0000 Subject: [PATCH 4/5] Document the discriminant macro implementation --- src/discriminant.rs | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/src/discriminant.rs b/src/discriminant.rs index abb9d08..1b500d4 100644 --- a/src/discriminant.rs +++ b/src/discriminant.rs @@ -37,7 +37,7 @@ pub struct Discriminant<'a> { /// [`discriminant!`]: crate::discriminant! /// /// ``` -/// # use const_type_layout::discriminant::{discriminant, Discriminant}; +/// # use const_type_layout::{discriminant, Discriminant}; /// // unsigned literal with inferred type /// const D1: Discriminant = discriminant!(4); /// @@ -64,16 +64,25 @@ macro_rules! discriminant { ($x:expr) => {{ const NBYTES: usize = { let mut x = $x; + + // number of bits required to represent x in negabinary let mut nbits = 0; + // has the sign of the input been flipped? + // since we cannot multiply by -1 if the input is unsigned, we keep + // track of sign flips instead let mut flipped_sign = false; while x != 0 { nbits += 1; - let floor_x_2 = if (x % 2) < 0 { (x / 2) - 1 } else { x / 2 }; + // x.div_floor(2) without requring type annotations + let (x_div_2, x_mod_2) = (x / 2, x % 2); + let floor_x_2 = if x_mod_2 < 0 { x_div_2 - 1 } else { x_div_2 }; - if (x % 2) == 0 { + // x := (x / -2) + ((x % -2) < 1) + // where x's sign is flipped before or after + if x_mod_2 == 0 { x = floor_x_2; } else if flipped_sign { x = floor_x_2 + 1; @@ -81,33 +90,46 @@ macro_rules! discriminant { x = floor_x_2; } + // dividing by -2 will flip the sign flipped_sign = !flipped_sign; } + // round up to the number of bytes required for x in negabinary (nbits + 7) / 8 }; const NEGABINARY: [u8; NBYTES] = { let mut x = $x; - let mut bits = [0; NBYTES]; + // little endian byte array of the negabinary representation of x + let mut bytes = [0; NBYTES]; + // current index into bytes let mut i = 0; - + // current bit mask for bytes[i] let mut bit = 0x1_u8; + + // has the sign of the input been flipped? + // since we cannot multiply by -1 if the input is unsigned, we keep + // track of sign flips instead let mut flipped_sign = false; while x != 0 { + // if x is odd, output a 1 to the bit array if (x % 2) != 0 { - bits[i] |= bit; + bytes[i] |= bit; } - bit = bit.rotate_left(1); + // rotate the bit mask left, and increment i if it wraps around + bit = bit.rotate_left(1); if bit == 0x1_u8 { i += 1; } + // x.div_floor(2) without requring type annotations let floor_x_2 = if (x % 2) < 0 { (x / 2) - 1 } else { x / 2 }; + // x := (x / -2) + ((x % -2) < 1) + // where x's sign is flipped before or after if (x % 2) == 0 { x = floor_x_2; } else if flipped_sign { @@ -116,12 +138,14 @@ macro_rules! discriminant { x = floor_x_2; } + // dividing by -2 will flip the sign flipped_sign = !flipped_sign; } - bits + bytes }; + // const-construct the discriminant $crate::Discriminant { value: &NEGABINARY } }}; } From fce7a275c06029ac77b09414e1fa519c2e10bebc Mon Sep 17 00:00:00 2001 From: Juniper Tyree <50025784+juntyr@users.noreply.github.com> Date: Thu, 14 Nov 2024 13:50:46 +0000 Subject: [PATCH 5/5] Add a test for the negabinary implementation --- src/discriminant.rs | 96 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/src/discriminant.rs b/src/discriminant.rs index 1b500d4..380d7f4 100644 --- a/src/discriminant.rs +++ b/src/discriminant.rs @@ -149,3 +149,99 @@ macro_rules! discriminant { $crate::Discriminant { value: &NEGABINARY } }}; } + +#[cfg(test)] +mod tests { + #[test] + fn negabinary() { + macro_rules! check { + ($($n:expr => [$($c:literal),*]),*) => { + $( + assert_eq!( + crate::discriminant!($n), crate::Discriminant { + value: &[$($c),*], + }, "wrong negabinary for {}", $n, + ); + )* + }; + } + + // https://oeis.org/A053985 + // https://oeis.org/A053985/b053985.txt + check! { + 0 => [], + 1 => [1], + -2 => [2], + -1 => [3], + 4 => [4], + 5 => [5], + 2 => [6], + 3 => [7], + -8 => [8], + -7 => [9], + -10 => [10], + -9 => [11], + -4 => [12], + -3 => [13], + -6 => [14], + -5 => [15], + 16 => [16], + 17 => [17], + 14 => [18], + 15 => [19], + 20 => [20], + 21 => [21], + 18 => [22], + 19 => [23], + 8 => [24], + 9 => [25], + 6 => [26], + 7 => [27], + 12 => [28], + 13 => [29], + 10 => [30], + 11 => [31], + -32 => [32], + -31 => [33], + -34 => [34], + -33 => [35], + -28 => [36], + -27 => [37], + -30 => [38], + -29 => [39], + -40 => [40], + -39 => [41], + -42 => [42], + -41 => [43], + -36 => [44], + -35 => [45], + -38 => [46], + -37 => [47], + -16 => [48], + -15 => [49], + -18 => [50], + -17 => [51], + -12 => [52], + -11 => [53], + -14 => [54], + -13 => [55], + -24 => [56], + -23 => [57], + -26 => [58], + -25 => [59], + -20 => [60], + -19 => [61], + -22 => [62], + -21 => [63], + 64 => [64], + // ... + 256 => [0, 1], + 257 => [1, 1], + 255 => [3, 1], + -256 => [0, 3], + -255 => [1, 3], + -257 => [3, 3] + // ... + } + } +}