From 859b3abad01ce05b4e515caff2cc68d60bfacaed Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Sat, 11 Jan 2025 18:16:14 +1300 Subject: [PATCH 1/5] Use *const() to represent CompactLength and add implementation for 32 bit platforms Signed-off-by: Nico Burns --- examples/custom_tree_owned_partial.rs | 2 +- examples/custom_tree_owned_unsafe.rs | 2 +- examples/custom_tree_vec.rs | 2 +- src/compute/grid/explicit_grid.rs | 4 +- src/compute/grid/track_sizing.rs | 2 +- src/compute/grid/types/grid_item.rs | 4 +- src/compute/leaf.rs | 2 +- src/style/compact_length.rs | 265 +++++++++++++++++++------- src/style/dimension.rs | 4 +- src/style/grid.rs | 30 ++- src/tree/taffy_tree.rs | 2 +- src/tree/traits.rs | 4 +- src/util/resolve.rs | 26 +-- 13 files changed, 248 insertions(+), 101 deletions(-) diff --git a/examples/custom_tree_owned_partial.rs b/examples/custom_tree_owned_partial.rs index ac070e257..5dd4a7a0a 100644 --- a/examples/custom_tree_owned_partial.rs +++ b/examples/custom_tree_owned_partial.rs @@ -146,7 +146,7 @@ impl taffy::LayoutPartialTree for Node { self.node_from_id_mut(node_id).layout = *layout } - fn resolve_calc_value(&self, _val: u64, _basis: f32) -> f32 { + fn resolve_calc_value(&self, _val: *const (), _basis: f32) -> f32 { 0.0 } diff --git a/examples/custom_tree_owned_unsafe.rs b/examples/custom_tree_owned_unsafe.rs index ed13d653a..bb5b9bc94 100644 --- a/examples/custom_tree_owned_unsafe.rs +++ b/examples/custom_tree_owned_unsafe.rs @@ -143,7 +143,7 @@ impl LayoutPartialTree for StatelessLayoutTree { unsafe { node_from_id_mut(node_id).unrounded_layout = *layout }; } - fn resolve_calc_value(&self, _val: u64, _basis: f32) -> f32 { + fn resolve_calc_value(&self, _val: *const (), _basis: f32) -> f32 { 0.0 } diff --git a/examples/custom_tree_vec.rs b/examples/custom_tree_vec.rs index a7125cb94..41c70f2d8 100644 --- a/examples/custom_tree_vec.rs +++ b/examples/custom_tree_vec.rs @@ -152,7 +152,7 @@ impl taffy::LayoutPartialTree for Tree { self.node_from_id_mut(node_id).unrounded_layout = *layout; } - fn resolve_calc_value(&self, _val: u64, _basis: f32) -> f32 { + fn resolve_calc_value(&self, _val: *const (), _basis: f32) -> f32 { 0.0 } diff --git a/src/compute/grid/explicit_grid.rs b/src/compute/grid/explicit_grid.rs index fbb5168bd..709c0dd36 100644 --- a/src/compute/grid/explicit_grid.rs +++ b/src/compute/grid/explicit_grid.rs @@ -14,7 +14,7 @@ pub(crate) fn compute_explicit_grid_size_in_axis( style: &impl GridContainerStyle, template: &[TrackSizingFunction], inner_container_size: Size>, - resolve_calc_value: impl Fn(u64, f32) -> f32, + resolve_calc_value: impl Fn(*const (), f32) -> f32, axis: AbsoluteAxis, ) -> u16 { // If template contains no tracks, then there are trivially zero explicit tracks @@ -106,7 +106,7 @@ pub(crate) fn compute_explicit_grid_size_in_axis( fn track_definite_value( sizing_function: &NonRepeatedTrackSizingFunction, parent_size: Option, - calc_resolver: impl Fn(u64, f32) -> f32, + calc_resolver: impl Fn(*const (), f32) -> f32, ) -> f32 { let max_size = sizing_function.max.definite_value(parent_size, &calc_resolver); let min_size = sizing_function.min.definite_value(parent_size, &calc_resolver); diff --git a/src/compute/grid/track_sizing.rs b/src/compute/grid/track_sizing.rs index 0cd971747..9d0a8952a 100644 --- a/src/compute/grid/track_sizing.rs +++ b/src/compute/grid/track_sizing.rs @@ -110,7 +110,7 @@ where /// Simple pass-through function to `LayoutPartialTreeExt::calc` #[inline(always)] - fn calc(&self, val: u64, basis: f32) -> f32 { + fn calc(&self, val: *const (), basis: f32) -> f32 { self.tree.calc(val, basis) } diff --git a/src/compute/grid/types/grid_item.rs b/src/compute/grid/types/grid_item.rs index 28f81a125..a84c77429 100644 --- a/src/compute/grid/types/grid_item.rs +++ b/src/compute/grid/types/grid_item.rs @@ -189,7 +189,7 @@ impl GridItem { axis: AbstractAxis, axis_tracks: &[GridTrack], axis_parent_size: Option, - resolve_calc_value: &dyn Fn(u64, f32) -> f32, + resolve_calc_value: &dyn Fn(*const (), f32) -> f32, ) -> Option { let spanned_tracks = &axis_tracks[self.track_range_excluding_lines(axis)]; let tracks_all_fixed = spanned_tracks.iter().all(|track| { @@ -215,7 +215,7 @@ impl GridItem { axis: AbstractAxis, axis_tracks: &[GridTrack], axis_parent_size: Option, - resolve_calc_value: &dyn Fn(u64, f32) -> f32, + resolve_calc_value: &dyn Fn(*const (), f32) -> f32, ) -> Option { let spanned_tracks = &axis_tracks[self.track_range_excluding_lines(axis)]; let tracks_all_fixed = spanned_tracks.iter().all(|track| { diff --git a/src/compute/leaf.rs b/src/compute/leaf.rs index 9adbcc9cf..be7e49b02 100644 --- a/src/compute/leaf.rs +++ b/src/compute/leaf.rs @@ -15,7 +15,7 @@ use core::unreachable; pub fn compute_leaf_layout( inputs: LayoutInput, style: &impl CoreStyle, - resolve_calc_value: impl Fn(u64, f32) -> f32, + resolve_calc_value: impl Fn(*const (), f32) -> f32, measure_function: MeasureFunction, ) -> LayoutOutput where diff --git a/src/style/compact_length.rs b/src/style/compact_length.rs index fa32308a4..0a8aa9d5a 100644 --- a/src/style/compact_length.rs +++ b/src/style/compact_length.rs @@ -4,43 +4,184 @@ use super::LengthPercentage; use crate::style_helpers::{ FromFr, FromLength, FromPercent, TaffyAuto, TaffyFitContent, TaffyMaxContent, TaffyMinContent, TaffyZero, }; -use compat::{f32_from_bits, f32_to_bits}; -/// A representation of a length as a compact 64-bit tagged pointer -#[derive(Copy, Clone, PartialEq, Debug)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct CompactLength(u64); +/// Note: these two functions are copied directly from the std (core) library. But by duplicating them +/// here we can reduce MSRV from 1.83 all the way down to 1.65 while retaining const constructors. +mod compat { + #![allow(unsafe_code)] -impl CompactLength { - // Masks + /// Raw transmutation from `f32` to `u32`. + pub const fn f32_to_bits(val: f32) -> u32 { + // SAFETY: `u32` is a plain old datatype so we can always transmute to it. + unsafe { core::mem::transmute(val) } + } + /// Raw transmutation from `u32` to `f32`. + pub const fn f32_from_bits(v: u32) -> f32 { + // SAFETY: `u32` is a plain old datatype so we can always transmute from it. + unsafe { core::mem::transmute(v) } + } +} + +#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] +std::compile_error!("Taffy only supports targets with a pointer width of 32 or 64 bits"); + +/// CompactLengthInner implementation for 64 bit platforms +#[cfg(target_pointer_width = "64")] +mod inner { + use super::compat::{f32_from_bits, f32_to_bits}; /// The low byte (8 bits) - pub const TAG_MASK: u64 = 0b11111111; + const TAG_MASK: usize = 0b11111111; /// The low 3 bits - pub const CALC_TAG_MASK: u64 = 0b111; - /// The high 63 bits - pub const CALC_PTR_MASK: u64 = u64::MAX ^ 0b111; + const CALC_TAG_MASK: usize = 0b111; + // The high 63 bits + // const CALC_PTR_MASK: usize = usize::MAX ^ 0b111; + + /// On 64 bit platforms the tag, value and pointer are packed into a single 64 bit pointer + /// + /// The tagged pointer always has a tag and may contain an f32 value or a pointer + /// (or neither) depending on the variant indicated by the tag. + #[derive(Copy, Clone, Debug, PartialEq)] + pub(super) struct CompactLengthInner { + /// The tagged pointer + tagged_ptr: *const (), + } + impl CompactLengthInner { + /// Construct a `CompactLengthInner` from a tag and pointer + #[inline(always)] + pub(super) fn from_ptr(ptr: *const (), tag: usize) -> Self { + let tagged_ptr = (ptr as usize | tag) as *const (); + Self { tagged_ptr } + } + + /// Construct a `CompactLengthInner` from a tag and numeric value + #[inline(always)] + pub(super) const fn from_val(val: f32, tag: usize) -> Self { + let tagged_ptr = (((f32_to_bits(val) as usize) << 32) | tag) as *const (); + Self { tagged_ptr } + } + + /// Construct a `CompactLengthInner` from only a tag + #[inline(always)] + pub(super) const fn from_tag(tag: usize) -> Self { + let tagged_ptr = tag as *const (); + Self { tagged_ptr } + } + + /// Get the calc tag (low 3 bits) + #[inline(always)] + pub(super) fn calc_tag(self) -> usize { + (self.tagged_ptr as usize) & CALC_TAG_MASK + } + + /// Get the general tag (low 8 bits) + #[inline(always)] + pub(super) fn tag(self) -> usize { + (self.tagged_ptr as usize) & TAG_MASK + } + + /// Get the pointer value + #[inline(always)] + pub(super) fn ptr(self) -> *const () { + self.tagged_ptr + } + + /// Get the numeric value + #[inline(always)] + pub(super) fn value(self) -> f32 { + f32_from_bits((self.tagged_ptr as usize >> 32) as u32) + } + } +} + +/// CompactLengthInner implementation for 32 bit platforms +#[cfg(target_pointer_width = "32")] +mod inner { + use super::compat::{f32_from_bits, f32_to_bits}; + + /// On 32 bit platforms the tag is stored separately. + /// Either an f32 value or a pointer (or neither) are packed into the ptr field + /// depending on the variant indicated by the tag + #[derive(Copy, Clone, Debug, PartialEq)] + pub(super) struct CompactLengthInner { + /// The tag indicating what kind of value we are storing + tag: usize, + /// The pointer of numeric value + ptr: *const (), + } + + impl CompactLengthInner { + /// Construct a `CompactLengthInner` from a tag and pointer + #[inline(always)] + pub(super) fn from_ptr(ptr: *const (), tag: usize) -> Self { + Self { ptr, tag } + } + + /// Construct a `CompactLengthInner` from a tag and numeric value + #[inline(always)] + pub(super) const fn from_val(val: f32, tag: usize) -> Self { + Self { ptr: f32_to_bits(val) as usize as *const (), tag } + } + + /// Construct a `CompactLengthInner` from only a tag + #[inline(always)] + pub(super) const fn from_tag(tag: usize) -> Self { + Self { ptr: 0 as *const (), tag } + } + + /// Get the calc tag (low 3 bits) + #[inline(always)] + pub(super) fn calc_tag(self) -> usize { + self.tag + } + + /// Get the general tag (low 8 bits) + #[inline(always)] + pub(super) fn tag(self) -> usize { + self.tag + } + + /// Get the pointer value + #[inline(always)] + pub(super) fn ptr(self) -> *const () { + self.ptr + } + + /// Get the numeric value + #[inline(always)] + pub(super) fn value(self) -> f32 { + f32_from_bits(self.ptr as u32) + } + } +} - // Primary tags +use inner::CompactLengthInner; +/// A representation of a length as a compact 64-bit tagged pointer +#[derive(Copy, Clone, PartialEq, Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(transparent)] +pub struct CompactLength(CompactLengthInner); + +impl CompactLength { /// The tag indicating a calc() value - pub const CALC_TAG: u64 = 0b000; + pub const CALC_TAG: usize = 0b000; /// The tag indicating a length value - pub const LENGTH_TAG: u64 = 0b0000_0001; + pub const LENGTH_TAG: usize = 0b0000_0001; /// The tag indicating a percentage value - pub const PERCENT_TAG: u64 = 0b0000_0010; + pub const PERCENT_TAG: usize = 0b0000_0010; /// The tag indicating an auto value - pub const AUTO_TAG: u64 = 0b0000_0011; + pub const AUTO_TAG: usize = 0b0000_0011; /// The tag indicating an fr value - pub const FR_TAG: u64 = 0b0000_0100; + pub const FR_TAG: usize = 0b0000_0100; /// The tag indicating a min-content value - pub const MIN_CONTENT_TAG: u64 = 0b00000111; + pub const MIN_CONTENT_TAG: usize = 0b00000111; /// The tag indicating a max-content value - pub const MAX_CONTENT_TAG: u64 = 0b00001111; + pub const MAX_CONTENT_TAG: usize = 0b00001111; /// The tag indicating a fit-content value with px limit - pub const FIT_CONTENT_PX_TAG: u64 = 0b00010111; + pub const FIT_CONTENT_PX_TAG: usize = 0b00010111; /// The tag indicating a fit-content value with percent limit - pub const FIT_CONTENT_PERCENT_TAG: u64 = 0b00011111; + pub const FIT_CONTENT_PERCENT_TAG: usize = 0b00011111; } impl CompactLength { @@ -48,7 +189,7 @@ impl CompactLength { /// to in their application (pixels, logical pixels, mm, etc) as they see fit. #[inline(always)] pub const fn length(val: f32) -> Self { - Self(((f32_to_bits(val) as u64) << 32) | Self::LENGTH_TAG) + Self(CompactLengthInner::from_val(val, Self::LENGTH_TAG)) } /// A percentage length relative to the size of the containing block. @@ -56,7 +197,7 @@ impl CompactLength { /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]** #[inline(always)] pub const fn percent(val: f32) -> Self { - Self(((f32_to_bits(val) as u64) << 32) | Self::PERCENT_TAG) + Self(CompactLengthInner::from_val(val, Self::PERCENT_TAG)) } /// A `calc()` value. The value passed here is treated as an opaque handle to @@ -67,14 +208,14 @@ impl CompactLength { pub fn calc(ptr: *const ()) -> Self { assert_ne!(ptr as u64, 0); assert_eq!(ptr as u64 & 0b111, 0); - Self(ptr as u64 | Self::CALC_TAG) + Self(CompactLengthInner::from_ptr(ptr, Self::CALC_TAG)) } /// The dimension should be automatically computed according to algorithm-specific rules /// regarding the default size of boxes. #[inline(always)] pub const fn auto() -> Self { - Self(Self::AUTO_TAG) + Self(CompactLengthInner::from_tag(Self::AUTO_TAG)) } /// The dimension as a fraction of the total available grid space (`fr` units in CSS) @@ -82,21 +223,21 @@ impl CompactLength { /// Spec: #[inline(always)] pub const fn fr(val: f32) -> Self { - Self(((f32_to_bits(val) as u64) << 32) | Self::FR_TAG) + Self(CompactLengthInner::from_val(val, Self::FR_TAG)) } /// The size should be the "min-content" size. /// This is the smallest size that can fit the item's contents with ALL soft line-wrapping opportunities taken #[inline(always)] pub const fn min_content() -> Self { - Self(Self::MIN_CONTENT_TAG) + Self(CompactLengthInner::from_tag(Self::MIN_CONTENT_TAG)) } /// The size should be the "max-content" size. /// This is the smallest size that can fit the item's contents with NO soft line-wrapping opportunities taken #[inline(always)] pub const fn max_content() -> Self { - Self(Self::MAX_CONTENT_TAG) + Self(CompactLengthInner::from_tag(Self::MAX_CONTENT_TAG)) } /// The size should be computed according to the "fit content" formula: @@ -110,7 +251,7 @@ impl CompactLength { /// by the min-content and max-content sizes. #[inline(always)] pub const fn fit_content_px(limit: f32) -> Self { - Self(((f32_to_bits(limit) as u64) << 32) | Self::FIT_CONTENT_PX_TAG) + Self(CompactLengthInner::from_val(limit, Self::FIT_CONTENT_PX_TAG)) } /// The size should be computed according to the "fit content" formula: @@ -124,74 +265,73 @@ impl CompactLength { /// by the min-content and max-content sizes. #[inline(always)] pub const fn fit_content_percent(limit: f32) -> Self { - Self(((f32_to_bits(limit) as u64) << 32) | Self::FIT_CONTENT_PERCENT_TAG) + Self(CompactLengthInner::from_val(limit, Self::FIT_CONTENT_PERCENT_TAG)) } /// Get the primary tag #[inline(always)] - pub const fn tag(self) -> u64 { - self.0 & Self::TAG_MASK + pub fn tag(self) -> usize { + self.0.tag() } /// Get the numeric value associated with the `CompactLength` /// (e.g. the pixel value for a LENGTH variant) #[inline(always)] - pub const fn value(self) -> f32 { - f32_from_bits((self.0 >> 32) as u32) + pub fn value(self) -> f32 { + self.0.value() } - /// Get the numeric value associated with the `CompactLength` - /// (e.g. the pixel value for a LENGTH variant) + /// Get the calc pointer of the `CompactLength` #[inline(always)] - pub const fn calc_value(self) -> u64 { - self.0 + pub fn calc_value(self) -> *const () { + self.0.ptr() } /// Returns true if the value is 0 px #[inline(always)] - pub const fn is_calc(self) -> bool { - self.0 & Self::CALC_TAG_MASK == 0 + pub fn is_calc(self) -> bool { + self.0.calc_tag() == 0 } /// Returns true if the value is 0 px #[inline(always)] - pub const fn is_zero(self) -> bool { + pub fn is_zero(self) -> bool { self.0 == Self::ZERO.0 } /// Returns true if the value is a length or percentage value #[inline(always)] - pub const fn is_length_or_percentage(self) -> bool { + pub fn is_length_or_percentage(self) -> bool { matches!(self.tag(), Self::LENGTH_TAG | Self::PERCENT_TAG) } /// Returns true if the value is auto #[inline(always)] - pub const fn is_auto(self) -> bool { + pub fn is_auto(self) -> bool { self.tag() == Self::AUTO_TAG } /// Returns true if the value is min-content #[inline(always)] - pub const fn is_min_content(self) -> bool { + pub fn is_min_content(self) -> bool { matches!(self.tag(), Self::MIN_CONTENT_TAG) } /// Returns true if the value is max-content #[inline(always)] - pub const fn is_max_content(self) -> bool { + pub fn is_max_content(self) -> bool { matches!(self.tag(), Self::MAX_CONTENT_TAG) } /// Returns true if the value is a fit-content(...) value #[inline(always)] - pub const fn is_fit_content(self) -> bool { + pub fn is_fit_content(self) -> bool { matches!(self.tag(), Self::FIT_CONTENT_PX_TAG | Self::FIT_CONTENT_PERCENT_TAG) } /// Returns true if the value is max-content or a fit-content(...) value #[inline(always)] - pub const fn is_max_or_fit_content(self) -> bool { + pub fn is_max_or_fit_content(self) -> bool { matches!(self.tag(), Self::MAX_CONTENT_TAG | Self::FIT_CONTENT_PX_TAG | Self::FIT_CONTENT_PERCENT_TAG) } @@ -217,7 +357,7 @@ impl CompactLength { /// Returns true if the value is auto, min-content, max-content, or fit-content(...) #[inline(always)] - pub const fn is_intrinsic(self) -> bool { + pub fn is_intrinsic(self) -> bool { matches!( self.tag(), Self::AUTO_TAG @@ -230,13 +370,13 @@ impl CompactLength { /// Returns true if the value is and fr value #[inline(always)] - pub const fn is_fr(self) -> bool { + pub fn is_fr(self) -> bool { self.tag() == Self::FR_TAG } /// Whether the track sizing functions depends on the size of the parent node #[inline(always)] - pub const fn uses_percentage(self) -> bool { + pub fn uses_percentage(self) -> bool { // TODO: handle calc() values matches!(self.tag(), CompactLength::PERCENT_TAG | CompactLength::FIT_CONTENT_PERCENT_TAG) || self.is_calc() } @@ -244,10 +384,14 @@ impl CompactLength { /// Resolve percentage values against the passed parent_size, returning Some(value) /// Non-percentage values always return None. #[inline(always)] - pub fn resolved_percentage_size(self, parent_size: f32, calc_resolver: impl Fn(u64, f32) -> f32) -> Option { + pub fn resolved_percentage_size( + self, + parent_size: f32, + calc_resolver: impl Fn(*const (), f32) -> f32, + ) -> Option { match self.tag() { CompactLength::PERCENT_TAG => Some(self.value() * parent_size), - _ if self.is_calc() => Some(calc_resolver(self.0, parent_size)), + _ if self.is_calc() => Some(calc_resolver(self.0.ptr(), parent_size)), _ => None, } } @@ -290,20 +434,3 @@ impl TaffyFitContent for CompactLength { } } } - -/// Note: these two functions are copied directly from the std (core) library. But by duplicating them -/// here we can reduce MSRV from 1.83 all the way down to 1.65 while retaining const constructors. -mod compat { - #![allow(unsafe_code)] - - /// Raw transmutation from `f32` to `u32`. - pub const fn f32_to_bits(val: f32) -> u32 { - // SAFETY: `u32` is a plain old datatype so we can always transmute to it. - unsafe { core::mem::transmute(val) } - } - /// Raw transmutation from `u32` to `f32`. - pub const fn f32_from_bits(v: u32) -> f32 { - // SAFETY: `u32` is a plain old datatype so we can always transmute from it. - unsafe { core::mem::transmute(v) } - } -} diff --git a/src/style/dimension.rs b/src/style/dimension.rs index 238805609..343796f8c 100644 --- a/src/style/dimension.rs +++ b/src/style/dimension.rs @@ -139,7 +139,7 @@ impl LengthPercentageAuto { /// - Some(resolved) using the provided context for Percent variants /// - None for Auto variants #[inline(always)] - pub fn resolve_to_option(self, context: f32, calc_resolver: impl Fn(u64, f32) -> f32) -> Option { + pub fn resolve_to_option(self, context: f32, calc_resolver: impl Fn(*const (), f32) -> f32) -> Option { match self.0.tag() { CompactLength::LENGTH_TAG => Some(self.0.value()), CompactLength::PERCENT_TAG => Some(context * self.0.value()), @@ -249,7 +249,7 @@ impl Dimension { } /// Get the raw `CompactLength` tag - pub fn tag(self) -> u64 { + pub fn tag(self) -> usize { self.0.tag() } diff --git a/src/style/grid.rs b/src/style/grid.rs index e32e38f53..28c71dd6c 100644 --- a/src/style/grid.rs +++ b/src/style/grid.rs @@ -581,7 +581,11 @@ impl MaxTrackSizingFunction { /// the passed available_space and returns if this results in a concrete value (which it /// will if the available_space is `Some`). Otherwise returns None. #[inline(always)] - pub fn definite_value(self, parent_size: Option, calc_resolver: impl Fn(u64, f32) -> f32) -> Option { + pub fn definite_value( + self, + parent_size: Option, + calc_resolver: impl Fn(*const (), f32) -> f32, + ) -> Option { match self.0.tag() { CompactLength::LENGTH_TAG => Some(self.0.value()), CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size), @@ -597,7 +601,11 @@ impl MaxTrackSizingFunction { /// - A fit-content sizing function with percentage argument (with definite available space) /// All other kinds of track sizing function return None. #[inline(always)] - pub fn definite_limit(self, parent_size: Option, calc_resolver: impl Fn(u64, f32) -> f32) -> Option { + pub fn definite_limit( + self, + parent_size: Option, + calc_resolver: impl Fn(*const (), f32) -> f32, + ) -> Option { match self.0.tag() { CompactLength::FIT_CONTENT_PX_TAG => Some(self.0.value()), CompactLength::FIT_CONTENT_PERCENT_TAG => parent_size.map(|size| self.0.value() * size), @@ -608,7 +616,11 @@ impl MaxTrackSizingFunction { /// Resolve percentage values against the passed parent_size, returning Some(value) /// Non-percentage values always return None. #[inline(always)] - pub fn resolved_percentage_size(self, parent_size: f32, calc_resolver: impl Fn(u64, f32) -> f32) -> Option { + pub fn resolved_percentage_size( + self, + parent_size: f32, + calc_resolver: impl Fn(*const (), f32) -> f32, + ) -> Option { self.0.resolved_percentage_size(parent_size, calc_resolver) } @@ -764,7 +776,11 @@ impl MinTrackSizingFunction { /// the passed available_space and returns if this results in a concrete value (which it /// will if the available_space is `Some`). Otherwise returns `None`. #[inline(always)] - pub fn definite_value(self, parent_size: Option, calc_resolver: impl Fn(u64, f32) -> f32) -> Option { + pub fn definite_value( + self, + parent_size: Option, + calc_resolver: impl Fn(*const (), f32) -> f32, + ) -> Option { match self.0.tag() { CompactLength::LENGTH_TAG => Some(self.0.value()), CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size), @@ -776,7 +792,11 @@ impl MinTrackSizingFunction { /// Resolve percentage values against the passed parent_size, returning Some(value) /// Non-percentage values always return None. #[inline(always)] - pub fn resolved_percentage_size(self, parent_size: f32, calc_resolver: impl Fn(u64, f32) -> f32) -> Option { + pub fn resolved_percentage_size( + self, + parent_size: f32, + calc_resolver: impl Fn(*const (), f32) -> f32, + ) -> Option { self.0.resolved_percentage_size(parent_size, calc_resolver) } diff --git a/src/tree/taffy_tree.rs b/src/tree/taffy_tree.rs index 67666e691..b607122c6 100644 --- a/src/tree/taffy_tree.rs +++ b/src/tree/taffy_tree.rs @@ -336,7 +336,7 @@ where } #[inline(always)] - fn resolve_calc_value(&self, _val: u64, _basis: f32) -> f32 { + fn resolve_calc_value(&self, _val: *const (), _basis: f32) -> f32 { 0.0 } diff --git a/src/tree/traits.rs b/src/tree/traits.rs index 8837d3f18..095d01137 100644 --- a/src/tree/traits.rs +++ b/src/tree/traits.rs @@ -181,7 +181,7 @@ pub trait LayoutPartialTree: TraversePartialTree { fn get_core_container_style(&self, node_id: NodeId) -> Self::CoreContainerStyle<'_>; /// Resolve calc value - fn resolve_calc_value(&self, val: u64, basis: f32) -> f32; + fn resolve_calc_value(&self, val: *const (), basis: f32) -> f32; /// Set the node's unrounded layout fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout); @@ -366,7 +366,7 @@ pub(crate) trait LayoutPartialTreeExt: LayoutPartialTree { /// Alias to `resolve_calc_value` with a shorter function name #[inline(always)] - fn calc(&self, val: u64, basis: f32) -> f32 { + fn calc(&self, val: *const (), basis: f32) -> f32 { self.resolve_calc_value(val, basis) } } diff --git a/src/util/resolve.rs b/src/util/resolve.rs index e52861cec..a0e47b0ee 100644 --- a/src/util/resolve.rs +++ b/src/util/resolve.rs @@ -12,7 +12,7 @@ use crate::CompactLength; /// Will return a `None` if it unable to resolve. pub trait MaybeResolve { /// Resolve a dimension that might be dependent on a context, with `None` as fallback value - fn maybe_resolve(self, context: In, calc: impl Fn(u64, f32) -> f32) -> Out; + fn maybe_resolve(self, context: In, calc: impl Fn(*const (), f32) -> f32) -> Out; } /// Trait to encapsulate behaviour where we need to resolve from a @@ -22,13 +22,13 @@ pub trait MaybeResolve { /// Will return a default value if it unable to resolve. pub trait ResolveOrZero { /// Resolve a dimension that might be dependent on a context, with a default fallback value - fn resolve_or_zero(self, context: TContext, calc: impl Fn(u64, f32) -> f32) -> TOutput; + fn resolve_or_zero(self, context: TContext, calc: impl Fn(*const (), f32) -> f32) -> TOutput; } impl MaybeResolve, Option> for LengthPercentage { /// Converts the given [`LengthPercentage`] into an absolute length /// Can return `None` - fn maybe_resolve(self, context: Option, calc: impl Fn(u64, f32) -> f32) -> Option { + fn maybe_resolve(self, context: Option, calc: impl Fn(*const (), f32) -> f32) -> Option { match self.0.tag() { CompactLength::LENGTH_TAG => Some(self.0.value()), CompactLength::PERCENT_TAG => context.map(|dim| dim * self.0.value()), @@ -41,7 +41,7 @@ impl MaybeResolve, Option> for LengthPercentage { impl MaybeResolve, Option> for LengthPercentageAuto { /// Converts the given [`LengthPercentageAuto`] into an absolute length /// Can return `None` - fn maybe_resolve(self, context: Option, calc: impl Fn(u64, f32) -> f32) -> Option { + fn maybe_resolve(self, context: Option, calc: impl Fn(*const (), f32) -> f32) -> Option { match self.0.tag() { CompactLength::AUTO_TAG => None, CompactLength::LENGTH_TAG => Some(self.0.value()), @@ -56,7 +56,7 @@ impl MaybeResolve, Option> for Dimension { /// Converts the given [`Dimension`] into an absolute length /// /// Can return `None` - fn maybe_resolve(self, context: Option, calc: impl Fn(u64, f32) -> f32) -> Option { + fn maybe_resolve(self, context: Option, calc: impl Fn(*const (), f32) -> f32) -> Option { match self.0.tag() { CompactLength::AUTO_TAG => None, CompactLength::LENGTH_TAG => Some(self.0.value()), @@ -72,7 +72,7 @@ impl MaybeResolve, Option> for Dimension { impl, Option>> MaybeResolve> for T { /// Converts the given MaybeResolve value into an absolute length /// Can return `None` - fn maybe_resolve(self, context: f32, calc: impl Fn(u64, f32) -> f32) -> Option { + fn maybe_resolve(self, context: f32, calc: impl Fn(*const (), f32) -> f32) -> Option { self.maybe_resolve(Some(context), calc) } } @@ -80,7 +80,7 @@ impl, Option>> MaybeResolve> f // Generic MaybeResolve for Size impl> MaybeResolve, Size> for Size { /// Converts any `parent`-relative values for size into an absolute size - fn maybe_resolve(self, context: Size, calc: impl Fn(u64, f32) -> f32) -> Size { + fn maybe_resolve(self, context: Size, calc: impl Fn(*const (), f32) -> f32) -> Size { Size { width: self.width.maybe_resolve(context.width, &calc), height: self.height.maybe_resolve(context.height, &calc), @@ -90,21 +90,21 @@ impl> MaybeResolve, Size> for Si impl ResolveOrZero, f32> for LengthPercentage { /// Will return a default value of result is evaluated to `None` - fn resolve_or_zero(self, context: Option, calc: impl Fn(u64, f32) -> f32) -> f32 { + fn resolve_or_zero(self, context: Option, calc: impl Fn(*const (), f32) -> f32) -> f32 { self.maybe_resolve(context, calc).unwrap_or(0.0) } } impl ResolveOrZero, f32> for LengthPercentageAuto { /// Will return a default value of result is evaluated to `None` - fn resolve_or_zero(self, context: Option, calc: impl Fn(u64, f32) -> f32) -> f32 { + fn resolve_or_zero(self, context: Option, calc: impl Fn(*const (), f32) -> f32) -> f32 { self.maybe_resolve(context, calc).unwrap_or(0.0) } } impl ResolveOrZero, f32> for Dimension { /// Will return a default value of result is evaluated to `None` - fn resolve_or_zero(self, context: Option, calc: impl Fn(u64, f32) -> f32) -> f32 { + fn resolve_or_zero(self, context: Option, calc: impl Fn(*const (), f32) -> f32) -> f32 { self.maybe_resolve(context, calc).unwrap_or(0.0) } } @@ -112,7 +112,7 @@ impl ResolveOrZero, f32> for Dimension { // Generic ResolveOrZero for Size impl> ResolveOrZero, Size> for Size { /// Converts any `parent`-relative values for size into an absolute size - fn resolve_or_zero(self, context: Size, calc: impl Fn(u64, f32) -> f32) -> Size { + fn resolve_or_zero(self, context: Size, calc: impl Fn(*const (), f32) -> f32) -> Size { Size { width: self.width.resolve_or_zero(context.width, &calc), height: self.height.resolve_or_zero(context.height, &calc), @@ -123,7 +123,7 @@ impl> ResolveOrZero, Size // Generic ResolveOrZero for resolving Rect against Size impl> ResolveOrZero, Rect> for Rect { /// Converts any `parent`-relative values for Rect into an absolute Rect - fn resolve_or_zero(self, context: Size, calc: impl Fn(u64, f32) -> f32) -> Rect { + fn resolve_or_zero(self, context: Size, calc: impl Fn(*const (), f32) -> f32) -> Rect { Rect { left: self.left.resolve_or_zero(context.width, &calc), right: self.right.resolve_or_zero(context.width, &calc), @@ -136,7 +136,7 @@ impl> ResolveOrZero // Generic ResolveOrZero for resolving Rect against Option impl, Out>> ResolveOrZero, Rect> for Rect { /// Converts any `parent`-relative values for Rect into an absolute Rect - fn resolve_or_zero(self, context: Option, calc: impl Fn(u64, f32) -> f32) -> Rect { + fn resolve_or_zero(self, context: Option, calc: impl Fn(*const (), f32) -> f32) -> Rect { Rect { left: self.left.resolve_or_zero(context, &calc), right: self.right.resolve_or_zero(context, &calc), From 49434c88da347022faf0042ea6486b0ba86d33a4 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Sat, 11 Jan 2025 18:38:21 +1300 Subject: [PATCH 2/5] Add strict_provenance feature --- Cargo.toml | 2 ++ src/style/compact_length.rs | 21 ++++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0e86eedb9..ccf201ff2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,8 @@ grid = ["alloc", "dep:grid"] content_size = [] ## Causes algorithms to stores detailed information of the nodes in TaffyTree, with only CSS Grid supporting this. detailed_layout_info = [] +## Use strict provenance APIs for pointer manipulation. Using this feature requires Rust 1.84 or higher. +strict_provenance = [] #! ### Taffy Tree diff --git a/src/style/compact_length.rs b/src/style/compact_length.rs index 0a8aa9d5a..2aacb074d 100644 --- a/src/style/compact_length.rs +++ b/src/style/compact_length.rs @@ -6,7 +6,8 @@ use crate::style_helpers::{ }; /// Note: these two functions are copied directly from the std (core) library. But by duplicating them -/// here we can reduce MSRV from 1.83 all the way down to 1.65 while retaining const constructors. +/// here we can reduce MSRV from 1.84 all the way down to 1.65 while retaining const constructors and +/// strict pointer provenance mod compat { #![allow(unsafe_code)] @@ -20,6 +21,20 @@ mod compat { // SAFETY: `u32` is a plain old datatype so we can always transmute from it. unsafe { core::mem::transmute(v) } } + + /// Tag a pointer preserving provenance (requires Rust 1.84) + #[inline(always)] + #[cfg(all(target_pointer_width = "64", feature = "strict_provenance"))] + pub fn tag_ptr(ptr: *const (), tag: usize) -> *const () { + ptr.map_addr(|a| a | tag) + } + + /// Tag a pointer exposing provenance (works back to Rust 1.0) + #[inline(always)] + #[cfg(all(target_pointer_width = "64", not(feature = "strict_provenance")))] + pub fn tag_ptr(ptr: *const (), tag: usize) -> *const () { + (ptr as usize | tag) as *const () + } } #[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] @@ -28,7 +43,7 @@ std::compile_error!("Taffy only supports targets with a pointer width of 32 or 6 /// CompactLengthInner implementation for 64 bit platforms #[cfg(target_pointer_width = "64")] mod inner { - use super::compat::{f32_from_bits, f32_to_bits}; + use super::compat::{f32_from_bits, f32_to_bits, tag_ptr}; /// The low byte (8 bits) const TAG_MASK: usize = 0b11111111; @@ -50,7 +65,7 @@ mod inner { /// Construct a `CompactLengthInner` from a tag and pointer #[inline(always)] pub(super) fn from_ptr(ptr: *const (), tag: usize) -> Self { - let tagged_ptr = (ptr as usize | tag) as *const (); + let tagged_ptr = tag_ptr(ptr, tag); Self { tagged_ptr } } From abb346acea554ffa09858804c9ef0abd8dcff991 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Sat, 11 Jan 2025 18:49:44 +1300 Subject: [PATCH 3/5] Add serde impls for CompactLengthInner Signed-off-by: Nico Burns --- src/style/compact_length.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/style/compact_length.rs b/src/style/compact_length.rs index 2aacb074d..c6c864d25 100644 --- a/src/style/compact_length.rs +++ b/src/style/compact_length.rs @@ -57,6 +57,7 @@ mod inner { /// The tagged pointer always has a tag and may contain an f32 value or a pointer /// (or neither) depending on the variant indicated by the tag. #[derive(Copy, Clone, Debug, PartialEq)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub(super) struct CompactLengthInner { /// The tagged pointer tagged_ptr: *const (), @@ -118,6 +119,7 @@ mod inner { /// Either an f32 value or a pointer (or neither) are packed into the ptr field /// depending on the variant indicated by the tag #[derive(Copy, Clone, Debug, PartialEq)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub(super) struct CompactLengthInner { /// The tag indicating what kind of value we are storing tag: usize, From d8b5fab1e0c61fcd06471f675fee1557a9bbff6a Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Sat, 11 Jan 2025 22:50:07 +1300 Subject: [PATCH 4/5] Add strict_provenance feature to benchmarks --- benches/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/benches/Cargo.toml b/benches/Cargo.toml index c7a0cd2a4..40f732f09 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -27,6 +27,7 @@ yoga = ["dep:yoga", "dep:slotmap", "dep:ordered-float"] yoga-super-deep = ["yoga"] taffy03 = ["dep:taffy_03"] content_size = ["taffy/content_size"] +strict_provenance = ["taffy/strict_provenance"] small = [] large = [] From ff0f2c5727167f054f37835113b26f54ad50a457 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Mon, 13 Jan 2025 23:56:19 +1300 Subject: [PATCH 5/5] serde WIP Signed-off-by: Nico Burns --- src/style/compact_length.rs | 18 ++++++++++++- src/style/dimension.rs | 51 +++++++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/style/compact_length.rs b/src/style/compact_length.rs index c6c864d25..35343dc0d 100644 --- a/src/style/compact_length.rs +++ b/src/style/compact_length.rs @@ -119,7 +119,6 @@ mod inner { /// Either an f32 value or a pointer (or neither) are packed into the ptr field /// depending on the variant indicated by the tag #[derive(Copy, Clone, Debug, PartialEq)] - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub(super) struct CompactLengthInner { /// The tag indicating what kind of value we are storing tag: usize, @@ -170,6 +169,23 @@ mod inner { f32_from_bits(self.ptr as u32) } } + + #[cfg(feature = "serde")] + impl serde::Serialize for LengthPercentage { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer { + serializer.serialize_u64(self.ptr as u64 << 32 | self.tag as u64) + } + } + #[cfg(feature = "serde")] + impl<'de, D: serde::Deserializer<'de>> serde::Deserialize for LengthPercentage { + fn deserialize(deserializer: D) -> Result + where + { + ExpandedLengthPercentage::deserialize(deserializer) + } + } } use inner::CompactLengthInner; diff --git a/src/style/dimension.rs b/src/style/dimension.rs index 343796f8c..b17d671f5 100644 --- a/src/style/dimension.rs +++ b/src/style/dimension.rs @@ -3,11 +3,40 @@ use super::CompactLength; use crate::geometry::Rect; use crate::style_helpers::{FromLength, FromPercent, TaffyAuto, TaffyZero}; +/// A enum representing the tag of `LengthPercentage` +#[derive(Copy, Clone, PartialEq, Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum LengthPercentageTag { + /// An absolute length in some abstract units. + Length, + /// A percentage length relative to the size of the containing block. + Percentage, + /// A `calc()` value. + Calc +} + +/// A enum representing the tag of a `LengthPercentage` +#[derive(Copy, Clone, PartialEq, Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum ExpandedLengthPercentage { + /// An absolute length in some abstract units. Users of Taffy may define what they correspond + /// to in their application (pixels, logical pixels, mm, etc) as they see fit. + Length(f32), + /// A percentage length relative to the size of the containing block. + /// + /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]** + Percentage(f32), + /// A `calc()` value. The value passed here is treated as an opaque handle to + /// the actual calc representation and may be a pointer, index, etc. + /// + /// The low 3 bits are used as a tag value and will be returned as 0. + Calc(*const ()) +} + /// A unit of linear measurement /// /// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size`](crate::geometry::Size). #[derive(Copy, Clone, PartialEq, Debug)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct LengthPercentage(pub(crate) CompactLength); impl TaffyZero for LengthPercentage { const ZERO: Self = Self(CompactLength::ZERO); @@ -42,7 +71,7 @@ impl LengthPercentage { /// the actual calc representation and may be a pointer, index, etc. /// /// The low 3 bits are used as a tag value and will be returned as 0. - #[inline] + #[inline(always)] pub fn calc(ptr: *const ()) -> Self { Self(CompactLength::calc(ptr)) } @@ -59,6 +88,24 @@ impl LengthPercentage { pub fn into_raw(self) -> CompactLength { self.0 } + + /// Get the tag of the `LengthPercentage` + pub fn tag(self) -> LengthPercentageTag { + match self.0.tag() { + CompactLength::LENGTH_TAG => LengthPercentageTag::Length, + CompactLength::PERCENT_TAG => LengthPercentageTag::Percentage, + _ => LengthPercentageTag::Calc + } + } + + /// Expand the compact `LengthPercentage` into a regular enum + pub fn expanded(self) -> ExpandedLengthPercentage { + match self.0.tag() { + CompactLength::LENGTH_TAG => ExpandedLengthPercentage::Length(self.0.value()), + CompactLength::PERCENT_TAG => ExpandedLengthPercentage::Percentage(self.0.value()), + _ => ExpandedLengthPercentage::Calc(self.0.calc_value()), + } + } } /// A unit of linear measurement