[WinEH][ARM64] Split Unwind Info for Fucntions Larger than 1MB

Create function segments and emit unwind info of them.

A segment must be less than 1MB and no prolog or epilog is splitted between two
segments.

This patch should generate correct, though not optimal, unwind info for large
functions. Currently it only generate pacted info (.pdata) only for functions
that are less than 1MB (single-segment functions). This is NFC from before this
patch.

The next step is to enable (.pdata) only unwind info for the first segment or
segments that have neither prolog or epilog in a multi-segment function.

Another future work item is to further split segments that require more than 255
code words or have more than 65535 epilogs.

Reference:
https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#function-fragments

Differential Revision: https://reviews.llvm.org/D130049
This commit is contained in:
Zhaoshi Zheng 2022-07-08 11:48:44 -07:00
parent f1eb945f9a
commit 99e50e5838
5 changed files with 844 additions and 140 deletions

View File

@ -46,6 +46,7 @@ struct FrameInfo {
const MCSymbol *Symbol = nullptr;
MCSection *TextSection = nullptr;
uint32_t PackedInfo = 0;
uint32_t PrologCodeBytes = 0;
bool HandlesUnwind = false;
bool HandlesExceptions = false;
@ -62,6 +63,21 @@ struct FrameInfo {
};
MapVector<MCSymbol *, Epilog> EpilogMap;
// For splitting unwind info of large functions
struct Segment {
int64_t Offset;
int64_t Length;
bool HasProlog;
MCSymbol *Symbol;
// Map an Epilog's symbol to its offset within the function.
MapVector<MCSymbol *, int64_t> Epilogs;
Segment(int64_t Offset, int64_t Length, bool HasProlog = false)
: Offset(Offset), Length(Length), HasProlog(HasProlog) {}
};
std::vector<Segment> Segments;
FrameInfo() = default;
FrameInfo(const MCSymbol *Function, const MCSymbol *BeginFuncEHLabel)
: Begin(BeginFuncEHLabel), Function(Function) {}

View File

@ -128,6 +128,17 @@ static void EmitUnwindCode(MCStreamer &streamer, const MCSymbol *begin,
}
}
static void EmitSymbolRefWithOfs(MCStreamer &streamer,
const MCSymbol *Base,
int64_t Offset) {
MCContext &Context = streamer.getContext();
const MCConstantExpr *OffExpr = MCConstantExpr::create(Offset, Context);
const MCSymbolRefExpr *BaseRefRel = MCSymbolRefExpr::create(Base,
MCSymbolRefExpr::VK_COFF_IMGREL32,
Context);
streamer.emitValue(MCBinaryExpr::createAdd(BaseRefRel, OffExpr, Context), 4);
}
static void EmitSymbolRefWithOfs(MCStreamer &streamer,
const MCSymbol *Base,
const MCSymbol *Other) {
@ -642,26 +653,29 @@ getARM64OffsetInProlog(const std::vector<WinEH::Instruction> &Prolog,
return -1;
}
// If the epilog was a subset of the prolog, find its offset.
if (Epilog.size() == Prolog.size())
return 0;
// If the epilog was a subset of the prolog, find its offset.
return ARM64CountOfUnwindCodes(ArrayRef<WinEH::Instruction>(
&Prolog[Epilog.size()], Prolog.size() - Epilog.size()));
}
static int checkARM64PackedEpilog(MCStreamer &streamer, WinEH::FrameInfo *info,
WinEH::FrameInfo::Segment *Seg,
int PrologCodeBytes) {
// Can only pack if there's one single epilog
if (info->EpilogMap.size() != 1)
if (Seg->Epilogs.size() != 1)
return -1;
MCSymbol *Sym = Seg->Epilogs.begin()->first;
const std::vector<WinEH::Instruction> &Epilog =
info->EpilogMap.begin()->second.Instructions;
info->EpilogMap[Sym].Instructions;
// Check that the epilog actually is at the very end of the function,
// otherwise it can't be packed.
uint32_t DistanceFromEnd = (uint32_t)GetAbsDifference(
streamer, info->FuncletOrFuncEnd, info->EpilogMap.begin()->first);
uint32_t DistanceFromEnd =
(uint32_t)(Seg->Offset + Seg->Length - Seg->Epilogs.begin()->second);
if (DistanceFromEnd / 4 != Epilog.size())
return -1;
@ -686,7 +700,7 @@ static int checkARM64PackedEpilog(MCStreamer &streamer, WinEH::FrameInfo *info,
// As we choose to express the epilog as part of the prolog, remove the
// epilog from the map, so we don't try to emit its opcodes.
info->EpilogMap.clear();
info->EpilogMap.erase(Sym);
return Offset;
}
@ -923,6 +937,268 @@ static bool tryARM64PackedUnwind(WinEH::FrameInfo *info, uint32_t FuncLength,
return true;
}
static void ARM64ProcessEpilogs(WinEH::FrameInfo *info,
WinEH::FrameInfo::Segment *Seg,
uint32_t &TotalCodeBytes,
MapVector<MCSymbol *, uint32_t> &EpilogInfo) {
std::vector<MCSymbol *> EpilogStarts;
for (auto &I : Seg->Epilogs)
EpilogStarts.push_back(I.first);
// Epilogs processed so far.
std::vector<MCSymbol *> AddedEpilogs;
for (auto S : EpilogStarts) {
MCSymbol *EpilogStart = S;
auto &EpilogInstrs = info->EpilogMap[S].Instructions;
uint32_t CodeBytes = ARM64CountOfUnwindCodes(EpilogInstrs);
MCSymbol* MatchingEpilog =
FindMatchingEpilog(EpilogInstrs, AddedEpilogs, info);
int PrologOffset;
if (MatchingEpilog) {
assert(EpilogInfo.find(MatchingEpilog) != EpilogInfo.end() &&
"Duplicate epilog not found");
EpilogInfo[EpilogStart] = EpilogInfo.lookup(MatchingEpilog);
// Clear the unwind codes in the EpilogMap, so that they don't get output
// in ARM64EmitUnwindInfoForSegment().
EpilogInstrs.clear();
} else if ((PrologOffset = getARM64OffsetInProlog(info->Instructions,
EpilogInstrs)) >= 0) {
EpilogInfo[EpilogStart] = PrologOffset;
// If the segment doesn't have a prolog, an end_c will be emitted before
// prolog opcodes. So epilog start index in opcodes array is moved by 1.
if (!Seg->HasProlog)
EpilogInfo[EpilogStart] += 1;
// Clear the unwind codes in the EpilogMap, so that they don't get output
// in ARM64EmitUnwindInfoForSegment().
EpilogInstrs.clear();
} else {
EpilogInfo[EpilogStart] = TotalCodeBytes;
TotalCodeBytes += CodeBytes;
AddedEpilogs.push_back(EpilogStart);
}
}
}
static void ARM64FindSegmentsInFunction(MCStreamer &streamer,
WinEH::FrameInfo *info,
int64_t RawFuncLength) {
struct EpilogStartEnd {
MCSymbol *Start;
int64_t Offset;
int64_t End;
};
// Record Start and End of each epilog.
SmallVector<struct EpilogStartEnd, 4> Epilogs;
for (auto &I : info->EpilogMap) {
MCSymbol *Start = I.first;
auto &Instrs = I.second.Instructions;
int64_t Offset = GetAbsDifference(streamer, Start, info->Begin);
assert((Epilogs.size() == 0 || Offset >= Epilogs.back().End) &&
"Epilogs should be monotonically ordered");
Epilogs.push_back({Start, Offset, Offset + (int64_t)Instrs.size() * 4});
}
unsigned E = 0;
int64_t SegLimit = 0xFFFFC;
int64_t SegOffset = 0;
if (RawFuncLength > SegLimit) {
int64_t RemainingLength = RawFuncLength;
while (RemainingLength > SegLimit) {
// Try divide the function into segments, requirements:
// 1. Segment length <= 0xFFFFC;
// 2. Each Prologue or Epilogue must be fully within a segment.
int64_t SegLength = SegLimit;
int64_t SegEnd = SegOffset + SegLength;
// Keep record on symbols and offsets of epilogs in this segment.
MapVector<MCSymbol *, int64_t> EpilogsInSegment;
while (E < Epilogs.size() && Epilogs[E].End < SegEnd) {
// Epilogs within current segment.
EpilogsInSegment[Epilogs[E].Start] = Epilogs[E].Offset;
++E;
}
// At this point, we have:
// 1. Put all epilogs in segments already. No action needed here; or
// 2. Found an epilog that will cross segments boundry. We need to
// move back current segment's end boundry, so the epilog is entirely
// in the next segment; or
// 3. Left at least one epilog that is entirely after this segment.
// It'll be handled by the next iteration, or the last segment.
if (E < Epilogs.size() && Epilogs[E].Offset <= SegEnd)
// Move back current Segment's end boundry.
SegLength = Epilogs[E].Offset - SegOffset;
auto Seg = WinEH::FrameInfo::Segment(
SegOffset, SegLength, /* HasProlog */!SegOffset);
Seg.Epilogs = std::move(EpilogsInSegment);
info->Segments.push_back(Seg);
SegOffset += SegLength;
RemainingLength -= SegLength;
}
}
// Add the last segment when RawFuncLength > 0xFFFFC,
// or the only segment otherwise.
auto LastSeg =
WinEH::FrameInfo::Segment(SegOffset, RawFuncLength - SegOffset,
/* HasProlog */!SegOffset);
for (; E < Epilogs.size(); ++E)
LastSeg.Epilogs[Epilogs[E].Start] = Epilogs[E].Offset;
info->Segments.push_back(LastSeg);
}
static void ARM64EmitUnwindInfoForSegment(MCStreamer &streamer,
WinEH::FrameInfo *info,
WinEH::FrameInfo::Segment &Seg,
bool TryPacked = true) {
MCContext &context = streamer.getContext();
MCSymbol *Label = context.createTempSymbol();
streamer.emitValueToAlignment(4);
streamer.emitLabel(Label);
Seg.Symbol = Label;
// Use the 1st segemnt's label as function's.
if (Seg.Offset == 0)
info->Symbol = Label;
bool HasProlog = Seg.HasProlog;
bool HasEpilogs = (Seg.Epilogs.size() != 0);
uint32_t SegLength = (uint32_t)Seg.Length / 4;
uint32_t PrologCodeBytes = info->PrologCodeBytes;
int PackedEpilogOffset = HasEpilogs ?
checkARM64PackedEpilog(streamer, info, &Seg, PrologCodeBytes) : -1;
// TODO:
// 1. Enable packed unwind info (.pdata only) for multi-segment functions.
// 2. Emit packed unwind info (.pdata only) for segments that have neithor
// prolog nor epilog.
if (info->Segments.size() == 1 && PackedEpilogOffset >= 0 &&
uint32_t(PackedEpilogOffset) < PrologCodeBytes &&
!info->HandlesExceptions && SegLength <= 0x7ff && TryPacked) {
// Matching prolog/epilog and no exception handlers; check if the
// prolog matches the patterns that can be described by the packed
// format.
// info->Symbol was already set even if we didn't actually write any
// unwind info there. Keep using that as indicator that this unwind
// info has been generated already.
if (tryARM64PackedUnwind(info, SegLength, PackedEpilogOffset))
return;
}
// If the prolog is not in this segment, we need to emit an end_c, which takes
// 1 byte, before prolog unwind ops.
if (!HasProlog) {
PrologCodeBytes += 1;
if (PackedEpilogOffset >= 0)
PackedEpilogOffset += 1;
// If a segment has neither prolog nor epilog, "With full .xdata record,
// Epilog Count = 1. Epilog Start Index points to end_c."
// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#function-fragments
// TODO: We can remove this if testing shows zero epilog scope is ok with
// MS unwinder.
if (!HasEpilogs)
// Pack the fake epilog into phantom prolog.
PackedEpilogOffset = 0;
}
uint32_t TotalCodeBytes = PrologCodeBytes;
// Process epilogs.
MapVector<MCSymbol *, uint32_t> EpilogInfo;
ARM64ProcessEpilogs(info, &Seg, TotalCodeBytes, EpilogInfo);
// Code Words, Epilog count, E, X, Vers, Function Length
uint32_t row1 = 0x0;
uint32_t CodeWords = TotalCodeBytes / 4;
uint32_t CodeWordsMod = TotalCodeBytes % 4;
if (CodeWordsMod)
CodeWords++;
uint32_t EpilogCount =
PackedEpilogOffset >= 0 ? PackedEpilogOffset : Seg.Epilogs.size();
bool ExtensionWord = EpilogCount > 31 || TotalCodeBytes > 124;
if (!ExtensionWord) {
row1 |= (EpilogCount & 0x1F) << 22;
row1 |= (CodeWords & 0x1F) << 27;
}
if (info->HandlesExceptions) // X
row1 |= 1 << 20;
if (PackedEpilogOffset >= 0) // E
row1 |= 1 << 21;
row1 |= SegLength & 0x3FFFF;
streamer.emitInt32(row1);
// Extended Code Words, Extended Epilog Count
if (ExtensionWord) {
// FIXME: We should be able to split unwind info into multiple sections.
if (CodeWords > 0xFF || EpilogCount > 0xFFFF)
report_fatal_error(
"SEH unwind data splitting is only implemnted for large functions, "
"cases of too many code words or too many epilogs will be done later"
);
uint32_t row2 = 0x0;
row2 |= (CodeWords & 0xFF) << 16;
row2 |= (EpilogCount & 0xFFFF);
streamer.emitInt32(row2);
}
if (PackedEpilogOffset < 0) {
// Epilog Start Index, Epilog Start Offset
for (auto &I : EpilogInfo) {
MCSymbol *EpilogStart = I.first;
uint32_t EpilogIndex = I.second;
// Epilog offset within the Segment.
uint32_t EpilogOffset = (uint32_t)(Seg.Epilogs[EpilogStart] - Seg.Offset);
if (EpilogOffset)
EpilogOffset /= 4;
uint32_t row3 = EpilogOffset;
row3 |= (EpilogIndex & 0x3FF) << 22;
streamer.emitInt32(row3);
}
}
// Note that even for segments that have no prolog, we still need to emit
// prolog unwinding opcodes so that the unwinder knows how to unwind from
// such a segment.
// The end_c opcode at the start indicates to the unwinder that the actual
// prolog is outside of the current segment, and the unwinder shouldn't try
// to check for unwinding from a partial prolog.
if (!HasProlog)
// Emit an end_c.
streamer.emitInt8((uint8_t)0xE5);
// Emit prolog unwind instructions (in reverse order).
for (auto Inst : llvm::reverse(info->Instructions))
ARM64EmitUnwindCode(streamer, Inst);
// Emit epilog unwind instructions
for (auto &I : Seg.Epilogs) {
auto &EpilogInstrs = info->EpilogMap[I.first].Instructions;
for (const WinEH::Instruction &inst : EpilogInstrs)
ARM64EmitUnwindCode(streamer, inst);
}
int32_t BytesMod = CodeWords * 4 - TotalCodeBytes;
assert(BytesMod >= 0);
for (int i = 0; i < BytesMod; i++)
streamer.emitInt8(0xE3);
if (info->HandlesExceptions)
streamer.emitValue(
MCSymbolRefExpr::create(info->ExceptionHandler,
MCSymbolRefExpr::VK_COFF_IMGREL32, context),
4);
}
// Populate the .xdata section. The format of .xdata on ARM64 is documented at
// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling
static void ARM64EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info,
@ -956,13 +1232,6 @@ static void ARM64EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info,
for (auto &I : info->EpilogMap)
simplifyARM64Opcodes(I.second.Instructions, true);
MCContext &context = streamer.getContext();
MCSymbol *Label = context.createTempSymbol();
streamer.emitValueToAlignment(4);
streamer.emitLabel(Label);
info->Symbol = Label;
int64_t RawFuncLength;
if (!info->FuncletOrFuncEnd) {
report_fatal_error("FuncletOrFuncEnd not set");
@ -996,134 +1265,15 @@ static void ARM64EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info,
RawFuncLength = GetAbsDifference(streamer, info->FuncletOrFuncEnd,
info->Begin);
}
if (RawFuncLength > 0xFFFFF)
report_fatal_error("SEH unwind data splitting not yet implemented");
uint32_t FuncLength = (uint32_t)RawFuncLength / 4;
uint32_t PrologCodeBytes = ARM64CountOfUnwindCodes(info->Instructions);
uint32_t TotalCodeBytes = PrologCodeBytes;
int PackedEpilogOffset =
checkARM64PackedEpilog(streamer, info, PrologCodeBytes);
ARM64FindSegmentsInFunction(streamer, info, RawFuncLength);
if (PackedEpilogOffset >= 0 &&
uint32_t(PackedEpilogOffset) < PrologCodeBytes &&
!info->HandlesExceptions && FuncLength <= 0x7ff && TryPacked) {
// Matching prolog/epilog and no exception handlers; check if the
// prolog matches the patterns that can be described by the packed
// format.
info->PrologCodeBytes = ARM64CountOfUnwindCodes(info->Instructions);
for (auto &S : info->Segments)
ARM64EmitUnwindInfoForSegment(streamer, info, S, TryPacked);
// info->Symbol was already set even if we didn't actually write any
// unwind info there. Keep using that as indicator that this unwind
// info has been generated already.
if (tryARM64PackedUnwind(info, FuncLength, PackedEpilogOffset))
return;
}
// Process epilogs.
MapVector<MCSymbol *, uint32_t> EpilogInfo;
// Epilogs processed so far.
std::vector<MCSymbol *> AddedEpilogs;
for (auto &I : info->EpilogMap) {
MCSymbol *EpilogStart = I.first;
auto &EpilogInstrs = I.second.Instructions;
uint32_t CodeBytes = ARM64CountOfUnwindCodes(EpilogInstrs);
MCSymbol* MatchingEpilog =
FindMatchingEpilog(EpilogInstrs, AddedEpilogs, info);
int PrologOffset;
if (MatchingEpilog) {
assert(EpilogInfo.find(MatchingEpilog) != EpilogInfo.end() &&
"Duplicate epilog not found");
EpilogInfo[EpilogStart] = EpilogInfo.lookup(MatchingEpilog);
// Clear the unwind codes in the EpilogMap, so that they don't get output
// in the logic below.
EpilogInstrs.clear();
} else if ((PrologOffset = getARM64OffsetInProlog(info->Instructions,
EpilogInstrs)) >= 0) {
EpilogInfo[EpilogStart] = PrologOffset;
// Clear the unwind codes in the EpilogMap, so that they don't get output
// in the logic below.
EpilogInstrs.clear();
} else {
EpilogInfo[EpilogStart] = TotalCodeBytes;
TotalCodeBytes += CodeBytes;
AddedEpilogs.push_back(EpilogStart);
}
}
// Code Words, Epilog count, E, X, Vers, Function Length
uint32_t row1 = 0x0;
uint32_t CodeWords = TotalCodeBytes / 4;
uint32_t CodeWordsMod = TotalCodeBytes % 4;
if (CodeWordsMod)
CodeWords++;
uint32_t EpilogCount =
PackedEpilogOffset >= 0 ? PackedEpilogOffset : info->EpilogMap.size();
bool ExtensionWord = EpilogCount > 31 || TotalCodeBytes > 124;
if (!ExtensionWord) {
row1 |= (EpilogCount & 0x1F) << 22;
row1 |= (CodeWords & 0x1F) << 27;
}
if (info->HandlesExceptions) // X
row1 |= 1 << 20;
if (PackedEpilogOffset >= 0) // E
row1 |= 1 << 21;
row1 |= FuncLength & 0x3FFFF;
streamer.emitInt32(row1);
// Extended Code Words, Extended Epilog Count
if (ExtensionWord) {
// FIXME: We should be able to split unwind info into multiple sections.
if (CodeWords > 0xFF || EpilogCount > 0xFFFF)
report_fatal_error("SEH unwind data splitting not yet implemented");
uint32_t row2 = 0x0;
row2 |= (CodeWords & 0xFF) << 16;
row2 |= (EpilogCount & 0xFFFF);
streamer.emitInt32(row2);
}
if (PackedEpilogOffset < 0) {
// Epilog Start Index, Epilog Start Offset
for (auto &I : EpilogInfo) {
MCSymbol *EpilogStart = I.first;
uint32_t EpilogIndex = I.second;
uint32_t EpilogOffset =
(uint32_t)GetAbsDifference(streamer, EpilogStart, info->Begin);
if (EpilogOffset)
EpilogOffset /= 4;
uint32_t row3 = EpilogOffset;
row3 |= (EpilogIndex & 0x3FF) << 22;
streamer.emitInt32(row3);
}
}
// Emit prolog unwind instructions (in reverse order).
uint8_t numInst = info->Instructions.size();
for (uint8_t c = 0; c < numInst; ++c) {
WinEH::Instruction inst = info->Instructions.back();
info->Instructions.pop_back();
ARM64EmitUnwindCode(streamer, inst);
}
// Emit epilog unwind instructions
for (auto &I : info->EpilogMap) {
auto &EpilogInstrs = I.second.Instructions;
for (const WinEH::Instruction &inst : EpilogInstrs)
ARM64EmitUnwindCode(streamer, inst);
}
int32_t BytesMod = CodeWords * 4 - TotalCodeBytes;
assert(BytesMod >= 0);
for (int i = 0; i < BytesMod; i++)
streamer.emitInt8(0xE3);
if (info->HandlesExceptions)
streamer.emitValue(
MCSymbolRefExpr::create(info->ExceptionHandler,
MCSymbolRefExpr::VK_COFF_IMGREL32, context),
4);
// Clear prolog instructions after unwind info is emitted for all segments.
info->Instructions.clear();
}
static uint32_t ARMCountOfUnwindCodes(ArrayRef<WinEH::Instruction> Insns) {
@ -2205,6 +2355,24 @@ static void ARMEmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info,
4);
}
static void ARM64EmitRuntimeFunction(MCStreamer &streamer,
const WinEH::FrameInfo *info) {
MCContext &context = streamer.getContext();
streamer.emitValueToAlignment(4);
for (auto &S : info->Segments) {
EmitSymbolRefWithOfs(streamer, info->Begin, S.Offset);
if (info->PackedInfo)
streamer.emitInt32(info->PackedInfo);
else
streamer.emitValue(
MCSymbolRefExpr::create(S.Symbol, MCSymbolRefExpr::VK_COFF_IMGREL32,
context),
4);
}
}
static void ARMEmitRuntimeFunction(MCStreamer &streamer,
const WinEH::FrameInfo *info) {
MCContext &context = streamer.getContext();
@ -2241,7 +2409,7 @@ void llvm::Win64EH::ARM64UnwindEmitter::Emit(MCStreamer &Streamer) const {
continue;
MCSection *PData = Streamer.getAssociatedPDataSection(CFI->TextSection);
Streamer.switchSection(PData);
ARMEmitRuntimeFunction(Streamer, Info);
ARM64EmitRuntimeFunction(Streamer, Info);
}
}

View File

@ -0,0 +1,308 @@
// This test checks that we emit unwind info correctly for epilogs that:
// 1. mirror the prolog; or
// 2. are subsequence at the end of the prolog; or
// 3. neither of above two.
// in the same segment.
// RUN: llvm-mc -triple aarch64-pc-win32 -filetype=obj %s -o %t.o
// RUN: llvm-readobj -S -r -u %t.o | FileCheck %s
// CHECK: Section {
// CHECK: Number: 4
// CHECK-NEXT: Name: .xdata (2E 78 64 61 74 61 00 00)
// CHECK-NEXT: VirtualSize: 0x0
// CHECK-NEXT: VirtualAddress: 0x0
// CHECK-NEXT: RawDataSize: 80
// CHECK-NEXT: PointerToRawData: 0x1251AC
// CHECK-NEXT: PointerToRelocations: 0x0
// CHECK-NEXT: PointerToLineNumbers: 0x0
// CHECK-NEXT: RelocationCount: 0
// CHECK-NEXT: LineNumberCount: 0
// CHECK-NEXT: Characteristics [ (0x40300040)
// CHECK-NEXT: IMAGE_SCN_ALIGN_4BYTES (0x300000)
// CHECK-NEXT: IMAGE_SCN_CNT_INITIALIZED_DATA (0x40)
// CHECK-NEXT: IMAGE_SCN_MEM_READ (0x40000000)
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: Section {
// CHECK-NEXT: Number: 5
// CHECK-NEXT: Name: .pdata (2E 70 64 61 74 61 00 00)
// CHECK-NEXT: VirtualSize: 0x0
// CHECK-NEXT: VirtualAddress: 0x0
// CHECK-NEXT: RawDataSize: 16
// CHECK-NEXT: PointerToRawData: 0x1251FC
// CHECK-NEXT: PointerToRelocations: 0x12520C
// CHECK-NEXT: PointerToLineNumbers: 0x0
// CHECK-NEXT: RelocationCount: 4
// CHECK-NEXT: LineNumberCount: 0
// CHECK-NEXT: Characteristics [ (0x40300040)
// CHECK-NEXT: IMAGE_SCN_ALIGN_4BYTES (0x300000)
// CHECK-NEXT: IMAGE_SCN_CNT_INITIALIZED_DATA (0x40)
// CHECK-NEXT: IMAGE_SCN_MEM_READ (0x40000000)
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT:]
// CHECK-LABEL:Relocations [
// CHECK-NEXT: Section (1) .text {
// CHECK-NEXT: 0x94 IMAGE_REL_ARM64_BRANCH26 foo (12)
// CHECK-NEXT: 0x125068 IMAGE_REL_ARM64_BRANCH26 foo (12)
// CHECK-NEXT: }
// CHECK-NEXT: Section (5) .pdata {
// CHECK-NEXT: 0x0 IMAGE_REL_ARM64_ADDR32NB .text (0)
// CHECK-NEXT: 0x4 IMAGE_REL_ARM64_ADDR32NB .xdata (7)
// CHECK-NEXT: 0x8 IMAGE_REL_ARM64_ADDR32NB .text (0)
// CHECK-NEXT: 0xC IMAGE_REL_ARM64_ADDR32NB .xdata (7)
// CHECK-NEXT: }
// CHECK-NEXT:]
// CHECK-LABEL:UnwindInformation [
// CHECK-NEXT: RuntimeFunction {
// CHECK-NEXT: Function: multi_epilog (0x0)
// CHECK-NEXT: ExceptionRecord: .xdata (0x0)
// CHECK-NEXT: ExceptionData {
// CHECK-NEXT: FunctionLength: 1048572
// CHECK-NEXT: Version: 0
// CHECK-NEXT: ExceptionData: No
// CHECK-NEXT: EpiloguePacked: No
// CHECK-NEXT: EpilogueScopes: 3
// CHECK-NEXT: ByteCodeLength: 24
// CHECK-NEXT: Prologue [
// CHECK-NEXT: 0xe1 ; mov fp, sp
// CHECK-NEXT: 0xca16 ; stp x27, x28, [sp, #176]
// CHECK-NEXT: 0xc998 ; stp x25, x26, [sp, #192]
// CHECK-NEXT: 0xc91a ; stp x23, x24, [sp, #208]
// CHECK-NEXT: 0xc89c ; stp x21, x22, [sp, #224]
// CHECK-NEXT: 0xc81e ; stp x19, x20, [sp, #240]
// CHECK-NEXT: 0x9f ; stp x29, x30, [sp, #-256]!
// CHECK-NEXT: 0xe4 ; end
// CHECK-NEXT: ]
// CHECK-NEXT: EpilogueScopes [
// CHECK-NEXT: EpilogueScope {
// CHECK-NEXT: StartOffset: 38
// CHECK-NEXT: EpilogueStartIndex: 0
// CHECK-NEXT: Opcodes [
// CHECK-NEXT: 0xe1 ; mov sp, fp
// CHECK-NEXT: 0xca16 ; ldp x27, x28, [sp, #176]
// CHECK-NEXT: 0xc998 ; ldp x25, x26, [sp, #192]
// CHECK-NEXT: 0xc91a ; ldp x23, x24, [sp, #208]
// CHECK-NEXT: 0xc89c ; ldp x21, x22, [sp, #224]
// CHECK-NEXT: 0xc81e ; ldp x19, x20, [sp, #240]
// CHECK-NEXT: 0x9f ; ldp x29, x30, [sp], #256
// CHECK-NEXT: 0xe4 ; end
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: EpilogueScope {
// CHECK-NEXT: StartOffset: 46
// CHECK-NEXT: EpilogueStartIndex: 3
// CHECK-NEXT: Opcodes [
// CHECK-NEXT: 0xc998 ; ldp x25, x26, [sp, #192]
// CHECK-NEXT: 0xc91a ; ldp x23, x24, [sp, #208]
// CHECK-NEXT: 0xc89c ; ldp x21, x22, [sp, #224]
// CHECK-NEXT: 0xc81e ; ldp x19, x20, [sp, #240]
// CHECK-NEXT: 0x9f ; ldp x29, x30, [sp], #256
// CHECK-NEXT: 0xe4 ; end
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: EpilogueScope {
// CHECK-NEXT: StartOffset: 52
// CHECK-NEXT: EpilogueStartIndex: 13
// CHECK-NEXT: Opcodes [
// CHECK-NEXT: 0xe1 ; mov sp, fp
// CHECK-NEXT: 0xc91a ; ldp x23, x24, [sp, #208]
// CHECK-NEXT: 0xc89c ; ldp x21, x22, [sp, #224]
// CHECK-NEXT: 0xc81e ; ldp x19, x20, [sp, #240]
// CHECK-NEXT: 0x9f ; ldp x29, x30, [sp], #256
// CHECK-NEXT: 0xe4 ; end
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: RuntimeFunction {
// CHECK-NEXT: Function: multi_epilog +0xFFFFC (0xFFFFC)
// CHECK-NEXT: ExceptionRecord: .xdata +0x28 (0x28)
// CHECK-NEXT: ExceptionData {
// CHECK-NEXT: FunctionLength: 151744
// CHECK-NEXT: Version: 0
// CHECK-NEXT: ExceptionData: No
// CHECK-NEXT: EpiloguePacked: No
// CHECK-NEXT: EpilogueScopes: 3
// CHECK-NEXT: ByteCodeLength: 24
// CHECK-NEXT: Prologue [
// CHECK-NEXT: 0xe5 ; end_c
// CHECK-NEXT: 0xe1 ; mov fp, sp
// CHECK-NEXT: 0xca16 ; stp x27, x28, [sp, #176]
// CHECK-NEXT: 0xc998 ; stp x25, x26, [sp, #192]
// CHECK-NEXT: 0xc91a ; stp x23, x24, [sp, #208]
// CHECK-NEXT: 0xc89c ; stp x21, x22, [sp, #224]
// CHECK-NEXT: 0xc81e ; stp x19, x20, [sp, #240]
// CHECK-NEXT: 0x9f ; stp x29, x30, [sp, #-256]!
// CHECK-NEXT: 0xe4 ; end
// CHECK-NEXT: ]
// CHECK-NEXT: EpilogueScopes [
// CHECK-NEXT: EpilogueScope {
// CHECK-NEXT: StartOffset: 37916
// CHECK-NEXT: EpilogueStartIndex: 1
// CHECK-NEXT: Opcodes [
// CHECK-NEXT: 0xe1 ; mov sp, fp
// CHECK-NEXT: 0xca16 ; ldp x27, x28, [sp, #176]
// CHECK-NEXT: 0xc998 ; ldp x25, x26, [sp, #192]
// CHECK-NEXT: 0xc91a ; ldp x23, x24, [sp, #208]
// CHECK-NEXT: 0xc89c ; ldp x21, x22, [sp, #224]
// CHECK-NEXT: 0xc81e ; ldp x19, x20, [sp, #240]
// CHECK-NEXT: 0x9f ; ldp x29, x30, [sp], #256
// CHECK-NEXT: 0xe4 ; end
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: EpilogueScope {
// CHECK-NEXT: StartOffset: 37924
// CHECK-NEXT: EpilogueStartIndex: 4
// CHECK-NEXT: Opcodes [
// CHECK-NEXT: 0xc998 ; ldp x25, x26, [sp, #192]
// CHECK-NEXT: 0xc91a ; ldp x23, x24, [sp, #208]
// CHECK-NEXT: 0xc89c ; ldp x21, x22, [sp, #224]
// CHECK-NEXT: 0xc81e ; ldp x19, x20, [sp, #240]
// CHECK-NEXT: 0x9f ; ldp x29, x30, [sp], #256
// CHECK-NEXT: 0xe4 ; end
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: EpilogueScope {
// CHECK-NEXT: StartOffset: 37930
// CHECK-NEXT: EpilogueStartIndex: 14
// CHECK-NEXT: Opcodes [
// CHECK-NEXT: 0xe1 ; mov sp, fp
// CHECK-NEXT: 0xc91a ; ldp x23, x24, [sp, #208]
// CHECK-NEXT: 0xc89c ; ldp x21, x22, [sp, #224]
// CHECK-NEXT: 0xc81e ; ldp x19, x20, [sp, #240]
// CHECK-NEXT: 0x9f ; ldp x29, x30, [sp], #256
// CHECK-NEXT: 0xe4 ; end
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT:]
.text
.global multi_epilog
.p2align 2
.seh_proc multi_epilog
multi_epilog:
stp x29, lr, [sp, #-256]!
.seh_save_fplr_x 256
stp x19, x20, [sp, #240]
.seh_save_regp x19, 240
stp x21, x22, [sp, #224]
.seh_save_regp x21, 224
stp x23, x24, [sp, #208]
.seh_save_regp x23, 208
stp x25, x26, [sp, #192]
.seh_save_regp x25, 192
stp x27, x28, [sp, #176]
.seh_save_regp x27, 176
mov x29, fp
.seh_set_fp
.seh_endprologue
.rept 30
nop
.endr
bl foo
// Epilogs 1, 2 and 3 are in the same segment as prolog.
// epilog1 - mirroring prolog
.seh_startepilogue
mov sp, x29
.seh_set_fp
stp x27, x28, [sp, #176]
.seh_save_regp x27, 176
stp x25, x26, [sp, #192]
.seh_save_regp x25, 192
stp x23, x24, [sp, #208]
.seh_save_regp x23, 208
stp x21, x22, [sp, #224]
.seh_save_regp x21, 224
ldp x19, x20, [sp, #240]
.seh_save_regp x19, 240
ldp x29, lr, [sp], #256
.seh_save_fplr_x 256
.seh_endepilogue
ret
// epilog2 - a subsequence at the end of prolog, can use prolog's opcodes.
.seh_startepilogue
stp x25, x26, [sp, #192]
.seh_save_regp x25, 192
stp x23, x24, [sp, #208]
.seh_save_regp x23, 208
stp x21, x22, [sp, #224]
.seh_save_regp x21, 224
ldp x19, x20, [sp, #240]
.seh_save_regp x19, 240
ldp x29, lr, [sp], #256
.seh_save_fplr_x 256
.seh_endepilogue
ret
// epilog3 - cannot use prolog's opcode.
.seh_startepilogue
mov sp, x29
.seh_set_fp
stp x23, x24, [sp, #208]
.seh_save_regp x23, 208
stp x21, x22, [sp, #224]
.seh_save_regp x21, 224
ldp x19, x20, [sp, #240]
.seh_save_regp x19, 240
ldp x29, lr, [sp], #256
.seh_save_fplr_x 256
.seh_endepilogue
ret
.rept 300000
nop
.endr
bl foo
// Epilogs below are in a segment without prolog
// epilog4 - mirroring prolog, its start index should be 1, counting the end_c.
.seh_startepilogue
mov sp, x29
.seh_set_fp
stp x27, x28, [sp, #176]
.seh_save_regp x27, 176
stp x25, x26, [sp, #192]
.seh_save_regp x25, 192
stp x23, x24, [sp, #208]
.seh_save_regp x23, 208
stp x21, x22, [sp, #224]
.seh_save_regp x21, 224
ldp x19, x20, [sp, #240]
.seh_save_regp x19, 240
ldp x29, lr, [sp], #256
.seh_save_fplr_x 256
.seh_endepilogue
ret
// epilog5 - same as epilog2, its start index should be: 1 + epilog2's index.
.seh_startepilogue
stp x25, x26, [sp, #192]
.seh_save_regp x25, 192
stp x23, x24, [sp, #208]
.seh_save_regp x23, 208
stp x21, x22, [sp, #224]
.seh_save_regp x21, 224
ldp x19, x20, [sp, #240]
.seh_save_regp x19, 240
ldp x29, lr, [sp], #256
.seh_save_fplr_x 256
.seh_endepilogue
ret
// epilog6 - same as epilog3, cannot use prolog's opcode. Again its start index
// should be: 1 + epilog3's index.
.seh_startepilogue
mov sp, x29
.seh_set_fp
stp x23, x24, [sp, #208]
.seh_save_regp x23, 208
stp x21, x22, [sp, #224]
.seh_save_regp x21, 224
ldp x19, x20, [sp, #240]
.seh_save_regp x19, 240
ldp x29, lr, [sp], #256
.seh_save_fplr_x 256
.seh_endepilogue
ret
.seh_endfunclet
.seh_endproc

View File

@ -0,0 +1,212 @@
// This test checks that we emit unwind info correctly for functions
// larger than 1MB.
// RUN: llvm-mc -triple aarch64-pc-win32 -filetype=obj %s -o %t.o
// RUN: llvm-readobj -S -r -u %t.o | FileCheck %s
// CHECK: Section {
// CHECK: Number: 4
// CHECK-NEXT: Name: .xdata (2E 78 64 61 74 61 00 00)
// CHECK-NEXT: VirtualSize: 0x0
// CHECK-NEXT: VirtualAddress: 0x0
// CHECK-NEXT: RawDataSize: 52
// CHECK-NEXT: PointerToRawData: 0x3D0A20
// CHECK-NEXT: PointerToRelocations: 0x0
// CHECK-NEXT: PointerToLineNumbers: 0x0
// CHECK-NEXT: RelocationCount: 0
// CHECK-NEXT: LineNumberCount: 0
// CHECK-NEXT: Characteristics [ (0x40300040)
// CHECK-NEXT: IMAGE_SCN_ALIGN_4BYTES (0x300000)
// CHECK-NEXT: IMAGE_SCN_CNT_INITIALIZED_DATA (0x40)
// CHECK-NEXT: IMAGE_SCN_MEM_READ (0x40000000)
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: Section {
// CHECK-NEXT: Number: 5
// CHECK-NEXT: Name: .pdata (2E 70 64 61 74 61 00 00)
// CHECK-NEXT: VirtualSize: 0x0
// CHECK-NEXT: VirtualAddress: 0x0
// CHECK-NEXT: RawDataSize: 40
// CHECK-NEXT: PointerToRawData: 0x3D0A54
// CHECK-NEXT: PointerToRelocations: 0x3D0A7C
// CHECK-NEXT: PointerToLineNumbers: 0x0
// CHECK-NEXT: RelocationCount: 10
// CHECK-NEXT: LineNumberCount: 0
// CHECK-NEXT: Characteristics [ (0x40300040)
// CHECK-NEXT: IMAGE_SCN_ALIGN_4BYTES (0x300000)
// CHECK-NEXT: IMAGE_SCN_CNT_INITIALIZED_DATA (0x40)
// CHECK-NEXT: IMAGE_SCN_MEM_READ (0x40000000)
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-LABEL: Relocations [
// CHECK-NEXT: Section (1) .text {
// CHECK-NEXT: 0x186A04 IMAGE_REL_ARM64_BRANCH26 foo (14)
// CHECK-NEXT: 0x3D091C IMAGE_REL_ARM64_BRANCH26 foo (14)
// CHECK-NEXT: }
// CHECK-NEXT: Section (5) .pdata {
// CHECK-NEXT: 0x0 IMAGE_REL_ARM64_ADDR32NB .text (0)
// CHECK-NEXT: 0x4 IMAGE_REL_ARM64_ADDR32NB .xdata (9)
// CHECK-NEXT: 0x8 IMAGE_REL_ARM64_ADDR32NB .text (0)
// CHECK-NEXT: 0xC IMAGE_REL_ARM64_ADDR32NB .xdata (9)
// CHECK-NEXT: 0x10 IMAGE_REL_ARM64_ADDR32NB $L.text_1 (2)
// CHECK-NEXT: 0x14 IMAGE_REL_ARM64_ADDR32NB .xdata (9)
// CHECK-NEXT: 0x18 IMAGE_REL_ARM64_ADDR32NB $L.text_2 (3)
// CHECK-NEXT: 0x1C IMAGE_REL_ARM64_ADDR32NB .xdata (9)
// CHECK-NEXT: 0x20 IMAGE_REL_ARM64_ADDR32NB $L.text_3 (4)
// CHECK-NEXT: 0x24 IMAGE_REL_ARM64_ADDR32NB .xdata (9)
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-LABEL: UnwindInformation [
// CHECK-NEXT: RuntimeFunction {
// CHECK-NEXT: Function: a (0x0)
// CHECK-NEXT: ExceptionRecord: .xdata (0x0)
// CHECK-NEXT: ExceptionData {
// CHECK-NEXT: FunctionLength: 1048572
// CHECK-NEXT: Version: 0
// CHECK-NEXT: ExceptionData: No
// CHECK-NEXT: EpiloguePacked: No
// CHECK-NEXT: EpilogueScopes: 0
// CHECK-NEXT: ByteCodeLength: 4
// CHECK-NEXT: Prologue [
// CHECK-NEXT: 0xd561 ; str x30, [sp, #-16]!
// CHECK-NEXT: 0xe4 ; end
// CHECK-NEXT: ]
// CHECK-NEXT: EpilogueScopes [
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: RuntimeFunction {
// CHECK-NEXT: Function: a +0xFFFFC (0xFFFFC)
// CHECK-NEXT: ExceptionRecord: .xdata +0x8 (0x8)
// CHECK-NEXT: ExceptionData {
// CHECK-NEXT: FunctionLength: 551444
// CHECK-NEXT: Version: 0
// CHECK-NEXT: ExceptionData: No
// CHECK-NEXT: EpiloguePacked: Yes
// CHECK-NEXT: EpilogueOffset: 1
// CHECK-NEXT: ByteCodeLength: 4
// CHECK-NEXT: Prologue [
// CHECK-NEXT: 0xe5 ; end_c
// CHECK-NEXT: 0xd561 ; str x30, [sp, #-16]!
// CHECK-NEXT: 0xe4 ; end
// CHECK-NEXT: ]
// CHECK-NEXT: Epilogue [
// CHECK-NEXT: 0xd561 ; ldr x30, [sp], #16
// CHECK-NEXT: 0xe4 ; end
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: RuntimeFunction {
// CHECK-NEXT: Function: b (0x186A10)
// CHECK-NEXT: ExceptionRecord: .xdata +0x10 (0x10)
// CHECK-NEXT: ExceptionData {
// CHECK-NEXT: FunctionLength: 1048572
// CHECK-NEXT: Version: 0
// CHECK-NEXT: ExceptionData: No
// CHECK-NEXT: EpiloguePacked: No
// CHECK-NEXT: EpilogueScopes: 0
// CHECK-NEXT: ByteCodeLength: 8
// CHECK-NEXT: Prologue [
// CHECK-NEXT: 0xe1 ; mov fp, sp
// CHECK-NEXT: 0xc81e ; stp x19, x20, [sp, #240]
// CHECK-NEXT: 0x9f ; stp x29, x30, [sp, #-256]!
// CHECK-NEXT: 0xe4 ; end
// CHECK-NEXT: ]
// CHECK-NEXT: EpilogueScopes [
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: RuntimeFunction {
// CHECK-NEXT: Function: $L.text_2 +0x86A0C (0x286A0C)
// CHECK-NEXT: ExceptionRecord: .xdata +0x1C (0x1C)
// CHECK-NEXT: ExceptionData {
// CHECK-NEXT: FunctionLength: 1048572
// CHECK-NEXT: Version: 0
// CHECK-NEXT: ExceptionData: No
// CHECK-NEXT: EpiloguePacked: Yes
// CHECK-NEXT: EpilogueOffset: 0
// CHECK-NEXT: ByteCodeLength: 8
// CHECK-NEXT: Prologue [
// CHECK-NEXT: 0xe5 ; end_c
// CHECK-NEXT: 0xe1 ; mov fp, sp
// CHECK-NEXT: 0xc81e ; stp x19, x20, [sp, #240]
// CHECK-NEXT: 0x9f ; stp x29, x30, [sp, #-256]!
// CHECK-NEXT: 0xe4 ; end
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: RuntimeFunction {
// CHECK-NEXT: Function: $L.text_3 +0x86A08 (0x386A08)
// CHECK-NEXT: ExceptionRecord: .xdata +0x28 (0x28)
// CHECK-NEXT: ExceptionData {
// CHECK-NEXT: FunctionLength: 302888
// CHECK-NEXT: Version: 0
// CHECK-NEXT: ExceptionData: No
// CHECK-NEXT: EpiloguePacked: Yes
// CHECK-NEXT: EpilogueOffset: 1
// CHECK-NEXT: ByteCodeLength: 8
// CHECK-NEXT: Prologue [
// CHECK-NEXT: 0xe5 ; end_c
// CHECK-NEXT: 0xe1 ; mov fp, sp
// CHECK-NEXT: 0xc81e ; stp x19, x20, [sp, #240]
// CHECK-NEXT: 0x9f ; stp x29, x30, [sp, #-256]!
// CHECK-NEXT: 0xe4 ; end
// CHECK-NEXT: ]
// CHECK-NEXT: Epilogue [
// CHECK-NEXT: 0xe1 ; mov sp, fp
// CHECK-NEXT: 0xc81e ; ldp x19, x20, [sp, #240]
// CHECK-NEXT: 0x9f ; ldp x29, x30, [sp], #256
// CHECK-NEXT: 0xe4 ; end
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: ]
.text
// A simple function with an single epilog mirroring the prolog.
.global a
.p2align 2
.seh_proc a
a:
str x30, [sp, #-16]!
.seh_save_reg_x x30, 16
.seh_endprologue
.rept 400000
nop
.endr
bl foo
.seh_startepilogue
ldr x30, [sp], #16
.seh_save_reg_x x30, 16
.seh_endepilogue
ret
.seh_endfunclet
.seh_endproc
// Example 1 from https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#function-fragments
.global b
.p2align 2
.seh_proc b
b:
stp x29, lr, [sp, #-256]!
.seh_save_fplr_x 256
stp x19, x20, [sp, #240]
.seh_save_regp x19, 240
mov x29, fp
.seh_set_fp
.seh_endprologue
.rept 600000
nop
.endr
bl foo
.seh_startepilogue
mov sp, x29
.seh_set_fp
ldp x19, x20, [sp, #240]
.seh_save_regp x19, 240
ldp x29, lr, [sp], #256
.seh_save_fplr_x 256
.seh_endepilogue
ret
.seh_endfunclet
.seh_endproc

View File

@ -855,7 +855,7 @@ bool Decoder::opcode_end_c(const uint8_t *OC, unsigned &Offset, unsigned Length,
bool Prologue) {
SW.startLine() << format("0x%02x ; end_c\n", OC[Offset]);
++Offset;
return true;
return false;
}
bool Decoder::opcode_save_next(const uint8_t *OC, unsigned &Offset,