Skip to content

Commit

Permalink
Add EH filters for generic catch(T) blocks (dotnet#72721)
Browse files Browse the repository at this point in the history
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
  • Loading branch information
EgorBo and jkotas authored Jul 29, 2022
1 parent a01aea9 commit 4cd3e78
Show file tree
Hide file tree
Showing 31 changed files with 389 additions and 317 deletions.
1 change: 1 addition & 0 deletions docs/design/coreclr/botr/readytorun-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,7 @@ enum ReadyToRunHelper
READYTORUN_HELPER_GenericGcTlsBase = 0x66,
READYTORUN_HELPER_GenericNonGcTlsBase = 0x67,
READYTORUN_HELPER_VirtualFuncPtr = 0x68,
READYTORUN_HELPER_IsInstanceOfException = 0x69,

// Long mul/div/shift ops
READYTORUN_HELPER_LMul = 0xC0,
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,8 @@ enum CorInfoHelpFunc
CORINFO_HELP_CHKCASTCLASS_SPECIAL, // Optimized helper for classes. Assumes that the trivial cases
// has been taken care of by the inlined check

CORINFO_HELP_ISINSTANCEOF_EXCEPTION,

CORINFO_HELP_BOX, // Fast box helper. Only possible exception is OutOfMemory
CORINFO_HELP_BOX_NULLABLE, // special form of boxing for Nullable<T>
CORINFO_HELP_UNBOX,
Expand Down
10 changes: 5 additions & 5 deletions src/coreclr/inc/jiteeversionguid.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ typedef const GUID *LPCGUID;
#define GUID_DEFINED
#endif // !GUID_DEFINED

constexpr GUID JITEEVersionIdentifier = { /* 0d853657-7a01-421f-b1b0-d22a8e691441 */
0x0d853657,
0x7a01,
0x421f,
{0xb1, 0xb0, 0xd2, 0x2a, 0x8e, 0x69, 0x14, 0x41}
constexpr GUID JITEEVersionIdentifier = { /* 4efa8fe2-8489-4b61-aac9-b4df74af15b7 */
0x4efa8fe2,
0x8489,
0x4b61,
{0xaa, 0xc9, 0xb4, 0xdf, 0x74, 0xaf, 0x15, 0xb7}
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/inc/jithelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@
DYNAMICJITHELPER(CORINFO_HELP_CHKCASTANY, NULL, CORINFO_HELP_SIG_REG_ONLY)
DYNAMICJITHELPER(CORINFO_HELP_CHKCASTCLASS_SPECIAL, NULL, CORINFO_HELP_SIG_REG_ONLY)

JITHELPER(CORINFO_HELP_ISINSTANCEOF_EXCEPTION, JIT_IsInstanceOfException, CORINFO_HELP_SIG_REG_ONLY)

DYNAMICJITHELPER(CORINFO_HELP_BOX, JIT_Box, CORINFO_HELP_SIG_REG_ONLY)
JITHELPER(CORINFO_HELP_BOX_NULLABLE, JIT_Box, CORINFO_HELP_SIG_REG_ONLY)
DYNAMICJITHELPER(CORINFO_HELP_UNBOX, NULL, CORINFO_HELP_SIG_REG_ONLY)
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/inc/readytorun.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

// Keep these in sync with src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs
#define READYTORUN_MAJOR_VERSION 0x0007
#define READYTORUN_MINOR_VERSION 0x0000
#define READYTORUN_MINOR_VERSION 0x0001

#define MINIMUM_READYTORUN_MAJOR_VERSION 0x006

Expand Down Expand Up @@ -329,6 +329,7 @@ enum ReadyToRunHelper
READYTORUN_HELPER_GenericGcTlsBase = 0x66,
READYTORUN_HELPER_GenericNonGcTlsBase = 0x67,
READYTORUN_HELPER_VirtualFuncPtr = 0x68,
READYTORUN_HELPER_IsInstanceOfException = 0x69,

// Long mul/div/shift ops
READYTORUN_HELPER_LMul = 0xC0,
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/inc/readytorunhelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ HELPER(READYTORUN_HELPER_GenericGcTlsBase, CORINFO_HELP_GETGENERICS_GCT
HELPER(READYTORUN_HELPER_GenericNonGcTlsBase, CORINFO_HELP_GETGENERICS_NONGCTHREADSTATIC_BASE,)

HELPER(READYTORUN_HELPER_VirtualFuncPtr, CORINFO_HELP_VIRTUAL_FUNC_PTR, )
HELPER(READYTORUN_HELPER_IsInstanceOfException, CORINFO_HELP_ISINSTANCEOF_EXCEPTION, )

HELPER(READYTORUN_HELPER_LMul, CORINFO_HELP_LMUL, )
HELPER(READYTORUN_HELPER_LMulOfv, CORINFO_HELP_LMUL_OVF, )
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -2216,6 +2216,8 @@ class Compiler
bool fgNormalizeEHCase2();
bool fgNormalizeEHCase3();

void fgCreateFiltersForGenericExceptions();

void fgCheckForLoopsInHandlers();

#ifdef DEBUG
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/jit/flowgraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2590,6 +2590,10 @@ void Compiler::fgAddInternal()
{
noway_assert(!compIsForInlining());

// For runtime determined Exception types we're going to emit a fake EH filter with isinst for this
// type with a runtime lookup
fgCreateFiltersForGenericExceptions();

// The backend requires a scratch BB into which it can safely insert a P/Invoke method prolog if one is
// required. Similarly, we need a scratch BB for poisoning. Create it here.
if (compMethodRequiresPInvokeFrame() || compShouldPoisonFrame())
Expand Down
87 changes: 87 additions & 0 deletions src/coreclr/jit/jiteh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2506,6 +2506,93 @@ bool Compiler::fgNormalizeEHCase2()
return modified;
}

//------------------------------------------------------------------------
// fgCreateFiltersForGenericExceptions:
// For Exception types which require runtime lookup it creates a "fake" single-block
// EH filter that performs "catchArg isinst T!!" and in case of success forwards to the
// original EH handler.
//

void Compiler::fgCreateFiltersForGenericExceptions()
{
for (unsigned ehNum = 0; ehNum < compHndBBtabCount; ehNum++)
{
EHblkDsc* eh = ehGetDsc(ehNum);
if (eh->ebdHandlerType == EH_HANDLER_CATCH)
{
// Resolve Exception type and check if it needs a runtime lookup
CORINFO_RESOLVED_TOKEN resolvedToken;
resolvedToken.tokenContext = impTokenLookupContextHandle;
resolvedToken.tokenScope = info.compScopeHnd;
resolvedToken.token = eh->ebdTyp;
resolvedToken.tokenType = CORINFO_TOKENKIND_Casting;
info.compCompHnd->resolveToken(&resolvedToken);

CORINFO_GENERICHANDLE_RESULT embedInfo;
info.compCompHnd->embedGenericHandle(&resolvedToken, true, &embedInfo);
if (!embedInfo.lookup.lookupKind.needsRuntimeLookup)
{
// Exception type does not need runtime lookup
continue;
}

// Create a new bb for the fake filter
BasicBlock* filterBb = bbNewBasicBlock(BBJ_EHFILTERRET);
BasicBlock* handlerBb = eh->ebdHndBeg;

// Now we need to spill CATCH_ARG (it should be the first thing evaluated)
GenTree* arg = new (this, GT_CATCH_ARG) GenTree(GT_CATCH_ARG, TYP_REF);
arg->gtFlags |= GTF_ORDER_SIDEEFF;
unsigned tempNum = lvaGrabTemp(false DEBUGARG("SpillCatchArg"));
lvaTable[tempNum].lvType = TYP_REF;
GenTree* argAsg = gtNewTempAssign(tempNum, arg);
arg = gtNewLclvNode(tempNum, TYP_REF);
fgInsertStmtAtBeg(filterBb, gtNewStmt(argAsg, handlerBb->firstStmt()->GetDebugInfo()));

// Create "catchArg is TException" tree
GenTree* runtimeLookup;
if (embedInfo.lookup.runtimeLookup.indirections == CORINFO_USEHELPER)
{
GenTree* ctxTree = getRuntimeContextTree(embedInfo.lookup.lookupKind.runtimeLookupKind);
runtimeLookup = impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_GENERIC_HANDLE,
TYP_I_IMPL, &embedInfo.lookup.lookupKind, ctxTree);
}
else
{
runtimeLookup = getTokenHandleTree(&resolvedToken, true);
}
GenTree* isInstOfT = gtNewHelperCallNode(CORINFO_HELP_ISINSTANCEOF_EXCEPTION, TYP_INT, runtimeLookup, arg);
GenTree* retFilt = gtNewOperNode(GT_RETFILT, TYP_INT, isInstOfT);

// Insert it right before the handler (and make it a pred of the handler)
fgInsertBBbefore(handlerBb, filterBb);
fgAddRefPred(handlerBb, filterBb);
fgNewStmtAtEnd(filterBb, retFilt, handlerBb->firstStmt()->GetDebugInfo());

filterBb->bbCatchTyp = BBCT_FILTER;
filterBb->bbCodeOffs = handlerBb->bbCodeOffs;
filterBb->bbHndIndex = handlerBb->bbHndIndex;
filterBb->bbTryIndex = handlerBb->bbTryIndex;
filterBb->bbJumpDest = handlerBb;
filterBb->bbSetRunRarely();
filterBb->bbFlags |= BBF_INTERNAL | BBF_DONT_REMOVE;

handlerBb->bbCatchTyp = BBCT_FILTER_HANDLER;
eh->ebdHandlerType = EH_HANDLER_FILTER;
eh->ebdFilter = filterBb;

#ifdef DEBUG
if (verbose)
{
JITDUMP("EH%d: Adding EH filter block " FMT_BB " in front of generic handler " FMT_BB ":\n", ehNum,
filterBb->bbNum, handlerBb->bbNum);
fgDumpBlock(filterBb);
}
#endif // DEBUG
}
}
}

bool Compiler::fgNormalizeEHCase3()
{
bool modified = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -847,7 +847,7 @@ private static bool ShouldTypedClauseCatchThisException(object exception, Method
AssertNotRuntimeObject(pClauseType);
#endif

return TypeCast.IsInstanceOfClass(pClauseType, exception) != null;
return TypeCast.IsInstanceOfException(pClauseType, exception);
}

private static void InvokeSecondPass(ref ExInfo exInfo, uint idxStart)
Expand Down
37 changes: 37 additions & 0 deletions src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,43 @@ public static unsafe object IsInstanceOf(MethodTable* pTargetType, object obj)
return IsInstanceOfClass(pTargetType, obj);
}

[RuntimeExport("RhTypeCast_IsInstanceOfException")]
public static unsafe bool IsInstanceOfException(MethodTable* pTargetType, object? obj)
{
// Based on IsInstanceOfClass_Helper

if (obj == null)
return false;

MethodTable* pObjType = obj.GetMethodTable();

if (pTargetType->IsCloned)
pTargetType = pTargetType->CanonicalEEType;

if (pObjType->IsCloned)
pObjType = pObjType->CanonicalEEType;

if (pObjType == pTargetType)
return true;

// arrays can be cast to System.Object and System.Array
if (pObjType->IsArray)
return WellKnownEETypes.IsSystemObject(pTargetType) || WellKnownEETypes.IsSystemArray(pTargetType);

while (true)
{
pObjType = pObjType->NonClonedNonArrayBaseType;
if (pObjType == null)
return false;

if (pObjType->IsCloned)
pObjType = pObjType->CanonicalEEType;

if (pObjType == pTargetType)
return true;
}
}

[RuntimeExport("RhTypeCast_CheckCast")]
public static unsafe object CheckCast(MethodTable* pTargetType, object obj)
{
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ struct ReadyToRunHeaderConstants
static const uint32_t Signature = 0x00525452; // 'RTR'

static const uint32_t CurrentMajorVersion = 7;
static const uint32_t CurrentMinorVersion = 0;
static const uint32_t CurrentMinorVersion = 1;
};

struct ReadyToRunHeader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,10 @@ internal static unsafe object IsInstanceOf(EETypePtr pTargetType, object obj)
[RuntimeImport(RuntimeLibrary, "RhTypeCast_IsInstanceOfInterface")]
internal static extern unsafe object IsInstanceOfInterface(MethodTable* pTargetType, object obj);

[MethodImpl(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhTypeCast_IsInstanceOfException")]
internal static extern unsafe bool IsInstanceOfException(MethodTable* pTargetType, object obj);

internal static unsafe object IsInstanceOfInterface(EETypePtr pTargetType, object obj)
=> IsInstanceOfInterface(pTargetType.ToPointer(), obj);

Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal struct ReadyToRunHeaderConstants
public const uint Signature = 0x00525452; // 'RTR'

public const ushort CurrentMajorVersion = 7;
public const ushort CurrentMinorVersion = 0;
public const ushort CurrentMinorVersion = 1;
}

#pragma warning disable 0169
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ public enum ReadyToRunHelper
GenericGcTlsBase = 0x66,
GenericNonGcTlsBase = 0x67,
VirtualFuncPtr = 0x68,
IsInstanceOfException = 0x69,

// Long mul/div/shift ops
LMul = 0xC0,
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ which is the right helper to use to allocate an object of a given type. */
CORINFO_HELP_CHKCASTCLASS_SPECIAL, // Optimized helper for classes. Assumes that the trivial cases
// has been taken care of by the inlined check

CORINFO_HELP_ISINSTANCEOF_EXCEPTION,

CORINFO_HELP_BOX, // Fast box helper. Only possible exception is OutOfMemory
CORINFO_HELP_BOX_NULLABLE, // special form of boxing for Nullable<T>
CORINFO_HELP_UNBOX,
Expand Down
18 changes: 0 additions & 18 deletions src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -737,24 +737,6 @@ private bool Get_CORINFO_METHOD_INFO(MethodDesc method, MethodIL methodIL, CORIN
Get_CORINFO_SIG_INFO(method, sig: &methodInfo->args, methodIL);
Get_CORINFO_SIG_INFO(methodIL.GetLocals(), &methodInfo->locals);

#if READYTORUN
if ((methodInfo->options & CorInfoOptions.CORINFO_GENERICS_CTXT_MASK) != 0)
{
foreach (var region in exceptionRegions)
{
if (region.Kind == ILExceptionRegionKind.Catch)
{
TypeDesc catchType = (TypeDesc)methodIL.GetObject(region.ClassToken);
if (catchType.IsCanonicalSubtype(CanonicalFormKind.Any))
{
methodInfo->options |= CorInfoOptions.CORINFO_GENERICS_CTXT_KEEP_ALIVE;
break;
}
}
}
}
#endif

return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ public static void GetEntryPoint(TypeSystemContext context, ReadyToRunHelper id,
case ReadyToRunHelper.CheckInstanceAny:
mangledName = "RhTypeCast_IsInstanceOf";
break;
case ReadyToRunHelper.IsInstanceOfException:
mangledName = "RhTypeCast_IsInstanceOfException";
break;
case ReadyToRunHelper.CheckCastInterface:
mangledName = "RhTypeCast_CheckCastInterface";
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,15 +196,15 @@ private void StartImportingBasicBlock(BasicBlock basicBlock)
if (region.Kind == ILExceptionRegionKind.Filter)
MarkBasicBlock(_basicBlocks[region.FilterOffset]);

// Once https://github.com/dotnet/corert/issues/3460 is done, this should be deleted.
// Throwing InvalidProgram is not great, but we want to do *something* if this happens
// because doing nothing means problems at runtime. This is not worth piping a
// a new exception with a fancy message for.
if (region.Kind == ILExceptionRegionKind.Catch)
{
TypeDesc catchType = (TypeDesc)_methodIL.GetObject(region.ClassToken);
if (catchType.IsRuntimeDeterminedSubtype)
ThrowHelper.ThrowInvalidProgramException();
{
// For runtime determined Exception types we're going to emit a fake EH filter with isinst for this
// type with a runtime lookup
_dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandleForCasting, catchType), "EH filter");
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,9 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum)
id = ReadyToRunHelper.GetRuntimeTypeHandle;
break;

case CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOF_EXCEPTION:
id = ReadyToRunHelper.IsInstanceOfException;
break;
case CorInfoHelpFunc.CORINFO_HELP_BOX:
id = ReadyToRunHelper.Box;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1782,6 +1782,10 @@ private void ParseHelper(StringBuilder builder)
builder.Append("CHECK_INSTANCE_ANY");
break;

case ReadyToRunHelper.IsInstanceOfException:
builder.Append("SIMPLE_ISINSTANCE_OF");
break;

case ReadyToRunHelper.GenericGcStaticBase:
builder.Append("GENERIC_GC_STATIC_BASE");
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,9 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum)
id = ReadyToRunHelper.AreTypesEquivalent;
break;

case CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOF_EXCEPTION:
id = ReadyToRunHelper.IsInstanceOfException;
break;
case CorInfoHelpFunc.CORINFO_HELP_BOX:
id = ReadyToRunHelper.Box;
break;
Expand Down Expand Up @@ -906,13 +909,6 @@ private ObjectNode.ObjectData EncodeEHInfo()
var methodIL = (MethodIL)HandleToObject((IntPtr)_methodScope);
var type = (TypeDesc)methodIL.GetObject((int)clause.ClassTokenOrOffset);

// Once https://github.com/dotnet/corert/issues/3460 is done, this should be an assert.
// Throwing InvalidProgram is not great, but we want to do *something* if this happens
// because doing nothing means problems at runtime. This is not worth piping a
// a new exception with a fancy message for.
if (type.IsCanonicalSubtype(CanonicalFormKind.Any))
ThrowHelper.ThrowInvalidProgramException();

var typeSymbol = _compilation.NecessaryTypeSymbolIfPossible(type);

RelocType rel = (_compilation.NodeFactory.Target.IsWindows) ?
Expand Down
Loading

0 comments on commit 4cd3e78

Please sign in to comment.