Skip to content

Commit b86157d

Browse files
committed
Store optionality on the field object, and embed whether or not the field is set to None in the struct's bitmask
Signed-off-by: Adam Glustein <[email protected]>
1 parent 02ab5f3 commit b86157d

File tree

8 files changed

+382
-242
lines changed

8 files changed

+382
-242
lines changed

cpp/csp/engine/Struct.cpp

Lines changed: 128 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,19 @@ namespace csp
99
{
1010

1111
StructField::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

3943
StructMeta::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

148203
StructMeta::~StructMeta()
149204
{
150205
m_default.reset();
206+
delete[] m_optionalFieldsSetBits;
207+
delete[] m_optionalFieldsNoneBits;
151208
}
152209

153210
Struct * 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+
471550
bool 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

520590
void 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

Comments
 (0)