Skip to content

Commit c343b5a

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 c343b5a

File tree

8 files changed

+380
-243
lines changed

8 files changed

+380
-243
lines changed

cpp/csp/engine/Struct.cpp

Lines changed: 126 additions & 61 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 )
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

148200
StructMeta::~StructMeta()
149201
{
150202
m_default.reset();
203+
delete[] m_optionalFieldsSetBits;
204+
delete[] m_optionalFieldsNoneBits;
151205
}
152206

153207
Struct * 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+
471547
bool 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

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

Comments
 (0)