Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
85 changes: 1 addition & 84 deletions docs/design/coreclr/botr/clr-abi.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,7 @@ For Windows/x86 on NativeAOT and Linux/x86, funclets are used just like on other

## Cloned finallys

JIT64 attempts to speed the normal control flow by 'inlining' a called finally along the 'normal' control flow (i.e., leaving a try body in a non-exceptional manner via C# fall-through). Because the VM semantics for non-rude Thread.Abort dictate that handlers will not be aborted, the JIT must mark these 'inlined' finally bodies. These show up as special entries at the end of the EH tables and are marked with `COR_ILEXCEPTION_CLAUSE_FINALLY | COR_ILEXCEPTION_CLAUSE_DUPLICATED`, and the try_start, try_end, and handler_start are all the same: the start of the cloned finally.

RyuJit also implements finally cloning, for all supported architectures. However, the implementation does not yet handle the thread abort case; cloned finally bodies are not guaranteed to remain intact and are not reported to the runtime. Because of this, finally cloning is disabled for VMs that support thread abort (desktop clr).

JIT32 does not implement finally cloning.
RyuJIT attempts to speed the normal control flow by 'inlining' a called finally along the 'normal' control flow (i.e., leaving a try body in a non-exceptional manner via C# fall-through). This optimization is supported on all architectures.

## Invoking Finallys/Non-local exits

Expand Down Expand Up @@ -499,85 +495,6 @@ When the inner "throw new UserException4" is executed, the exception handling fi

Filters are invoked in the 1st pass of EH processing and as such execution might resume back at the faulting address, or in the filter-handler, or someplace else. Because the VM must allow GC's to occur during and after a filter invocation, but before the EH subsystem knows where it will resume, we need to keep everything alive at both the faulting address **and** within the filter. This is accomplished by 3 means: (1) the VM's stackwalker and GCInfoDecoder report as live both the filter frame and its corresponding parent frame, (2) the JIT encodes all stack slots that are live within the filter as being pinned, and (3) the JIT reports as live (and possible zero-initializes) anything live-out of the filter. Because of (1) it is likely that a stack variable that is live within the filter and the try body will be double reported. During the mark phase of the GC double reporting is not a problem. The problem only arises if the object is relocated: if the same location is reported twice, the GC will try to relocate the address stored at that location twice. Thus we prevent the object from being relocated by pinning it, which leads us to why we must do (2). (3) is done so that after the filter returns, we can still safely incur a GC before executing the filter-handler or any outer handler within the same frame. For the same reason, control must exit a filter region via its final block (in other words, a filter region must terminate with the instruction that leaves the filter region, and the program may not exit the filter region via other paths).

## Duplicated Clauses

Duplicated clauses are a special set of entries in the EH tables to assist the VM. Specifically, if handler 'A' is also protected by an outer EH clause 'B', then the JIT must emit a duplicated clause, a duplicate of 'B', that marks the whole handler 'A' (which is now lexically disjoint for the range of code for the corresponding try body 'A') as being protected by the handler for 'B'.

Duplicated clauses are not needed for x86 and for NativeAOT ABI.

During exception dispatch the VM uses these duplicated clauses to know when to skip any frames between the handler and its parent function. After skipping to the parent function, due to a duplicated clause, the VM searches for a regular/non-duplicate clause in the parent function. The order of duplicated clauses is important. They should appear after all of the main function clauses. They should still follow the normal sorting rules (inner-to-outer, top-to-bottom), but because the try-start/try-end will all be the same for a given handler, they should maintain the ordering, regarding inner-to-outer, as the corresponding original clause.

Example:

```
A: try {
B: ...
C: try {
D: ...
E: try {
F: ...
G: }
H: catch {
I: ...
J: }
K: ...
L: }
M: finally {
N: ...
O: }
P: ...
Q: }
R: catch {
S: ...
T: }
```

In MSIL this would generate 3 EH clauses:

```
.try E-G catch H-J
.try C-L finally M-O
.try A-Q catch R-T
```

The native code would be laid out as follows (the order of the handlers is irrelevant except they are after the main method body) with their corresponding (fake) native offsets:

```
A: -> 1
B: -> 2
C: -> 3
D: -> 4
E: -> 5
F: -> 6
G: -> 7
K: -> 8
L: -> 9
P: -> 10
Q: -> 11
H: -> 12
I: -> 13
J: -> 14
M: -> 15
N: -> 16
O: -> 17
R: -> 18
S: -> 19
T: -> 20
```

The native EH clauses would be listed as follows:

```
1. .try 5-7 catch 12-14 (top-most & inner-most first)
2. .try 3-9 finally 15-17 (top-most & next inner-most)
3. .try 1-11 catch 18-20 (top-most & outer-most)
4. .try 12-14 finally 15-17 duplicated (inner-most because clause 2 is inside clause 3, top-most because handler H-J is first)
5. .try 12-14 catch 18-20 duplicated
6. .try 15-17 catch 18-20
```

If the handlers were in a different order, then clause 6 might appear before clauses 4 and 5, but never in between.

## Clauses covering the same try region

Several consecutive clauses may cover the same `try` block. A clause covering the same region as the previous one is marked by the `COR_ILEXCEPTION_CLAUSE_SAMETRY` flag. When exception ex1 is thrown while running handler for another exception ex2 and the exception ex2 escapes the ex1's handler frame, this enables the runtime to skip clauses that cover the same `try` block as the clause that handled the ex1.
Expand Down
1 change: 0 additions & 1 deletion src/coreclr/debug/daccess/request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3458,7 +3458,6 @@ ClrDataAccess::TraverseEHInfo(CLRDATA_ADDRESS ip, DUMPEHINFO pFunc, LPVOID token
deh.tryEndOffset = EHClause.TryEndPC;
deh.handlerStartOffset = EHClause.HandlerStartPC;
deh.handlerEndOffset = EHClause.HandlerEndPC;
deh.isDuplicateClause = IsDuplicateClause(&EHClause);

if (!(pFunc)(i, EHCount, &deh, token))
{
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/ilasm/assembler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1129,7 +1129,7 @@ void Assembler::AddException(DWORD pcStart, DWORD pcEnd, DWORD pcHandler, DWORD
clause->SetHandlerLength(pcHandlerTo - pcHandler);
clause->SetClassToken(crException);

int flags = COR_ILEXCEPTION_CLAUSE_OFFSETLEN;
int flags = 0;
if (isFilter) {
flags |= COR_ILEXCEPTION_CLAUSE_FILTER;
}
Expand Down
5 changes: 2 additions & 3 deletions src/coreclr/inc/corhdr.h
Original file line number Diff line number Diff line change
Expand Up @@ -1136,12 +1136,11 @@ typedef struct IMAGE_COR_ILMETHOD_SECT_FAT
typedef enum CorExceptionFlag // definitions for the Flags field below (for both big and small)
{
COR_ILEXCEPTION_CLAUSE_NONE, // This is a typed handler
COR_ILEXCEPTION_CLAUSE_OFFSETLEN = 0x0000, // Deprecated
COR_ILEXCEPTION_CLAUSE_DEPRECATED = 0x0000, // Deprecated
COR_ILEXCEPTION_CLAUSE_FILTER = 0x0001, // If this bit is on, then this EH entry is for a filter
COR_ILEXCEPTION_CLAUSE_FINALLY = 0x0002, // This clause is a finally clause
COR_ILEXCEPTION_CLAUSE_FAULT = 0x0004, // Fault clause (finally that is called on exception only)
COR_ILEXCEPTION_CLAUSE_DUPLICATED = 0x0008, // duplicated clause. This clause was duplicated to a funclet which was pulled out of line
COR_ILEXCEPTION_CLAUSE_DUPLICATED = 0x0008, // Deprecated: Duplicated clause. This clause was duplicated to a funclet which was pulled out of line
COR_ILEXCEPTION_CLAUSE_SAMETRY = 0x0010, // This clause covers same try block as the previous one
} CorExceptionFlag;

/***********************************/
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -813,13 +813,14 @@ enum CORINFO_ACCESS_FLAGS
};

// These are the flags set on an CORINFO_EH_CLAUSE
// Keep values in sync with COR_ILEXCEPTION_CLAUSE flags
enum CORINFO_EH_CLAUSE_FLAGS
{
CORINFO_EH_CLAUSE_NONE = 0,
CORINFO_EH_CLAUSE_FILTER = 0x0001, // If this bit is on, then this EH entry is for a filter
CORINFO_EH_CLAUSE_FINALLY = 0x0002, // This clause is a finally clause
CORINFO_EH_CLAUSE_FAULT = 0x0004, // This clause is a fault clause
CORINFO_EH_CLAUSE_DUPLICATE = 0x0008, // Duplicated clause. This clause was duplicated to a funclet which was pulled out of line
// UNUSED = 0x0008,
CORINFO_EH_CLAUSE_SAMETRY = 0x0010, // This clause covers same try block as the previous one
};

Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/inc/dacprivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,7 @@ struct MSLAYOUT DACEHInfo
CLRDATA_ADDRESS tryEndOffset = 0;
CLRDATA_ADDRESS handlerStartOffset = 0;
CLRDATA_ADDRESS handlerEndOffset = 0;
BOOL isDuplicateClause = FALSE;
BOOL isDuplicateClause = FALSE; // unused
CLRDATA_ADDRESS filterOffset = 0; // valid when clauseType is EHFilter
BOOL isCatchAllHandler = FALSE; // valid when clauseType is EHTyped
CLRDATA_ADDRESS moduleAddr = 0; // when == 0 mtCatch contains a MethodTable, when != 0 tokCatch contains a type token
Expand Down
29 changes: 0 additions & 29 deletions src/coreclr/inc/eexcp.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,34 +112,5 @@ inline BOOL IsTypedHandler(EE_ILEXCEPTION_CLAUSE *EHClause)
return ! (IsFilterHandler(EHClause) || IsFaultOrFinally(EHClause));
}

inline BOOL IsDuplicateClause(EE_ILEXCEPTION_CLAUSE* pEHClause)
{
return pEHClause->Flags & COR_ILEXCEPTION_CLAUSE_DUPLICATED;
}

#if defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
// Finally is the only EH construct that can be part of the execution as being fall-through.
//
// "Cloned" finally is a construct that represents a finally block that is used as
// fall through for normal try-block execution. Such a "cloned" finally will:
//
// 1) Have its try-clause's Start and End PC the same as its handler's start PC (i.e. will have
// zero length try block), AND
// 2) Is marked duplicate
//
// Because of their fall-through nature, JIT guarantees that only finally constructs can be cloned,
// and not catch or fault (since they cannot be fallen through but are invoked as funclets).
//
// The cloned finally construct is also used to mark "call to finally" thunks that are not within
// the EH region protected by the finally, and also not within the enclosing region. This is done
// to prevent ThreadAbortException from creating an infinite loop of calling the same finally.
inline BOOL IsClonedFinally(EE_ILEXCEPTION_CLAUSE* pEHClause)
{
return ((pEHClause->TryStartPC == pEHClause->TryEndPC) &&
(pEHClause->TryStartPC == pEHClause->HandlerStartPC) &&
IsFinally(pEHClause) && IsDuplicateClause(pEHClause));
}
#endif // defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)

#endif // __eexcp_h__

10 changes: 5 additions & 5 deletions src/coreclr/inc/jiteeversionguid.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@

#include <minipal/guid.h>

constexpr GUID JITEEVersionIdentifier = { /* 72704ed3-9bc5-4e07-bfe9-a4e091812ba8 */
0x72704ed3,
0x9bc5,
0x4e07,
{0xbf, 0xe9, 0xa4, 0xe0, 0x91, 0x81, 0x2b, 0xa8}
constexpr GUID JITEEVersionIdentifier = { /* caad5bcc-1f14-4f63-81e6-61e7e88535d0 */
0xcaad5bcc,
0x1f14,
0x4f63,
{0x81, 0xe6, 0x61, 0xe7, 0xe8, 0x85, 0x35, 0xd0}
};

#endif // JIT_EE_VERSIONING_GUID_H
Loading
Loading