Skip to content

Commit a7ed1fd

Browse files
authored
Fix for field layout verification across version bubble boundary (#50364)
* Fix for field layout verification across version bubble boundary When verifying field offset consistency, we shouldn't be checking base class size when the base class doesn't belong to the current version bubble. I have fixed this by adding a special case for the existing fixup encoding indicated by base class size being zero. I have also created a simple regression test that was previously failing when run against the framework compiled with CG2 assembly by assembly. I have fixed several other corner field layout cases - we shouldn't be 8-aligning the base class on x86, zero-sized base classes with explicit layout are treated as if they are 1 byte long, improved offset bias handling based on parallel corerun / crossgen2 debugging. Thanks Tomas
1 parent 89ea4c4 commit a7ed1fd

File tree

10 files changed

+153
-75
lines changed

10 files changed

+153
-75
lines changed

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

Lines changed: 39 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,11 @@ protected static ComputedInstanceFieldLayout ComputeExplicitFieldLayout(Metadata
308308
{
309309
// Instance slice size is the total size of instance not including the base type.
310310
// It is calculated as the field whose offset and size add to the greatest value.
311+
LayoutInt offsetBias = !type.IsValueType ? new LayoutInt(type.Context.Target.PointerSize) : LayoutInt.Zero;
311312
LayoutInt cumulativeInstanceFieldPos =
312313
type.HasBaseType && !type.IsValueType ? type.BaseType.InstanceByteCount : LayoutInt.Zero;
313314
LayoutInt instanceSize = cumulativeInstanceFieldPos;
315+
cumulativeInstanceFieldPos -= offsetBias;
314316

315317
var layoutMetadata = type.GetClassLayout();
316318

@@ -333,7 +335,7 @@ protected static ComputedInstanceFieldLayout ComputeExplicitFieldLayout(Metadata
333335
if (fieldAndOffset.Offset == FieldAndOffset.InvalidOffset)
334336
ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadBadFormat, type);
335337

336-
LayoutInt computedOffset = fieldAndOffset.Offset + cumulativeInstanceFieldPos;
338+
LayoutInt computedOffset = fieldAndOffset.Offset + cumulativeInstanceFieldPos + offsetBias;
337339

338340
// GC pointers MUST be aligned.
339341
// We treat byref-like structs as GC pointers too.
@@ -379,29 +381,20 @@ protected static ComputedInstanceFieldLayout ComputeExplicitFieldLayout(Metadata
379381
return computedLayout;
380382
}
381383

382-
private static LayoutInt AlignUpInstanceFieldOffset(TypeDesc typeWithField, LayoutInt cumulativeInstanceFieldPos, LayoutInt alignment, TargetDetails target, bool armAlignFromStartOfFields = false)
384+
private static LayoutInt AlignUpInstanceFieldOffset(TypeDesc typeWithField, LayoutInt cumulativeInstanceFieldPos, LayoutInt alignment, TargetDetails target)
383385
{
384-
if (!typeWithField.IsValueType && (target.Architecture == TargetArchitecture.X86 || (armAlignFromStartOfFields && target.Architecture == TargetArchitecture.ARM)) && cumulativeInstanceFieldPos != new LayoutInt(0))
385-
{
386-
// Alignment of fields is relative to the start of the field list, not the start of the object
387-
//
388-
// The code in the VM is written as if this is the rule for all architectures, but for ARM 32bit platforms
389-
// there is an additional adjustment via dwOffsetBias that aligns fields based on the start of the object
390-
cumulativeInstanceFieldPos = cumulativeInstanceFieldPos - new LayoutInt(target.PointerSize);
391-
return LayoutInt.AlignUp(cumulativeInstanceFieldPos, alignment, target) + new LayoutInt(target.PointerSize);
392-
}
393-
else
394-
{
395-
return LayoutInt.AlignUp(cumulativeInstanceFieldPos, alignment, target);
396-
}
386+
return LayoutInt.AlignUp(cumulativeInstanceFieldPos, alignment, target);
397387
}
398388

399-
protected static ComputedInstanceFieldLayout ComputeSequentialFieldLayout(MetadataType type, int numInstanceFields)
389+
protected ComputedInstanceFieldLayout ComputeSequentialFieldLayout(MetadataType type, int numInstanceFields)
400390
{
401391
var offsets = new FieldAndOffset[numInstanceFields];
402392

403393
// For types inheriting from another type, field offsets continue on from where they left off
404-
LayoutInt cumulativeInstanceFieldPos = ComputeBytesUsedInParentType(type);
394+
// For reference types, we calculate field alignment as if the address after the method table pointer
395+
// has offset 0 (on 32-bit platforms, this location is guaranteed to be 8-aligned).
396+
LayoutInt offsetBias = !type.IsValueType ? new LayoutInt(type.Context.Target.PointerSize) : LayoutInt.Zero;
397+
LayoutInt cumulativeInstanceFieldPos = CalculateFieldBaseOffset(type, requiresAlign8: false, requiresAlignedBase: false) - offsetBias;
405398

406399
var layoutMetadata = type.GetClassLayout();
407400

@@ -421,18 +414,15 @@ protected static ComputedInstanceFieldLayout ComputeSequentialFieldLayout(Metada
421414

422415
largestAlignmentRequirement = LayoutInt.Max(fieldSizeAndAlignment.Alignment, largestAlignmentRequirement);
423416

424-
cumulativeInstanceFieldPos = AlignUpInstanceFieldOffset(type, cumulativeInstanceFieldPos, fieldSizeAndAlignment.Alignment, type.Context.Target,
425-
armAlignFromStartOfFields: true // In what appears to have been a bug in the design of the arm32 type layout code
426-
// this portion of the layout algorithm does not layout from the start of the object
427-
);
428-
offsets[fieldOrdinal] = new FieldAndOffset(field, cumulativeInstanceFieldPos);
417+
cumulativeInstanceFieldPos = AlignUpInstanceFieldOffset(type, cumulativeInstanceFieldPos, fieldSizeAndAlignment.Alignment, type.Context.Target);
418+
offsets[fieldOrdinal] = new FieldAndOffset(field, cumulativeInstanceFieldPos + offsetBias);
429419
cumulativeInstanceFieldPos = checked(cumulativeInstanceFieldPos + fieldSizeAndAlignment.Size);
430420

431421
fieldOrdinal++;
432422
}
433423

434424
SizeAndAlignment instanceByteSizeAndAlignment;
435-
var instanceSizeAndAlignment = ComputeInstanceSize(type, cumulativeInstanceFieldPos, largestAlignmentRequirement, layoutMetadata.Size, out instanceByteSizeAndAlignment);
425+
var instanceSizeAndAlignment = ComputeInstanceSize(type, cumulativeInstanceFieldPos + offsetBias, largestAlignmentRequirement, layoutMetadata.Size, out instanceByteSizeAndAlignment);
436426

437427
ComputedInstanceFieldLayout computedLayout = new ComputedInstanceFieldLayout();
438428
computedLayout.FieldAlignment = instanceSizeAndAlignment.Alignment;
@@ -445,14 +435,12 @@ protected static ComputedInstanceFieldLayout ComputeSequentialFieldLayout(Metada
445435
return computedLayout;
446436
}
447437

448-
protected virtual void AlignBaseOffsetIfNecessary(MetadataType type, ref LayoutInt baseOffset, bool requiresAlign8)
438+
protected virtual void AlignBaseOffsetIfNecessary(MetadataType type, ref LayoutInt baseOffset, bool requiresAlign8, bool requiresAlignedBase)
449439
{
450440
}
451441

452442
protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, int numInstanceFields)
453443
{
454-
// For types inheriting from another type, field offsets continue on from where they left off
455-
LayoutInt cumulativeInstanceFieldPos = ComputeBytesUsedInParentType(type);
456444
TypeSystemContext context = type.Context;
457445

458446
var layoutMetadata = type.GetClassLayout();
@@ -463,7 +451,6 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type,
463451
var offsets = new FieldAndOffset[numInstanceFields];
464452
int fieldOrdinal = 0;
465453

466-
467454
// Iterate over the instance fields and keep track of the number of fields of each category
468455
// For the non-GC Pointer fields, we will keep track of the number of fields by log2(size)
469456
int maxLog2Size = CalculateLog2(TargetDetails.MaximumPrimitiveSize);
@@ -552,18 +539,15 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type,
552539
largestAlignmentRequired = context.Target.GetObjectAlignment(largestAlignmentRequired);
553540
bool requiresAlign8 = !largestAlignmentRequired.IsIndeterminate && largestAlignmentRequired.AsInt > 4;
554541

555-
if (!type.IsValueType)
542+
// For types inheriting from another type, field offsets continue on from where they left off
543+
// Base alignment is not always required, it's only applied when there's a version bubble boundary
544+
// between base type and the current type.
545+
LayoutInt cumulativeInstanceFieldPos = CalculateFieldBaseOffset(type, requiresAlign8, requiresAlignedBase: false);
546+
LayoutInt offsetBias = LayoutInt.Zero;
547+
if (!type.IsValueType && cumulativeInstanceFieldPos != LayoutInt.Zero && type.Context.Target.Architecture == TargetArchitecture.X86)
556548
{
557-
DefType baseType = type.BaseType;
558-
if (baseType != null && !baseType.IsObject)
559-
{
560-
if (!requiresAlign8 && baseType.RequiresAlign8())
561-
{
562-
requiresAlign8 = true;
563-
}
564-
565-
AlignBaseOffsetIfNecessary(type, ref cumulativeInstanceFieldPos, requiresAlign8);
566-
}
549+
offsetBias = new LayoutInt(type.Context.Target.PointerSize);
550+
cumulativeInstanceFieldPos -= offsetBias;
567551
}
568552

569553
// We've finished placing the fields into their appropriate arrays
@@ -638,7 +622,7 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type,
638622
// Place the field
639623
j = instanceNonGCPointerFieldsCount[i];
640624
FieldDesc field = instanceNonGCPointerFieldsArr[i][j];
641-
PlaceInstanceField(field, packingSize, offsets, ref cumulativeInstanceFieldPos, ref fieldOrdinal);
625+
PlaceInstanceField(field, packingSize, offsets, ref cumulativeInstanceFieldPos, ref fieldOrdinal, offsetBias);
642626

643627
instanceNonGCPointerFieldsCount[i]++;
644628
}
@@ -655,14 +639,14 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type,
655639
{
656640
for (int j = 0; j < instanceGCPointerFieldsArr.Length; j++)
657641
{
658-
PlaceInstanceField(instanceGCPointerFieldsArr[j], packingSize, offsets, ref cumulativeInstanceFieldPos, ref fieldOrdinal);
642+
PlaceInstanceField(instanceGCPointerFieldsArr[j], packingSize, offsets, ref cumulativeInstanceFieldPos, ref fieldOrdinal, offsetBias);
659643
}
660644
}
661645

662646
// The start index will be the index that may have been increased in the previous optimization
663647
for (int j = instanceNonGCPointerFieldsCount[i]; j < instanceNonGCPointerFieldsArr[i].Length; j++)
664648
{
665-
PlaceInstanceField(instanceNonGCPointerFieldsArr[i][j], packingSize, offsets, ref cumulativeInstanceFieldPos, ref fieldOrdinal);
649+
PlaceInstanceField(instanceNonGCPointerFieldsArr[i][j], packingSize, offsets, ref cumulativeInstanceFieldPos, ref fieldOrdinal, offsetBias);
666650
}
667651
}
668652

@@ -685,7 +669,7 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type,
685669
LayoutInt AlignmentRequired = LayoutInt.Max(fieldSizeAndAlignment.Alignment, context.Target.LayoutPointerSize);
686670
cumulativeInstanceFieldPos = AlignUpInstanceFieldOffset(type, cumulativeInstanceFieldPos, AlignmentRequired, context.Target);
687671
}
688-
offsets[fieldOrdinal] = new FieldAndOffset(instanceValueClassFieldsArr[i], cumulativeInstanceFieldPos);
672+
offsets[fieldOrdinal] = new FieldAndOffset(instanceValueClassFieldsArr[i], cumulativeInstanceFieldPos + offsetBias);
689673

690674
// If the field has an indeterminate size, align the cumulative field offset to the indeterminate value
691675
// Otherwise, align the cumulative field offset to the aligned-instance field size
@@ -720,7 +704,7 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type,
720704
}
721705

722706
SizeAndAlignment instanceByteSizeAndAlignment;
723-
var instanceSizeAndAlignment = ComputeInstanceSize(type, cumulativeInstanceFieldPos, minAlign, 0/* specified field size unused */, out instanceByteSizeAndAlignment);
707+
var instanceSizeAndAlignment = ComputeInstanceSize(type, cumulativeInstanceFieldPos + offsetBias, minAlign, 0/* specified field size unused */, out instanceByteSizeAndAlignment);
724708

725709
ComputedInstanceFieldLayout computedLayout = new ComputedInstanceFieldLayout();
726710
computedLayout.FieldAlignment = instanceSizeAndAlignment.Alignment;
@@ -733,12 +717,12 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type,
733717
return computedLayout;
734718
}
735719

736-
private static void PlaceInstanceField(FieldDesc field, int packingSize, FieldAndOffset[] offsets, ref LayoutInt instanceFieldPos, ref int fieldOrdinal)
720+
private static void PlaceInstanceField(FieldDesc field, int packingSize, FieldAndOffset[] offsets, ref LayoutInt instanceFieldPos, ref int fieldOrdinal, LayoutInt offsetBias)
737721
{
738722
var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(field.FieldType, packingSize, out bool _);
739723

740724
instanceFieldPos = AlignUpInstanceFieldOffset(field.OwningType, instanceFieldPos, fieldSizeAndAlignment.Alignment, field.Context.Target);
741-
offsets[fieldOrdinal] = new FieldAndOffset(field, instanceFieldPos);
725+
offsets[fieldOrdinal] = new FieldAndOffset(field, instanceFieldPos + offsetBias);
742726
instanceFieldPos = checked(instanceFieldPos + fieldSizeAndAlignment.Size);
743727

744728
fieldOrdinal++;
@@ -775,13 +759,22 @@ private static bool IsByValueClass(TypeDesc type)
775759
return type.IsValueType && !type.IsPrimitive && !type.IsEnum;
776760
}
777761

778-
private static LayoutInt ComputeBytesUsedInParentType(DefType type)
762+
public LayoutInt CalculateFieldBaseOffset(MetadataType type, bool requiresAlign8, bool requiresAlignedBase)
779763
{
780764
LayoutInt cumulativeInstanceFieldPos = LayoutInt.Zero;
781765

782766
if (!type.IsValueType && type.HasBaseType)
783767
{
784768
cumulativeInstanceFieldPos = type.BaseType.InstanceByteCountUnaligned;
769+
if (!type.BaseType.InstanceByteCountUnaligned.IsIndeterminate)
770+
{
771+
cumulativeInstanceFieldPos = type.BaseType.InstanceByteCountUnaligned;
772+
if (type.BaseType.IsZeroSizedReferenceType && ((MetadataType)type.BaseType).HasLayout())
773+
{
774+
cumulativeInstanceFieldPos += LayoutInt.One;
775+
}
776+
AlignBaseOffsetIfNecessary(type, ref cumulativeInstanceFieldPos, requiresAlign8, requiresAlignedBase);
777+
}
785778
}
786779

787780
return cumulativeInstanceFieldPos;

src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CompilationModuleGroup.ReadyToRun.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ public bool EnforceOwningType(EcmaModule module)
8181
/// </summary>
8282
public abstract bool IsInputBubble { get; }
8383

84+
/// <summary>
85+
/// Returns true when the base type and derived type don't reside in the same version bubble
86+
/// in which case the runtime aligns the field base offset.
87+
/// </summary>
88+
public abstract bool NeedsAlignmentBetweenBaseTypeAndDerived(MetadataType baseType, MetadataType derivedType);
89+
8490
/// <summary>
8591
/// List of input modules to use for the compilation.
8692
/// </summary>

src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/FieldFixupSignature.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,31 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
4141

4242
EcmaModule targetModule = factory.SignatureContext.GetTargetModule(_fieldDesc);
4343
SignatureContext innerContext = dataBuilder.EmitFixup(factory, _fixupKind, targetModule, factory.SignatureContext);
44+
uint baseOffset = 0;
45+
uint fieldOffset = (uint)_fieldDesc.Offset.AsInt;
4446

4547
if (_fixupKind == ReadyToRunFixupKind.Verify_FieldOffset)
4648
{
4749
TypeDesc baseType = _fieldDesc.OwningType.BaseType;
48-
if ((_fieldDesc.OwningType.BaseType != null) && !_fieldDesc.IsStatic && !_fieldDesc.OwningType.IsValueType)
50+
if ((_fieldDesc.OwningType.BaseType != null)
51+
&& !_fieldDesc.IsStatic
52+
&& !_fieldDesc.OwningType.IsValueType)
4953
{
50-
dataBuilder.EmitUInt((uint)_fieldDesc.OwningType.FieldBaseOffset().AsInt);
54+
MetadataType owningType = (MetadataType)_fieldDesc.OwningType;
55+
baseOffset = (uint)owningType.FieldBaseOffset().AsInt;
56+
if (factory.CompilationModuleGroup.NeedsAlignmentBetweenBaseTypeAndDerived((MetadataType)baseType, owningType))
57+
{
58+
fieldOffset -= baseOffset;
59+
baseOffset = 0;
60+
}
5161
}
52-
else
53-
dataBuilder.EmitUInt(0);
62+
dataBuilder.EmitUInt(baseOffset);
5463
}
5564

5665
if ((_fixupKind == ReadyToRunFixupKind.Check_FieldOffset) ||
5766
(_fixupKind == ReadyToRunFixupKind.Verify_FieldOffset))
5867
{
59-
dataBuilder.EmitUInt((uint)_fieldDesc.Offset.AsInt);
68+
dataBuilder.EmitUInt(fieldOffset);
6069
}
6170

6271
dataBuilder.EmitFieldSignature(_fieldDesc, innerContext);

src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,11 @@ private bool ModuleMatchesCompilationUnitIndex(ModuleDesc module1, ModuleDesc mo
267267
return ModuleToCompilationUnitIndex(module1) == ModuleToCompilationUnitIndex(module2);
268268
}
269269

270-
public bool NeedsAlignmentBetweenBaseTypeAndDerived(MetadataType baseType, MetadataType derivedType)
270+
public override bool NeedsAlignmentBetweenBaseTypeAndDerived(MetadataType baseType, MetadataType derivedType)
271271
{
272+
if (baseType.IsObject)
273+
return false;
274+
272275
if (!ModuleMatchesCompilationUnitIndex(derivedType.Module, baseType.Module) ||
273276
TypeLayoutCompilationUnits(baseType).HasMultipleCompilationUnits)
274277
{

src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ public override FieldLayoutAlgorithm GetLayoutAlgorithmForType(DefType type)
6868
}
6969
}
7070

71+
/// <summary>
72+
/// This is a rough equivalent of the CoreCLR runtime method ReadyToRunInfo::GetFieldBaseOffset.
73+
/// In contrast to the auto field layout algorithm, this method unconditionally applies alignment
74+
/// between base and derived class (even when they reside in the same version bubble).
75+
/// </summary>
76+
public LayoutInt CalculateFieldBaseOffset(MetadataType type) => _r2rFieldLayoutAlgorithm.CalculateFieldBaseOffset(type, type.RequiresAlign8(), requiresAlignedBase: true);
77+
7178
public void SetCompilationGroup(ReadyToRunCompilationModuleGroupBase compilationModuleGroup)
7279
{
7380
_r2rFieldLayoutAlgorithm.SetCompilationGroup(compilationModuleGroup);

0 commit comments

Comments
 (0)