Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0b82c57

Browse files
Try skipping generation of empty method dictionaries (#82591)
Say we're compiling `Foo<__Canon>.Method` for: ```csharp class Foo<T> { static void Method() => GenericMethod<List<T>>(); static void GenericMethod<T>() { } } ``` In the method body, we're generating a call to `GenericMethod<__Canon>` with a generic dictionary that we looked up from `Foo`s dictionary. But as you can see, the dictionary is empty because `GenericMethod` doesn't do anything with it's T. RyuJIT might even inline it. The problem is that we computed how the dictionary will look like during scanning and we're forever stuck with instruction to generate a generic dictionary for every `Foo<T>` instantiation. This is adding an optimization - if during scanning we find out that the dictionary layout of the target method is empty, we instruct RyuJIT to generate a null generic context pointer. Saves 1.5% on BasicMinimalApi. Cc @dotnet/ilc-contrib
1 parent 04f6655 commit 0b82c57

File tree

13 files changed

+132
-46
lines changed

13 files changed

+132
-46
lines changed

src/coreclr/inc/corinfo.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,6 +1246,7 @@ struct CORINFO_LOOKUP_KIND
12461246
//
12471247
#define CORINFO_MAXINDIRECTIONS 4
12481248
#define CORINFO_USEHELPER ((uint16_t) 0xffff)
1249+
#define CORINFO_USENULL ((uint16_t) 0xfffe)
12491250
#define CORINFO_NO_SIZE_CHECK ((uint16_t) 0xffff)
12501251

12511252
struct CORINFO_RUNTIME_LOOKUP
@@ -1258,6 +1259,7 @@ struct CORINFO_RUNTIME_LOOKUP
12581259

12591260
// Number of indirections to get there
12601261
// CORINFO_USEHELPER = don't know how to get it, so use helper function at run-time instead
1262+
// CORINFO_USENULL = the context should be null because the callee doesn't actually use it
12611263
// 0 = use the this pointer itself (e.g. token is C<!0> inside code in sealed class C)
12621264
// or method desc itself (e.g. token is method void M::mymeth<!!0>() inside code in M::mymeth)
12631265
// Otherwise, follow each byte-offset stored in the "offsets[]" array (may be negative)

src/coreclr/jit/importer.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1743,7 +1743,9 @@ GenTree* Compiler::getRuntimeContextTree(CORINFO_RUNTIME_LOOKUP_KIND kind)
17431743

17441744
1. pLookup->indirections == CORINFO_USEHELPER : Call a helper passing it the
17451745
instantiation-specific handle, and the tokens to lookup the handle.
1746-
2. pLookup->indirections != CORINFO_USEHELPER :
1746+
2. pLookup->indirections == CORINFO_USENULL : Pass null. Callee won't dereference
1747+
the context.
1748+
3. pLookup->indirections != CORINFO_USEHELPER or CORINFO_USENULL :
17471749
2a. pLookup->testForNull == false : Dereference the instantiation-specific handle
17481750
to get the handle.
17491751
2b. pLookup->testForNull == true : Dereference the instantiation-specific handle.
@@ -1771,6 +1773,13 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken
17711773
return gtNewRuntimeLookupHelperCallNode(pRuntimeLookup, ctxTree, compileTimeHandle);
17721774
}
17731775

1776+
#ifdef FEATURE_READYTORUN
1777+
if (pRuntimeLookup->indirections == CORINFO_USENULL)
1778+
{
1779+
return gtNewIconNode(0, TYP_I_IMPL);
1780+
}
1781+
#endif
1782+
17741783
// Slot pointer
17751784
GenTree* slotPtrTree = ctxTree;
17761785

src/coreclr/jit/morph.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7054,8 +7054,8 @@ GenTree* Compiler::getRuntimeLookupTree(CORINFO_RESOLVED_TOKEN* pResolvedToken,
70547054

70557055
// If pRuntimeLookup->indirections is equal to CORINFO_USEHELPER, it specifies that a run-time helper should be
70567056
// used; otherwise, it specifies the number of indirections via pRuntimeLookup->offsets array.
7057-
if ((pRuntimeLookup->indirections == CORINFO_USEHELPER) || pRuntimeLookup->testForNull ||
7058-
pRuntimeLookup->testForFixup)
7057+
if ((pRuntimeLookup->indirections == CORINFO_USEHELPER) || (pRuntimeLookup->indirections == CORINFO_USENULL) ||
7058+
pRuntimeLookup->testForNull || pRuntimeLookup->testForFixup)
70597059
{
70607060
// If the first condition is true, runtime lookup tree is available only via the run-time helper function.
70617061
// TODO-CQ If the second or third condition is true, we are always using the slow path since we can't

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public static class CORINFO
2121
// This accounts for up to 2 indirections to get at a dictionary followed by a possible spill slot
2222
public const uint MAXINDIRECTIONS = 4;
2323
public const ushort USEHELPER = 0xffff;
24+
public const ushort USENULL = 0xfffe;
2425
public const ushort CORINFO_NO_SIZE_CHECK = 0xffff;
2526
}
2627

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,8 +396,7 @@ public GenericDictionaryLookup ComputeGenericLookup(MethodDesc contextMethod, Re
396396
int pointerSize = _nodeFactory.Target.PointerSize;
397397

398398
GenericLookupResult lookup = ReadyToRunGenericHelperNode.GetLookupSignature(_nodeFactory, lookupKind, targetOfLookup);
399-
int dictionarySlot = dictionaryLayout.GetSlotForFixedEntry(lookup);
400-
if (dictionarySlot != -1)
399+
if (dictionaryLayout.TryGetSlotForEntry(lookup, out int dictionarySlot))
401400
{
402401
int dictionaryOffset = dictionarySlot * pointerSize;
403402

@@ -414,6 +413,10 @@ public GenericDictionaryLookup ComputeGenericLookup(MethodDesc contextMethod, Re
414413
return GenericDictionaryLookup.CreateFixedLookup(contextSource, vtableOffset, dictionaryOffset, indirectLastOffset: indirectLastOffset);
415414
}
416415
}
416+
else
417+
{
418+
return GenericDictionaryLookup.CreateNullLookup(contextSource);
419+
}
417420
}
418421
}
419422

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

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,14 @@ public virtual ObjectNodeSection DictionarySection(NodeFactory factory)
7777
/// <summary>
7878
/// Get a slot index for a given entry. Slot indices are never expected to change once given out.
7979
/// </summary>
80-
public abstract int GetSlotForEntry(GenericLookupResult entry);
80+
public abstract bool TryGetSlotForEntry(GenericLookupResult entry, out int slot);
8181

82-
/// <summary>
83-
/// Get the slot for an entry which is fixed already. Otherwise return -1
84-
/// </summary>
85-
/// <param name="entry"></param>
86-
/// <returns></returns>
87-
public virtual int GetSlotForFixedEntry(GenericLookupResult entry)
82+
public abstract IEnumerable<GenericLookupResult> Entries
8883
{
89-
return GetSlotForEntry(entry);
84+
get;
9085
}
9186

92-
public abstract IEnumerable<GenericLookupResult> Entries
87+
public abstract bool IsEmpty
9388
{
9489
get;
9590
}
@@ -195,41 +190,39 @@ public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDep
195190
public class PrecomputedDictionaryLayoutNode : DictionaryLayoutNode
196191
{
197192
private readonly GenericLookupResult[] _layout;
193+
private readonly GenericLookupResult[] _discardedSlots;
198194

199195
public override bool HasFixedSlots => true;
200196

201-
public PrecomputedDictionaryLayoutNode(TypeSystemEntity owningMethodOrType, IEnumerable<GenericLookupResult> layout)
197+
public override bool IsEmpty => _layout.Length == 0;
198+
199+
public PrecomputedDictionaryLayoutNode(TypeSystemEntity owningMethodOrType, GenericLookupResult[] layout, GenericLookupResult[] discardedSlots)
202200
: base(owningMethodOrType)
203201
{
204-
ArrayBuilder<GenericLookupResult> l = default(ArrayBuilder<GenericLookupResult>);
205-
foreach (var entry in layout)
206-
l.Add(entry);
207-
208-
_layout = l.ToArray();
202+
_layout = layout;
203+
_discardedSlots = discardedSlots;
209204
}
210205

211206
public override void EnsureEntry(GenericLookupResult entry)
212207
{
213-
int index = Array.IndexOf(_layout, entry);
214-
215-
if (index == -1)
216-
{
217-
// Using EnsureEntry to add a slot to a PrecomputedDictionaryLayoutNode is not supported
218-
throw new NotSupportedException();
219-
}
208+
throw new NotSupportedException();
220209
}
221210

222-
public override int GetSlotForEntry(GenericLookupResult entry)
211+
public override bool TryGetSlotForEntry(GenericLookupResult entry, out int slot)
223212
{
224-
int index = Array.IndexOf(_layout, entry);
213+
slot = Array.IndexOf(_layout, entry);
214+
215+
// If this is a slot we should discard, respond false
216+
if (slot < 0 && Array.IndexOf(_discardedSlots, entry) >= 0)
217+
return false;
225218

226219
// This entry wasn't precomputed. If this is an optimized build with scanner, it might suggest
227220
// the scanner didn't see the need for this. There is a discrepancy between scanning and compiling.
228221
// This is a fatal error to prevent bad codegen.
229-
if (index < 0)
222+
if (slot < 0)
230223
throw new InvalidOperationException($"{OwningMethodOrType}: {entry}");
231224

232-
return index;
225+
return true;
233226
}
234227

235228
public override IEnumerable<GenericLookupResult> Entries
@@ -284,19 +277,19 @@ private void ComputeLayout()
284277
_layout = layout;
285278
}
286279

287-
public override int GetSlotForEntry(GenericLookupResult entry)
280+
public override bool TryGetSlotForEntry(GenericLookupResult entry, out int slot)
288281
{
289282
if (_layout == null)
290283
ComputeLayout();
291284

292-
int index = Array.IndexOf(_layout, entry);
285+
slot = Array.IndexOf(_layout, entry);
293286

294287
// We never called EnsureEntry on this during compilation?
295288
// This is a fatal error to prevent bad codegen.
296-
if (index < 0)
289+
if (slot < 0)
297290
throw new InvalidOperationException($"{OwningMethodOrType}: {entry}");
298291

299-
return index;
292+
return true;
300293
}
301294

302295
public override IEnumerable<GenericLookupResult> Entries
@@ -309,5 +302,16 @@ public override IEnumerable<GenericLookupResult> Entries
309302
return _layout;
310303
}
311304
}
305+
306+
public override bool IsEmpty
307+
{
308+
get
309+
{
310+
if (_layout == null)
311+
ComputeLayout();
312+
313+
return _layout.Length == 0;
314+
}
315+
}
312316
}
313317
}

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM/ARMReadyToRunGenericHelperNode.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ protected void EmitDictionaryLookup(NodeFactory factory, ref ARMEmitter encoder,
3030
if (!relocsOnly)
3131
{
3232
// The concrete slot won't be known until we're emitting data - don't ask for it in relocsOnly.
33-
dictionarySlot = factory.GenericDictionaryLayout(_dictionaryOwner).GetSlotForEntry(lookup);
33+
if (!factory.GenericDictionaryLayout(_dictionaryOwner).TryGetSlotForEntry(lookup, out dictionarySlot))
34+
{
35+
encoder.EmitMOV(result, 0);
36+
return;
37+
}
3438
}
3539

3640
// Load the generic dictionary cell

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM64/ARM64ReadyToRunGenericHelperNode.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ protected void EmitDictionaryLookup(NodeFactory factory, ref ARM64Emitter encode
3030
if (!relocsOnly)
3131
{
3232
// The concrete slot won't be known until we're emitting data - don't ask for it in relocsOnly.
33-
dictionarySlot = factory.GenericDictionaryLayout(_dictionaryOwner).GetSlotForEntry(lookup);
33+
if (!factory.GenericDictionaryLayout(_dictionaryOwner).TryGetSlotForEntry(lookup, out dictionarySlot))
34+
{
35+
encoder.EmitMOV(result, (ushort)0);
36+
return;
37+
}
3438
}
3539

3640
// Load the generic dictionary cell

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_LoongArch64/LoongArch64ReadyToRunGenericHelperNode.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ protected void EmitDictionaryLookup(NodeFactory factory, ref LoongArch64Emitter
3030
if (!relocsOnly)
3131
{
3232
// The concrete slot won't be known until we're emitting data - don't ask for it in relocsOnly.
33-
dictionarySlot = factory.GenericDictionaryLayout(_dictionaryOwner).GetSlotForEntry(lookup);
33+
if (!factory.GenericDictionaryLayout(_dictionaryOwner).TryGetSlotForEntry(lookup, out dictionarySlot))
34+
{
35+
encoder.EmitMOV(result, (ushort)0);
36+
return;
37+
}
3438
}
3539

3640
// Load the generic dictionary cell

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunGenericHelperNode.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ protected void EmitDictionaryLookup(NodeFactory factory, ref X64Emitter encoder,
3030
if (!relocsOnly)
3131
{
3232
// The concrete slot won't be known until we're emitting data - don't ask for it in relocsOnly.
33-
dictionarySlot = factory.GenericDictionaryLayout(_dictionaryOwner).GetSlotForEntry(lookup);
33+
if (!factory.GenericDictionaryLayout(_dictionaryOwner).TryGetSlotForEntry(lookup, out dictionarySlot))
34+
{
35+
encoder.EmitZeroReg(result);
36+
return;
37+
}
3438
}
3539

3640
// Load the generic dictionary cell

0 commit comments

Comments
 (0)