diff --git a/src/coreclr/tools/Common/TypeSystem/Common/TargetDetails.cs b/src/coreclr/tools/Common/TypeSystem/Common/TargetDetails.cs
index 42ccc8994e64a..bb7929b88affe 100644
--- a/src/coreclr/tools/Common/TypeSystem/Common/TargetDetails.cs
+++ b/src/coreclr/tools/Common/TypeSystem/Common/TargetDetails.cs
@@ -348,5 +348,17 @@ public int MaxHomogeneousAggregateElementCount
/// CodeDelta - encapsulate the fact that ARM requires a thumb bit
///
public int CodeDelta { get => (Architecture == TargetArchitecture.ARM) ? 1 : 0; }
+
+ ///
+ /// Encapsulates the fact that some architectures require 8-byte (larger than pointer
+ /// size) alignment on some value types and arrays.
+ ///
+ public bool SupportsAlign8
+ {
+ get
+ {
+ return Architecture is TargetArchitecture.ARM or TargetArchitecture.Wasm32;
+ }
+ }
}
}
diff --git a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemHelpers.cs b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemHelpers.cs
index 749136b698b20..27a0ce99541da 100644
--- a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemHelpers.cs
+++ b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemHelpers.cs
@@ -415,7 +415,7 @@ public static bool RequiresSlotUnification(this MethodDesc method)
///
public static bool RequiresAlign8(this TypeDesc type)
{
- if (type.Context.Target.Architecture != TargetArchitecture.ARM && type.Context.Target.Architecture != TargetArchitecture.Wasm32)
+ if (!type.Context.Target.SupportsAlign8)
{
return false;
}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GCStaticEETypeNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GCStaticEETypeNode.cs
index 62df975327641..9e2c0d74220f6 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GCStaticEETypeNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GCStaticEETypeNode.cs
@@ -20,11 +20,13 @@ public class GCStaticEETypeNode : DehydratableObjectNode, ISymbolDefinitionNode
{
private GCPointerMap _gcMap;
private TargetDetails _target;
+ private bool _requiresAlign8;
- public GCStaticEETypeNode(TargetDetails target, GCPointerMap gcMap)
+ public GCStaticEETypeNode(TargetDetails target, GCPointerMap gcMap, bool requiresAlign8)
{
_gcMap = gcMap;
_target = target;
+ _requiresAlign8 = requiresAlign8;
}
protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);
@@ -83,6 +85,13 @@ protected override ObjectData GetDehydratableData(NodeFactory factory, bool relo
if (containsPointers)
flags |= (uint)EETypeFlags.HasPointersFlag;
+ if (_requiresAlign8)
+ {
+ // Mark the method table as non-value type that requires 8-byte alignment
+ flags |= (uint)EETypeFlagsEx.RequiresAlign8Flag;
+ flags |= (uint)EETypeElementType.Class << (byte)EETypeFlags.ElementTypeShift;
+ }
+
dataBuilder.EmitUInt(flags);
totalSize = Math.Max(totalSize, _target.PointerSize * 3); // minimum GC MethodTable size is 3 pointers
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GCStaticsNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GCStaticsNode.cs
index 5b07a130fa3a3..bf645650170c3 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GCStaticsNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GCStaticsNode.cs
@@ -47,7 +47,8 @@ public static string GetMangledName(TypeDesc type, NameMangler nameMangler)
private ISymbolNode GetGCStaticEETypeNode(NodeFactory factory)
{
GCPointerMap map = GCPointerMap.FromStaticLayout(_type);
- return factory.GCStaticEEType(map);
+ bool requiresAlign8 = _type.GCStaticFieldAlignment.AsInt > factory.Target.PointerSize;
+ return factory.GCStaticEEType(map, requiresAlign8);
}
protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory)
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs
index eca454ffeb712..cec7762015272 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs
@@ -993,7 +993,8 @@ private static TypeDesc GetActualTemplateTypeForType(NodeFactory factory, TypeDe
private ISymbolNode GetStaticsNode(NodeFactory context, out BagElementKind staticsBagKind)
{
MetadataType closestCanonDefType = (MetadataType)_type.GetClosestDefType().ConvertToCanonForm(CanonicalFormKind.Specific);
- ISymbolNode symbol = context.GCStaticEEType(GCPointerMap.FromStaticLayout(closestCanonDefType));
+ bool requiresAlign8 = closestCanonDefType.GCStaticFieldAlignment.AsInt > context.Target.PointerSize;
+ ISymbolNode symbol = context.GCStaticEEType(GCPointerMap.FromStaticLayout(closestCanonDefType), requiresAlign8);
staticsBagKind = BagElementKind.GcStaticDesc;
return symbol;
@@ -1002,7 +1003,8 @@ private ISymbolNode GetStaticsNode(NodeFactory context, out BagElementKind stati
private ISymbolNode GetThreadStaticsNode(NodeFactory context, out BagElementKind staticsBagKind)
{
MetadataType closestCanonDefType = (MetadataType)_type.GetClosestDefType().ConvertToCanonForm(CanonicalFormKind.Specific);
- ISymbolNode symbol = context.GCStaticEEType(GCPointerMap.FromThreadStaticLayout(closestCanonDefType));
+ bool requiresAlign8 = closestCanonDefType.ThreadGcStaticFieldAlignment.AsInt > context.Target.PointerSize;
+ ISymbolNode symbol = context.GCStaticEEType(GCPointerMap.FromThreadStaticLayout(closestCanonDefType), requiresAlign8);
staticsBagKind = BagElementKind.ThreadStaticDesc;
return symbol;
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs
index 01de1d162cd21..aa73c735ae70a 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs
@@ -241,9 +241,9 @@ private void CreateNodeCaches()
return new TypeThreadStaticIndexNode(type, null);
});
- _GCStaticEETypes = new NodeCache((GCPointerMap gcMap) =>
+ _GCStaticEETypes = new NodeCache<(GCPointerMap, bool), GCStaticEETypeNode>(((GCPointerMap gcMap, bool requiresAlign8) key) =>
{
- return new GCStaticEETypeNode(Target, gcMap);
+ return new GCStaticEETypeNode(Target, key.gcMap, key.requiresAlign8);
});
_readOnlyDataBlobs = new NodeCache(key =>
@@ -810,11 +810,12 @@ public EmbeddedTrimmingDescriptorNode EmbeddedTrimmingDescriptor(EcmaModule modu
return _embeddedTrimmingDescriptors.GetOrAdd(module);
}
- private NodeCache _GCStaticEETypes;
+ private NodeCache<(GCPointerMap, bool), GCStaticEETypeNode> _GCStaticEETypes;
- public ISymbolNode GCStaticEEType(GCPointerMap gcMap)
+ public ISymbolNode GCStaticEEType(GCPointerMap gcMap, bool requiredAlign8)
{
- return _GCStaticEETypes.GetOrAdd(gcMap);
+ requiredAlign8 &= Target.SupportsAlign8;
+ return _GCStaticEETypes.GetOrAdd((gcMap, requiredAlign8));
}
private NodeCache _readOnlyDataBlobs;
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ThreadStaticsNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ThreadStaticsNode.cs
index 8935ce8d3d056..f024dd24e1345 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ThreadStaticsNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ThreadStaticsNode.cs
@@ -61,8 +61,9 @@ private ISymbolNode GetGCStaticEETypeNode(NodeFactory factory)
_inlined.GetOffsets(),
_inlined.GetSize(),
factory.Target.PointerSize);
+ bool requiresAlign8 = _type is not null && _type.ThreadGcStaticFieldAlignment.AsInt > factory.Target.PointerSize;
- return factory.GCStaticEEType(map);
+ return factory.GCStaticEEType(map, requiresAlign8);
}
public override IEnumerable GetStaticDependencies(NodeFactory factory)
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs
index 5bf78fb125efc..f2c31155d5f45 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs
@@ -850,8 +850,9 @@ public ScannedInlinedThreadStatics(NodeFactory factory, ImmutableArray PointerSize alignments.
- // GCStaticEEType does not currently set RequiresAlign8Flag
+ // N.B. for ARM32, we would need to deal with > PointerSize alignments. We
+ // currently don't support inlined thread statics on ARM32, regular GCStaticEEType
+ // handles this with RequiresAlign8Flag
Debug.Assert(t.ThreadGcStaticFieldAlignment.AsInt <= factory.Target.PointerSize);
nextDataOffset = nextDataOffset.AlignUp(t.ThreadGcStaticFieldAlignment.AsInt);
diff --git a/src/tests/nativeaot/SmokeTests/UnitTests/BasicThreading.cs b/src/tests/nativeaot/SmokeTests/UnitTests/BasicThreading.cs
index 0b62338d53628..2c826e37e1291 100644
--- a/src/tests/nativeaot/SmokeTests/UnitTests/BasicThreading.cs
+++ b/src/tests/nativeaot/SmokeTests/UnitTests/BasicThreading.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@@ -18,6 +19,9 @@ internal static int Run()
ThreadStaticsTestWithTasks.Run();
+ if (ThreadStaticAlignmentTest.Run() != Pass)
+ return Fail;
+
if (ThreadTest.Run() != Pass)
return Fail;
@@ -187,6 +191,96 @@ public static void Run()
}
}
+class ThreadStaticAlignmentTest
+{
+ public static int Run()
+ {
+ // Check for 8-byte alignment requirement
+ if (RuntimeInformation.ProcessArchitecture is Architecture.Arm or Architecture.Wasm)
+ {
+ // Assume that these are allocated sequentially, use a padding object of size 12 (mod 8 is not 0)
+ // to move the alignment of the second AddressOfReturnArea in case the first is coincidentally aligned 8.
+ var ts1Addr = ThreadStaticAlignCheck1.returnArea.AddressOfReturnArea();
+ var p = new Padder();
+ var ts2Addr = ThreadStaticAlignCheck2.returnArea.AddressOfReturnArea();
+
+ if (((nint)ts1Addr) % 8 != 0)
+ return BasicThreading.Fail;
+ if (((nint)ts2Addr) % 8 != 0)
+ return BasicThreading.Fail;
+
+ return (int)typeof(ThreadStaticAlignmentTest).GetMethod("RunGeneric").MakeGenericMethod(GetAtom()).Invoke(null, []);
+ }
+
+ return BasicThreading.Pass;
+ }
+
+ public static int RunGeneric()
+ {
+ // Assume that these are allocated sequentially, use a padding object of size 12 (mod 8 is not 0)
+ // to move the alignment of the second AddressOfReturnArea in case the first is coincidentally aligned 8.
+ var ts1Addr = ThreadStaticAlignCheck1.returnArea.AddressOfReturnArea();
+ var p = new Padder();
+ var ts2Addr = ThreadStaticAlignCheck2.returnArea.AddressOfReturnArea();
+
+ if (((nint)ts1Addr) % 8 != 0)
+ return BasicThreading.Fail;
+ if (((nint)ts2Addr) % 8 != 0)
+ return BasicThreading.Fail;
+
+ return BasicThreading.Pass;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static Type GetAtom() => typeof(Atom);
+
+ [InlineArray(3)]
+ private struct ReturnArea
+ {
+ private ulong buffer;
+
+ internal unsafe nint AddressOfReturnArea()
+ {
+ return (nint)Unsafe.AsPointer(ref buffer);
+ }
+ }
+
+ private class ThreadStaticAlignCheck1
+ {
+ [ThreadStatic]
+ [FixedAddressValueType]
+ internal static ReturnArea returnArea = default;
+ }
+
+ private class Padder
+ {
+ private object o1;
+ }
+
+ private class ThreadStaticAlignCheck2
+ {
+ [ThreadStatic]
+ [FixedAddressValueType]
+ internal static ReturnArea returnArea = default;
+ }
+
+ private class ThreadStaticAlignCheck1
+ {
+ [ThreadStatic]
+ [FixedAddressValueType]
+ internal static ReturnArea returnArea = default;
+ }
+
+ private class ThreadStaticAlignCheck2
+ {
+ [ThreadStatic]
+ [FixedAddressValueType]
+ internal static ReturnArea returnArea = default;
+ }
+
+ private class Atom { }
+}
+
class ThreadTest
{
private static readonly List s_startedThreads = new List();