Skip to content

Commit 3b8d8da

Browse files
authored
Fix InlineArray and some other corner cases in Swift lowering (#99337)
1 parent fa49755 commit 3b8d8da

File tree

18 files changed

+370
-172
lines changed

18 files changed

+370
-172
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Runtime.CompilerServices
5+
{
6+
[AttributeUsage(AttributeTargets.Struct, AllowMultiple = false)]
7+
public sealed class InlineArrayAttribute : Attribute
8+
{
9+
public InlineArrayAttribute(int length)
10+
{
11+
Length = length;
12+
}
13+
14+
public int Length { get; }
15+
}
16+
}

src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@
218218
<Compile Include="Internal\Runtime\MethodTable.Runtime.cs" />
219219
<Compile Include="System\Runtime\CompilerServices\CastCache.cs" />
220220
<Compile Include="System\Runtime\CompilerServices\ClassConstructorRunner.cs" />
221+
<Compile Include="System\Runtime\CompilerServices\InlineArrayAttribute.cs" />
221222
<Compile Include="System\Runtime\CompilerServices\StaticClassConstructionContext.cs" />
222223
<Compile Include="System\Runtime\InteropServices\InAttribute.cs" />
223224
<Compile Include="System\Diagnostics\CodeAnalysis\DoesNotReturnIfAttribute.cs" />

src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Diagnostics.CodeAnalysis;
77
using System.Runtime.CompilerServices;
88
using System.Runtime.InteropServices;
9+
using System.Text;
910
using Internal.Pgo;
1011

1112
namespace Internal.JitInterface
@@ -1507,11 +1508,22 @@ private struct SwiftLoweredTypes
15071508
public CorInfoType type;
15081509
}
15091510

1511+
[InlineArray(4)]
1512+
private struct LoweredOffsets
1513+
{
1514+
public uint offset;
1515+
}
1516+
15101517
private SwiftLoweredTypes _loweredElements;
15111518

15121519
[UnscopedRef]
15131520
public Span<CorInfoType> LoweredElements => _loweredElements;
15141521

1522+
private LoweredOffsets _offsets;
1523+
1524+
[UnscopedRef]
1525+
public Span<uint> Offsets => _offsets;
1526+
15151527
public nint numLoweredElements;
15161528

15171529
// Override for a better unit test experience
@@ -1522,7 +1534,20 @@ public override string ToString()
15221534
return "byReference";
15231535
}
15241536

1525-
return string.Join(", ", LoweredElements[0..(int)numLoweredElements].ToArray());
1537+
var stringBuilder = new StringBuilder();
1538+
stringBuilder.Append('{');
1539+
for (int i = 0; i < numLoweredElements; i++)
1540+
{
1541+
if (i != 0)
1542+
{
1543+
stringBuilder.Append(", ");
1544+
}
1545+
stringBuilder.Append(LoweredElements[i]);
1546+
stringBuilder.Append(": ");
1547+
stringBuilder.Append(Offsets[i]);
1548+
}
1549+
stringBuilder.Append('}');
1550+
return stringBuilder.ToString();
15261551
}
15271552
}
15281553
}

src/coreclr/tools/Common/JitInterface/SwiftPhysicalLowering.cs

Lines changed: 85 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
54
using System.Collections.Generic;
6-
using System.Collections.Immutable;
75
using System.Diagnostics;
8-
using System.Linq;
96
using System.Runtime.InteropServices;
107
using Internal.TypeSystem;
118

@@ -29,7 +26,19 @@ private sealed class LoweringVisitor(int pointerSize) : FieldLayoutIntervalCalcu
2926
protected override bool IntervalsHaveCompatibleTags(LoweredType existingTag, LoweredType nextTag)
3027
{
3128
// Adjacent ranges mapped to opaque or empty can be combined.
32-
return existingTag is LoweredType.Opaque or LoweredType.Empty && nextTag is LoweredType.Opaque or LoweredType.Empty;
29+
if (existingTag is LoweredType.Empty
30+
&& nextTag is LoweredType.Empty)
31+
{
32+
return true;
33+
}
34+
35+
if (existingTag is LoweredType.Opaque
36+
&& nextTag is LoweredType.Opaque)
37+
{
38+
return true;
39+
}
40+
41+
return false;
3342
}
3443

3544
protected override FieldLayoutInterval CombineIntervals(FieldLayoutInterval firstInterval, FieldLayoutInterval nextInterval)
@@ -94,13 +103,39 @@ protected override LoweredType GetIntervalDataForType(int offset, TypeDesc field
94103

95104
protected override bool NeedsRecursiveLayout(int offset, TypeDesc fieldType) => fieldType.IsValueType && !fieldType.IsPrimitiveNumeric;
96105

97-
public List<CorInfoType> GetLoweredTypeSequence()
106+
private List<FieldLayoutInterval> CreateConsolidatedIntervals()
98107
{
99-
// We need to track the sequence size to ensure we break up the opaque ranges
100-
// into correctly-sized integers that do not require padding.
101-
int loweredSequenceSize = 0;
102-
List<CorInfoType> loweredTypes = new();
108+
// First, let's make a list of exclusively non-empty intervals.
109+
List<FieldLayoutInterval> consolidatedIntervals = new(Intervals.Count);
103110
foreach (var interval in Intervals)
111+
{
112+
if (interval.Tag != LoweredType.Empty)
113+
{
114+
consolidatedIntervals.Add(interval);
115+
}
116+
}
117+
118+
// Now, we'll look for adjacent opaque intervals and combine them.
119+
for (int i = 0; i < consolidatedIntervals.Count - 1; i++)
120+
{
121+
// Only merge sequential opaque intervals that are within the same PointerSize-sized chunk.
122+
if (consolidatedIntervals[i].Tag == LoweredType.Opaque
123+
&& consolidatedIntervals[i + 1].Tag == LoweredType.Opaque
124+
&& (consolidatedIntervals[i].EndSentinel - 1) / PointerSize == consolidatedIntervals[i + 1].Start / PointerSize)
125+
{
126+
consolidatedIntervals[i] = CombineIntervals(consolidatedIntervals[i], consolidatedIntervals[i + 1]);
127+
consolidatedIntervals.RemoveAt(i + 1);
128+
i--;
129+
}
130+
}
131+
132+
return consolidatedIntervals;
133+
}
134+
135+
public List<(CorInfoType, int)> GetLoweredTypeSequence()
136+
{
137+
List<(CorInfoType, int)> loweredTypes = new();
138+
foreach (var interval in CreateConsolidatedIntervals())
104139
{
105140
// Empty intervals at this point don't need to be represented in the lowered type sequence.
106141
// We want to skip over them.
@@ -109,26 +144,20 @@ public List<CorInfoType> GetLoweredTypeSequence()
109144

110145
if (interval.Tag == LoweredType.Float)
111146
{
112-
loweredTypes.Add(CorInfoType.CORINFO_TYPE_FLOAT);
113-
loweredSequenceSize = loweredSequenceSize.AlignUp(4);
114-
loweredSequenceSize += 4;
147+
loweredTypes.Add((CorInfoType.CORINFO_TYPE_FLOAT, interval.Start));
115148
}
116149

117150
if (interval.Tag == LoweredType.Double)
118151
{
119-
loweredTypes.Add(CorInfoType.CORINFO_TYPE_DOUBLE);
120-
loweredSequenceSize = loweredSequenceSize.AlignUp(8);
121-
loweredSequenceSize += 8;
152+
loweredTypes.Add((CorInfoType.CORINFO_TYPE_DOUBLE, interval.Start));
122153
}
123154

124155
if (interval.Tag == LoweredType.Int64)
125156
{
126-
loweredTypes.Add(CorInfoType.CORINFO_TYPE_LONG);
127-
loweredSequenceSize = loweredSequenceSize.AlignUp(8);
128-
loweredSequenceSize += 8;
157+
loweredTypes.Add((CorInfoType.CORINFO_TYPE_LONG, interval.Start));
129158
}
130159

131-
if (interval.Tag == LoweredType.Opaque)
160+
if (interval.Tag is LoweredType.Opaque)
132161
{
133162
// We need to split the opaque ranges into integer parameters.
134163
// As part of this splitting, we must ensure that we don't introduce alignment padding.
@@ -138,40 +167,42 @@ public List<CorInfoType> GetLoweredTypeSequence()
138167
// The lowered range is allowed to extend past the end of the opaque range (including past the end of the struct),
139168
// but not into the next non-empty interval.
140169
// However, due to the properties of the lowering (the only non-8 byte elements of the lowering are 4-byte floats),
141-
// we'll never encounter a scneario where we need would need to account for a correctly-aligned
170+
// we'll never encounter a scenario where we need would need to account for a correctly-aligned
142171
// opaque range of > 4 bytes that we must not pad to 8 bytes.
143172

144173

145-
// As long as we need to fill more than 4 bytes and the sequence is currently 8-byte aligned, we'll split into 8-byte integers.
146-
// If we have more than 2 bytes but less than 4 and the sequence is 4-byte aligned, we'll use a 4-byte integer to represent the rest of the parameters.
147-
// If we have 2 bytes and the sequence is 2-byte aligned, we'll use a 2-byte integer to represent the rest of the parameters.
148-
// If we have 1 byte, we'll use a 1-byte integer to represent the rest of the parameters.
174+
// While we need to fill more than 4 bytes and the sequence is currently 8-byte aligned, we'll split into 8-byte integers.
175+
// While we need to fill more than 2 bytes but less than 4 and the sequence is 4-byte aligned, we'll use a 4-byte integer to represent the rest of the parameters.
176+
// While we need to fill more than 1 bytes and the sequence is 2-byte aligned, we'll use a 2-byte integer to represent the rest of the parameters.
177+
// While we need to fill at least 1 byte, we'll use a 1-byte integer to represent the rest of the parameters.
178+
int opaqueIntervalStart = interval.Start;
149179
int remainingIntervalSize = interval.Size;
150-
while (remainingIntervalSize > 4 && loweredSequenceSize == loweredSequenceSize.AlignUp(8))
180+
while (remainingIntervalSize > 0)
151181
{
152-
loweredTypes.Add(CorInfoType.CORINFO_TYPE_LONG);
153-
loweredSequenceSize += 8;
154-
remainingIntervalSize -= 8;
155-
}
156-
157-
if (remainingIntervalSize > 2 && loweredSequenceSize == loweredSequenceSize.AlignUp(4))
158-
{
159-
loweredTypes.Add(CorInfoType.CORINFO_TYPE_INT);
160-
loweredSequenceSize += 4;
161-
remainingIntervalSize -= 4;
162-
}
163-
164-
if (remainingIntervalSize > 1 && loweredSequenceSize == loweredSequenceSize.AlignUp(2))
165-
{
166-
loweredTypes.Add(CorInfoType.CORINFO_TYPE_SHORT);
167-
loweredSequenceSize += 2;
168-
remainingIntervalSize -= 2;
169-
}
170-
171-
if (remainingIntervalSize == 1)
172-
{
173-
loweredSequenceSize += 1;
174-
loweredTypes.Add(CorInfoType.CORINFO_TYPE_BYTE);
182+
if (remainingIntervalSize > 4 && opaqueIntervalStart == opaqueIntervalStart.AlignUp(8))
183+
{
184+
loweredTypes.Add((CorInfoType.CORINFO_TYPE_LONG, opaqueIntervalStart));
185+
opaqueIntervalStart += 8;
186+
remainingIntervalSize -= 8;
187+
}
188+
else if (remainingIntervalSize > 2 && opaqueIntervalStart == opaqueIntervalStart.AlignUp(4))
189+
{
190+
loweredTypes.Add((CorInfoType.CORINFO_TYPE_INT, opaqueIntervalStart));
191+
opaqueIntervalStart += 4;
192+
remainingIntervalSize -= 4;
193+
}
194+
else if (remainingIntervalSize > 1 && opaqueIntervalStart == opaqueIntervalStart.AlignUp(2))
195+
{
196+
loweredTypes.Add((CorInfoType.CORINFO_TYPE_SHORT, opaqueIntervalStart));
197+
opaqueIntervalStart += 2;
198+
remainingIntervalSize -= 2;
199+
}
200+
else
201+
{
202+
opaqueIntervalStart++;
203+
remainingIntervalSize--;
204+
loweredTypes.Add((CorInfoType.CORINFO_TYPE_BYTE, opaqueIntervalStart));
205+
}
175206
}
176207
}
177208
}
@@ -190,7 +221,7 @@ public static CORINFO_SWIFT_LOWERING LowerTypeForSwiftSignature(TypeDesc type)
190221
LoweringVisitor visitor = new(type.Context.Target.PointerSize);
191222
visitor.AddFields(type, addTrailingEmptyInterval: false);
192223

193-
List<CorInfoType> loweredTypes = visitor.GetLoweredTypeSequence();
224+
List<(CorInfoType type, int offset)> loweredTypes = visitor.GetLoweredTypeSequence();
194225

195226
// If a type has a primitive sequence with more than 4 elements, Swift passes it by reference.
196227
if (loweredTypes.Count > 4)
@@ -204,7 +235,11 @@ public static CORINFO_SWIFT_LOWERING LowerTypeForSwiftSignature(TypeDesc type)
204235
numLoweredElements = loweredTypes.Count
205236
};
206237

207-
CollectionsMarshal.AsSpan(loweredTypes).CopyTo(lowering.LoweredElements);
238+
for (int i = 0; i < loweredTypes.Count; i++)
239+
{
240+
lowering.LoweredElements[i] = loweredTypes[i].type;
241+
lowering.Offsets[i] = (uint)loweredTypes[i].offset;
242+
}
208243

209244
return lowering;
210245
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ public void AddToFieldLayout(int offset, TypeDesc fieldType, bool addTrailingEmp
8686
{
8787
if (NeedsRecursiveLayout(offset, fieldType))
8888
{
89+
if (fieldType is MetadataType { IsInlineArray: true } mdType)
90+
{
91+
fieldType = new TypeWithRepeatedFields(mdType);
92+
}
93+
8994
List<FieldLayoutInterval> nestedIntervals = new List<FieldLayoutInterval>();
9095
foreach (FieldDesc field in fieldType.GetFields())
9196
{
@@ -123,6 +128,11 @@ private void AddToFieldLayout(List<FieldLayoutInterval> fieldLayout, int offset,
123128
{
124129
if (NeedsRecursiveLayout(offset, fieldType))
125130
{
131+
if (fieldType is MetadataType { IsInlineArray: true } mdType)
132+
{
133+
fieldType = new TypeWithRepeatedFields(mdType);
134+
}
135+
126136
foreach (FieldDesc field in fieldType.GetFields())
127137
{
128138
int fieldOffset = offset + field.Offset.AsInt;
@@ -246,7 +256,7 @@ private void MergeIntervalWithNeighboringIntervals(List<FieldLayoutInterval> fie
246256
break;
247257
}
248258

249-
if ((previousInterval.EndSentinel == expandedInterval.Start) && !IntervalsHaveCompatibleTags(expandedInterval.Tag, previousInterval.Tag))
259+
if ((previousInterval.EndSentinel == expandedInterval.Start) && !IntervalsHaveCompatibleTags(previousInterval.Tag, expandedInterval.Tag))
250260
{
251261
// Expanded interval starts just after previous interval, but does not match tag. Expansion succeeded
252262
break;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Internal.TypeSystem
5+
{
6+
public sealed partial class ImpliedRepeatedFieldDesc : FieldDesc
7+
{
8+
protected internal override int CompareToImpl(FieldDesc other, TypeSystemComparer comparer)
9+
{
10+
var impliedRepeatedFieldDesc = (ImpliedRepeatedFieldDesc)other;
11+
12+
int result = comparer.Compare(_underlyingFieldDesc, impliedRepeatedFieldDesc._underlyingFieldDesc);
13+
14+
if (result != 0)
15+
{
16+
return result;
17+
}
18+
19+
return FieldIndex.CompareTo(impliedRepeatedFieldDesc.FieldIndex);
20+
}
21+
22+
protected internal override int ClassCode => 31666958;
23+
}
24+
}

src/coreclr/tools/Common/Compiler/ImpliedRepeatedFieldDesc.cs renamed to src/coreclr/tools/Common/TypeSystem/Common/ImpliedRepeatedFieldDesc.cs

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using Internal.TypeSystem;
5-
6-
namespace ILCompiler
4+
namespace Internal.TypeSystem
75
{
8-
public sealed class ImpliedRepeatedFieldDesc : FieldDesc
6+
public sealed partial class ImpliedRepeatedFieldDesc : FieldDesc
97
{
108
private readonly FieldDesc _underlyingFieldDesc;
119

@@ -36,26 +34,10 @@ public ImpliedRepeatedFieldDesc(DefType owningType, FieldDesc underlyingFieldDes
3634

3735
public int FieldIndex { get; }
3836

39-
protected override int ClassCode => 31666958;
40-
4137
public override EmbeddedSignatureData[] GetEmbeddedSignatureData() => _underlyingFieldDesc.GetEmbeddedSignatureData();
4238

4339
public override bool HasCustomAttribute(string attributeNamespace, string attributeName) => _underlyingFieldDesc.HasCustomAttribute(attributeNamespace, attributeName);
4440

45-
protected override int CompareToImpl(FieldDesc other, TypeSystemComparer comparer)
46-
{
47-
var impliedRepeatedFieldDesc = (ImpliedRepeatedFieldDesc)other;
48-
49-
int result = comparer.Compare(_underlyingFieldDesc, impliedRepeatedFieldDesc._underlyingFieldDesc);
50-
51-
if (result != 0)
52-
{
53-
return result;
54-
}
55-
56-
return FieldIndex.CompareTo(impliedRepeatedFieldDesc.FieldIndex);
57-
}
58-
5941
public override MarshalAsDescriptor GetMarshalAsDescriptor() => _underlyingFieldDesc.GetMarshalAsDescriptor();
6042

6143
public override string Name => $"{_underlyingFieldDesc.Name}[{FieldIndex}]";

0 commit comments

Comments
 (0)