Skip to content

Commit a969995

Browse files
github-actions[bot]davidwrightontrylek
authored
[release/7.0-rc1] Fix auto layout algorithm to compute structure alignment correctly (#74091)
* New test * Fix auto layout algorithm to compute structure alignment correctly - In particular: 1. The alignment requirement imposed by of a non-primitive, non-enum valuetype field is the alignment of that field 2. The alignment requirement imposed by a primitive is the pointer size of the target platform, unless running on Arm32, in which case if the primitive or enum is 8 bytes in size, the alignment requirement is 8. - The previous implementation produced an alignment of pointer size, unless running on Arm32 and one of the fields had an alignment requirement of 8 (in which case the alignment requirement computed for the structure would be 8) In addition, add a test which verifies that the instance field layout test types are actually producing R2R compatible results at runtime. - This test shows that we have some issues around explicit layout, so I was forced to disable that portion of the test for now. Fixes #65281 * Re-enable disabled test * Remove file that shouldn't be added as part of the new test * Make a few test types public to silence unassigned field errors * Update comments and add more testing Co-authored-by: David Wrighton <[email protected]> Co-authored-by: Tomas Rylek <[email protected]>
1 parent dc98f58 commit a969995

File tree

10 files changed

+704
-138
lines changed

10 files changed

+704
-138
lines changed

src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,7 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type,
469469

470470
if (IsByValueClass(fieldType))
471471
{
472+
// Valuetypes which are not primitives or enums
472473
instanceValueClassFieldCount++;
473474
}
474475
else if (fieldType.IsGCPointer)
@@ -520,25 +521,47 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type,
520521
if (!fieldLayoutAbiStable)
521522
layoutAbiStable = false;
522523

523-
largestAlignmentRequired = LayoutInt.Max(fieldSizeAndAlignment.Alignment, largestAlignmentRequired);
524-
525524
if (IsByValueClass(fieldType))
526525
{
526+
// This block handles valuetypes which are not primitives or enums, it only has a meaningful effect, if the
527+
// type has an alignment greater than pointer size.
528+
largestAlignmentRequired = LayoutInt.Max(fieldSizeAndAlignment.Alignment, largestAlignmentRequired);
527529
instanceValueClassFieldsArr[instanceValueClassFieldCount++] = field;
528530
}
529-
else if (fieldType.IsGCPointer)
530-
{
531-
instanceGCPointerFieldsArr[instanceGCPointerFieldsCount++] = field;
532-
}
533531
else
534532
{
535-
int log2size = CalculateLog2(fieldSizeAndAlignment.Size.AsInt);
536-
instanceNonGCPointerFieldsArr[log2size][instanceNonGCPointerFieldsCount[log2size]++] = field;
533+
// non-value-type (and primitive type) fields will add an alignment requirement of pointer size
534+
// This alignment requirement will not be significant in the final alignment calculation unlesss the
535+
// type is greater than the size of a single pointer.
536+
//
537+
// This does not account for types that are marked IsAlign8Candidate due to 8-byte fields
538+
// but that is explicitly handled when we calculate the final alignment for the type.
539+
540+
// This behavior is extremely strange for primitive types, as it makes a struct with a single byte in it
541+
// have 8 byte alignment, but that is the current implementation.
542+
543+
largestAlignmentRequired = LayoutInt.Max(new LayoutInt(context.Target.PointerSize), largestAlignmentRequired);
544+
545+
if (fieldType.IsGCPointer)
546+
{
547+
instanceGCPointerFieldsArr[instanceGCPointerFieldsCount++] = field;
548+
}
549+
else
550+
{
551+
Debug.Assert(fieldType.IsPrimitive || fieldType.IsPointer || fieldType.IsFunctionPointer || fieldType.IsEnum || fieldType.IsByRef);
552+
int log2size = CalculateLog2(fieldSizeAndAlignment.Size.AsInt);
553+
instanceNonGCPointerFieldsArr[log2size][instanceNonGCPointerFieldsCount[log2size]++] = field;
554+
555+
if (fieldType.IsPrimitive || fieldType.IsEnum)
556+
{
557+
// Handle alignment of long/ulong/double on ARM32
558+
largestAlignmentRequired = LayoutInt.Max(context.Target.GetObjectAlignment(fieldSizeAndAlignment.Size), largestAlignmentRequired);
559+
}
560+
}
537561
}
538562
}
539563

540-
largestAlignmentRequired = context.Target.GetObjectAlignment(largestAlignmentRequired);
541-
bool requiresAlign8 = !largestAlignmentRequired.IsIndeterminate && largestAlignmentRequired.AsInt > 4;
564+
bool requiresAlign8 = !largestAlignmentRequired.IsIndeterminate && context.Target.PointerSize == 4 && context.Target.GetObjectAlignment(largestAlignmentRequired).AsInt > 4 && context.Target.PointerSize == 4;
542565

543566
// For types inheriting from another type, field offsets continue on from where they left off
544567
// Base alignment is not always required, it's only applied when there's a version bubble boundary

src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ArchitectureSpecificFieldLayoutTests.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,5 +414,67 @@ public void TestAlignmentBehavior_ShortByteEnumStructAuto()
414414
Assert.Equal(0x4, tX86FieldStruct.GetField("_struct").Offset.AsInt);
415415
Assert.Equal(0x4, tARMFieldStruct.GetField("_struct").Offset.AsInt);
416416
}
417+
418+
[Fact]
419+
public void TestAlignmentBehavior_StructStructByte_StructByteAuto()
420+
{
421+
string _namespace = "Sequential";
422+
string _type = "StructStructByte_StructByteAuto";
423+
424+
MetadataType tX64 = _testModuleX64.GetType(_namespace, _type);
425+
MetadataType tX86 = _testModuleX86.GetType(_namespace, _type);
426+
MetadataType tARM = _testModuleARM.GetType(_namespace, _type);
427+
428+
Assert.Equal(0x1, tX64.InstanceFieldAlignment.AsInt);
429+
Assert.Equal(0x1, tARM.InstanceFieldAlignment.AsInt);
430+
Assert.Equal(0x1, tX86.InstanceFieldAlignment.AsInt);
431+
432+
Assert.Equal(0x2, tX64.InstanceFieldSize.AsInt);
433+
Assert.Equal(0x2, tARM.InstanceFieldSize.AsInt);
434+
Assert.Equal(0x2, tX86.InstanceFieldSize.AsInt);
435+
436+
Assert.Equal(0x0, tX64.GetField("fld1").Offset.AsInt);
437+
Assert.Equal(0x0, tARM.GetField("fld1").Offset.AsInt);
438+
Assert.Equal(0x0, tX86.GetField("fld1").Offset.AsInt);
439+
440+
Assert.Equal(0x1, tX64.GetField("fld2").Offset.AsInt);
441+
Assert.Equal(0x1, tARM.GetField("fld2").Offset.AsInt);
442+
Assert.Equal(0x1, tX86.GetField("fld2").Offset.AsInt);
443+
}
444+
445+
[Theory]
446+
[InlineData("StructStructByte_StructByteAuto", new int[]{1,1,1}, new int[]{2,2,2})]
447+
[InlineData("StructStructByte_Struct2BytesAuto", new int[]{2,2,2}, new int[]{4,4,4})]
448+
[InlineData("StructStructByte_Struct3BytesAuto", new int[]{4,4,4}, new int[]{8,8,8})]
449+
[InlineData("StructStructByte_Struct4BytesAuto", new int[]{4,4,4}, new int[]{8,8,8})]
450+
[InlineData("StructStructByte_Struct5BytesAuto", new int[]{8,4,4}, new int[]{16,12,12})]
451+
[InlineData("StructStructByte_Struct8BytesAuto", new int[]{8,4,4}, new int[]{16,12,12})]
452+
[InlineData("StructStructByte_Struct9BytesAuto", new int[]{8,4,4}, new int[]{24,16,16})]
453+
public void TestAlignmentBehavior_AutoAlignmentRules(string wrapperType, int[] alignment, int[] size)
454+
{
455+
string _namespace = "Sequential";
456+
string _type = wrapperType;
457+
458+
MetadataType tX64 = _testModuleX64.GetType(_namespace, _type);
459+
MetadataType tX86 = _testModuleX86.GetType(_namespace, _type);
460+
MetadataType tARM = _testModuleARM.GetType(_namespace, _type);
461+
462+
Assert.Equal(alignment[0], tX64.InstanceFieldAlignment.AsInt);
463+
Assert.Equal(alignment[1], tARM.InstanceFieldAlignment.AsInt);
464+
Assert.Equal(alignment[2], tX86.InstanceFieldAlignment.AsInt);
465+
466+
Assert.Equal(size[0], tX64.InstanceFieldSize.AsInt);
467+
Assert.Equal(size[1], tARM.InstanceFieldSize.AsInt);
468+
Assert.Equal(size[2], tX86.InstanceFieldSize.AsInt);
469+
470+
Assert.Equal(0x0, tX64.GetField("fld1").Offset.AsInt);
471+
Assert.Equal(0x0, tARM.GetField("fld1").Offset.AsInt);
472+
Assert.Equal(0x0, tX86.GetField("fld1").Offset.AsInt);
473+
474+
Assert.Equal(alignment[0], tX64.GetField("fld2").Offset.AsInt);
475+
Assert.Equal(alignment[1], tARM.GetField("fld2").Offset.AsInt);
476+
Assert.Equal(alignment[2], tX86.GetField("fld2").Offset.AsInt);
477+
}
478+
417479
}
418480
}

0 commit comments

Comments
 (0)