Skip to content

Commit a524db6

Browse files
authored
[NativeAOT] Emit Align8 flag for thread statics (#105931)
1 parent 99f7f93 commit a524db6

File tree

9 files changed

+134
-13
lines changed

9 files changed

+134
-13
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,5 +348,17 @@ public int MaxHomogeneousAggregateElementCount
348348
/// CodeDelta - encapsulate the fact that ARM requires a thumb bit
349349
/// </summary>
350350
public int CodeDelta { get => (Architecture == TargetArchitecture.ARM) ? 1 : 0; }
351+
352+
/// <summary>
353+
/// Encapsulates the fact that some architectures require 8-byte (larger than pointer
354+
/// size) alignment on some value types and arrays.
355+
/// </summary>
356+
public bool SupportsAlign8
357+
{
358+
get
359+
{
360+
return Architecture is TargetArchitecture.ARM or TargetArchitecture.Wasm32;
361+
}
362+
}
351363
}
352364
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ public static bool RequiresSlotUnification(this MethodDesc method)
415415
/// </summary>
416416
public static bool RequiresAlign8(this TypeDesc type)
417417
{
418-
if (type.Context.Target.Architecture != TargetArchitecture.ARM && type.Context.Target.Architecture != TargetArchitecture.Wasm32)
418+
if (!type.Context.Target.SupportsAlign8)
419419
{
420420
return false;
421421
}

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GCStaticEETypeNode.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ public class GCStaticEETypeNode : DehydratableObjectNode, ISymbolDefinitionNode
2020
{
2121
private GCPointerMap _gcMap;
2222
private TargetDetails _target;
23+
private bool _requiresAlign8;
2324

24-
public GCStaticEETypeNode(TargetDetails target, GCPointerMap gcMap)
25+
public GCStaticEETypeNode(TargetDetails target, GCPointerMap gcMap, bool requiresAlign8)
2526
{
2627
_gcMap = gcMap;
2728
_target = target;
29+
_requiresAlign8 = requiresAlign8;
2830
}
2931

3032
protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);
@@ -83,6 +85,13 @@ protected override ObjectData GetDehydratableData(NodeFactory factory, bool relo
8385
if (containsPointers)
8486
flags |= (uint)EETypeFlags.HasPointersFlag;
8587

88+
if (_requiresAlign8)
89+
{
90+
// Mark the method table as non-value type that requires 8-byte alignment
91+
flags |= (uint)EETypeFlagsEx.RequiresAlign8Flag;
92+
flags |= (uint)EETypeElementType.Class << (byte)EETypeFlags.ElementTypeShift;
93+
}
94+
8695
dataBuilder.EmitUInt(flags);
8796

8897
totalSize = Math.Max(totalSize, _target.PointerSize * 3); // minimum GC MethodTable size is 3 pointers

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GCStaticsNode.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ public static string GetMangledName(TypeDesc type, NameMangler nameMangler)
4747
private ISymbolNode GetGCStaticEETypeNode(NodeFactory factory)
4848
{
4949
GCPointerMap map = GCPointerMap.FromStaticLayout(_type);
50-
return factory.GCStaticEEType(map);
50+
bool requiresAlign8 = _type.GCStaticFieldAlignment.AsInt > factory.Target.PointerSize;
51+
return factory.GCStaticEEType(map, requiresAlign8);
5152
}
5253

5354
protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory)

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -993,7 +993,8 @@ private static TypeDesc GetActualTemplateTypeForType(NodeFactory factory, TypeDe
993993
private ISymbolNode GetStaticsNode(NodeFactory context, out BagElementKind staticsBagKind)
994994
{
995995
MetadataType closestCanonDefType = (MetadataType)_type.GetClosestDefType().ConvertToCanonForm(CanonicalFormKind.Specific);
996-
ISymbolNode symbol = context.GCStaticEEType(GCPointerMap.FromStaticLayout(closestCanonDefType));
996+
bool requiresAlign8 = closestCanonDefType.GCStaticFieldAlignment.AsInt > context.Target.PointerSize;
997+
ISymbolNode symbol = context.GCStaticEEType(GCPointerMap.FromStaticLayout(closestCanonDefType), requiresAlign8);
997998
staticsBagKind = BagElementKind.GcStaticDesc;
998999

9991000
return symbol;
@@ -1002,7 +1003,8 @@ private ISymbolNode GetStaticsNode(NodeFactory context, out BagElementKind stati
10021003
private ISymbolNode GetThreadStaticsNode(NodeFactory context, out BagElementKind staticsBagKind)
10031004
{
10041005
MetadataType closestCanonDefType = (MetadataType)_type.GetClosestDefType().ConvertToCanonForm(CanonicalFormKind.Specific);
1005-
ISymbolNode symbol = context.GCStaticEEType(GCPointerMap.FromThreadStaticLayout(closestCanonDefType));
1006+
bool requiresAlign8 = closestCanonDefType.ThreadGcStaticFieldAlignment.AsInt > context.Target.PointerSize;
1007+
ISymbolNode symbol = context.GCStaticEEType(GCPointerMap.FromThreadStaticLayout(closestCanonDefType), requiresAlign8);
10061008
staticsBagKind = BagElementKind.ThreadStaticDesc;
10071009

10081010
return symbol;

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,9 @@ private void CreateNodeCaches()
241241
return new TypeThreadStaticIndexNode(type, null);
242242
});
243243

244-
_GCStaticEETypes = new NodeCache<GCPointerMap, GCStaticEETypeNode>((GCPointerMap gcMap) =>
244+
_GCStaticEETypes = new NodeCache<(GCPointerMap, bool), GCStaticEETypeNode>(((GCPointerMap gcMap, bool requiresAlign8) key) =>
245245
{
246-
return new GCStaticEETypeNode(Target, gcMap);
246+
return new GCStaticEETypeNode(Target, key.gcMap, key.requiresAlign8);
247247
});
248248

249249
_readOnlyDataBlobs = new NodeCache<ReadOnlyDataBlobKey, BlobNode>(key =>
@@ -810,11 +810,12 @@ public EmbeddedTrimmingDescriptorNode EmbeddedTrimmingDescriptor(EcmaModule modu
810810
return _embeddedTrimmingDescriptors.GetOrAdd(module);
811811
}
812812

813-
private NodeCache<GCPointerMap, GCStaticEETypeNode> _GCStaticEETypes;
813+
private NodeCache<(GCPointerMap, bool), GCStaticEETypeNode> _GCStaticEETypes;
814814

815-
public ISymbolNode GCStaticEEType(GCPointerMap gcMap)
815+
public ISymbolNode GCStaticEEType(GCPointerMap gcMap, bool requiredAlign8)
816816
{
817-
return _GCStaticEETypes.GetOrAdd(gcMap);
817+
requiredAlign8 &= Target.SupportsAlign8;
818+
return _GCStaticEETypes.GetOrAdd((gcMap, requiredAlign8));
818819
}
819820

820821
private NodeCache<ReadOnlyDataBlobKey, BlobNode> _readOnlyDataBlobs;

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ThreadStaticsNode.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ private ISymbolNode GetGCStaticEETypeNode(NodeFactory factory)
6161
_inlined.GetOffsets(),
6262
_inlined.GetSize(),
6363
factory.Target.PointerSize);
64+
bool requiresAlign8 = _type is not null && _type.ThreadGcStaticFieldAlignment.AsInt > factory.Target.PointerSize;
6465

65-
return factory.GCStaticEEType(map);
66+
return factory.GCStaticEEType(map, requiresAlign8);
6667
}
6768

6869
public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory)

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -850,8 +850,9 @@ public ScannedInlinedThreadStatics(NodeFactory factory, ImmutableArray<Dependenc
850850

851851
types.Add(t);
852852

853-
// N.B. for ARM32, we would need to deal with > PointerSize alignments.
854-
// GCStaticEEType does not currently set RequiresAlign8Flag
853+
// N.B. for ARM32, we would need to deal with > PointerSize alignments. We
854+
// currently don't support inlined thread statics on ARM32, regular GCStaticEEType
855+
// handles this with RequiresAlign8Flag
855856
Debug.Assert(t.ThreadGcStaticFieldAlignment.AsInt <= factory.Target.PointerSize);
856857
nextDataOffset = nextDataOffset.AlignUp(t.ThreadGcStaticFieldAlignment.AsInt);
857858

src/tests/nativeaot/SmokeTests/UnitTests/BasicThreading.cs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Runtime.CompilerServices;
7+
using System.Runtime.InteropServices;
78
using System.Threading;
89
using System.Threading.Tasks;
910

@@ -18,6 +19,9 @@ internal static int Run()
1819

1920
ThreadStaticsTestWithTasks.Run();
2021

22+
if (ThreadStaticAlignmentTest.Run() != Pass)
23+
return Fail;
24+
2125
if (ThreadTest.Run() != Pass)
2226
return Fail;
2327

@@ -187,6 +191,96 @@ public static void Run()
187191
}
188192
}
189193

194+
class ThreadStaticAlignmentTest
195+
{
196+
public static int Run()
197+
{
198+
// Check for 8-byte alignment requirement
199+
if (RuntimeInformation.ProcessArchitecture is Architecture.Arm or Architecture.Wasm)
200+
{
201+
// Assume that these are allocated sequentially, use a padding object of size 12 (mod 8 is not 0)
202+
// to move the alignment of the second AddressOfReturnArea in case the first is coincidentally aligned 8.
203+
var ts1Addr = ThreadStaticAlignCheck1.returnArea.AddressOfReturnArea();
204+
var p = new Padder();
205+
var ts2Addr = ThreadStaticAlignCheck2.returnArea.AddressOfReturnArea();
206+
207+
if (((nint)ts1Addr) % 8 != 0)
208+
return BasicThreading.Fail;
209+
if (((nint)ts2Addr) % 8 != 0)
210+
return BasicThreading.Fail;
211+
212+
return (int)typeof(ThreadStaticAlignmentTest).GetMethod("RunGeneric").MakeGenericMethod(GetAtom()).Invoke(null, []);
213+
}
214+
215+
return BasicThreading.Pass;
216+
}
217+
218+
public static int RunGeneric<T>()
219+
{
220+
// Assume that these are allocated sequentially, use a padding object of size 12 (mod 8 is not 0)
221+
// to move the alignment of the second AddressOfReturnArea in case the first is coincidentally aligned 8.
222+
var ts1Addr = ThreadStaticAlignCheck1<T>.returnArea.AddressOfReturnArea();
223+
var p = new Padder();
224+
var ts2Addr = ThreadStaticAlignCheck2<T>.returnArea.AddressOfReturnArea();
225+
226+
if (((nint)ts1Addr) % 8 != 0)
227+
return BasicThreading.Fail;
228+
if (((nint)ts2Addr) % 8 != 0)
229+
return BasicThreading.Fail;
230+
231+
return BasicThreading.Pass;
232+
}
233+
234+
[MethodImpl(MethodImplOptions.NoInlining)]
235+
static Type GetAtom() => typeof(Atom);
236+
237+
[InlineArray(3)]
238+
private struct ReturnArea
239+
{
240+
private ulong buffer;
241+
242+
internal unsafe nint AddressOfReturnArea()
243+
{
244+
return (nint)Unsafe.AsPointer(ref buffer);
245+
}
246+
}
247+
248+
private class ThreadStaticAlignCheck1
249+
{
250+
[ThreadStatic]
251+
[FixedAddressValueType]
252+
internal static ReturnArea returnArea = default;
253+
}
254+
255+
private class Padder
256+
{
257+
private object o1;
258+
}
259+
260+
private class ThreadStaticAlignCheck2
261+
{
262+
[ThreadStatic]
263+
[FixedAddressValueType]
264+
internal static ReturnArea returnArea = default;
265+
}
266+
267+
private class ThreadStaticAlignCheck1<T>
268+
{
269+
[ThreadStatic]
270+
[FixedAddressValueType]
271+
internal static ReturnArea returnArea = default;
272+
}
273+
274+
private class ThreadStaticAlignCheck2<T>
275+
{
276+
[ThreadStatic]
277+
[FixedAddressValueType]
278+
internal static ReturnArea returnArea = default;
279+
}
280+
281+
private class Atom { }
282+
}
283+
190284
class ThreadTest
191285
{
192286
private static readonly List<Thread> s_startedThreads = new List<Thread>();

0 commit comments

Comments
 (0)