An attempt to FH4 - #7
Conversation
The FH4 UnwindMap entry offsets were incorrectly calculated using a hardcoded approximation of \5 * (I + 1)\ bytes per entry. However, compressed integers in FH4 can be 1-5 bytes depending on value magnitude, causing incorrect offsets when values require different encoding sizes. This caused crashes (STATUS_STACK_BUFFER_OVERRUN 0xC0000409) with deeply nested try-catch blocks where the accumulated offset errors led to the runtime reading invalid unwind data. Changes: - Add getCompressedIntSize() helper to calculate actual byte sizes - Pre-calculate entry sizes and accumulate actual byte offsets before emitting UnwindMap entries - Fix emitCompressedInt() to properly mask values for multi-byte encodings - Defer IP2State table emission to MC layer (MCWin64EH) where symbol offsets are known after layout - Add FH4IP2StateEntry struct and related fields to WinEH::FrameInfo - Fix SelectionDAGBuilder to use isMSVCCXXPersonality() for both MSVC_CXX (FH3) and MSVC_CXX4 (FH4) personalities Fixes exception handling crashes with 6+ levels of nested try-catch blocks when using __CxxFrameHandler4.
| // For FH4, the parent function and catch funclets need a reference to | ||
| // the FuncInfo4 structure ($cppxdata$) following the unwind info. | ||
| StringRef FuncLinkageName = GlobalValue::dropLLVMManglingEscape(F.getName()); | ||
| MCSymbol *FuncInfoXData = Asm->OutContext.getOrCreateSymbol( |
There was a problem hiding this comment.
This looks identical to the FH3 info above, what am I missing?
| } | ||
|
|
||
| static void emitCompressedInt(MCStreamer &OS, uint32_t Value) { | ||
| if (Value < 128) { |
There was a problem hiding this comment.
I think I'd prefer to switch on getCompressedIntSize instead of duplicating the magic value comparisons here?
| if (I == 0) { | ||
| // First entry: offset to "before" the unwind map (state -1) | ||
| // This is the size of the NumEntries field | ||
| NextOffset = getCompressedIntSize(FuncInfo.CxxUnwindMap.size()); |
There was a problem hiding this comment.
Seems odd to be re-calculating this every time, can we store it in AccumulatedOffset instead (and, maybe, collapse this if statement)?
| .concat(Twine(I)) | ||
| .concat("$") | ||
| .concat(FuncLinkageName)); | ||
| HandlerMaps.push_back(HandlerMapXData); |
There was a problem hiding this comment.
Should this be in the if statement? Or is it ok to push null pointers?
| emitCompressedInt(OS, TBME.TryLow); | ||
| emitCompressedInt(OS, TBME.TryHigh); | ||
| emitCompressedInt(OS, TBME.CatchHigh); | ||
| OS.emitValue(create32bitRef(HandlerMapXData), 4); |
There was a problem hiding this comment.
I'm assuming that create32bitRef knows how to handle nulls?
| /// - Values 128-16383: 2 bytes, (value << 2) | 1 (low 2 bits 01) | ||
| /// - Values 16384+: 4 bytes, (value << 3) | 3 (low 3 bits 011) | ||
| /// This matches Microsoft's encoding in CxxFrameHandler4. | ||
| static void emitFH4NetEncodedInt(MCStreamer &Streamer, uint32_t Value) { |
There was a problem hiding this comment.
Is there a common place we can put this?
| if (MaybeOffset && *MaybeOffset >= 0) { | ||
| Delta = static_cast<uint32_t>(*MaybeOffset); | ||
| } else { | ||
| Delta = 1; // Fallback for cross-fragment labels |
There was a problem hiding this comment.
Do we emit a fixup to correct this after relaxation?
|
|
||
| ; CHECK-LABEL: $handlerMap$0$multiple_catch: | ||
| ; Should have 2 handlers | ||
| ; CHECK: .byte |
There was a problem hiding this comment.
Is there a value you should be checking here?
No description provided.