1212using System . Diagnostics . CodeAnalysis ;
1313using System . Numerics ;
1414using System . Reflection . Emit ;
15+ using System . Runtime . InteropServices ;
1516using System . Text ;
1617
1718using Microsoft . Scripting ;
@@ -266,7 +267,7 @@ private void SetFields(object? fields) {
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 prevSize = UpdateSizeAndAlignment ( cdata , bitCount , ref lastType , ref size , ref alignment , ref curBitCount ) ;
270271
271272 var newField = new Field ( fieldName , cdata , prevSize , allFields . Count , bitCount , curBitCount - bitCount ) ;
272273 allFields . Add ( newField ) ;
@@ -275,8 +276,6 @@ private void SetFields(object? fields) {
275276 if ( anonFields != null && anonFields . Contains ( fieldName ) ) {
276277 AddAnonymousFields ( this , allFields , cdata , newField ) ;
277278 }
278-
279- lastType = cdata ;
280279 }
281280
282281 CheckAnonymousFields ( allFields , anonFields ) ;
@@ -359,63 +358,126 @@ private List<Field> GetBaseSizeAlignmentAndFields(out int size, out int alignmen
359358 st . EnsureFinal ( ) ;
360359 foreach ( Field f in st . _fields ) {
361360 allFields . Add ( f ) ;
362- UpdateSizeAndAlignment ( f . NativeType , f . BitCount , lastType , ref size , ref alignment , ref totalBitCount ) ;
361+ UpdateSizeAndAlignment ( f . NativeType , f . BitCount , ref lastType , ref size , ref alignment , ref totalBitCount ) ;
363362
364363 if ( f . NativeType == this ) {
365364 throw StructureCannotContainSelf ( ) ;
366365 }
367-
368- lastType = f . NativeType ;
369366 }
370367 }
371368 }
372369 return allFields ;
373370 }
374371
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- }
383372
384- size = PythonStruct . Align ( size , cdata . Alignment ) ;
385-
386- if ( totalBitCount != null ) {
387- if ( ( bitCount + totalBitCount + 7 ) / 8 <= cdata . Size ) {
388- totalBitCount = bitCount + totalBitCount ;
373+ /// <summary>
374+ /// Processes one field definition and allocates its data payload within the struct.
375+ /// </summary>
376+ /// <param name="cdata">
377+ /// The type of the field to process.</param>
378+ /// <param name="bitCount">
379+ /// Width of the bitfield in bits. If the fields to process is not a bitfield, this value is null</param>
380+ /// <param name="lastType">
381+ /// 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.
382+ /// 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>
383+ /// <param name="size">
384+ /// The total size of the struct in bytes excluding an open bitfield container, if any.
385+ /// On input, the size of the struct before the field was allocated.
386+ /// On return, the size of the struct after the field has been processed.
387+ /// 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.
388+ /// So the full (current) struct size in bits is size * 8 + totalBitCount. </param>
389+ /// <param name="alignment">
390+ /// The total alignment of the struct (the common denominator of all fields).
391+ /// This value is being updated as necessary with the alignment of the processed field.</param>
392+ /// <param name="totalBitCount">
393+ /// The number of already occupied bits in the currently open containment unit for bitfields.
394+ /// If the previous field is not a bitfield, this value is null.
395+ /// On return, the count is updated with the number of occupied bits.</param>
396+ /// <returns>
397+ /// 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>
398+ private int UpdateSizeAndAlignment ( INativeType cdata , int ? bitCount , ref INativeType ? lastType , ref int size , ref int alignment , ref int ? totalBitCount ) {
399+ int fieldOffset ;
400+ if ( bitCount != null ) {
401+ // process a bitfield
402+ Debug . Assert ( bitCount <= cdata . Size * 8 ) ;
403+ Debug . Assert ( totalBitCount == null || lastType != null ) ;
404+
405+ if ( _pack != null ) throw new NotImplementedException ( "pack with bitfields" ) ; // TODO: implement
406+
407+ if ( UseMsvcBitfieldAlignmentRules ) {
408+ if ( totalBitCount != null ) {
409+ // this bitfield is not the first field in the struct
410+ if ( totalBitCount != null ) { // there is already a bitfield container open
411+ // under the MSVC rules, only bitfields of type that has the same size/alignment, are packed into the same container unit
412+ if ( lastType ! . Size != cdata . Size || lastType . Alignment != cdata . Alignment ) {
413+ // if the bitfield type is not compatible with the type of the previous container unit, close the previous container unit
414+ size += lastType . Size ;
415+ fieldOffset = size = PythonStruct . Align ( size , cdata . Alignment ) ; // TODO: _pack
416+ totalBitCount = null ;
417+ }
418+ }
419+ }
420+ if ( totalBitCount != null ) {
421+ // container unit open
422+ if ( ( bitCount + totalBitCount + 7 ) / 8 <= cdata . Size ) {
423+ // new bitfield fits into the container unit
424+ fieldOffset = size ;
425+ totalBitCount = bitCount + totalBitCount ;
426+ } else {
427+ // new bitfield does not fit into the container unit, close it
428+ size += lastType ! . Size ;
429+ // and open a new container unit for the bitfield
430+ fieldOffset = size = PythonStruct . Align ( size , cdata . Alignment ) ; // TODO: _pack
431+ totalBitCount = bitCount ;
432+ lastType = cdata ;
433+ }
389434 } else {
390- size += lastType ! . Size ;
391- prevSize = size ;
435+ // open a new container unit for the bitfield
436+ fieldOffset = size = PythonStruct . Align ( size , cdata . Alignment ) ; // TODO: _pack
392437 totalBitCount = bitCount ;
438+ lastType = cdata ;
393439 }
394- } else {
395- totalBitCount = bitCount ;
440+ } else { // GCC bitfield alignment rules
441+ // under the GCC rules, all bitfields are packed into the same container unit or an overlapping container unit of a different type,
442+ // as long as they fit and match the alignment
443+ int containerOffset = AlignBack ( size , cdata . Alignment ) ; // TODO: _pack
444+ int containerBitCount = ( totalBitCount ?? 0 ) + ( size - containerOffset ) * 8 ;
445+ while ( containerBitCount + bitCount > cdata . Size * 8 ) {
446+ // the bitfield does not fit into the container unit at this offset, try the next allowed offset
447+ int deltaOffset = cdata . Alignment ; // TODO: _pack
448+ containerOffset += deltaOffset ;
449+ containerBitCount = Math . Max ( 0 , containerBitCount - deltaOffset * 8 ) ;
450+ }
451+ // the bitfield now fits into the container unit at this offset
452+ fieldOffset = size = containerOffset ;
453+ totalBitCount = containerBitCount + bitCount ;
454+ lastType = cdata ;
396455 }
456+ alignment = Math . Max ( alignment , lastType ! . Alignment ) ; // TODO: _pack
397457 } else {
458+ // process a regular field
398459 if ( totalBitCount != null ) {
460+ // last field was a bitfield; close its container unit to prepare for the next regular field
399461 size += lastType ! . Size ;
400- prevSize = size ;
401462 totalBitCount = null ;
402463 }
403464
404465 if ( _pack != null ) {
405466 alignment = _pack . Value ;
406- prevSize = size = PythonStruct . Align ( size , _pack . Value ) ;
407-
467+ fieldOffset = size = PythonStruct . Align ( size , _pack . Value ) ;
408468 size += cdata . Size ;
409469 } else {
410470 alignment = Math . Max ( alignment , cdata . Alignment ) ;
411- prevSize = size = PythonStruct . Align ( size , cdata . Alignment ) ;
471+ fieldOffset = size = PythonStruct . Align ( size , cdata . Alignment ) ;
412472 size += cdata . Size ;
413473 }
474+ lastType = cdata ;
414475 }
415476
416- return prevSize ;
477+ return fieldOffset ; // this is the offset of the field or the container unit for the bitfield
417478 }
418479
480+
419481 [ MemberNotNull ( nameof ( _fields ) , nameof ( _size ) , nameof ( _alignment ) ) ]
420482 internal void EnsureFinal ( ) {
421483 if ( _fields == null ) {
@@ -452,6 +514,12 @@ private void EnsureSizeAndAlignment() {
452514 throw new InvalidOperationException ( "size and alignment should always be initialized together" ) ;
453515 }
454516 }
517+
518+ private static int AlignBack ( int length , int size )
519+ => length & ~ ( size - 1 ) ;
520+
521+ private static bool UseMsvcBitfieldAlignmentRules
522+ => RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ;
455523 }
456524 }
457525}
0 commit comments