@@ -9,15 +9,19 @@ namespace csp
99{
1010
1111StructField::StructField ( CspTypePtr type, const std::string & fieldname,
12- size_t size, size_t alignment ) :
12+ size_t size, size_t alignment, bool isOptional ) :
1313 m_fieldname ( fieldname ),
1414 m_offset ( 0 ),
1515 m_size ( size ),
1616 m_alignment ( alignment ),
1717 m_maskOffset ( 0 ),
18+ m_noneMaskOffset ( 0 ),
1819 m_maskBit ( 0 ),
20+ m_noneMaskBit ( 0 ),
1921 m_maskBitMask ( 0 ),
20- m_type ( type )
22+ m_noneMaskBitMask ( 0 ),
23+ m_type ( type ),
24+ m_isOptional ( isOptional )
2125{
2226}
2327
@@ -37,10 +41,11 @@ and adjustments required for the hidden fields
3741*/
3842
3943StructMeta::StructMeta ( const std::string & name, const Fields & fields, bool isStrict,
40- std::shared_ptr<StructMeta> base ) : m_name( name ), m_base( base ), m_isStrict( isStrict ), m_fields( fields ),
44+ std::shared_ptr<StructMeta> base ) : m_name( name ), m_base( base ), m_fields( fields ),
4145 m_size ( 0 ), m_partialSize( 0 ), m_partialStart( 0 ), m_nativeStart( 0 ), m_basePadding( 0 ),
4246 m_maskLoc( 0 ), m_maskSize( 0 ), m_firstPartialField( 0 ), m_firstNativePartialField( 0 ),
43- m_isPartialNative( true ), m_isFullyNative( true )
47+ m_optionalFieldsSetBits( nullptr ), m_optionalFieldsNoneBits( nullptr ), m_isPartialNative( true ),
48+ m_isFullyNative( true ), m_isStrict( isStrict )
4449{
4550 if ( m_fields.empty () && !m_base)
4651 CSP_THROW ( TypeError, " Struct types must define at least 1 field" );
@@ -98,20 +103,70 @@ StructMeta::StructMeta( const std::string & name, const Fields & fields, bool is
98103 // Setup masking bits for our fields
99104 // NOTE we can be more efficient by sticking masks into any potential alignment gaps, dont want to spend time on it
100105 // at this point
101- m_maskSize = !m_fields.empty () ? 1 + ( ( m_fields.size () - 1 ) / 8 ) : 0 ;
106+
107+ size_t optionalFieldCount = std::count_if ( m_fields.begin (), m_fields.end (), []( const auto & f ) { return f -> isOptional (); } );
108+
109+ m_maskSize = !m_fields.empty () ? 1 + ( ( m_fields.size () + optionalFieldCount - 1 ) / 8 ) : 0 ;
102110 m_size = offset + m_maskSize;
103111 m_partialSize = m_size - baseSize;
104112 m_maskLoc = m_size - m_maskSize;
113+
114+ uint8_t numRemainingBits = ( m_fields.size () - m_firstPartialField + optionalFieldCount ) % 8 ;
115+ m_lastByteMask = ( 1u << numRemainingBits ) - 1 ;
105116
106117 size_t maskLoc = m_maskLoc;
107118 uint8_t maskBit = 0 ;
108- for ( auto & f : m_fields )
119+
120+ // Set optional fields first so that their 2-bits never cross a byte boundary
121+ m_optionalFieldsSetBits = new uint8_t [ m_maskSize ]();
122+ m_optionalFieldsNoneBits = new uint8_t [ m_maskSize ]();
123+ for ( size_t i = 0 ; i < m_fields.size (); ++i )
124+ {
125+ auto & f = m_fields[ i ];
126+ if ( f -> isOptional () )
127+ {
128+ f -> setMaskOffset ( maskLoc, maskBit );
129+ f -> setNoneMaskOffset ( maskLoc, ++maskBit ); // use adjacent bit for None bit
130+ if ( ++maskBit == 8 )
131+ {
132+ m_optionalFieldsSetBits[ maskLoc - m_maskLoc ] = 0xAA ;
133+ m_optionalFieldsNoneBits[ maskLoc - m_maskLoc ] = 0x55 ;
134+ maskBit = 0 ;
135+ ++maskLoc;
136+ }
137+ }
138+ }
139+ // deal with last (partial) byte filled with optional fields
140+ auto lastOptIndex = maskLoc - m_maskLoc;
141+ switch ( maskBit / 2 )
109142 {
110- f -> setMaskOffset ( maskLoc, maskBit );
111- if ( ++maskBit == 8 )
143+ case 1 :
144+ m_optionalFieldsSetBits[ lastOptIndex ] = 0x1 ;
145+ m_optionalFieldsNoneBits[ lastOptIndex ] = 0x2 ;
146+ break ;
147+ case 2 :
148+ m_optionalFieldsSetBits[ lastOptIndex ] = 0x5 ;
149+ m_optionalFieldsNoneBits[ lastOptIndex ] = 0xA ;
150+ break ;
151+ case 3 :
152+ m_optionalFieldsSetBits[ lastOptIndex ] = 0x15 ;
153+ m_optionalFieldsNoneBits[ lastOptIndex ] = 0x2A ;
154+ break ;
155+ default :
156+ break ; // default initialized to 0
157+ }
158+
159+ for ( size_t i = 0 ; i < m_fields.size (); ++i )
160+ {
161+ auto & f = m_fields[ i ];
162+ if ( !f -> isOptional () )
112163 {
113- maskBit = 0 ;
114- ++maskLoc;
164+ f -> setMaskOffset ( maskLoc, maskBit );
165+ if ( ++maskBit == 8 )
166+ {
167+ maskBit = 0 ;
168+ ++maskLoc;
169+ }
115170 }
116171 }
117172
@@ -132,22 +187,24 @@ StructMeta::StructMeta( const std::string & name, const Fields & fields, bool is
132187 CSP_THROW ( ValueError, " csp Struct " << name << " attempted to add existing field " << m_fields[ idx ] -> fieldname () );
133188 }
134189
135- // A non-strict struct may not inherit (directly or indirectly) from a strict base
136- bool encountered_non_strict = false ;
137- for ( const StructMeta * cur = this ; cur; cur = cur -> m_base.get () )
190+ // The complete inheritance hierarchy must agree on strict/non-strict
191+ for ( const StructMeta * cur = this ; cur; cur = cur -> m_base.get () )
138192 {
139- encountered_non_strict |= ! cur -> isStrict ();
140- if ( encountered_non_strict && cur -> isStrict () )
193+ if ( m_isStrict != cur -> isStrict () )
194+ {
141195 CSP_THROW ( ValueError,
142- " Strict '" << m_name
143- << " ' has non-strict inheritance of strict base '"
144- << cur -> name () << " '" );
196+ " Struct " << m_name << " was declared " << ( m_isStrict ? " strict" : " non-strict" ) << " but derives from "
197+ << cur -> name () << " which is " << ( cur -> isStrict () ? " strict" : " non-strict" )
198+ );
199+ }
145200 }
146201}
147202
148203StructMeta::~StructMeta ()
149204{
150205 m_default.reset ();
206+ delete[] m_optionalFieldsSetBits;
207+ delete[] m_optionalFieldsNoneBits;
151208}
152209
153210Struct * StructMeta::createRaw () const
@@ -468,53 +525,66 @@ void StructMeta::clear( Struct * s ) const
468525 m_base -> clear ( s );
469526}
470527
528+ std::string StructMeta::formatAllUnsetStrictFields ( const Struct * s ) const
529+ {
530+ bool first = true ;
531+ std::stringstream ss;
532+ ss << " [" ;
533+
534+ for ( size_t i = 0 ; i < m_fields.size (); ++i )
535+ {
536+ const auto & f = m_fields[ i ];
537+ if ( !f -> isSet ( s ) && !f -> isNone ( s ) )
538+ {
539+ if ( !first )
540+ ss << " , " ;
541+ else
542+ first = false ;
543+ ss << f -> fieldname ();
544+ }
545+ }
546+ ss << " ]" ;
547+ return ss.str ();
548+ }
549+
471550bool StructMeta::allFieldsSet ( const Struct * s ) const
472551{
473- size_t numLocalFields = m_fields.size () - m_firstPartialField;
474- uint8_t numRemainingBits = numLocalFields % 8 ;
552+ /*
553+ We can use bit operations to validate the struct.
554+ 1. Let M1 be the bitmask with 1s that align with the set bit of optional fields and
555+ 2. Let M2 be the bitmask with 1s that align with the none bit of optional fields.
556+ -- Both M1 and M2 are computed trivially when we create the meta.
557+ 3. Let M1* = M1 & mask. M1* now is the bitmask of all set optional fields.
558+ 4. Similarly, let M2* = M2 & mask, such that M2* is the bitmask of all none optional fields.
559+ 5. Let M3 = mask | (M1* << 1) | (M2* >> 1). Since the shifted set/none bitsets will fill in that optional fields other bit,
560+ the struct can validate iff M3 is all 1s.
561+
562+ Notes:
563+ 1) We do this on a byte by byte basis currently. If we add 32/64 bit padding to the struct mask, we could do this as one single step for most structs.
564+ 2) There is an edge condition if a) the set bit of an optional field is the last bit of a byte or b) the none bit of an optional field is the first bit of a byte.
565+ So, when we create the meta, we ensure this never happens by being smart about the ordering of fields in the mask.
566+ */
475567
476568 const uint8_t * m = reinterpret_cast <const uint8_t *>( s ) + m_maskLoc;
477- const uint8_t * e = m + m_maskSize - bool ( numRemainingBits );
478- for ( ; m < e; ++m )
569+ const uint8_t * e = m + m_maskSize - bool ( m_lastByteMask );
570+
571+ const uint8_t * M1 = m_optionalFieldsSetBits;
572+ const uint8_t * M2 = m_optionalFieldsNoneBits;
573+
574+ for ( ; m < e; ++m, ++M1, ++M2 )
479575 {
480- if ( *m != 0xFF )
576+ if ( ( *m | ( ( *m & *M1 ) << 1 ) | ( ( *m & *M2 ) >> 1 ) ) != 0xFF )
481577 return false ;
482578 }
483579
484- if ( numRemainingBits > 0 )
580+ if ( m_lastByteMask )
485581 {
486- uint8_t bitmask = ( 1u << numRemainingBits ) - 1 ;
487- if ( ( *m & bitmask ) != bitmask )
582+ uint8_t masked = *m & m_lastByteMask ;
583+ if ( ( masked | ( ( ( masked & *M1 ) << 1 ) & m_lastByteMask ) | ( ( masked & *M2 ) >> 1 ) ) != m_lastByteMask )
488584 return false ;
489585 }
490- return true ;
491- }
492-
493- std::string StructMeta::formatAllUnsetStrictFields ( const Struct * s ) const
494- {
495- bool first = true ;
496- std::stringstream ss;
497- ss << " [" ;
498586
499- for ( const StructMeta * cur = this ; cur; cur = cur -> m_base.get () )
500- {
501- if ( !cur -> isStrict () )
502- continue ;
503-
504- for ( size_t i = cur -> m_firstPartialField; i < cur -> m_fields.size (); ++i )
505- {
506- if ( !cur -> m_fields[ i ] -> isSet ( s ) )
507- {
508- if ( !first )
509- ss << " , " ;
510- else
511- first = false ;
512- ss << cur -> m_fields[ i ] -> fieldname ();
513- }
514- }
515- }
516- ss << " ]" ;
517- return ss.str ();
587+ return m_base ? m_base -> allFieldsSet ( s ) : true ;
518588}
519589
520590void StructMeta::destroy ( Struct * s ) const
@@ -536,18 +606,16 @@ void StructMeta::destroy( Struct * s ) const
536606}
537607
538608[[nodiscard]] bool StructMeta::validate ( const Struct * s ) const
539- {
609+ {
610+ if ( !s -> meta () -> isStrict () )
611+ return true ;
612+
540613 for ( const StructMeta * cur = this ; cur; cur = cur -> m_base.get () )
541614 {
542- if ( !cur -> isStrict () )
543- continue ;
544-
545- // Note that we do not recursively validate nested struct.
546- // We assume after any creation on the C++ side, these structs
547- // are validated properly prior to being set as field values
548615 if ( !cur -> allFieldsSet ( s ) )
549616 return false ;
550617 }
618+
551619 return true ;
552620}
553621
0 commit comments