Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for delegate GDV in R2R and NAOT #77267

Closed
wants to merge 10 commits into from
12 changes: 11 additions & 1 deletion src/coreclr/jit/codegenarmarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5259,7 +5259,17 @@ void CodeGen::genFnEpilog(BasicBlock* block)
if (jmpEpilog && lastNode->gtOper == GT_JMP)
{
methHnd = (CORINFO_METHOD_HANDLE)lastNode->AsVal()->gtVal1;
compiler->info.compCompHnd->getFunctionEntryPoint(methHnd, &addrInfo);

if (compiler->opts.IsReadyToRun())
{
// R2R entry points may require additional args that we do not
// handle here.
compiler->info.compCompHnd->getFunctionFixedEntryPoint(methHnd, false, &addrInfo);
}
else
{
compiler->info.compCompHnd->getFunctionEntryPoint(methHnd, &addrInfo);
}
}

#ifdef TARGET_ARM
Expand Down
11 changes: 10 additions & 1 deletion src/coreclr/jit/codegenloongarch64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1378,7 +1378,16 @@ void CodeGen::genFnEpilog(BasicBlock* block)
if (jmpEpilog && (lastNode->gtOper == GT_JMP))
{
methHnd = (CORINFO_METHOD_HANDLE)lastNode->AsVal()->gtVal1;
compiler->info.compCompHnd->getFunctionEntryPoint(methHnd, &addrInfo);
if (compiler->opts.IsReadyToRun())
{
// R2R entry points may require additional args that we do not
// handle here.
compiler->info.compCompHnd->getFunctionFixedEntryPoint(methHnd, false, &addrInfo);
}
else
{
compiler->info.compCompHnd->getFunctionEntryPoint(methHnd, &addrInfo);
}
}

compiler->unwindBegEpilog();
Expand Down
12 changes: 11 additions & 1 deletion src/coreclr/jit/codegenxarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9938,7 +9938,17 @@ void CodeGen::genFnEpilog(BasicBlock* block)
CORINFO_METHOD_HANDLE methHnd = (CORINFO_METHOD_HANDLE)jmpNode->AsVal()->gtVal1;

CORINFO_CONST_LOOKUP addrInfo;
compiler->info.compCompHnd->getFunctionEntryPoint(methHnd, &addrInfo);
if (compiler->opts.IsReadyToRun())
{
// R2R entry points may require additional args that we do not
// handle here.
compiler->info.compCompHnd->getFunctionFixedEntryPoint(methHnd, false, &addrInfo);
}
else
{
compiler->info.compCompHnd->getFunctionEntryPoint(methHnd, &addrInfo);
}

if (addrInfo.accessType != IAT_VALUE && addrInfo.accessType != IAT_PVALUE)
{
NO_WAY("Unsupported JMP indirection");
Expand Down
8 changes: 1 addition & 7 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4668,13 +4668,7 @@ void Compiler::pickGDV(GenTreeCall* call,
LikelyClassMethodRecord likelyMethods[maxLikelyMethods];
unsigned numberOfMethods = 0;

// TODO-GDV: R2R support requires additional work to reacquire the
// entrypoint, similar to what happens at the end of impDevirtualizeCall.
// As part of supporting this we should merge the tail of
// impDevirtualizeCall and what happens in
// GuardedDevirtualizationTransformer::CreateThen for method GDV.
//
if (!opts.IsReadyToRun() && (call->IsVirtualVtable() || call->IsDelegateInvoke()))
if (call->IsVirtualVtable() || call->IsDelegateInvoke())
{
numberOfMethods =
getLikelyMethods(likelyMethods, maxLikelyMethods, fgPgoSchema, fgPgoSchemaCount, fgPgoData, ilOffset);
Expand Down
14 changes: 12 additions & 2 deletions src/coreclr/jit/indirectcalltransformer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,9 @@ class IndirectCallTransformer
// path.
if (origCall->IsVirtualVtable())
{
// In R2R all virtual vtable calls should go through VSD, so we should not get here.
assert(!compiler->opts.IsReadyToRun() || compiler->IsTargetAbi(CORINFO_NATIVEAOT_ABI));

GenTree* tarTree = compiler->fgExpandVirtualVtableCallTarget(origCall);

CORINFO_METHOD_HANDLE methHnd = guardedInfo->guardedMethodHandle;
Expand Down Expand Up @@ -815,8 +818,15 @@ class IndirectCallTransformer
call->gtCallType = CT_USER_FUNC;
call->gtCallMoreFlags |= GTF_CALL_M_DEVIRTUALIZED;
call->gtCallMoreFlags &= ~GTF_CALL_M_DELEGATE_INV;
// TODO-GDV: To support R2R we need to get the entry point
// here. We should unify with the tail of impDevirtualizeCall.

#ifdef FEATURE_READYTORUN
if (compiler->opts.IsReadyToRun())
{
CORINFO_CONST_LOOKUP entryPoint;
compiler->info.compCompHnd->getFunctionEntryPoint(methodHnd, &entryPoint);
call->setEntryPoint(entryPoint);
}
#endif

if (origCall->IsVirtual())
{
Expand Down
26 changes: 13 additions & 13 deletions src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3280,9 +3280,6 @@ private uint getThreadTLSIndex(ref void* ppIndirection)
}
}

private void getFunctionFixedEntryPoint(CORINFO_METHOD_STRUCT_* ftn, bool isUnsafeFunctionPointer, ref CORINFO_CONST_LOOKUP pResult)
{ throw new NotImplementedException("getFunctionFixedEntryPoint"); }

#pragma warning disable CA1822 // Mark members as static
private CorInfoHelpFunc getLazyStringLiteralHelper(CORINFO_MODULE_STRUCT_* handle)
#pragma warning restore CA1822 // Mark members as static
Expand Down Expand Up @@ -3965,7 +3962,7 @@ private uint getJitFlags(ref CORJIT_FLAGS flags, uint sizeInBytes)

private MemoryStream _cachedMemoryStream = new MemoryStream();

public static void ComputeJitPgoInstrumentationSchema(Func<object, IntPtr> objectToHandle, PgoSchemaElem[] pgoResultsSchemas, out PgoInstrumentationSchema[] nativeSchemas, MemoryStream instrumentationData, Func<TypeDesc, bool> typeFilter = null)
public static void ComputeJitPgoInstrumentationSchema(Func<object, IntPtr> objectToHandle, PgoSchemaElem[] pgoResultsSchemas, out PgoInstrumentationSchema[] nativeSchemas, MemoryStream instrumentationData, Func<TypeDesc, bool> typeFilter = null, Func<MethodDesc, bool> methodFilter = null)
{
nativeSchemas = new PgoInstrumentationSchema[pgoResultsSchemas.Length];
instrumentationData.SetLength(0);
Expand Down Expand Up @@ -4009,11 +4006,11 @@ public static void ComputeJitPgoInstrumentationSchema(Func<object, IntPtr> objec

if (typeVal.AsType != null && (typeFilter == null || typeFilter(typeVal.AsType)))
{
ptrVal = (IntPtr)objectToHandle(typeVal.AsType);
ptrVal = objectToHandle(typeVal.AsType);
}
else if (typeVal.AsMethod != null)
else if (typeVal.AsMethod != null && (methodFilter == null || methodFilter(typeVal.AsMethod)))
{
ptrVal = (IntPtr)objectToHandle(typeVal.AsMethod);
ptrVal = objectToHandle(typeVal.AsMethod);
}
else
{
Expand Down Expand Up @@ -4059,13 +4056,16 @@ private HRESULT getPgoInstrumentationResults(CORINFO_METHOD_STRUCT_* ftnHnd, ref
}
else
{
#pragma warning disable SA1001, SA1113, SA1115 // Commas should be spaced correctly
ComputeJitPgoInstrumentationSchema(ObjectToHandle, pgoResultsSchemas, out var nativeSchemas, _cachedMemoryStream
#if !READYTORUN
, _compilation.CanConstructType
Func<TypeDesc, bool> typeFilter = null;
Func<MethodDesc, bool> methodFilter = null;

#if READYTORUN
methodFilter = CanGetEntryPoint;
#else
typeFilter = _compilation.CanConstructType;
#endif
);
#pragma warning restore SA1001, SA1113, SA1115 // Commas should be spaced correctly

ComputeJitPgoInstrumentationSchema(ObjectToHandle, pgoResultsSchemas, out var nativeSchemas, _cachedMemoryStream, typeFilter, methodFilter);

var instrumentationData = _cachedMemoryStream.ToArray();
pgoResults.pInstrumentationData = (byte*)GetPin(instrumentationData);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
using ILCompiler.IBC;
using Internal.IL;
Expand Down Expand Up @@ -199,7 +200,7 @@ private PgoSchemaElem[] SynthesizeSchema(Compilation comp, MethodDesc method)
if (targetMeth == null)
continue;

if (targetMeth.Signature.IsStatic || !targetMeth.IsTypicalMethodDefinition)
if (targetMeth.Signature.IsStatic)
continue;

bool isDelegateInvoke = targetMeth.OwningType.IsDelegate && targetMeth.Name == "Invoke";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1098,9 +1098,64 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum)
return _compilation.NodeFactory.GetReadyToRunHelperCell(id);
}

private bool CanGetEntryPoint(MethodDesc md)
{
if (!_compilation.CompilationModuleGroup.VersionsWithMethodBody(md))
return false;

// TODO: Currently generic instantiations are not supported.
if (!md.IsTypicalMethodDefinition)
return false;

if (md is not EcmaMethod ecmaMethod)
return false;

return true;
}

// Used to get entry points for methods for delegate GDV, so we only
// expect to see these used rarely. JIT needs both the prestub that is
// stored in the delegate and the R2R entry point that it can put on
// the devirtualized call.
// We could give it the prestub in both cases but in case the
// devirtualized call is not inlined the R2R entry point has better
// steady state performance.
private CORINFO_CONST_LOOKUP GetFunctionEntryPoint(CORINFO_METHOD_STRUCT_* ftn, bool prestub)
{
MethodDesc md = HandleToObject(ftn);

if (!CanGetEntryPoint(md))
{
// We only expect this to be called in rare situations:
// 1. For delegate GDV as part of the guard. We prefilter GDV data so that we do not fail it here.
// 2. For GT_JMP, which is very rare so we have no problem falling back to runtime JIT.
throw new RequiresRuntimeJitException($"Cannot get entry point for {md}");
}

EcmaMethod ecmaMethod = (EcmaMethod)md;

ModuleToken module = new ModuleToken(ecmaMethod.Module, ecmaMethod.Handle);
MethodWithToken mt = new MethodWithToken(md, module, null, false, null);

PrepareForUseAsAFunctionPointer(md);

return
CreateConstLookupToSymbol(
_compilation.NodeFactory.MethodEntrypoint(
mt,
isInstantiatingStub: false,
isPrecodeImportRequired: prestub,
isJumpableImportRequired: false));
}

private void getFunctionEntryPoint(CORINFO_METHOD_STRUCT_* ftn, ref CORINFO_CONST_LOOKUP pResult, CORINFO_ACCESS_FLAGS accessFlags)
{
throw new RequiresRuntimeJitException(HandleToObject(ftn).ToString());
pResult = GetFunctionEntryPoint(ftn, prestub: false);
}

private void getFunctionFixedEntryPoint(CORINFO_METHOD_STRUCT_* ftn, bool isUnsafeFunctionPointer, ref CORINFO_CONST_LOOKUP pResult)
{
pResult = GetFunctionEntryPoint(ftn, prestub: true);
}

private bool canTailCall(CORINFO_METHOD_STRUCT_* callerHnd, CORINFO_METHOD_STRUCT_* declaredCalleeHnd, CORINFO_METHOD_STRUCT_* exactCalleeHnd, bool fIsTailPrefix)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ public class ProfileDataManager
public ProfileDataManager(IEnumerable<string> mibcFiles,
CompilerTypeSystemContext context)
{
List<ProfileData> _inputData = new List<ProfileData>();
List<ProfileData> inputData = new List<ProfileData>();

foreach (string file in mibcFiles)
{
using (PEReader peReader = MIbcProfileParser.OpenMibcAsPEReader(file))
{
_inputData.Add(MIbcProfileParser.ParseMIbcFile(context, peReader, null, null));
inputData.Add(MIbcProfileParser.ParseMIbcFile(context, peReader, null, null));
}
}

// Merge all data together
foreach (ProfileData profileData in _inputData)
foreach (ProfileData profileData in inputData)
{
ProfileData.MergeProfileData(_mergedProfileData, profileData);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,11 @@ method.OwningType is MetadataType mdType &&
pResult = CreateConstLookupToSymbol(_compilation.NodeFactory.MethodEntrypoint(method));
}

private void getFunctionFixedEntryPoint(CORINFO_METHOD_STRUCT_* ftn, bool isUnsafeFunctionPointer, ref CORINFO_CONST_LOOKUP pResult)
{
getFunctionEntryPoint(ftn, ref pResult, CORINFO_ACCESS_FLAGS.CORINFO_ACCESS_LDFTN);
}

private bool canTailCall(CORINFO_METHOD_STRUCT_* callerHnd, CORINFO_METHOD_STRUCT_* declaredCalleeHnd, CORINFO_METHOD_STRUCT_* exactCalleeHnd, bool fIsTailPrefix)
{
// Assume we can tail call unless proved otherwise
Expand Down