Skip to content

Commit 11cfc90

Browse files
jtschusterCopilot
andauthored
Lay out resumption stubs next to async method variants (#128380)
In large enough composite compilations targeting ARM32, some async methods will be placed too far apart from their resumption stubs, and the offset doesn't fit into a reloc. To keep the offsets small (and perhaps a small perf win for icache locality), layout the resumption stubs next to the async methods. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 14c1d48 commit 11cfc90

5 files changed

Lines changed: 136 additions & 1 deletion

File tree

src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ static void Validate(ReadyToRunReader reader)
244244
Assert.True(R2RAssert.HasContinuationLayout(reader, "CaptureObjectAcrossAwait", out diag), diag);
245245
Assert.True(R2RAssert.HasContinuationLayout(reader, "CaptureMultipleRefsAcrossAwait", out diag), diag);
246246
Assert.True(R2RAssert.HasResumptionStubFixup(reader, "CaptureObjectAcrossAwait", out diag), diag);
247+
Assert.True(R2RAssert.AsyncMethodsWithResumptionStubsAreAdjacent(reader, out diag), diag);
247248
}
248249
}
249250

@@ -358,6 +359,7 @@ static void Validate(ReadyToRunReader reader)
358359
Assert.True(R2RAssert.HasResumptionStubFixup(reader, ".MultipleAwaitsWithRefs(", out diag), diag);
359360
Assert.True(R2RAssert.HasFixupKindCountOnMethod(reader, ReadyToRunFixupKind.ResumptionStubEntryPoint, ".MultipleAwaits(", 1, out diag), diag);
360361
Assert.True(R2RAssert.HasFixupKindCountOnMethod(reader, ReadyToRunFixupKind.ResumptionStubEntryPoint, ".MultipleAwaitsWithRefs(", 1, out diag), diag);
362+
Assert.True(R2RAssert.AsyncMethodsWithResumptionStubsAreAdjacent(reader, out diag), diag);
361363
}
362364
}
363365

src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RResultChecker.cs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,64 @@ public static bool HasResumptionStub(ReadyToRunReader reader, string methodName,
334334
return found;
335335
}
336336

337+
/// <summary>
338+
/// Returns true if every [ASYNC] method with a ResumptionStubEntryPoint fixup is followed
339+
/// immediately in emitted code by its [RESUME] stub.
340+
/// </summary>
341+
public static bool AsyncMethodsWithResumptionStubsAreAdjacent(ReadyToRunReader reader, out string diagnostic)
342+
{
343+
var methodsByRva = GetAllMethods(reader)
344+
.Select(method => (Method: method, EntryPointRva: GetEntryPointRva(method)))
345+
.Where(entry => entry.EntryPointRva >= 0)
346+
.OrderBy(entry => entry.EntryPointRva)
347+
.ToList();
348+
349+
var failures = new List<string>();
350+
int checkedMethodCount = 0;
351+
for (int i = 0; i < methodsByRva.Count; i++)
352+
{
353+
ReadyToRunMethod method = methodsByRva[i].Method;
354+
if (!HasSignaturePrefix(method, "[ASYNC]") ||
355+
!HasFixupKind(method, ReadyToRunFixupKind.ResumptionStubEntryPoint))
356+
{
357+
continue;
358+
}
359+
360+
checkedMethodCount++;
361+
if (i + 1 == methodsByRva.Count)
362+
{
363+
failures.Add($"'{method.SignatureString}' at RVA 0x{methodsByRva[i].EntryPointRva:X} is the final method.");
364+
continue;
365+
}
366+
367+
ReadyToRunMethod nextMethod = methodsByRva[i + 1].Method;
368+
if (!HasSignaturePrefix(nextMethod, "[RESUME]") ||
369+
StripAsyncMethodPrefix(nextMethod.SignatureString) != StripAsyncMethodPrefix(method.SignatureString))
370+
{
371+
failures.Add(
372+
$"'{method.SignatureString}' at RVA 0x{methodsByRva[i].EntryPointRva:X} " +
373+
$"is followed by '{nextMethod.SignatureString}' at RVA 0x{methodsByRva[i + 1].EntryPointRva:X}.");
374+
}
375+
}
376+
377+
if (checkedMethodCount == 0)
378+
{
379+
diagnostic = "No [ASYNC] methods with ResumptionStubEntryPoint fixups were found.";
380+
return false;
381+
}
382+
383+
if (failures.Count != 0)
384+
{
385+
diagnostic =
386+
$"Expected each [ASYNC] method with a ResumptionStubEntryPoint fixup to be followed by its [RESUME] stub, " +
387+
$"but found {failures.Count} mismatch(es):\n {string.Join("\n ", failures)}";
388+
return false;
389+
}
390+
391+
diagnostic = $"Found {checkedMethodCount} [ASYNC] method(s), each followed by its [RESUME] stub.";
392+
return true;
393+
}
394+
337395
/// <summary>
338396
/// Returns true if the R2R image contains at least one ContinuationLayout fixup.
339397
/// </summary>
@@ -441,6 +499,47 @@ public static bool HasFixupKind(ReadyToRunReader reader, ReadyToRunFixupKind kin
441499
return found;
442500
}
443501

502+
private static bool HasFixupKind(ReadyToRunMethod method, ReadyToRunFixupKind kind)
503+
{
504+
if (method.Fixups is null)
505+
return false;
506+
507+
foreach (var cell in method.Fixups)
508+
{
509+
if (cell.Signature is not null && cell.Signature.FixupKind == kind)
510+
return true;
511+
}
512+
513+
return false;
514+
}
515+
516+
private static bool HasSignaturePrefix(ReadyToRunMethod method, string prefix)
517+
=> method.SignaturePrefixes is not null && method.SignaturePrefixes.Contains(prefix);
518+
519+
private static int GetEntryPointRva(ReadyToRunMethod method)
520+
{
521+
foreach (RuntimeFunction runtimeFunction in method.RuntimeFunctions)
522+
{
523+
if (runtimeFunction.Id == method.EntryPointRuntimeFunctionId)
524+
return runtimeFunction.StartAddress;
525+
}
526+
527+
return -1;
528+
}
529+
530+
private static string StripAsyncMethodPrefix(string signature)
531+
{
532+
const string AsyncPrefix = "[ASYNC] ";
533+
const string ResumePrefix = "[RESUME] ";
534+
if (signature.StartsWith(AsyncPrefix, StringComparison.Ordinal))
535+
return signature.Substring(AsyncPrefix.Length);
536+
537+
if (signature.StartsWith(ResumePrefix, StringComparison.Ordinal))
538+
return signature.Substring(ResumePrefix.Length);
539+
540+
return signature;
541+
}
542+
444543
/// <summary>
445544
/// Returns true if exactly one method whose signature contains <paramref name="methodName"/>
446545
/// has at least one fixup of the given kind.

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,35 @@ public void InitializeDebugEHClauseInfos(DebugEHClauseInfo[] debugEHClauseInfos)
384384
public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
385385
{
386386
MethodWithGCInfo otherNode = (MethodWithGCInfo)other;
387+
MethodDesc methodLayoutAnchor = GetMethodLayoutAnchor(_method, out int methodLayoutRank);
388+
MethodDesc otherLayoutAnchor = GetMethodLayoutAnchor(otherNode._method, out int otherLayoutRank);
389+
390+
int result = comparer.Compare(methodLayoutAnchor, otherLayoutAnchor);
391+
if (result != 0)
392+
{
393+
return result;
394+
}
395+
396+
result = methodLayoutRank.CompareTo(otherLayoutRank);
397+
if (result != 0)
398+
{
399+
return result;
400+
}
401+
387402
return comparer.Compare(_method, otherNode._method);
403+
404+
static MethodDesc GetMethodLayoutAnchor(MethodDesc method, out int layoutRank)
405+
{
406+
if (method is AsyncResumptionStub resumptionStub)
407+
{
408+
Debug.Assert(resumptionStub.TargetMethod is not AsyncResumptionStub);
409+
layoutRank = 1;
410+
return resumptionStub.TargetMethod;
411+
}
412+
413+
layoutRank = 0;
414+
return method;
415+
}
388416
}
389417

390418
public void InitializeInliningInfo(MethodDesc[] inlinedMethods, NodeFactory factory)

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,10 @@ public IEnumerable<IMethodNode> GetCompiledMethods(EcmaModule moduleToEnumerate,
112112
int methodOnlyResult = comparer.Compare(x.Method, y.Method);
113113

114114
// Assert the two sorting techniques produce the same result unless there is a CustomSort applied
115+
// or MethodWithGCInfo ordering is keeping async resumption stubs next to their target methods.
115116
Debug.Assert((nodeComparerResult == methodOnlyResult) ||
117+
x.Method is AsyncResumptionStub ||
118+
y.Method is AsyncResumptionStub ||
116119
((x is SortableDependencyNode sortableX && sortableX.CustomSort != Int32.MaxValue) ||
117120
(y is SortableDependencyNode sortableY && sortableY.CustomSort != Int32.MaxValue)));
118121
#endif

src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1216,7 +1216,10 @@ private void MarkResumptionStubEntryPoints(bool[] isEntryPoint, ReadyToRunSectio
12161216
constrainedType: null,
12171217
instanceArgs: method.InstanceArgs,
12181218
signaturePrefixes: ["[RESUME]"],
1219-
fixupOffset: null);
1219+
fixupOffset: null)
1220+
{
1221+
RuntimeFunctionCount = 1,
1222+
};
12201223
_instanceMethods.Add(new InstanceMethod(0, stubMethod));
12211224
}
12221225
}

0 commit comments

Comments
 (0)