1212using System . Diagnostics . CodeAnalysis ;
1313using System . Numerics ;
1414using System . Reflection . Emit ;
15+ using System . Runtime . InteropServices ;
1516using System . Text ;
1617
1718using Microsoft . Scripting ;
@@ -261,22 +262,20 @@ private void SetFields(object? fields) {
261262 INativeType ? lastType = null ;
262263 List < Field > allFields = GetBaseSizeAlignmentAndFields ( out int size , out int alignment ) ;
263264
264- IList < object > ? anonFields = GetAnonymousFields ( this ) ;
265+ IList < string > ? anonFields = GetAnonymousFields ( this ) ;
265266
266267 foreach ( object fieldDef in fieldDefList ) {
267268 GetFieldInfo ( this , fieldDef , out string fieldName , out INativeType cdata , out bitCount ) ;
268269
269- int prevSize = UpdateSizeAndAlignment ( cdata , bitCount , lastType , ref size , ref alignment , ref curBitCount ) ;
270+ int fieldOffset = UpdateSizeAndAlignment ( cdata , bitCount , ref lastType , ref size , ref alignment , ref curBitCount ) ;
270271
271- var newField = new Field ( fieldName , cdata , prevSize , allFields . Count , bitCount , curBitCount - bitCount ) ;
272+ var newField = new Field ( fieldName , cdata , fieldOffset , allFields . Count , bitCount , curBitCount - bitCount ) ;
272273 allFields . Add ( newField ) ;
273274 AddSlot ( fieldName , newField ) ;
274275
275276 if ( anonFields != null && anonFields . Contains ( fieldName ) ) {
276277 AddAnonymousFields ( this , allFields , cdata , newField ) ;
277278 }
278-
279- lastType = cdata ;
280279 }
281280
282281 CheckAnonymousFields ( allFields , anonFields ) ;
@@ -293,7 +292,7 @@ private void SetFields(object? fields) {
293292 }
294293 }
295294
296- internal static void CheckAnonymousFields ( List < Field > allFields , IList < object > ? anonFields ) {
295+ internal static void CheckAnonymousFields ( List < Field > allFields , IList < string > ? anonFields ) {
297296 if ( anonFields != null ) {
298297 foreach ( string s in anonFields ) {
299298 bool found = false ;
@@ -311,18 +310,26 @@ internal static void CheckAnonymousFields(List<Field> allFields, IList<object>?
311310 }
312311 }
313312
314- internal static IList < object > ? GetAnonymousFields ( PythonType type ) {
315- object anonymous ;
316- IList < object > ? anonFields = null ;
317- if ( type . TryGetBoundAttr ( type . Context . SharedContext , type , "_anonymous_" , out anonymous ) ) {
318- anonFields = anonymous as IList < object > ;
319- if ( anonFields == null ) {
313+
314+ internal static IList < string > ? GetAnonymousFields ( PythonType type ) {
315+ IList < string > ? anonFieldNames = null ;
316+ if ( type . TryGetBoundAttr ( type . Context . SharedContext , type , "_anonymous_" , out object anonymous ) ) {
317+ if ( anonymous is not IList < object > anonFields ) {
320318 throw PythonOps . TypeError ( "_anonymous_ must be a sequence" ) ;
321319 }
320+ anonFieldNames = [ ] ;
321+ foreach ( object anonField in anonFields ) {
322+ if ( Converter . TryConvertToString ( anonField , out string ? anonFieldStr ) ) {
323+ anonFieldNames . Add ( anonFieldStr ) ;
324+ } else {
325+ throw PythonOps . TypeErrorForBadInstance ( "anonymous field must be a string, not '{0}'" , anonField ) ;
326+ }
327+ }
322328 }
323- return anonFields ;
329+ return anonFieldNames ;
324330 }
325331
332+
326333 internal static void AddAnonymousFields ( PythonType type , List < Field > allFields , INativeType cdata , Field newField ) {
327334 Field [ ] childFields ;
328335 if ( cdata is StructType st ) {
@@ -348,6 +355,7 @@ internal static void AddAnonymousFields(PythonType type, List<Field> allFields,
348355 }
349356 }
350357
358+
351359 private List < Field > GetBaseSizeAlignmentAndFields ( out int size , out int alignment ) {
352360 size = 0 ;
353361 alignment = 1 ;
@@ -359,63 +367,125 @@ private List<Field> GetBaseSizeAlignmentAndFields(out int size, out int alignmen
359367 st . EnsureFinal ( ) ;
360368 foreach ( Field f in st . _fields ) {
361369 allFields . Add ( f ) ;
362- UpdateSizeAndAlignment ( f . NativeType , f . BitCount , lastType , ref size , ref alignment , ref totalBitCount ) ;
370+ UpdateSizeAndAlignment ( f . NativeType , f . BitCount , ref lastType , ref size , ref alignment , ref totalBitCount ) ;
363371
364372 if ( f . NativeType == this ) {
365373 throw StructureCannotContainSelf ( ) ;
366374 }
367-
368- lastType = f . NativeType ;
369375 }
370376 }
371377 }
372378 return allFields ;
373379 }
374380
375- private int UpdateSizeAndAlignment ( INativeType cdata , int ? bitCount , INativeType ? lastType , ref int size , ref int alignment , ref int ? totalBitCount ) {
376- Debug . Assert ( totalBitCount == null || lastType != null ) ; // lastType is null only on the first iteration, when totalBitCount is null as well
377- int prevSize = size ;
378- if ( bitCount != null ) {
379- if ( lastType != null && lastType . Size != cdata . Size ) {
380- totalBitCount = null ;
381- prevSize = size += lastType . Size ;
382- }
383-
384- size = PythonStruct . Align ( size , cdata . Alignment ) ;
385381
386- if ( totalBitCount != null ) {
387- if ( ( bitCount + totalBitCount + 7 ) / 8 <= cdata . Size ) {
388- totalBitCount = bitCount + totalBitCount ;
382+ /// <summary>
383+ /// Processes one field definition and allocates its data payload within the struct.
384+ /// </summary>
385+ /// <param name="cdata">
386+ /// The type of the field to process.</param>
387+ /// <param name="bitCount">
388+ /// Width of the bitfield in bits. If the fields to process is not a bitfield, this value is null</param>
389+ /// <param name="lastType">
390+ /// The type of the last field (or container unit) processed in the struct. If processing the first field in the struct, this value is null.
391+ /// On return, this value is updated with the processed field's type, or, if the processed field was a bitfield, with its container unit type.</param>
392+ /// <param name="size">
393+ /// The total size of the struct in bytes excluding an open bitfield container, if any.
394+ /// On input, the size of the struct before the field was allocated.
395+ /// On return, the size of the struct after the field has been processed.
396+ /// If the processed field was a bitfield, the size may not have been increased yet, depending whether the bitfield fit in the current container unit.
397+ /// So the full (current) struct size in bits is size * 8 + totalBitCount. </param>
398+ /// <param name="alignment">
399+ /// The total alignment of the struct (the common denominator of all fields).
400+ /// This value is being updated as necessary with the alignment of the processed field.</param>
401+ /// <param name="totalBitCount">
402+ /// The number of already occupied bits in the currently open containment unit for bitfields.
403+ /// If the previous field is not a bitfield, this value is null.
404+ /// On return, the count is updated with the number of occupied bits.</param>
405+ /// <returns>
406+ /// The offset of the processed field within the struct. If the processed field was a bitfield, this is the offset of its container unit.</returns>
407+ private int UpdateSizeAndAlignment ( INativeType cdata , int ? bitCount , ref INativeType ? lastType , ref int size , ref int alignment , ref int ? totalBitCount ) {
408+ int fieldOffset ;
409+ if ( bitCount != null ) {
410+ // process a bitfield
411+ Debug . Assert ( bitCount <= cdata . Size * 8 ) ;
412+ Debug . Assert ( totalBitCount == null || lastType != null ) ;
413+
414+ if ( _pack != null ) throw new NotImplementedException ( "pack with bitfields" ) ; // TODO: implement
415+
416+ if ( UseMsvcBitfieldAlignmentRules ) {
417+ if ( totalBitCount != null ) { // there is already a bitfield container open
418+ // under the MSVC rules, only bitfields of type that has the same size/alignment, are packed into the same container unit
419+ if ( lastType ! . Size != cdata . Size || lastType . Alignment != cdata . Alignment ) {
420+ // if the bitfield type is not compatible with the type of the previous container unit, close the previous container unit
421+ size += lastType . Size ;
422+ fieldOffset = size = PythonStruct . Align ( size , cdata . Alignment ) ; // TODO: _pack
423+ totalBitCount = null ;
424+ }
425+ }
426+ if ( totalBitCount != null ) {
427+ // container unit open
428+ if ( ( bitCount + totalBitCount + 7 ) / 8 <= cdata . Size ) {
429+ // new bitfield fits into the container unit
430+ fieldOffset = size ;
431+ totalBitCount += bitCount ;
432+ } else {
433+ // new bitfield does not fit into the container unit, close it
434+ size += lastType ! . Size ;
435+ // and open a new container unit for the bitfield
436+ fieldOffset = size = PythonStruct . Align ( size , cdata . Alignment ) ; // TODO: _pack
437+ totalBitCount = bitCount ;
438+ lastType = cdata ;
439+ }
389440 } else {
390- size += lastType ! . Size ;
391- prevSize = size ;
441+ // open a new container unit for the bitfield
442+ fieldOffset = size = PythonStruct . Align ( size , cdata . Alignment ) ; // TODO: _pack
392443 totalBitCount = bitCount ;
444+ lastType = cdata ;
393445 }
394- } else {
395- totalBitCount = bitCount ;
446+ } else { // GCC bitfield alignment rules
447+ // under the GCC rules, all bitfields are packed into the same container unit or an overlapping container unit of a different type,
448+ // as long as they fit and match the alignment
449+ int containerOffset = AlignBack ( size , cdata . Alignment ) ; // TODO: _pack
450+ int containerBitCount = ( totalBitCount ?? 0 ) + ( size - containerOffset ) * 8 ;
451+ if ( containerBitCount + bitCount > cdata . Size * 8 ) {
452+ // the bitfield does not fit into the container unit at this offset, find the nearest allowed offset
453+ int deltaOffset = cdata . Alignment ; // TODO: _pack
454+ int numOffsets = Math . Max ( 1 , ( containerBitCount + bitCount . Value - 1 ) / ( deltaOffset * 8 ) ) ;
455+ containerOffset += numOffsets * deltaOffset ;
456+ containerBitCount = Math . Max ( 0 , containerBitCount - numOffsets * deltaOffset * 8 ) ;
457+ }
458+ // the bitfield now fits into the container unit at this offset
459+ Debug . Assert ( containerBitCount + bitCount <= cdata . Size * 8 ) ;
460+ fieldOffset = size = containerOffset ;
461+ totalBitCount = containerBitCount + bitCount ;
462+ lastType = cdata ;
396463 }
464+ alignment = Math . Max ( alignment , lastType ! . Alignment ) ; // TODO: _pack
397465 } else {
466+ // process a regular field
398467 if ( totalBitCount != null ) {
468+ // last field was a bitfield; close its container unit to prepare for the next regular field
399469 size += lastType ! . Size ;
400- prevSize = size ;
401470 totalBitCount = null ;
402471 }
403472
404473 if ( _pack != null ) {
405474 alignment = _pack . Value ;
406- prevSize = size = PythonStruct . Align ( size , _pack . Value ) ;
407-
475+ fieldOffset = size = PythonStruct . Align ( size , _pack . Value ) ;
408476 size += cdata . Size ;
409477 } else {
410478 alignment = Math . Max ( alignment , cdata . Alignment ) ;
411- prevSize = size = PythonStruct . Align ( size , cdata . Alignment ) ;
479+ fieldOffset = size = PythonStruct . Align ( size , cdata . Alignment ) ;
412480 size += cdata . Size ;
413481 }
482+ lastType = cdata ;
414483 }
415484
416- return prevSize ;
485+ return fieldOffset ;
417486 }
418487
488+
419489 [ MemberNotNull ( nameof ( _fields ) , nameof ( _size ) , nameof ( _alignment ) ) ]
420490 internal void EnsureFinal ( ) {
421491 if ( _fields == null ) {
@@ -452,6 +522,12 @@ private void EnsureSizeAndAlignment() {
452522 throw new InvalidOperationException ( "size and alignment should always be initialized together" ) ;
453523 }
454524 }
525+
526+ private static int AlignBack ( int length , int size )
527+ => length & ~ ( size - 1 ) ;
528+
529+ private static bool UseMsvcBitfieldAlignmentRules
530+ => RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ;
455531 }
456532 }
457533}
0 commit comments