@@ -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 )
109124 {
110- f -> setMaskOffset ( maskLoc, maskBit ) ;
111- if ( ++maskBit == 8 )
125+ auto & f = m_fields[ i ] ;
126+ if ( f -> isOptional () )
112127 {
113- maskBit = 0 ;
114- ++maskLoc;
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 )
142+ {
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 () )
163+ {
164+ f -> setMaskOffset ( maskLoc, maskBit );
165+ if ( ++maskBit == 8 )
166+ {
167+ maskBit = 0 ;
168+ ++maskLoc;
169+ }
115170 }
116171 }
117172
@@ -132,22 +187,21 @@ 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+ if ( m_base && m_isStrict != m_base -> isStrict () )
138192 {
139- encountered_non_strict |= !cur -> isStrict ();
140- if ( encountered_non_strict && cur -> isStrict () )
141- CSP_THROW ( ValueError,
142- " Strict '" << m_name
143- << " ' has non-strict inheritance of strict base '"
144- << cur -> name () << " '" );
193+ CSP_THROW ( ValueError,
194+ " Struct " << m_name << " was declared " << ( m_isStrict ? " strict" : " non-strict" ) << " but derives from "
195+ << m_base -> name () << " which is " << ( m_base -> isStrict () ? " strict" : " non-strict" )
196+ );
145197 }
146198}
147199
148200StructMeta::~StructMeta ()
149201{
150202 m_default.reset ();
203+ delete[] m_optionalFieldsSetBits;
204+ delete[] m_optionalFieldsNoneBits;
151205}
152206
153207Struct * StructMeta::createRaw () const
@@ -468,53 +522,66 @@ void StructMeta::clear( Struct * s ) const
468522 m_base -> clear ( s );
469523}
470524
525+ std::string StructMeta::formatAllUnsetStrictFields ( const Struct * s ) const
526+ {
527+ bool first = true ;
528+ std::stringstream ss;
529+ ss << " [" ;
530+
531+ for ( size_t i = 0 ; i < m_fields.size (); ++i )
532+ {
533+ const auto & f = m_fields[ i ];
534+ if ( !f -> isSet ( s ) && !f -> isNone ( s ) )
535+ {
536+ if ( !first )
537+ ss << " , " ;
538+ else
539+ first = false ;
540+ ss << f -> fieldname ();
541+ }
542+ }
543+ ss << " ]" ;
544+ return ss.str ();
545+ }
546+
471547bool StructMeta::allFieldsSet ( const Struct * s ) const
472548{
473- size_t numLocalFields = m_fields.size () - m_firstPartialField;
474- uint8_t numRemainingBits = numLocalFields % 8 ;
549+ /*
550+ We can use bit operations to validate the struct.
551+ 1. Let M1 be the bitmask with 1s that align with the set bit of optional fields and
552+ 2. Let M2 be the bitmask with 1s that align with the none bit of optional fields.
553+ -- Both M1 and M2 are computed trivially when we create the meta.
554+ 3. Let M1* = M1 & mask. M1* now is the bitmask of all set optional fields.
555+ 4. Similarly, let M2* = M2 & mask, such that M2* is the bitmask of all none optional fields.
556+ 5. Let M3 = mask | (M1* << 1) | (M2* >> 1). Since the shifted set/none bitsets will fill in that optional fields other bit,
557+ the struct can validate iff M3 is all 1s.
558+
559+ Notes:
560+ 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.
561+ 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.
562+ So, when we create the meta, we ensure this never happens by being smart about the ordering of fields in the mask.
563+ */
475564
476565 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 )
566+ const uint8_t * e = m + m_maskSize - bool ( m_lastByteMask );
567+
568+ const uint8_t * M1 = m_optionalFieldsSetBits;
569+ const uint8_t * M2 = m_optionalFieldsNoneBits;
570+
571+ for ( ; m < e; ++m, ++M1, ++M2 )
479572 {
480- if ( *m != 0xFF )
573+ if ( ( *m | ( ( *m & *M1 ) << 1 ) | ( ( *m & *M2 ) >> 1 ) ) != 0xFF )
481574 return false ;
482575 }
483576
484- if ( numRemainingBits > 0 )
577+ if ( m_lastByteMask )
485578 {
486- uint8_t bitmask = ( 1u << numRemainingBits ) - 1 ;
487- if ( ( *m & bitmask ) != bitmask )
579+ uint8_t masked = *m & m_lastByteMask ;
580+ if ( ( masked | ( ( ( masked & *M1 ) << 1 ) & m_lastByteMask ) | ( ( masked & *M2 ) >> 1 ) ) != m_lastByteMask )
488581 return false ;
489582 }
490- return true ;
491- }
492583
493- std::string StructMeta::formatAllUnsetStrictFields ( const Struct * s ) const
494- {
495- bool first = true ;
496- std::stringstream ss;
497- ss << " [" ;
498-
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 ();
584+ return m_base ? m_base -> allFieldsSet ( s ) : true ;
518585}
519586
520587void StructMeta::destroy ( Struct * s ) const
@@ -536,18 +603,16 @@ void StructMeta::destroy( Struct * s ) const
536603}
537604
538605[[nodiscard]] bool StructMeta::validate ( const Struct * s ) const
539- {
606+ {
607+ if ( !s -> meta () -> isStrict () )
608+ return true ;
609+
540610 for ( const StructMeta * cur = this ; cur; cur = cur -> m_base.get () )
541611 {
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
548612 if ( !cur -> allFieldsSet ( s ) )
549613 return false ;
550614 }
615+
551616 return true ;
552617}
553618
0 commit comments