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

Fix unwind info for cold EH funclets on ARM64 #1930

Conversation

amanasifkhalid
Copy link
Member

Follow-up to #1923. When reserving and allocating unwind info on ARM64, we now consider the possibility that EH funclets are in the cold section. Below are some examples of reserving/allocating unwind info with this new behavior:

Main body split, no EH funclets:

reserveUnwindInfo(isFunclet=false, isColdCode=false, unwindSize=0xc)
reserveUnwindInfo(isFunclet=false, isColdCode=true, unwindSize=0x10)

allocUnwindInfo(pHotCode=0x00000231387F5530, pColdCode=0x0000000000000000, startOffset=0x0, endOffset=0x1c, unwindSize=0xc, pUnwindBlock=0x000002313AC97674, funKind=0 (main function))
allocUnwindInfo(pHotCode=0x00000231387F5530, pColdCode=0x00000231387F2A40, startOffset=0x0, endOffset=0x4c, unwindSize=0x10, pUnwindBlock=0x000002313AA80A57, funKind=0 (main function))

Main body split, with cold EH funclets:

reserveUnwindInfo(isFunclet=false, isColdCode=false, unwindSize=0x10)
reserveUnwindInfo(isFunclet=false, isColdCode=true, unwindSize=0x1c)
reserveUnwindInfo(isFunclet=true, isColdCode=true, unwindSize=0xc)

allocUnwindInfo(pHotCode=0x0000028CBABC5420, pColdCode=0x0000000000000000, startOffset=0x0, endOffset=0x58, unwindSize=0x10, pUnwindBlock=0x0000028CBD150D70, funKind=0 (main function))
allocUnwindInfo(pHotCode=0x0000028CBABC5420, pColdCode=0x0000028CBC82BC70, startOffset=0x0, endOffset=0x4d0, unwindSize=0x1c, pUnwindBlock=0x0000028CBD3DEC83, funKind=0 (main function))
allocUnwindInfo(pHotCode=0x0000028CBABC5420, pColdCode=0x0000028CBC82BC70, startOffset=0x4d0, endOffset=0x560, unwindSize=0xc, pUnwindBlock=0x0000028CBD150ED1, funKind=1 (handler))

Main body NOT split, with cold EH funclets:

reserveUnwindInfo(isFunclet=false, isColdCode=false, unwindSize=0x10)
reserveUnwindInfo(isFunclet=true, isColdCode=true, unwindSize=0xc)
reserveUnwindInfo(isFunclet=true, isColdCode=true, unwindSize=0xc)
reserveUnwindInfo(isFunclet=true, isColdCode=true, unwindSize=0xc)
reserveUnwindInfo(isFunclet=true, isColdCode=true, unwindSize=0xc)

allocUnwindInfo(pHotCode=0x0000024CCC700300, pColdCode=0x0000000000000000, startOffset=0x0, endOffset=0x588, unwindSize=0x10, pUnwindBlock=0x0000024CCE21BC27, funKind=0 (main function))
allocUnwindInfo(pHotCode=0x0000024CCC700300, pColdCode=0x0000024CCC7008F0, startOffset=0x0, endOffset=0x38, unwindSize=0xc, pUnwindBlock=0x0000024CCE21BD8C, funKind=1 (handler))
allocUnwindInfo(pHotCode=0x0000024CCC700300, pColdCode=0x0000024CCC7008F0, startOffset=0x38, endOffset=0x60, unwindSize=0xc, pUnwindBlock=0x0000024CCE21BEEC, funKind=1 (handler))
allocUnwindInfo(pHotCode=0x0000024CCC700300, pColdCode=0x0000024CCC7008F0, startOffset=0x60, endOffset=0x128, unwindSize=0xc, pUnwindBlock=0x0000024CCE21C04C, funKind=1 (handler))
allocUnwindInfo(pHotCode=0x0000024CCC700300, pColdCode=0x0000024CCC7008F0, startOffset=0x128, endOffset=0x160, unwindSize=0xc, pUnwindBlock=0x0000024CCE21C1AC, funKind=1 (handler))

I apologize if I'm stating the obvious here, but note that when the main body is split, unwindSize > 0 for the cold part's unwind info; it looks like we don't use chained unwind info on ARM64. If this is true, then we will likely have to modify the Scratch table's lookup implementation to correctly find cold functions/funclets on ARM64.

@amanasifkhalid
Copy link
Member Author

@cshung @BruceForstall PTAL, thank you!

@AndyAyersMS
Copy link
Member

it looks like we don't use chained unwind info on ARM64.

Is there some explanation for this?

@amanasifkhalid
Copy link
Member Author

Is there some explanation for this?

I do not know the answer to this, but perhaps chained unwind info was never considered for ARM64 since hot/cold splitting wasn't implemented previously. It may be more complicated to implement here since we split each function/funclet into 512KB fragments, but the current crossgen/VM implementation relies on the use of chained unwind info, so this would improve portability.

@BruceForstall
Copy link
Member

There's no concept of chained unwind for arm64.

Every function fragment (including "whole" functions) gets its own unique and separate RUNTIME_FUNCTION (.pdata) and .xdata entry (we never use the compact form; we always have .xdata). Funclets have both a prolog and epilog, so have both kinds of unwind codes. Fragments without a prolog have a "phantom" prolog. There is no reference from one to another. See https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling for details.

Chained unwind info for x64 is described here: https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64

@BruceForstall
Copy link
Member

nit: btw, fragments on arm64 are 1MB; on arm32 they are 512KB.

Comment on lines 317 to 327
if (isFunclet)
{
eeAllocUnwindInfo((BYTE*)pHotCode, (BYTE*)pColdCode, startOffset, endOffset, unwindCodeBytes, pUnwindBlock,
(CorJitFuncKind)func->funKind);
}
else
{
// If the function is split, EH funclets are always cold, so pass in pColdCode to indicate so.
eeAllocUnwindInfo((BYTE*)pHotCode, nullptr /* pColdCode */, startOffset, endOffset, unwindCodeBytes,
pUnwindBlock, (CorJitFuncKind)func->funKind);
}
Copy link
Member

Choose a reason for hiding this comment

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

I'm confused by this:

  1. unwindEmitFuncCFI() should never get called, so why update it?
  2. Do we have no incoming (function argument) indication if the funclet has a hot part? I guess we're currently assuming a funclet is always all cold. In that case, shouldn't we just skip the entire "hot" part here, and let the cold code below execute instead?
  3. It seems confusing to me that the "else" branch comment isn't in the "if" part instead.

Copy link
Member Author

Choose a reason for hiding this comment

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

The cold code path below seems to be specifically for chained unwind info (see unwindCodeBytes = 0, pUnwindBlock = nullptr). I thought I would lay the groundwork here for if/when we enable hot/cold splitting on NativeAOT, though it's probably better we leave it unimplemented until we start testing it. I'll remove it.

else
{
// If the function is split, EH funclets are always cold.
const bool isHotCode = (fgFirstColdBlock == nullptr);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const bool isHotCode = (fgFirstColdBlock == nullptr);
const bool isHotCode = (func->funKind == FUNC_ROOT) || (fgFirstColdBlock == nullptr);

then get rid of the if condition.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for catching that. This logic will probably be removed altogether in my next refactor, though.

Comment on lines 693 to 694
// func->uwiCold is used only for the cold part of the main function,
// and is always NULL for funclets.
Copy link
Member

Choose a reason for hiding this comment

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

There's something confusing about the model here. In particular, it looks like cold funclets have their unwind info in the "hot" uwi structure instead of having an empty uwi and putting their unwind info in the uwiCold structure.

Wouldn't it be better to introduce a notion of the "hot" unwind info (in uwi) being empty, if the entire funclet is cold?

One of the things I'm worried about here is distributing the logic about "if a function with EH is split then all funclets are cold" instead of just setting up the FuncInfoDsc with the right information and simply iterating over it.

Copy link
Member Author

@amanasifkhalid amanasifkhalid Jul 12, 2022

Choose a reason for hiding this comment

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

Good point. I was thinking it would make more sense to use uwiCold for cold EH funclets, but the current implementation always utilizes uwi (and thus makes the assumption uwi has unwind info), so this may require some non-trivial refactoring. If we're okay with the higher churn, I can try this out.

Edit: I kept uwi initialized for cold funclets and just initialized uwiCold for them, and this allowed for a simpler implementation.

@amanasifkhalid
Copy link
Member Author

Pushed changes addressing @BruceForstall's feedback. Most notably, cold EH funclets now use func->uwiCold instead of func->uwi; this separation helps preserve the existing logic. Below are some sample reserve/alloc unwind info calls from various JIT dumps:

Main body split, no funclets:

reserveUnwindInfo(isFunclet=false, isColdCode=false, unwindSize=0x18)
reserveUnwindInfo(isFunclet=false, isColdCode=true, unwindSize=0x10)

allocUnwindInfo(pHotCode=0x0000019474090540, pColdCode=0x0000000000000000, startOffset=0x0, endOffset=0x600, unwindSize=0x18, pUnwindBlock=0x000001947269E4E0, funKind=0 (main function))
allocUnwindInfo(pHotCode=0x0000019474090540, pColdCode=0x000001947261D120, startOffset=0x0, endOffset=0x60, unwindSize=0x10, pUnwindBlock=0x0000019474A04027, funKind=0 (main function))

Main body split, cold funclets:

reserveUnwindInfo(isFunclet=false, isColdCode=false, unwindSize=0x1c)
reserveUnwindInfo(isFunclet=false, isColdCode=true, unwindSize=0x10)
reserveUnwindInfo(isFunclet=true, isColdCode=true, unwindSize=0x10)

allocUnwindInfo(pHotCode=0x00000194725FA580, pColdCode=0x0000000000000000, startOffset=0x0, endOffset=0x42c, unwindSize=0x1c, pUnwindBlock=0x0000019474D1E8B4, funKind=0 (main function))
allocUnwindInfo(pHotCode=0x00000194725FA580, pColdCode=0x000001947435C4C0, startOffset=0x0, endOffset=0x100, unwindSize=0x10, pUnwindBlock=0x0000019474D1E7AF, funKind=0 (main function))
allocUnwindInfo(pHotCode=0x00000194725FA580, pColdCode=0x000001947435C4C0, startOffset=0x100, endOffset=0x190, unwindSize=0x10, pUnwindBlock=0x0000019474D1E928, funKind=1 (handler))

Main body NOT split, hot funclets:

reserveUnwindInfo(isFunclet=false, isColdCode=false, unwindSize=0x18)
reserveUnwindInfo(isFunclet=true, isColdCode=false, unwindSize=0xc)

allocUnwindInfo(pHotCode=0x000001947260F340, pColdCode=0x0000000000000000, startOffset=0x0, endOffset=0x2ec, unwindSize=0x18, pUnwindBlock=0x0000019474CC17E7, funKind=0 (main function))
allocUnwindInfo(pHotCode=0x000001947260F340, pColdCode=0x0000000000000000, startOffset=0x2ec, endOffset=0x324, unwindSize=0xc, pUnwindBlock=0x0000019472630AB4, funKind=1 (handler))

Main body NOT split, cold funclets:

reserveUnwindInfo(isFunclet=false, isColdCode=false, unwindSize=0x10)
reserveUnwindInfo(isFunclet=true, isColdCode=true, unwindSize=0x10)
reserveUnwindInfo(isFunclet=true, isColdCode=true, unwindSize=0x10)
reserveUnwindInfo(isFunclet=true, isColdCode=true, unwindSize=0x10)
reserveUnwindInfo(isFunclet=true, isColdCode=true, unwindSize=0x10)

allocUnwindInfo(pHotCode=0x00000194743580C0, pColdCode=0x0000000000000000, startOffset=0x0, endOffset=0x588, unwindSize=0x10, pUnwindBlock=0x00000194726344B7, funKind=0 (main function))
allocUnwindInfo(pHotCode=0x00000194743580C0, pColdCode=0x0000019474F4A490, startOffset=0x0, endOffset=0x38, unwindSize=0x10, pUnwindBlock=0x00000194726D08BF, funKind=1 (handler))
allocUnwindInfo(pHotCode=0x00000194743580C0, pColdCode=0x0000019474F4A490, startOffset=0x38, endOffset=0x60, unwindSize=0x10, pUnwindBlock=0x00000194726D0A1F, funKind=1 (handler))
allocUnwindInfo(pHotCode=0x00000194743580C0, pColdCode=0x0000019474F4A490, startOffset=0x60, endOffset=0x128, unwindSize=0x10, pUnwindBlock=0x00000194726D0B7F, funKind=1 (handler))
allocUnwindInfo(pHotCode=0x00000194743580C0, pColdCode=0x0000019474F4A490, startOffset=0x128, endOffset=0x160, unwindSize=0x10, pUnwindBlock=0x00000194726D0CCF, funKind=1 (handler))

@@ -311,7 +311,6 @@ void Compiler::unwindEmitFuncCFI(FuncInfoDsc* func, void* pHotCode, void* pColdC
#endif // DEBUG

assert(endOffset <= info.compTotalHotCodeSize);

Copy link
Member

Choose a reason for hiding this comment

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

Looks like the only change in unwind.cpp is an arbitrary whitespace change. Maybe remove unwind.cpp from the PR.

@amanasifkhalid amanasifkhalid merged commit 37939e3 into dotnet:feature/hot-cold-splitting Jul 13, 2022
@amanasifkhalid amanasifkhalid deleted the arm64-unwind-info branch July 13, 2022 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants