@@ -14,7 +14,6 @@ use vortex_error::VortexExpect;
1414use vortex_error:: VortexResult ;
1515use vortex_error:: vortex_ensure;
1616use vortex_error:: vortex_ensure_eq;
17- use vortex_error:: vortex_err;
1817use vortex_error:: vortex_panic;
1918use vortex_session:: VortexSession ;
2019
@@ -65,22 +64,108 @@ impl ValidityChild<Patched> for Patched {
6564
6665#[ derive( Clone , prost:: Message ) ]
6766pub struct PatchedMetadata {
68- /// An offset into the first chunk's patches that should be considered in-view.
67+ /// A bitfield packed into a single u64 containing all the metadata needed to decode a
68+ /// serialized `PatchedArray`.
6969 ///
70- /// This may become nonzero after slicing.
71- #[ prost( uint32, tag = "1" ) ]
72- pub ( crate ) offset : u32 ,
70+ /// See [`PatchedMetadataFields`].
71+ #[ prost( uint64, tag = "1" ) ]
72+ pub ( crate ) packed : u64 ,
73+ }
74+
75+ /// A bitfield implemented on top of a `u64` containing the necessary metadata for reading a
76+ /// serialized `PatchedArray`.
77+ ///
78+ /// The bit fields are in the following order:
79+ ///
80+ /// * `offset`: 10 bits (always < 1024). An offset into the first chunk's patches that should be
81+ /// considered in-view.
82+ /// * `n_lanes_exp`: 3 bits. The binary exponent of `n_lanes`, which must be a power of two.
83+ /// A stored value of 0b000 represents n_lanes=1, and 0b111 represents n_lanes=128.
84+ /// * `n_patches`: 23 bits. The number of total patches, and the length of the indices and values
85+ /// child arrays.
86+ ///
87+ /// The remaining bits 36..64 are reserved for future use.
88+ pub ( crate ) struct PatchedMetadataFields ( u64 ) ;
89+
90+ impl std:: fmt:: Debug for PatchedMetadataFields {
91+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
92+ f. debug_struct ( "PatchedMetadataFields" )
93+ . field ( "offset" , & self . offset ( ) )
94+ . field ( "n_lanes" , & self . n_lanes ( ) )
95+ . field ( "n_patches" , & self . n_patches ( ) )
96+ . finish ( )
97+ }
98+ }
7399
74- /// Number of patches. This is the length of the `indices` and `values` children.
75- #[ prost( uint32, tag = "2" ) ]
76- pub ( crate ) n_patches : u32 ,
100+ impl PatchedMetadataFields {
101+ const OFFSET_BITS : u32 = 10 ;
102+ const N_LANES_EXP_BITS : u32 = 3 ;
103+ const N_PATCHES_BITS : u32 = 23 ;
77104
78- /// Number of lanes the patches get spread over.
105+ const OFFSET_MASK : u64 = ( 1 << Self :: OFFSET_BITS ) - 1 ;
106+ const N_LANES_EXP_MASK : u64 = ( 1 << Self :: N_LANES_EXP_BITS ) - 1 ;
107+ const N_PATCHES_MASK : u64 = ( 1 << Self :: N_PATCHES_BITS ) - 1 ;
108+
109+ const OFFSET_SHIFT : u32 = 0 ;
110+ const N_LANES_EXP_SHIFT : u32 = Self :: OFFSET_BITS ;
111+ const N_PATCHES_SHIFT : u32 = Self :: OFFSET_BITS + Self :: N_LANES_EXP_BITS ;
112+
113+ /// Create a new `PatchedMetadataFields` from the component values.
79114 ///
80- /// By default, this is either 16 or 32 depending on the width of the type, but may change
81- /// in the future, so we save it on write.
82- #[ prost( uint32, tag = "3" ) ]
83- pub ( crate ) n_lanes : u32 ,
115+ /// # Errors
116+ ///
117+ /// Returns an error if any value exceeds its bit width:
118+ /// - `offset` must be < 1024 (10 bits)
119+ /// - `n_lanes` must be a power of two between 1 and 128 inclusive
120+ /// - `n_patches` must be < 8388608 (23 bits)
121+ pub fn new ( offset : usize , n_lanes : usize , n_patches : usize ) -> VortexResult < Self > {
122+ vortex_ensure ! (
123+ offset < ( 1 << Self :: OFFSET_BITS ) ,
124+ "offset must be < 1024, got {offset}"
125+ ) ;
126+ vortex_ensure ! (
127+ n_lanes. is_power_of_two( ) && n_lanes <= 128 ,
128+ "n_lanes must be a power of two between 1 and 128, got {n_lanes}"
129+ ) ;
130+ vortex_ensure ! (
131+ n_patches < ( 1 << Self :: N_PATCHES_BITS ) ,
132+ "n_patches must be < 8388608, got {n_patches}"
133+ ) ;
134+
135+ let n_lanes_exp = n_lanes. trailing_zeros ( ) as u64 ;
136+
137+ let flags = ( offset as u64 )
138+ | ( n_lanes_exp << Self :: N_LANES_EXP_SHIFT )
139+ | ( ( n_patches as u64 ) << Self :: N_PATCHES_SHIFT ) ;
140+ Ok ( Self ( flags) )
141+ }
142+
143+ /// Extract the offset field (bits 0..10).
144+ pub fn offset ( & self ) -> usize {
145+ ( ( self . 0 >> Self :: OFFSET_SHIFT ) & Self :: OFFSET_MASK ) as usize
146+ }
147+
148+ /// Extract the n_lanes field (bits 10..13), converted from the stored exponent.
149+ pub fn n_lanes ( & self ) -> usize {
150+ let exp = ( self . 0 >> Self :: N_LANES_EXP_SHIFT ) & Self :: N_LANES_EXP_MASK ;
151+ 1 << exp
152+ }
153+
154+ /// Extract the n_patches field (bits 13..36).
155+ pub fn n_patches ( & self ) -> usize {
156+ ( ( self . 0 >> Self :: N_PATCHES_SHIFT ) & Self :: N_PATCHES_MASK ) as usize
157+ }
158+
159+ /// Return the underlying u64 representation.
160+ pub fn into_inner ( self ) -> u64 {
161+ self . 0
162+ }
163+ }
164+
165+ impl From < u64 > for PatchedMetadataFields {
166+ fn from ( value : u64 ) -> Self {
167+ Self ( value)
168+ }
84169}
85170
86171impl VTable for Patched {
@@ -166,24 +251,10 @@ impl VTable for Patched {
166251 }
167252
168253 fn metadata ( array : & Self :: Array ) -> VortexResult < Self :: Metadata > {
169- let n_patches: u32 =
170- array. indices . len ( ) . try_into ( ) . map_err ( |_| {
171- vortex_err ! ( "Cannot serialize Patched array with > u32::MAX patches" )
172- } ) ?;
173-
174- #[ expect(
175- clippy:: cast_possible_truncation,
176- reason = "array offset always < 1024"
177- ) ]
178- let offset = array. offset as u32 ;
179-
180- #[ expect( clippy:: cast_possible_truncation, reason = "n_lanes is always <= 64" ) ]
181- let n_lanes = array. n_lanes as u32 ;
254+ let fields = PatchedMetadataFields :: new ( array. offset , array. n_lanes , array. indices . len ( ) ) ?;
182255
183256 Ok ( ProstMetadata ( PatchedMetadata {
184- offset,
185- n_patches,
186- n_lanes,
257+ packed : fields. into_inner ( ) ,
187258 } ) )
188259 }
189260
@@ -263,17 +334,19 @@ impl VTable for Patched {
263334 _buffers : & [ BufferHandle ] ,
264335 children : & dyn ArrayChildren ,
265336 ) -> VortexResult < PatchedArray > {
266- let offset = metadata. offset as usize ;
337+ let fields = PatchedMetadataFields :: from ( metadata. packed ) ;
338+ let offset = fields. offset ( ) ;
339+ let n_lanes = fields. n_lanes ( ) ;
340+ let n_patches = fields. n_patches ( ) ;
267341
268342 // n_chunks should correspond to the chunk in the `inner`.
269343 // After slicing when offset > 0, there may be additional chunks.
270344 let n_chunks = ( len + offset) . div_ceil ( 1024 ) ;
271- let n_lanes = metadata. n_lanes as usize ;
272345
273346 let inner = children. get ( 0 , dtype, len) ?;
274347 let lane_offsets = children. get ( 1 , PType :: U32 . into ( ) , n_chunks * n_lanes + 1 ) ?;
275- let indices = children. get ( 2 , PType :: U16 . into ( ) , metadata . n_patches as usize ) ?;
276- let values = children. get ( 3 , dtype, metadata . n_patches as usize ) ?;
348+ let indices = children. get ( 2 , PType :: U16 . into ( ) , n_patches) ?;
349+ let values = children. get ( 3 , dtype, n_patches) ?;
277350
278351 Ok ( PatchedArray {
279352 inner,
@@ -744,4 +817,123 @@ mod tests {
744817
745818 Ok ( ( ) )
746819 }
820+
821+ mod metadata_fields_tests {
822+ use vortex_error:: VortexResult ;
823+
824+ use super :: super :: PatchedMetadataFields ;
825+
826+ #[ test]
827+ fn test_roundtrip_min_values ( ) -> VortexResult < ( ) > {
828+ let fields = PatchedMetadataFields :: new ( 0 , 1 , 0 ) ?;
829+ assert_eq ! ( fields. offset( ) , 0 ) ;
830+ assert_eq ! ( fields. n_lanes( ) , 1 ) ;
831+ assert_eq ! ( fields. n_patches( ) , 0 ) ;
832+ assert_eq ! ( fields. into_inner( ) , 0 ) ;
833+ Ok ( ( ) )
834+ }
835+
836+ #[ test]
837+ fn test_roundtrip_typical_values ( ) -> VortexResult < ( ) > {
838+ let fields = PatchedMetadataFields :: new ( 512 , 16 , 1000 ) ?;
839+ assert_eq ! ( fields. offset( ) , 512 ) ;
840+ assert_eq ! ( fields. n_lanes( ) , 16 ) ;
841+ assert_eq ! ( fields. n_patches( ) , 1000 ) ;
842+ Ok ( ( ) )
843+ }
844+
845+ #[ test]
846+ fn test_roundtrip_max_values ( ) -> VortexResult < ( ) > {
847+ let max_offset = ( 1 << 10 ) - 1 ; // 1023
848+ let max_n_lanes = 128 ; // 2^7
849+ let max_n_patches = ( 1 << 23 ) - 1 ; // 8388607
850+
851+ let fields = PatchedMetadataFields :: new ( max_offset, max_n_lanes, max_n_patches) ?;
852+ assert_eq ! ( fields. offset( ) , max_offset) ;
853+ assert_eq ! ( fields. n_lanes( ) , max_n_lanes) ;
854+ assert_eq ! ( fields. n_patches( ) , max_n_patches) ;
855+ Ok ( ( ) )
856+ }
857+
858+ #[ test]
859+ fn test_all_valid_n_lanes ( ) -> VortexResult < ( ) > {
860+ for exp in 0 ..=7 {
861+ let n_lanes = 1 << exp;
862+ let fields = PatchedMetadataFields :: new ( 0 , n_lanes, 0 ) ?;
863+ assert_eq ! ( fields. n_lanes( ) , n_lanes) ;
864+ }
865+ Ok ( ( ) )
866+ }
867+
868+ #[ test]
869+ fn test_from_u64 ( ) {
870+ // n_lanes=16 means exp=4, stored in bits 10..13
871+ let n_lanes_exp = 4u64 ; // log2(16)
872+ let raw: u64 = 512 | ( n_lanes_exp << 10 ) | ( 1000 << 13 ) ;
873+ let fields = PatchedMetadataFields :: from ( raw) ;
874+ assert_eq ! ( fields. offset( ) , 512 ) ;
875+ assert_eq ! ( fields. n_lanes( ) , 16 ) ;
876+ assert_eq ! ( fields. n_patches( ) , 1000 ) ;
877+ }
878+
879+ #[ test]
880+ fn test_offset_overflow ( ) {
881+ let result = PatchedMetadataFields :: new ( 1024 , 1 , 0 ) ;
882+ assert ! ( result. is_err( ) ) ;
883+ assert ! (
884+ result
885+ . unwrap_err( )
886+ . to_string( )
887+ . contains( "offset must be < 1024" )
888+ ) ;
889+ }
890+
891+ #[ test]
892+ fn test_n_lanes_not_power_of_two ( ) {
893+ let result = PatchedMetadataFields :: new ( 0 , 3 , 0 ) ;
894+ assert ! ( result. is_err( ) ) ;
895+ assert ! (
896+ result
897+ . unwrap_err( )
898+ . to_string( )
899+ . contains( "n_lanes must be a power of two" )
900+ ) ;
901+ }
902+
903+ #[ test]
904+ fn test_n_lanes_overflow ( ) {
905+ let result = PatchedMetadataFields :: new ( 0 , 256 , 0 ) ;
906+ assert ! ( result. is_err( ) ) ;
907+ assert ! (
908+ result
909+ . unwrap_err( )
910+ . to_string( )
911+ . contains( "n_lanes must be a power of two between 1 and 128" )
912+ ) ;
913+ }
914+
915+ #[ test]
916+ fn test_n_lanes_zero ( ) {
917+ let result = PatchedMetadataFields :: new ( 0 , 0 , 0 ) ;
918+ assert ! ( result. is_err( ) ) ;
919+ assert ! (
920+ result
921+ . unwrap_err( )
922+ . to_string( )
923+ . contains( "n_lanes must be a power of two" )
924+ ) ;
925+ }
926+
927+ #[ test]
928+ fn test_n_patches_overflow ( ) {
929+ let result = PatchedMetadataFields :: new ( 0 , 1 , 1 << 23 ) ;
930+ assert ! ( result. is_err( ) ) ;
931+ assert ! (
932+ result
933+ . unwrap_err( )
934+ . to_string( )
935+ . contains( "n_patches must be < 8388608" )
936+ ) ;
937+ }
938+ }
747939}
0 commit comments