Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8d9b3ef
NativeAOT: Implement a fake EH filter for generic exceptions
EgorBo Jul 23, 2022
7d4bcbd
Add smoke test
EgorBo Jul 23, 2022
39752c7
Update GenericExceptions.cs
EgorBo Jul 23, 2022
3a045da
CI test
EgorBo Jul 23, 2022
fd57c28
Merge branch 'nativeaot-eh-generic-exception' of github.com:EgorBo/ru…
EgorBo Jul 23, 2022
f24208a
Address feedback & fix AVE
EgorBo Jul 23, 2022
f7c55a7
Clean up
EgorBo Jul 23, 2022
861c6c3
Fix runtime lookup for NativeAOT & Crossgen
EgorBo Jul 23, 2022
8a72927
Another attempt to fix runtime lookup (now should be working)
EgorBo Jul 23, 2022
58d3b77
Remove tests from issues.targets
EgorBo Jul 24, 2022
54763a7
Move fgCreateFiltersForGenericExceptions to fgAddInternal
EgorBo Jul 24, 2022
a32b92a
simplify runtime lookup
EgorBo Jul 24, 2022
3668e25
Address feedback
EgorBo Jul 24, 2022
49635eb
Implement HELPER_SimpleIsInstanceOf
EgorBo Jul 25, 2022
f8a50a5
Merge branch 'main' of github.com:dotnet/runtime into nativeaot-eh-ge…
EgorBo Jul 25, 2022
2a47ff9
Clean up
EgorBo Jul 25, 2022
bee1fb3
Add Jan's IL test
EgorBo Jul 25, 2022
10ca969
Address feedback (NativeAOT's part is not finished yet)
EgorBo Jul 25, 2022
79e87b8
remove dead code
EgorBo Jul 25, 2022
5420035
Update src/tests/JIT/Generics/Exceptions/GenericCatchInterfaceProgram.cs
EgorBo Jul 25, 2022
9cf10d2
Fix test
EgorBo Jul 25, 2022
fd3130e
NativeAOT fix
EgorBo Jul 25, 2022
22f2520
remove "variance" block
EgorBo Jul 25, 2022
9110e5f
Address feedback
EgorBo Jul 25, 2022
d3c01cb
Remove IsPolyType
EgorBo Jul 25, 2022
aaa075e
rename csproj to ilproj
EgorBo Jul 25, 2022
98ce006
Update src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanne…
EgorBo Jul 26, 2022
5c7c157
Disable test on mono
EgorBo Jul 26, 2022
40ca568
Merge branch 'main' of github.com:dotnet/runtime into nativeaot-eh-ge…
EgorBo Jul 26, 2022
d13147d
Update src/coreclr/jit/jiteh.cpp
EgorBo Jul 28, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -2217,6 +2217,7 @@ class Compiler
bool fgNormalizeEHCase1();
bool fgNormalizeEHCase2();
bool fgNormalizeEHCase3();
bool fgNormalizeEHCase_NativeAot();

void fgCheckForLoopsInHandlers();

Expand Down
82 changes: 82 additions & 0 deletions src/coreclr/jit/jiteh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2127,6 +2127,12 @@ void Compiler::fgNormalizeEH()

#endif // 0

// For NativeAOT we need to create a fake exception filter for generic handlers
if (/*IsTargetAbi(CORINFO_NATIVEAOT_ABI) &&*/ fgNormalizeEHCase_NativeAot())
{
modified = true;
}

INDEBUG(fgNormalizeEHDone = true;)

if (modified)
Expand Down Expand Up @@ -2506,6 +2512,82 @@ bool Compiler::fgNormalizeEHCase2()
return modified;
}

//------------------------------------------------------------------------
// fgNormalizeEHCase_NativeAot:
// 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.
//
// Return Value:
// true if basic block layout was changed
//

bool Compiler::fgNormalizeEHCase_NativeAot()
{
// CI test, let's run the logic for JIT
//assert(IsTargetAbi(CORINFO_NATIVEAOT_ABI));

bool modified = false;
for (unsigned XTnum = 0; XTnum < compHndBBtabCount; XTnum++)
{
EHblkDsc* eh = ehGetDsc(XTnum);
if ((eh->ebdHandlerType == EH_HANDLER_CATCH) && !eh->HasFilter())
{
CORINFO_RESOLVED_TOKEN resolvedToken;
resolvedToken.tokenContext = impTokenLookupContextHandle;
resolvedToken.tokenScope = info.compScopeHnd;
resolvedToken.token = eh->ebdTyp;
resolvedToken.tokenType = CORINFO_TOKENKIND_Casting;
info.compCompHnd->resolveToken(&resolvedToken);

bool runtimeLookupNeeded = false;
GenTree* runtimeLookup = impTokenToHandle(&resolvedToken, &runtimeLookupNeeded, false);
if (!runtimeLookupNeeded)
{
// Exception type does not need runtime lookup
continue;
}

// Create a new bb for the fake filter
BasicBlock* handlerBb = eh->ebdHndBeg;
BasicBlock* filterBb = bbNewBasicBlock(BBJ_EHFILTERRET);
filterBb->bbCatchTyp = BBCT_FILTER;
filterBb->bbCodeOffs = handlerBb->bbCodeOffs; // Technically, we're in the handler
filterBb->bbHndIndex = handlerBb->bbHndIndex;
filterBb->bbTryIndex = handlerBb->bbTryIndex;
filterBb->bbJumpDest = handlerBb;
filterBb->bbSetRunRarely();
filterBb->bbFlags |= BBF_INTERNAL | BBF_DONT_REMOVE;

// 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);
filterBb->bbStkTempsIn = tempNum;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit -- you don't need to set this.

Suggested change
filterBb->bbStkTempsIn = tempNum;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm.. I think I had to actually, let me see if CI is happy then

fgInsertStmtAtBeg(filterBb, gtNewStmt(argAsg));

// Create "catchArg is TException" tree
GenTree* isInstOfT = gtNewHelperCallNode(CORINFO_HELP_ISINSTANCEOFANY, TYP_REF, runtimeLookup, arg);
GenTree* cmp = gtNewOperNode(GT_NE, TYP_INT, isInstOfT, gtNewNull());
GenTree* retFilt = gtNewOperNode(GT_RETFILT, TYP_INT, cmp);

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

handlerBb->bbCatchTyp = BBCT_FILTER_HANDLER;
eh->ebdHandlerType = EH_HANDLER_FILTER;
eh->ebdFilter = filterBb;
modified = true;
}
}
return modified;
}

bool Compiler::fgNormalizeEHCase3()
{
bool modified = false;
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 inside 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 @@ -906,13 +906,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
4 changes: 4 additions & 0 deletions src/tests/nativeaot/SmokeTests/Exceptions/Exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Diagnostics;
using System.Text;



public class BringUpTest
{
const int Pass = 100;
Expand All @@ -30,6 +32,8 @@ public static int Main()
new BringUpTest().ToString();
}

GenericExceptions.RunTests();

int counter = 0;
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionEventHandler;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="Exceptions.cs" />
<Compile Include="GenericExceptions.cs" />
</ItemGroup>
</Project>
116 changes: 116 additions & 0 deletions src/tests/nativeaot/SmokeTests/Exceptions/GenericExceptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;

public class GenericExceptions
{
public static void RunTests()
{
AssertEqual(GenericExceptions_Case1<DivideByZeroException, NullReferenceException>(1, 0, null), 44);
AssertExpects<NullReferenceException>(() => GenericExceptions_Case1<DivideByZeroException, NotSupportedException>(1, 0, null));
GenericExceptions_Case1<DivideByZeroException, NotSupportedException>(1, 0, "not null");
AssertExpects<NullReferenceException>(() => GenericExceptions_Case1<InvalidOperationException, NotSupportedException>(1, 10, null));
AssertExpects<DivideByZeroException>(() => GenericExceptions_Case1<InvalidOperationException, NotSupportedException>(1, 0, "not null"));

AssertEqual(GenericExceptions_Case2<string, Array>(() => throw new MyException<string>()), 111);
AssertEqual(GenericExceptions_Case2<string, Array>(() => throw new MyException<Array>()), 222);
AssertEqual(GenericExceptions_Case2<string, Array>(() => throw new MyException<List<int>>()), 42);
AssertEqual(GenericExceptions_Case2<string, Array>(() => throw new MyException<GenericExceptions>()), 42);
AssertEqual(GenericExceptions_Case2<string, Array>(() => throw new InvalidOperationException()), 42);
AssertEqual(_counter, 24);
}

public static void AssertEqual(int a, int b)
{
if (a != b)
throw new InvalidOperationException($"{a} != {b}");
}

public static void AssertExpects<T>(Action action) where T : Exception
{
try
{
action();
}
catch (T)
{
return;
}
throw new InvalidOperationException($"{typeof(T)} was expected to be thrown");
}

public class MyException<T> : Exception { }

private static int _counter = 0;

public static int GenericExceptions_Case1<T1, T2>(int a, int b, object o)
where T1 : Exception
where T2 : Exception
{
try
{
_counter++;
return a / b;
}
catch (T1)
{
_counter++;
return 44;
}
finally
{
try
{
_counter++;
Console.WriteLine(o.ToString());
}
catch (T2 t)
{
_counter++;
Console.WriteLine(t.GetType().Name);
}
}
}

public static int GenericExceptions_Case2<T1, T2>(Action action)
{
try
{
action?.Invoke();
return 1;
}
catch (MyException<T1> e1)
{
_counter++;
int hc = 100;
if (e1.GetHashCode() > 1000) // just some BBs in the catch handler
hc += 100;
return 111;
}
catch (MyException<T2> e2)
{
_counter++;
int hc = 100;
if (e2.GetHashCode() > 1000) // just some BBs in the catch handler
hc += 100;
return 222;
}
catch (MyException<object> e2)
{
_counter++;
Console.WriteLine("re-throw");
throw;
}
catch
{
_counter++;
return 42;
}
finally
{
_counter++;
}
}
}