tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 583HTML 275Markdown 8TOML 41586 Sutichaykuna

Use `*const ()` to represent `CompactLength` and add implementation for 32 bit platforms (#789)

* Use *const() to represent CompactLength and add implementation for 32 bit platforms

Signed-off-by: Nico Burns <nico@nicoburns.com>

* Add strict_provenance feature

* Add strict_provenance feature to benchmarks

* Fix serde impls for CompactLength

Signed-off-by: Nico Burns <nico@nicoburns.com>

---------

Signed-off-by: Nico Burns <nico@nicoburns.com>

8d2a036c193d008e1364d4fb37ba442c852c7525 · ruwaq Nico Burns · (1 wata ñawpaq) · tayta 9da7a72b9 · kay commitpi sach'ata qhaway

Semántico sayk'uy · 96

examples/custom_tree_owned_partial.rs

examples/custom_tree_owned_unsafe.rs

examples/custom_tree_vec.rs

src/compute/grid/explicit_grid.rs

src/compute/grid/track_sizing.rs

  • silq'uyninpi t'ikrakurqan fn calc

src/compute/grid/types/grid_item.rs

src/compute/leaf.rs

src/style/compact_length.rs

src/style/dimension.rs

src/style/grid.rs

src/tree/taffy_tree.rs

src/tree/traits.rs

src/util/resolve.rs

15 qillqakuna · +327 −99

diff --git a/Cargo.toml b/Cargo.toml
index ef7b9f669..c7bba5fa5 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/benches/Cargo.toml b/benches/Cargo.toml
index d4e72bc4b..ec9a30af5 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 = []
 
diff --git a/examples/custom_tree_owned_partial.rs b/examples/custom_tree_owned_partial.rs
index a53af338e..fbf4c435b 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 c5fa77ad1..50481f207 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 5a9db60ae..20684a2e6 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<Option<f32>>,
-    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<f32>,
-                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 8bd4e7f34..0b353b19a 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<f32>,
-        resolve_calc_value: &dyn Fn(u64, f32) -> f32,
+        resolve_calc_value: &dyn Fn(*const (), f32) -> f32,
     ) -> Option<f32> {
         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<f32>,
-        resolve_calc_value: &dyn Fn(u64, f32) -> f32,
+        resolve_calc_value: &dyn Fn(*const (), f32) -> f32,
     ) -> Option<f32> {
         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<MeasureFunction>(
     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 cca0d48b6..7e6be88f8 100644
--- a/src/style/compact_length.rs
+++ b/src/style/compact_length.rs
@@ -4,43 +4,233 @@ 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.84 all the way down to 1.65 while retaining const constructors and
+/// strict pointer provenance
+mod compat {
+    #![allow(unsafe_code)]
+    #![allow(clippy::transmute_float_to_int)]
 
-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) }
+    }
+
+    /// 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")))]
+std::compile_error!("Taffy only supports targets with a pointer width of 32 or 64 bits");
+
+// /// Error serializing
+// enum SerializeErr {
+//     /// Serializing calc values is not supported
+//     CalcValue,
+// }
+
+/// CompactLengthInner implementation for 64 bit platforms
+#[cfg(target_pointer_width = "64")]
+mod inner {
+    use super::compat::{f32_from_bits, f32_to_bits, tag_ptr};
 
     /// 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 = tag_ptr(ptr, tag);
+            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 }
+        }
 
-    // Primary tags
+        /// 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)
+        }
+
+        /// Get the serialized value.
+        #[inline(always)]
+        #[cfg(feature = "serde")]
+        pub(super) fn serialized(self) -> u64 {
+            (self.tagged_ptr as usize as u64).rotate_left(32)
+        }
+
+        /// Derialized from a value.
+        #[inline(always)]
+        #[cfg(feature = "serde")]
+        pub(super) fn from_serialized(value: u64) -> Self {
+            Self { tagged_ptr: value.rotate_right(32) as usize as *const () }
+        }
+    }
+}
+
+/// 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)
+        }
+
+        /// Get the serialized value.
+        #[inline(always)]
+        #[cfg(feature = "serde")]
+        pub(super) fn serialized(self) -> u64 {
+            (self.tag as u64) << 32 | (self.ptr as u64)
+        }
+
+        /// Derialized from a value.
+        #[inline(always)]
+        #[cfg(feature = "serde")]
+        pub(super) fn from_serialized(value: u64) -> Self {
+            Self { tag: value >> 32 as usize, ptr: value & 0xFFFFFFFF as usize as *const () }
+        }
+    }
+}
+
+use inner::CompactLengthInner;
+
+/// A representation of a length as a compact 64-bit tagged pointer
+#[derive(Copy, Clone, PartialEq, Debug)]
+#[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 +238,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 +246,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 +257,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 +272,21 @@ impl CompactLength {
     /// Spec: <https://www.w3.org/TR/css3-grid-layout/#fr-unit>
     #[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 +300,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 +314,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 +406,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 +419,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 +433,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<f32> {
+    pub fn resolved_percentage_size(
+        self,
+        parent_size: f32,
+        calc_resolver: impl Fn(*const (), f32) -> f32,
+    ) -> Option<f32> {
         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,
         }
     }
@@ -291,20 +484,32 @@ 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)]
-    #![allow(clippy::transmute_float_to_int)]
-
-    /// 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) }
+#[cfg(feature = "serde")]
+impl serde::Serialize for CompactLength {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        if self.tag() == Self::CALC_TAG {
+            Err(serde::ser::Error::custom("Cannot serialize Calc value"))
+        } else {
+            serializer.serialize_u64(self.0.serialized())
+        }
     }
-    /// 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(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for CompactLength {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let bits: u64 = u64::deserialize(deserializer)?;
+        let value = Self(CompactLengthInner::from_serialized(bits));
+        if value.is_calc() {
+            Err(serde::de::Error::custom("Cannot deserialize Calc value"))
+        } else {
+            Ok(value)
+        }
     }
 }
diff --git a/src/style/dimension.rs b/src/style/dimension.rs
index 238805609..83cddd79b 100644
--- a/src/style/dimension.rs
+++ b/src/style/dimension.rs
@@ -42,7 +42,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))
     }
@@ -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<f32> {
+    pub fn resolve_to_option(self, context: f32, calc_resolver: impl Fn(*const (), f32) -> f32) -> Option<f32> {
         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<f32>, calc_resolver: impl Fn(u64, f32) -> f32) -> Option<f32> {
+    pub fn definite_value(
+        self,
+        parent_size: Option<f32>,
+        calc_resolver: impl Fn(*const (), f32) -> f32,
+    ) -> Option<f32> {
         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<f32>, calc_resolver: impl Fn(u64, f32) -> f32) -> Option<f32> {
+    pub fn definite_limit(
+        self,
+        parent_size: Option<f32>,
+        calc_resolver: impl Fn(*const (), f32) -> f32,
+    ) -> Option<f32> {
         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<f32> {
+    pub fn resolved_percentage_size(
+        self,
+        parent_size: f32,
+        calc_resolver: impl Fn(*const (), f32) -> f32,
+    ) -> Option<f32> {
         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<f32>, calc_resolver: impl Fn(u64, f32) -> f32) -> Option<f32> {
+    pub fn definite_value(
+        self,
+        parent_size: Option<f32>,
+        calc_resolver: impl Fn(*const (), f32) -> f32,
+    ) -> Option<f32> {
         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<f32> {
+    pub fn resolved_percentage_size(
+        self,
+        parent_size: f32,
+        calc_resolver: impl Fn(*const (), f32) -> f32,
+    ) -> Option<f32> {
         self.0.resolved_percentage_size(parent_size, calc_resolver)
     }
 
diff --git a/src/tree/taffy_tree.rs b/src/tree/taffy_tree.rs
index 014c49952..4c4df42e5 100644
--- a/src/tree/taffy_tree.rs
+++ b/src/tree/taffy_tree.rs
@@ -338,7 +338,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 9c291c824..191de42cb 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<In, Out> {
     /// 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<In, Out> {
 /// Will return a default value if it unable to resolve.
 pub trait ResolveOrZero<TContext, TOutput: TaffyZero> {
     /// 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<f32>, Option<f32>> for LengthPercentage {
     /// Converts the given [`LengthPercentage`] into an absolute length
     /// Can return `None`
-    fn maybe_resolve(self, context: Option<f32>, calc: impl Fn(u64, f32) -> f32) -> Option<f32> {
+    fn maybe_resolve(self, context: Option<f32>, calc: impl Fn(*const (), f32) -> f32) -> Option<f32> {
         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<f32>, Option<f32>> for LengthPercentage {
 impl MaybeResolve<Option<f32>, Option<f32>> for LengthPercentageAuto {
     /// Converts the given [`LengthPercentageAuto`] into an absolute length
     /// Can return `None`
-    fn maybe_resolve(self, context: Option<f32>, calc: impl Fn(u64, f32) -> f32) -> Option<f32> {
+    fn maybe_resolve(self, context: Option<f32>, calc: impl Fn(*const (), f32) -> f32) -> Option<f32> {
         match self.0.tag() {
             CompactLength::AUTO_TAG => None,
             CompactLength::LENGTH_TAG => Some(self.0.value()),
@@ -56,7 +56,7 @@ impl MaybeResolve<Option<f32>, Option<f32>> for Dimension {
     /// Converts the given [`Dimension`] into an absolute length
     ///
     /// Can return `None`
-    fn maybe_resolve(self, context: Option<f32>, calc: impl Fn(u64, f32) -> f32) -> Option<f32> {
+    fn maybe_resolve(self, context: Option<f32>, calc: impl Fn(*const (), f32) -> f32) -> Option<f32> {
         match self.0.tag() {
             CompactLength::AUTO_TAG => None,
             CompactLength::LENGTH_TAG => Some(self.0.value()),
@@ -72,7 +72,7 @@ impl MaybeResolve<Option<f32>, Option<f32>> for Dimension {
 impl<T: MaybeResolve<Option<f32>, Option<f32>>> MaybeResolve<f32, Option<f32>> 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<f32> {
+    fn maybe_resolve(self, context: f32, calc: impl Fn(*const (), f32) -> f32) -> Option<f32> {
         self.maybe_resolve(Some(context), calc)
     }
 }
@@ -80,7 +80,7 @@ impl<T: MaybeResolve<Option<f32>, Option<f32>>> MaybeResolve<f32, Option<f32>> f
 // Generic MaybeResolve for Size
 impl<In, Out, T: MaybeResolve<In, Out>> MaybeResolve<Size<In>, Size<Out>> for Size<T> {
     /// Converts any `parent`-relative values for size into an absolute size
-    fn maybe_resolve(self, context: Size<In>, calc: impl Fn(u64, f32) -> f32) -> Size<Out> {
+    fn maybe_resolve(self, context: Size<In>, calc: impl Fn(*const (), f32) -> f32) -> Size<Out> {
         Size {
             width: self.width.maybe_resolve(context.width, &calc),
             height: self.height.maybe_resolve(context.height, &calc),
@@ -90,21 +90,21 @@ impl<In, Out, T: MaybeResolve<In, Out>> MaybeResolve<Size<In>, Size<Out>> for Si
 
 impl ResolveOrZero<Option<f32>, f32> for LengthPercentage {
     /// Will return a default value of result is evaluated to `None`
-    fn resolve_or_zero(self, context: Option<f32>, calc: impl Fn(u64, f32) -> f32) -> f32 {
+    fn resolve_or_zero(self, context: Option<f32>, calc: impl Fn(*const (), f32) -> f32) -> f32 {
         self.maybe_resolve(context, calc).unwrap_or(0.0)
     }
 }
 
 impl ResolveOrZero<Option<f32>, f32> for LengthPercentageAuto {
     /// Will return a default value of result is evaluated to `None`
-    fn resolve_or_zero(self, context: Option<f32>, calc: impl Fn(u64, f32) -> f32) -> f32 {
+    fn resolve_or_zero(self, context: Option<f32>, calc: impl Fn(*const (), f32) -> f32) -> f32 {
         self.maybe_resolve(context, calc).unwrap_or(0.0)
     }
 }
 
 impl ResolveOrZero<Option<f32>, f32> for Dimension {
     /// Will return a default value of result is evaluated to `None`
-    fn resolve_or_zero(self, context: Option<f32>, calc: impl Fn(u64, f32) -> f32) -> f32 {
+    fn resolve_or_zero(self, context: Option<f32>, calc: impl Fn(*const (), f32) -> f32) -> f32 {
         self.maybe_resolve(context, calc).unwrap_or(0.0)
     }
 }
@@ -112,7 +112,7 @@ impl ResolveOrZero<Option<f32>, f32> for Dimension {
 // Generic ResolveOrZero for Size
 impl<In, Out: TaffyZero, T: ResolveOrZero<In, Out>> ResolveOrZero<Size<In>, Size<Out>> for Size<T> {
     /// Converts any `parent`-relative values for size into an absolute size
-    fn resolve_or_zero(self, context: Size<In>, calc: impl Fn(u64, f32) -> f32) -> Size<Out> {
+    fn resolve_or_zero(self, context: Size<In>, calc: impl Fn(*const (), f32) -> f32) -> Size<Out> {
         Size {
             width: self.width.resolve_or_zero(context.width, &calc),
             height: self.height.resolve_or_zero(context.height, &calc),
@@ -123,7 +123,7 @@ impl<In, Out: TaffyZero, T: ResolveOrZero<In, Out>> ResolveOrZero<Size<In>, Size
 // Generic ResolveOrZero for resolving Rect against Size
 impl<In: Copy, Out: TaffyZero, T: ResolveOrZero<In, Out>> ResolveOrZero<Size<In>, Rect<Out>> for Rect<T> {
     /// Converts any `parent`-relative values for Rect into an absolute Rect
-    fn resolve_or_zero(self, context: Size<In>, calc: impl Fn(u64, f32) -> f32) -> Rect<Out> {
+    fn resolve_or_zero(self, context: Size<In>, calc: impl Fn(*const (), f32) -> f32) -> Rect<Out> {
         Rect {
             left: self.left.resolve_or_zero(context.width, &calc),
             right: self.right.resolve_or_zero(context.width, &calc),
@@ -136,7 +136,7 @@ impl<In: Copy, Out: TaffyZero, T: ResolveOrZero<In, Out>> ResolveOrZero<Size<In>
 // Generic ResolveOrZero for resolving Rect against Option
 impl<Out: TaffyZero, T: ResolveOrZero<Option<f32>, Out>> ResolveOrZero<Option<f32>, Rect<Out>> for Rect<T> {
     /// Converts any `parent`-relative values for Rect into an absolute Rect
-    fn resolve_or_zero(self, context: Option<f32>, calc: impl Fn(u64, f32) -> f32) -> Rect<Out> {
+    fn resolve_or_zero(self, context: Option<f32>, calc: impl Fn(*const (), f32) -> f32) -> Rect<Out> {
         Rect {
             left: self.left.resolve_or_zero(context, &calc),
             right: self.right.resolve_or_zero(context, &calc),