diff --git a/llvm/include/llvm/MC/MCStreamer.h b/llvm/include/llvm/MC/MCStreamer.h --- a/llvm/include/llvm/MC/MCStreamer.h +++ b/llvm/include/llvm/MC/MCStreamer.h @@ -171,6 +171,17 @@ void emitConstantPools() override; + virtual void emitARMWinCFIAllocStack(unsigned Size, bool Wide); + virtual void emitARMWinCFISaveRegMask(unsigned Mask, bool Wide); + virtual void emitARMWinCFISetFP(unsigned Reg); + virtual void emitARMWinCFISaveFRegs(unsigned First, unsigned Last); + virtual void emitARMWinCFISaveLR(unsigned Offset); + virtual void emitARMWinCFIPrologEnd(); + virtual void emitARMWinCFINop(bool Wide); + virtual void emitARMWinCFIEpilogStart(); + virtual void emitARMWinCFIEpilogEnd(bool Nop, bool WideNop); + virtual void emitARMWinCFICustom(unsigned Opcode); + /// Reset any state between object emissions, i.e. the equivalent of /// MCStreamer's reset method. virtual void reset(); diff --git a/llvm/include/llvm/MC/MCWin64EH.h b/llvm/include/llvm/MC/MCWin64EH.h --- a/llvm/include/llvm/MC/MCWin64EH.h +++ b/llvm/include/llvm/MC/MCWin64EH.h @@ -57,13 +57,19 @@ bool HandlerData) const override; }; -class ARM64UnwindEmitter : public WinEH::UnwindEmitter { +class ARMUnwindEmitter : public WinEH::UnwindEmitter { public: void Emit(MCStreamer &Streamer) const override; void EmitUnwindInfo(MCStreamer &Streamer, WinEH::FrameInfo *FI, bool HandlerData) const override; }; +class ARM64UnwindEmitter : public WinEH::UnwindEmitter { +public: + void Emit(MCStreamer &Streamer) const override; + void EmitUnwindInfo(MCStreamer &Streamer, WinEH::FrameInfo *FI, + bool HandlerData) const override; +}; } } // end namespace llvm diff --git a/llvm/include/llvm/Support/Win64EH.h b/llvm/include/llvm/Support/Win64EH.h --- a/llvm/include/llvm/Support/Win64EH.h +++ b/llvm/include/llvm/Support/Win64EH.h @@ -24,6 +24,9 @@ /// UnwindOpcodes - Enumeration whose values specify a single operation in /// the prolog of a function. enum UnwindOpcodes { + // The following set of unwind opcodes is for x86_64. They are documented at + // https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64. + // Some generic values from this set are used for other architectures too. UOP_PushNonVol = 0, UOP_AllocLarge, UOP_AllocSmall, @@ -57,7 +60,38 @@ UOP_SaveNext, UOP_TrapFrame, UOP_Context, - UOP_ClearUnwoundToCall + UOP_ClearUnwoundToCall, + // The following set of unwind opcodes is for ARM. They are documented at + // https://docs.microsoft.com/en-us/cpp/build/arm-exception-handling + + // Stack allocations use UOP_AllocSmall, UOP_AllocLarge from above, plus + // the following. AllocSmall, AllocLarge and AllocHuge represent a 16 bit + // instruction, while the WideAlloc* opcodes represent a 32 bit instruction. + // Small can represent a stack offset of 0x7f*4 (252) bytes, Medium can + // represent up to 0x3ff*4 (4092) bytes, Large up to 0xffff*4 (262140) bytes, + // and Huge up to 0xffffff*4 (67108860) bytes. + UOP_AllocHuge, + UOP_WideAllocMedium, + UOP_WideAllocLarge, + UOP_WideAllocHuge, + + UOP_WideSaveRegMask, + // Using UOP_SetFP from above + UOP_SaveRegsR4R7LR, + UOP_WideSaveRegsR4R11LR, + UOP_SaveFRegD8D15, + UOP_SaveRegMask, + UOP_SaveLR, + UOP_SaveFRegD0D15, + UOP_SaveFRegD16D31, + // Using UOP_Nop from above + UOP_WideNop, + // Using UOP_End from above + UOP_EndNop, + UOP_WideEndNop, + // A custom unspecified opcode, consisting of one or more bytes. This + // allows producing opcodes in the implementation defined/reserved range. + UOP_Custom, }; /// UnwindCode - This union describes a single operation in a function prolog, diff --git a/llvm/lib/CodeGen/AsmPrinter/WinException.cpp b/llvm/lib/CodeGen/AsmPrinter/WinException.cpp --- a/llvm/lib/CodeGen/AsmPrinter/WinException.cpp +++ b/llvm/lib/CodeGen/AsmPrinter/WinException.cpp @@ -693,7 +693,8 @@ } int UnwindHelpOffset = 0; - if (Asm->MAI->usesWindowsCFI()) + if (Asm->MAI->usesWindowsCFI() && + FuncInfo.UnwindHelpFrameIdx != std::numeric_limits::max()) UnwindHelpOffset = getFrameIndexOffset(FuncInfo.UnwindHelpFrameIdx, FuncInfo); @@ -755,7 +756,8 @@ AddComment("IPToStateXData"); OS.emitValue(create32bitRef(IPToStateXData), 4); - if (Asm->MAI->usesWindowsCFI()) { + if (Asm->MAI->usesWindowsCFI() && + FuncInfo.UnwindHelpFrameIdx != std::numeric_limits::max()) { AddComment("UnwindHelp"); OS.emitInt32(UnwindHelpOffset); } diff --git a/llvm/lib/MC/MCAsmStreamer.cpp b/llvm/lib/MC/MCAsmStreamer.cpp --- a/llvm/lib/MC/MCAsmStreamer.cpp +++ b/llvm/lib/MC/MCAsmStreamer.cpp @@ -2069,10 +2069,14 @@ OS << "\t.seh_handler "; Sym->print(OS, MAI); + char Marker = '@'; + const Triple &T = getContext().getTargetTriple(); + if (T.getArch() == Triple::arm || T.getArch() == Triple::thumb) + Marker = '%'; if (Unwind) - OS << ", @unwind"; + OS << ", " << Marker << "unwind"; if (Except) - OS << ", @except"; + OS << ", " << Marker << "except"; EmitEOL(); } diff --git a/llvm/lib/MC/MCParser/COFFAsmParser.cpp b/llvm/lib/MC/MCParser/COFFAsmParser.cpp --- a/llvm/lib/MC/MCParser/COFFAsmParser.cpp +++ b/llvm/lib/MC/MCParser/COFFAsmParser.cpp @@ -699,8 +699,8 @@ bool COFFAsmParser::ParseAtUnwindOrAtExcept(bool &unwind, bool &except) { StringRef identifier; - if (getLexer().isNot(AsmToken::At)) - return TokError("a handler attribute must begin with '@'"); + if (getLexer().isNot(AsmToken::At) && getLexer().isNot(AsmToken::Percent)) + return TokError("a handler attribute must begin with '@' or '%'"); SMLoc startLoc = getLexer().getLoc(); Lex(); if (getParser().parseIdentifier(identifier)) diff --git a/llvm/lib/MC/MCWin64EH.cpp b/llvm/lib/MC/MCWin64EH.cpp --- a/llvm/lib/MC/MCWin64EH.cpp +++ b/llvm/lib/MC/MCWin64EH.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "llvm/MC/MCWin64EH.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/Twine.h" #include "llvm/MC/MCContext.h" #include "llvm/MC/MCExpr.h" @@ -250,8 +251,21 @@ ::EmitUnwindInfo(Streamer, info); } -static int64_t GetAbsDifference(MCStreamer &Streamer, const MCSymbol *LHS, - const MCSymbol *RHS) { +static const MCExpr *GetSubDivExpr(MCStreamer &Streamer, const MCSymbol *LHS, + const MCSymbol *RHS, int Div) { + MCContext &Context = Streamer.getContext(); + const MCExpr *Expr = + MCBinaryExpr::createSub(MCSymbolRefExpr::create(LHS, Context), + MCSymbolRefExpr::create(RHS, Context), Context); + if (Div != 1) + Expr = MCBinaryExpr::createDiv(Expr, MCConstantExpr::create(Div, Context), + Context); + return Expr; +} + +static Optional GetOptionalAbsDifference(MCStreamer &Streamer, + const MCSymbol *LHS, + const MCSymbol *RHS) { MCContext &Context = Streamer.getContext(); const MCExpr *Diff = MCBinaryExpr::createSub(MCSymbolRefExpr::create(LHS, Context), @@ -262,10 +276,18 @@ // unusual constructs, like an inline asm with an alignment directive. int64_t value; if (!Diff->evaluateAsAbsolute(value, OS->getAssembler())) - report_fatal_error("Failed to evaluate function length in SEH unwind info"); + return None; return value; } +static int64_t GetAbsDifference(MCStreamer &Streamer, const MCSymbol *LHS, + const MCSymbol *RHS) { + Optional MaybeDiff = GetOptionalAbsDifference(Streamer, LHS, RHS); + if (!MaybeDiff) + report_fatal_error("Failed to evaluate function length in SEH unwind info"); + return *MaybeDiff; +} + static uint32_t ARM64CountOfUnwindCodes(ArrayRef Insns) { uint32_t Count = 0; for (const auto &I : Insns) { @@ -1094,8 +1116,388 @@ 4); } -static void ARM64EmitRuntimeFunction(MCStreamer &streamer, - const WinEH::FrameInfo *info) { +static uint32_t ARMCountOfUnwindCodes(ArrayRef Insns) { + uint32_t Count = 0; + for (const auto &I : Insns) { + switch (static_cast(I.Operation)) { + default: + llvm_unreachable("Unsupported ARM unwind code"); + case Win64EH::UOP_AllocSmall: + Count += 1; + break; + case Win64EH::UOP_AllocLarge: + Count += 3; + break; + case Win64EH::UOP_AllocHuge: + Count += 4; + break; + case Win64EH::UOP_WideAllocMedium: + Count += 2; + break; + case Win64EH::UOP_WideAllocLarge: + Count += 3; + break; + case Win64EH::UOP_WideAllocHuge: + Count += 4; + break; + case Win64EH::UOP_WideSaveRegMask: + Count += 2; + break; + case Win64EH::UOP_SetFP: + Count += 1; + break; + case Win64EH::UOP_SaveRegsR4R7LR: + Count += 1; + break; + case Win64EH::UOP_WideSaveRegsR4R11LR: + Count += 1; + break; + case Win64EH::UOP_SaveFRegD8D15: + Count += 1; + break; + case Win64EH::UOP_SaveRegMask: + Count += 2; + break; + case Win64EH::UOP_SaveLR: + Count += 2; + break; + case Win64EH::UOP_SaveFRegD0D15: + Count += 2; + break; + case Win64EH::UOP_SaveFRegD16D31: + Count += 2; + break; + case Win64EH::UOP_Nop: + case Win64EH::UOP_WideNop: + case Win64EH::UOP_End: + case Win64EH::UOP_EndNop: + case Win64EH::UOP_WideEndNop: + Count += 1; + break; + case Win64EH::UOP_Custom: { + int J; + for (J = 3; J > 0; J--) + if (I.Offset & (0xffu << (8 * J))) + break; + Count += J + 1; + break; + } + } + } + return Count; +} + +// Unwind opcode encodings and restrictions are documented at +// https://docs.microsoft.com/en-us/cpp/build/arm-exception-handling +static void ARMEmitUnwindCode(MCStreamer &streamer, const MCSymbol *begin, + WinEH::Instruction &inst) { + uint32_t w, lr; + int i; + switch (static_cast(inst.Operation)) { + default: + llvm_unreachable("Unsupported ARM unwind code"); + case Win64EH::UOP_AllocSmall: + assert((inst.Offset & 3) == 0); + assert(inst.Offset / 4 <= 0x7f); + streamer.emitInt8(inst.Offset / 4); + break; + case Win64EH::UOP_WideSaveRegMask: + assert((inst.Register & ~0x5fff) == 0); + lr = (inst.Register >> 14) & 1; + w = 0x8000 | (inst.Register & 0x1fff) | (lr << 13); + streamer.emitInt8((w >> 8) & 0xff); + streamer.emitInt8((w >> 0) & 0xff); + break; + case Win64EH::UOP_SetFP: + assert(inst.Register <= 0x0f); + streamer.emitInt8(0xc0 | inst.Register); + break; + case Win64EH::UOP_SaveRegsR4R7LR: + assert(inst.Register >= 4 && inst.Register <= 7); + assert(inst.Offset <= 1); + streamer.emitInt8(0xd0 | (inst.Register - 4) | (inst.Offset << 2)); + break; + case Win64EH::UOP_WideSaveRegsR4R11LR: + assert(inst.Register >= 8 && inst.Register <= 11); + assert(inst.Offset <= 1); + streamer.emitInt8(0xd8 | (inst.Register - 8) | (inst.Offset << 2)); + break; + case Win64EH::UOP_SaveFRegD8D15: + assert(inst.Register >= 8 && inst.Register <= 15); + streamer.emitInt8(0xe0 | (inst.Register - 8)); + break; + case Win64EH::UOP_WideAllocMedium: + assert((inst.Offset & 3) == 0); + assert(inst.Offset / 4 <= 0x3ff); + w = 0xe800 | (inst.Offset / 4); + streamer.emitInt8((w >> 8) & 0xff); + streamer.emitInt8((w >> 0) & 0xff); + break; + case Win64EH::UOP_SaveRegMask: + assert((inst.Register & ~0x40ff) == 0); + lr = (inst.Register >> 14) & 1; + w = 0xec00 | (inst.Register & 0x0ff) | (lr << 8); + streamer.emitInt8((w >> 8) & 0xff); + streamer.emitInt8((w >> 0) & 0xff); + break; + case Win64EH::UOP_SaveLR: + assert((inst.Offset & 3) == 0); + assert(inst.Offset / 4 <= 0x0f); + streamer.emitInt8(0xef); + streamer.emitInt8(inst.Offset / 4); + break; + case Win64EH::UOP_SaveFRegD0D15: + assert(inst.Register <= 15); + assert(inst.Offset <= 15); + assert(inst.Register <= inst.Offset); + streamer.emitInt8(0xf5); + streamer.emitInt8((inst.Register << 4) | inst.Offset); + break; + case Win64EH::UOP_SaveFRegD16D31: + assert(inst.Register >= 16 && inst.Register <= 31); + assert(inst.Offset >= 16 && inst.Offset <= 31); + assert(inst.Register <= inst.Offset); + streamer.emitInt8(0xf6); + streamer.emitInt8(((inst.Register - 16) << 4) | (inst.Offset - 16)); + break; + case Win64EH::UOP_AllocLarge: + assert((inst.Offset & 3) == 0); + assert(inst.Offset / 4 <= 0xffff); + w = inst.Offset / 4; + streamer.emitInt8(0xf7); + streamer.emitInt8((w >> 8) & 0xff); + streamer.emitInt8((w >> 0) & 0xff); + break; + case Win64EH::UOP_AllocHuge: + assert((inst.Offset & 3) == 0); + assert(inst.Offset / 4 <= 0xffffff); + w = inst.Offset / 4; + streamer.emitInt8(0xf8); + streamer.emitInt8((w >> 16) & 0xff); + streamer.emitInt8((w >> 8) & 0xff); + streamer.emitInt8((w >> 0) & 0xff); + break; + case Win64EH::UOP_WideAllocLarge: + assert((inst.Offset & 3) == 0); + assert(inst.Offset / 4 <= 0xffff); + w = inst.Offset / 4; + streamer.emitInt8(0xf9); + streamer.emitInt8((w >> 8) & 0xff); + streamer.emitInt8((w >> 0) & 0xff); + break; + case Win64EH::UOP_WideAllocHuge: + assert((inst.Offset & 3) == 0); + assert(inst.Offset / 4 <= 0xffffff); + w = inst.Offset / 4; + streamer.emitInt8(0xfa); + streamer.emitInt8((w >> 16) & 0xff); + streamer.emitInt8((w >> 8) & 0xff); + streamer.emitInt8((w >> 0) & 0xff); + break; + case Win64EH::UOP_Nop: + streamer.emitInt8(0xfb); + break; + case Win64EH::UOP_WideNop: + streamer.emitInt8(0xfc); + break; + case Win64EH::UOP_EndNop: + streamer.emitInt8(0xfd); + break; + case Win64EH::UOP_WideEndNop: + streamer.emitInt8(0xfe); + break; + case Win64EH::UOP_End: + streamer.emitInt8(0xff); + break; + case Win64EH::UOP_Custom: + for (i = 3; i > 0; i--) + if (inst.Offset & (0xffu << (8 * i))) + break; + for (; i >= 0; i--) + streamer.emitInt8((inst.Offset >> (8 * i)) & 0xff); + break; + } +} + +// Populate the .xdata section. The format of .xdata on ARM is documented at +// https://docs.microsoft.com/en-us/cpp/build/arm-exception-handling +static void ARMEmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info, + bool TryPacked = true) { + // If this UNWIND_INFO already has a symbol, it's already been emitted. + if (info->Symbol) + return; + // If there's no unwind info here (not even a terminating UOP_End), the + // unwind info is considered bogus and skipped. If this was done in + // response to an explicit .seh_handlerdata, the associated trailing + // handler data is left orphaned in the xdata section. + if (info->empty()) { + info->EmitAttempted = true; + return; + } + if (info->EmitAttempted) { + // If we tried to emit unwind info before (due to an explicit + // .seh_handlerdata directive), but skipped it (because there was no + // valid information to emit at the time), and it later got valid unwind + // opcodes, we can't emit it here, because the trailing handler data + // was already emitted elsewhere in the xdata section. + streamer.getContext().reportError( + SMLoc(), "Earlier .seh_handlerdata for " + info->Function->getName() + + " skipped due to no unwind info at the time " + "(.seh_handlerdata too early?), but the function later " + "did get unwind info that can't be emitted"); + return; + } + + MCContext &context = streamer.getContext(); + MCSymbol *Label = context.createTempSymbol(); + + streamer.emitValueToAlignment(4); + streamer.emitLabel(Label); + info->Symbol = Label; + + Optional RawFuncLength; + const MCExpr *FuncLengthExpr = nullptr; + if (!info->FuncletOrFuncEnd) { + report_fatal_error("FuncletOrFuncEnd not set"); + } else { + // As the size of many thumb2 instructions isn't known until later, + // we can't always rely on being able to calculate the absolute + // length of the function here. If we can't calculate it, defer it + // to a relocation. + // + // In such a case, we won't know if the function is too long so that + // the unwind info would need to be split (but this isn't implemented + // anyway). FIXME: But we probably should emit this as an ARM specific + // fixup, that would have the appropriate range checking of the value. + RawFuncLength = + GetOptionalAbsDifference(streamer, info->FuncletOrFuncEnd, info->Begin); + if (!RawFuncLength) + FuncLengthExpr = + GetSubDivExpr(streamer, info->FuncletOrFuncEnd, info->Begin, 2); + } + uint32_t FuncLength = 0; + if (RawFuncLength) + FuncLength = (uint32_t)*RawFuncLength / 2; + if (FuncLength > 0x3FFFF) + report_fatal_error("SEH unwind data splitting not yet implemented"); + uint32_t PrologCodeBytes = ARMCountOfUnwindCodes(info->Instructions); + uint32_t TotalCodeBytes = PrologCodeBytes; + + // Process epilogs. + MapVector EpilogInfo; + // Epilogs processed so far. + std::vector AddedEpilogs; + + for (auto &I : info->EpilogMap) { + MCSymbol *EpilogStart = I.first; + auto &EpilogInstrs = I.second; + uint32_t CodeBytes = ARMCountOfUnwindCodes(EpilogInstrs); + + MCSymbol *MatchingEpilog = + FindMatchingEpilog(EpilogInstrs, AddedEpilogs, info); + 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 { + EpilogInfo[EpilogStart] = TotalCodeBytes; + TotalCodeBytes += CodeBytes; + AddedEpilogs.push_back(EpilogStart); + } + } + + // Code Words, Epilog count, F, 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 = info->EpilogMap.size(); + bool ExtensionWord = EpilogCount > 31 || CodeWords > 15; + if (!ExtensionWord) { + row1 |= (EpilogCount & 0x1F) << 23; + row1 |= (CodeWords & 0x0F) << 28; + } + if (info->HandlesExceptions) // X + row1 |= 1 << 20; + row1 |= FuncLength & 0x3FFFF; + if (RawFuncLength) + streamer.emitInt32(row1); + else + streamer.emitValue( + MCBinaryExpr::createOr(FuncLengthExpr, + MCConstantExpr::create(row1, context), context), + 4); + + // 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); + } + + // Epilog Start Index, Epilog Start Offset + for (auto &I : EpilogInfo) { + MCSymbol *EpilogStart = I.first; + uint32_t EpilogIndex = I.second; + Optional MaybeEpilogOffset = + GetOptionalAbsDifference(streamer, EpilogStart, info->Begin); + const MCExpr *OffsetExpr = nullptr; + uint32_t EpilogOffset = 0; + if (MaybeEpilogOffset) + EpilogOffset = *MaybeEpilogOffset / 2; + else + OffsetExpr = GetSubDivExpr(streamer, EpilogStart, info->Begin, 2); + uint32_t row3 = EpilogOffset; + row3 |= 0xe << 20; // Condition, always + row3 |= (EpilogIndex & 0x3FF) << 24; + if (MaybeEpilogOffset) + streamer.emitInt32(row3); + else + streamer.emitValue( + MCBinaryExpr::createOr( + OffsetExpr, MCConstantExpr::create(row3, context), context), + 4); + } + + // 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(); + ARMEmitUnwindCode(streamer, info->Begin, inst); + } + + // Emit epilog unwind instructions + for (auto &I : info->EpilogMap) { + auto &EpilogInstrs = I.second; + for (uint32_t i = 0; i < EpilogInstrs.size(); i++) { + WinEH::Instruction inst = EpilogInstrs[i]; + ARMEmitUnwindCode(streamer, info->Begin, inst); + } + } + + int32_t BytesMod = CodeWords * 4 - TotalCodeBytes; + assert(BytesMod >= 0); + for (int i = 0; i < BytesMod; i++) + streamer.emitInt8(0xFB); + + if (info->HandlesExceptions) + streamer.emitValue( + MCSymbolRefExpr::create(info->ExceptionHandler, + MCSymbolRefExpr::VK_COFF_IMGREL32, context), + 4); +} + +static void ARMEmitRuntimeFunction(MCStreamer &streamer, + const WinEH::FrameInfo *info) { MCContext &context = streamer.getContext(); streamer.emitValueToAlignment(4); @@ -1130,7 +1532,7 @@ continue; MCSection *PData = Streamer.getAssociatedPDataSection(CFI->TextSection); Streamer.SwitchSection(PData); - ARM64EmitRuntimeFunction(Streamer, Info); + ARMEmitRuntimeFunction(Streamer, Info); } } @@ -1153,3 +1555,48 @@ Streamer.SwitchSection(XData); ARM64EmitUnwindInfo(Streamer, info, /* TryPacked = */ !HandlerData); } + +void llvm::Win64EH::ARMUnwindEmitter::Emit(MCStreamer &Streamer) const { + // Emit the unwind info structs first. + for (const auto &CFI : Streamer.getWinFrameInfos()) { + WinEH::FrameInfo *Info = CFI.get(); + if (Info->empty()) + continue; + MCSection *XData = Streamer.getAssociatedXDataSection(CFI->TextSection); + Streamer.SwitchSection(XData); + ARMEmitUnwindInfo(Streamer, Info); + } + + // Now emit RUNTIME_FUNCTION entries. + for (const auto &CFI : Streamer.getWinFrameInfos()) { + WinEH::FrameInfo *Info = CFI.get(); + // ARMEmitUnwindInfo above clears the info struct, so we can't check + // empty here. But if a Symbol is set, we should create the corresponding + // pdata entry. + if (!Info->Symbol) + continue; + MCSection *PData = Streamer.getAssociatedPDataSection(CFI->TextSection); + Streamer.SwitchSection(PData); + ARMEmitRuntimeFunction(Streamer, Info); + } +} + +void llvm::Win64EH::ARMUnwindEmitter::EmitUnwindInfo(MCStreamer &Streamer, + WinEH::FrameInfo *info, + bool HandlerData) const { + // Called if there's an .seh_handlerdata directive before the end of the + // function. This forces writing the xdata record already here - and + // in this case, the function isn't actually ended already, but the xdata + // record needs to know the function length. In these cases, if the funclet + // end hasn't been marked yet, the xdata function length won't cover the + // whole function, only up to this point. + if (!info->FuncletOrFuncEnd) { + Streamer.SwitchSection(info->TextSection); + info->FuncletOrFuncEnd = Streamer.emitCFILabel(); + } + // Switch sections (the static function above is meant to be called from + // here and from Emit(). + MCSection *XData = Streamer.getAssociatedXDataSection(info->TextSection); + Streamer.SwitchSection(XData); + ARMEmitUnwindInfo(Streamer, info, /* TryPacked = */ !HandlerData); +} diff --git a/llvm/lib/Target/ARM/AsmParser/ARMAsmParser.cpp b/llvm/lib/Target/ARM/AsmParser/ARMAsmParser.cpp --- a/llvm/lib/Target/ARM/AsmParser/ARMAsmParser.cpp +++ b/llvm/lib/Target/ARM/AsmParser/ARMAsmParser.cpp @@ -453,6 +453,7 @@ bool AllowRAAC = false); bool parseMemory(OperandVector &); bool parseOperand(OperandVector &, StringRef Mnemonic); + bool parseImmExpr(int64_t &Out); bool parsePrefix(ARMMCExpr::VariantKind &RefKind); bool parseMemRegOffsetShift(ARM_AM::ShiftOpc &ShiftType, unsigned &ShiftAmount); @@ -488,6 +489,17 @@ bool parseDirectiveAlign(SMLoc L); bool parseDirectiveThumbSet(SMLoc L); + bool parseDirectiveSEHAllocStack(SMLoc L, bool Wide); + bool parseDirectiveSEHSaveRegs(SMLoc L, bool Wide); + bool parseDirectiveSEHSetFP(SMLoc L); + bool parseDirectiveSEHSaveFRegs(SMLoc L); + bool parseDirectiveSEHSaveLR(SMLoc L); + bool parseDirectiveSEHPrologEnd(SMLoc L); + bool parseDirectiveSEHNop(SMLoc L, bool Wide); + bool parseDirectiveSEHEpilogStart(SMLoc L); + bool parseDirectiveSEHEpilogEnd(SMLoc L, bool Nop, bool WideNop); + bool parseDirectiveSEHCustom(SMLoc L); + bool isMnemonicVPTPredicable(StringRef Mnemonic, StringRef ExtraToken); StringRef splitMnemonic(StringRef Mnemonic, StringRef ExtraToken, unsigned &PredicationCode, @@ -6317,6 +6329,18 @@ } } +bool ARMAsmParser::parseImmExpr(int64_t &Out) { + const MCExpr *Expr = nullptr; + SMLoc L = getParser().getTok().getLoc(); + if (check(getParser().parseExpression(Expr), L, "expected expression")) + return true; + const MCConstantExpr *Value = dyn_cast_or_null(Expr); + if (check(!Value, L, "expected constant expression")) + return true; + Out = Value->getValue(); + return false; +} + // parsePrefix - Parse ARM 16-bit relocations expression prefix, i.e. // :lower16: and :upper16:. bool ARMAsmParser::parsePrefix(ARMMCExpr::VariantKind &RefKind) { @@ -11082,6 +11106,39 @@ parseDirectiveTLSDescSeq(DirectiveID.getLoc()); else return true; + } else if (IsCOFF) { + if (IDVal == ".seh_stackalloc") + parseDirectiveSEHAllocStack(DirectiveID.getLoc(), false); + else if (IDVal == ".seh_stackalloc_w") + parseDirectiveSEHAllocStack(DirectiveID.getLoc(), true); + else if (IDVal == ".seh_save_regs") + parseDirectiveSEHSaveRegs(DirectiveID.getLoc(), false); + else if (IDVal == ".seh_save_regs_w") + parseDirectiveSEHSaveRegs(DirectiveID.getLoc(), true); + else if (IDVal == ".seh_set_fp") + parseDirectiveSEHSetFP(DirectiveID.getLoc()); + else if (IDVal == ".seh_save_fregs") + parseDirectiveSEHSaveFRegs(DirectiveID.getLoc()); + else if (IDVal == ".seh_save_lr") + parseDirectiveSEHSaveLR(DirectiveID.getLoc()); + else if (IDVal == ".seh_endprologue") + parseDirectiveSEHPrologEnd(DirectiveID.getLoc()); + else if (IDVal == ".seh_nop") + parseDirectiveSEHNop(DirectiveID.getLoc(), false); + else if (IDVal == ".seh_nop_w") + parseDirectiveSEHNop(DirectiveID.getLoc(), true); + else if (IDVal == ".seh_startepilogue") + parseDirectiveSEHEpilogStart(DirectiveID.getLoc()); + else if (IDVal == ".seh_endepilogue") + parseDirectiveSEHEpilogEnd(DirectiveID.getLoc(), false, false); + else if (IDVal == ".seh_endepilogue_nop") + parseDirectiveSEHEpilogEnd(DirectiveID.getLoc(), true, false); + else if (IDVal == ".seh_endepilogue_nop_w") + parseDirectiveSEHEpilogEnd(DirectiveID.getLoc(), true, true); + else if (IDVal == ".seh_custom") + parseDirectiveSEHCustom(DirectiveID.getLoc()); + else + return true; } else return true; return false; @@ -12018,6 +12075,164 @@ return false; } +/// parseDirectiveSEHAllocStack +/// ::= .seh_stackalloc +/// ::= .seh_stackalloc_w +bool ARMAsmParser::parseDirectiveSEHAllocStack(SMLoc L, bool Wide) { + int64_t Size; + if (parseImmExpr(Size)) + return true; + getTargetStreamer().emitARMWinCFIAllocStack(Size, Wide); + return false; +} + +/// parseDirectiveSEHSaveRegs +/// ::= .seh_save_regs +/// ::= .seh_save_regs_w +bool ARMAsmParser::parseDirectiveSEHSaveRegs(SMLoc L, bool Wide) { + SmallVector, 1> Operands; + + if (parseRegisterList(Operands) || + parseToken(AsmToken::EndOfStatement, "unexpected token in directive")) + return true; + ARMOperand &Op = (ARMOperand &)*Operands[0]; + if (!Op.isRegList()) + return Error(L, ".seh_save_regs{_w} expects GPR registers"); + const SmallVectorImpl &RegList = Op.getRegList(); + uint32_t Mask = 0; + for (size_t i = 0; i < RegList.size(); ++i) { + unsigned Reg = MRI->getEncodingValue(RegList[i]); + if (Reg == 15) // pc -> lr + Reg = 14; + if (Reg == 13) + return Error(L, ".seh_save_regs{_w} can't include SP"); + assert(Reg < 16U && "Register out of range"); + unsigned Bit = (1u << Reg); + Mask |= Bit; + } + if (!Wide && (Mask & 0x1f00) != 0) + return Error(L, + ".seh_save_regs cannot save R8-R12, needs .seh_save_regs_w"); + getTargetStreamer().emitARMWinCFISaveRegMask(Mask, Wide); + return false; +} + +/// parseDirectiveSEHSetFP +/// ::= .seh_set_fp +bool ARMAsmParser::parseDirectiveSEHSetFP(SMLoc L) { + int Reg = tryParseRegister(); + if (Reg == -1 || !MRI->getRegClass(ARM::GPRRegClassID).contains(Reg)) + return Error(L, "expected GPR"); + unsigned Index = MRI->getEncodingValue(Reg); + if (Index > 14 || Index == 13) + return Error(L, "invalid register for .seh_set_fp"); + getTargetStreamer().emitARMWinCFISetFP(Index); + return false; +} + +/// parseDirectiveSEHSaveFRegs +/// ::= .seh_save_fregs +bool ARMAsmParser::parseDirectiveSEHSaveFRegs(SMLoc L) { + SmallVector, 1> Operands; + + if (parseRegisterList(Operands) || + parseToken(AsmToken::EndOfStatement, "unexpected token in directive")) + return true; + ARMOperand &Op = (ARMOperand &)*Operands[0]; + if (!Op.isDPRRegList()) + return Error(L, ".seh_save_fregs expects DPR registers"); + const SmallVectorImpl &RegList = Op.getRegList(); + uint32_t Mask = 0; + for (size_t i = 0; i < RegList.size(); ++i) { + unsigned Reg = MRI->getEncodingValue(RegList[i]); + assert(Reg < 32U && "Register out of range"); + unsigned Bit = (1u << Reg); + Mask |= Bit; + } + + if (Mask == 0) + return Error(L, ".seh_save_fregs missing registers"); + + unsigned First = 0; + while ((Mask & 1) == 0) { + First++; + Mask >>= 1; + } + if (((Mask + 1) & Mask) != 0) + return Error(L, + ".seh_save_fregs must take a contiguous range of registers"); + unsigned Last = First; + while ((Mask & 2) != 0) { + Last++; + Mask >>= 1; + } + if (First < 16 && Last >= 16) + return Error(L, ".seh_save_fregs must be all d0-d15 or d16-d31"); + getTargetStreamer().emitARMWinCFISaveFRegs(First, Last); + return false; +} + +/// parseDirectiveSEHSaveLR +/// ::= .seh_save_lr +bool ARMAsmParser::parseDirectiveSEHSaveLR(SMLoc L) { + int64_t Offset; + if (parseImmExpr(Offset)) + return true; + getTargetStreamer().emitARMWinCFISaveLR(Offset); + return false; +} + +/// parseDirectiveSEHPrologEnd +/// ::= .seh_endprologue +bool ARMAsmParser::parseDirectiveSEHPrologEnd(SMLoc L) { + getTargetStreamer().emitARMWinCFIPrologEnd(); + return false; +} + +/// parseDirectiveSEHNop +/// ::= .seh_nop +/// ::= .seh_nop_w +bool ARMAsmParser::parseDirectiveSEHNop(SMLoc L, bool Wide) { + getTargetStreamer().emitARMWinCFINop(Wide); + return false; +} + +/// parseDirectiveSEHEpilogStart +/// ::= .seh_startepilogue +bool ARMAsmParser::parseDirectiveSEHEpilogStart(SMLoc L) { + getTargetStreamer().emitARMWinCFIEpilogStart(); + return false; +} + +/// parseDirectiveSEHEpilogEnd +/// ::= .seh_endepilogue +/// ::= .seh_endepilogue_nop +/// ::= .seh_endepilogue_nop_w +bool ARMAsmParser::parseDirectiveSEHEpilogEnd(SMLoc L, bool Nop, bool WideNop) { + getTargetStreamer().emitARMWinCFIEpilogEnd(Nop, WideNop); + return false; +} + +/// parseDirectiveSEHCustom +/// ::= .seh_custom +bool ARMAsmParser::parseDirectiveSEHCustom(SMLoc L) { + unsigned Opcode = 0; + do { + int64_t Byte; + if (parseImmExpr(Byte)) + return true; + if (Byte > 0xff || Byte < 0) + return Error(L, "Invalid byte value in .seh_custom"); + if (Opcode > 0x00ffffff) + return Error(L, "Too many bytes in .seh_custom"); + // Store the bytes as one big endian number in Opcode. In a multi byte + // opcode sequence, the first byte can't be zero. + Opcode = (Opcode << 8) | Byte; + } while (parseOptionalToken(AsmToken::Comma)); + getTargetStreamer().emitARMWinCFICustom(Opcode); + return false; +} + /// Force static initialization. extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeARMAsmParser() { RegisterMCAsmParser X(getTheARMLETarget()); diff --git a/llvm/lib/Target/ARM/MCTargetDesc/ARMELFStreamer.cpp b/llvm/lib/Target/ARM/MCTargetDesc/ARMELFStreamer.cpp --- a/llvm/lib/Target/ARM/MCTargetDesc/ARMELFStreamer.cpp +++ b/llvm/lib/Target/ARM/MCTargetDesc/ARMELFStreamer.cpp @@ -101,6 +101,17 @@ void AnnotateTLSDescriptorSequence(const MCSymbolRefExpr *SRE) override; void emitThumbSet(MCSymbol *Symbol, const MCExpr *Value) override; + void emitARMWinCFIAllocStack(unsigned Size, bool Wide) override; + void emitARMWinCFISaveRegMask(unsigned Mask, bool Wide) override; + void emitARMWinCFISetFP(unsigned Reg) override; + void emitARMWinCFISaveFRegs(unsigned First, unsigned Last) override; + void emitARMWinCFISaveLR(unsigned Offset) override; + void emitARMWinCFIPrologEnd() override; + void emitARMWinCFINop(bool Wide) override; + void emitARMWinCFIEpilogStart() override; + void emitARMWinCFIEpilogEnd(bool Nop, bool Wide) override; + void emitARMWinCFICustom(unsigned Opcode) override; + public: ARMTargetAsmStreamer(MCStreamer &S, formatted_raw_ostream &OS, MCInstPrinter &InstPrinter, bool VerboseAsm); @@ -269,6 +280,99 @@ OS << '\n'; } +void ARMTargetAsmStreamer::emitARMWinCFIAllocStack(unsigned Size, bool Wide) { + if (Wide) + OS << "\t.seh_stackalloc_w\t" << Size << "\n"; + else + OS << "\t.seh_stackalloc\t" << Size << "\n"; +} + +static void printRegs(formatted_raw_ostream &OS, ListSeparator &LS, int First, + int Last) { + if (First != Last) + OS << LS << "r" << First << "-r" << Last; + else + OS << LS << "r" << First; +} + +void ARMTargetAsmStreamer::emitARMWinCFISaveRegMask(unsigned Mask, bool Wide) { + if (Wide) + OS << "\t.seh_save_regs_w\t"; + else + OS << "\t.seh_save_regs\t"; + ListSeparator LS; + int First = -1; + OS << "{"; + for (int I = 0; I <= 12; I++) { + if (Mask & (1 << I)) { + if (First < 0) + First = I; + } else { + if (First >= 0) { + printRegs(OS, LS, First, I - 1); + First = -1; + } + } + } + if (First >= 0) + printRegs(OS, LS, First, 12); + if (Mask & (1 << 14)) + OS << LS << "lr"; + OS << "}\n"; +} + +void ARMTargetAsmStreamer::emitARMWinCFISetFP(unsigned Reg) { + OS << "\t.seh_set_fp\tr" << Reg << "\n"; +} + +void ARMTargetAsmStreamer::emitARMWinCFISaveFRegs(unsigned First, + unsigned Last) { + if (First != Last) + OS << "\t.seh_save_fregs\t{d" << First << "-d" << Last << "}\n"; + else + OS << "\t.seh_save_fregs\t{d" << First << "}\n"; +} + +void ARMTargetAsmStreamer::emitARMWinCFISaveLR(unsigned Offset) { + OS << "\t.seh_save_lr\t" << Offset << "\n"; +} + +void ARMTargetAsmStreamer::emitARMWinCFIPrologEnd() { + OS << "\t.seh_endprologue\n"; +} + +void ARMTargetAsmStreamer::emitARMWinCFINop(bool Wide) { + if (Wide) + OS << "\t.seh_nop_w\n"; + else + OS << "\t.seh_nop\n"; +} + +void ARMTargetAsmStreamer::emitARMWinCFIEpilogStart() { + OS << "\t.seh_startepilogue\n"; +} + +void ARMTargetAsmStreamer::emitARMWinCFIEpilogEnd(bool Nop, bool WideNop) { + if (Nop && WideNop) + OS << "\t.seh_endepilogue_nop_w\n"; + else if (Nop) + OS << "\t.seh_endepilogue_nop\n"; + else + OS << "\t.seh_endepilogue\n"; +} + +void ARMTargetAsmStreamer::emitARMWinCFICustom(unsigned Opcode) { + int I; + for (I = 3; I > 0; I--) + if (Opcode & (0xffu << (8 * I))) + break; + ListSeparator LS; + OS << "\t.seh_custom\t"; + for (; I >= 0; I--) + OS << LS << ((Opcode >> (8 * I)) & 0xff); + OS << "\n"; +} + class ARMTargetELFStreamer : public ARMTargetStreamer { private: StringRef CurrentVendor; @@ -1369,12 +1473,8 @@ return new ARMTargetStreamer(S); } -MCTargetStreamer *createARMObjectTargetStreamer(MCStreamer &S, - const MCSubtargetInfo &STI) { - const Triple &TT = STI.getTargetTriple(); - if (TT.isOSBinFormatELF()) - return new ARMTargetELFStreamer(S); - return new ARMTargetStreamer(S); +MCTargetStreamer *createARMObjectTargetELFStreamer(MCStreamer &S) { + return new ARMTargetELFStreamer(S); } MCELFStreamer *createARMELFStreamer(MCContext &Context, diff --git a/llvm/lib/Target/ARM/MCTargetDesc/ARMMCAsmInfo.cpp b/llvm/lib/Target/ARM/MCTargetDesc/ARMMCAsmInfo.cpp --- a/llvm/lib/Target/ARM/MCTargetDesc/ARMMCAsmInfo.cpp +++ b/llvm/lib/Target/ARM/MCTargetDesc/ARMMCAsmInfo.cpp @@ -89,6 +89,7 @@ AlignmentIsInBytes = false; SupportsDebugInformation = true; ExceptionsType = ExceptionHandling::WinEH; + WinEHEncodingType = WinEH::EncodingType::Itanium; PrivateGlobalPrefix = "$M"; PrivateLabelPrefix = "$M"; CommentString = "@"; diff --git a/llvm/lib/Target/ARM/MCTargetDesc/ARMMCTargetDesc.h b/llvm/lib/Target/ARM/MCTargetDesc/ARMMCTargetDesc.h --- a/llvm/lib/Target/ARM/MCTargetDesc/ARMMCTargetDesc.h +++ b/llvm/lib/Target/ARM/MCTargetDesc/ARMMCTargetDesc.h @@ -71,6 +71,8 @@ bool isVerboseAsm); MCTargetStreamer *createARMObjectTargetStreamer(MCStreamer &S, const MCSubtargetInfo &STI); +MCTargetStreamer *createARMObjectTargetELFStreamer(MCStreamer &S); +MCTargetStreamer *createARMObjectTargetWinCOFFStreamer(MCStreamer &S); MCCodeEmitter *createARMLEMCCodeEmitter(const MCInstrInfo &MCII, MCContext &Ctx); diff --git a/llvm/lib/Target/ARM/MCTargetDesc/ARMTargetStreamer.cpp b/llvm/lib/Target/ARM/MCTargetDesc/ARMTargetStreamer.cpp --- a/llvm/lib/Target/ARM/MCTargetDesc/ARMTargetStreamer.cpp +++ b/llvm/lib/Target/ARM/MCTargetDesc/ARMTargetStreamer.cpp @@ -118,6 +118,17 @@ ARMTargetStreamer::AnnotateTLSDescriptorSequence(const MCSymbolRefExpr *SRE) {} void ARMTargetStreamer::emitThumbSet(MCSymbol *Symbol, const MCExpr *Value) {} +void ARMTargetStreamer::emitARMWinCFIAllocStack(unsigned Size, bool Wide) {} +void ARMTargetStreamer::emitARMWinCFISaveRegMask(unsigned Mask, bool Wide) {} +void ARMTargetStreamer::emitARMWinCFISetFP(unsigned Reg) {} +void ARMTargetStreamer::emitARMWinCFISaveFRegs(unsigned First, unsigned Last) {} +void ARMTargetStreamer::emitARMWinCFISaveLR(unsigned Offset) {} +void ARMTargetStreamer::emitARMWinCFINop(bool Wide) {} +void ARMTargetStreamer::emitARMWinCFIPrologEnd() {} +void ARMTargetStreamer::emitARMWinCFIEpilogStart() {} +void ARMTargetStreamer::emitARMWinCFIEpilogEnd(bool Nop, bool WideNop) {} +void ARMTargetStreamer::emitARMWinCFICustom(unsigned Opcode) {} + static ARMBuildAttrs::CPUArch getArchForCPU(const MCSubtargetInfo &STI) { if (STI.getCPU() == "xscale") return ARMBuildAttrs::v5TEJ; @@ -307,3 +318,13 @@ emitAttribute(ARMBuildAttrs::BTI_extension, ARMBuildAttrs::AllowBTI); } } + +MCTargetStreamer * +llvm::createARMObjectTargetStreamer(MCStreamer &S, const MCSubtargetInfo &STI) { + const Triple &TT = STI.getTargetTriple(); + if (TT.isOSBinFormatELF()) + return createARMObjectTargetELFStreamer(S); + if (TT.isOSBinFormatCOFF()) + return createARMObjectTargetWinCOFFStreamer(S); + return new ARMTargetStreamer(S); +} diff --git a/llvm/lib/Target/ARM/MCTargetDesc/ARMWinCOFFStreamer.cpp b/llvm/lib/Target/ARM/MCTargetDesc/ARMWinCOFFStreamer.cpp --- a/llvm/lib/Target/ARM/MCTargetDesc/ARMWinCOFFStreamer.cpp +++ b/llvm/lib/Target/ARM/MCTargetDesc/ARMWinCOFFStreamer.cpp @@ -11,28 +11,55 @@ #include "llvm/MC/MCAssembler.h" #include "llvm/MC/MCCodeEmitter.h" #include "llvm/MC/MCObjectWriter.h" +#include "llvm/MC/MCWin64EH.h" #include "llvm/MC/MCWinCOFFStreamer.h" using namespace llvm; namespace { class ARMWinCOFFStreamer : public MCWinCOFFStreamer { + Win64EH::ARMUnwindEmitter EHStreamer; + public: ARMWinCOFFStreamer(MCContext &C, std::unique_ptr AB, std::unique_ptr CE, std::unique_ptr OW) : MCWinCOFFStreamer(C, std::move(AB), std::move(CE), std::move(OW)) {} + void EmitWinEHHandlerData(SMLoc Loc) override; + void EmitWindowsUnwindTables() override; + void EmitWindowsUnwindTables(WinEH::FrameInfo *Frame) override; + void emitThumbFunc(MCSymbol *Symbol) override; void finishImpl() override; }; +void ARMWinCOFFStreamer::EmitWinEHHandlerData(SMLoc Loc) { + MCStreamer::EmitWinEHHandlerData(Loc); + + // We have to emit the unwind info now, because this directive + // actually switches to the .xdata section! + EHStreamer.EmitUnwindInfo(*this, getCurrentWinFrameInfo(), + /* HandlerData = */ true); +} + +void ARMWinCOFFStreamer::EmitWindowsUnwindTables(WinEH::FrameInfo *Frame) { + EHStreamer.EmitUnwindInfo(*this, Frame, /* HandlerData = */ false); +} + +void ARMWinCOFFStreamer::EmitWindowsUnwindTables() { + if (!getNumWinFrameInfos()) + return; + EHStreamer.Emit(*this); +} + void ARMWinCOFFStreamer::emitThumbFunc(MCSymbol *Symbol) { getAssembler().setIsThumbFunc(Symbol); } void ARMWinCOFFStreamer::finishImpl() { emitFrames(nullptr); + EmitWindowsUnwindTables(); MCWinCOFFStreamer::finishImpl(); } @@ -49,3 +76,191 @@ return S; } +namespace { +class ARMTargetWinCOFFStreamer : public llvm::ARMTargetStreamer { +private: + // True if we are processing SEH directives in an epilogue. + bool InEpilogCFI = false; + + // Symbol of the current epilog for which we are processing SEH directives. + MCSymbol *CurrentEpilog = nullptr; + +public: + ARMTargetWinCOFFStreamer(llvm::MCStreamer &S) : ARMTargetStreamer(S) {} + + // The unwind codes on ARM Windows are documented at + // https://docs.microsoft.com/en-us/cpp/build/arm-exception-handling + void emitARMWinCFIAllocStack(unsigned Size, bool Wide) override; + void emitARMWinCFISaveRegMask(unsigned Mask, bool Wide) override; + void emitARMWinCFISetFP(unsigned Reg) override; + void emitARMWinCFISaveFRegs(unsigned First, unsigned Last) override; + void emitARMWinCFISaveLR(unsigned Offset) override; + void emitARMWinCFIPrologEnd() override; + void emitARMWinCFINop(bool Wide) override; + void emitARMWinCFIEpilogStart() override; + void emitARMWinCFIEpilogEnd(bool Nop, bool WideNop) override; + void emitARMWinCFICustom(unsigned Opcode) override; + +private: + void emitARMWinCFIEpilogEnd(unsigned UnwindCode); + + void emitARMWinUnwindCode(unsigned UnwindCode, int Reg, int Offset); +}; + +// Helper function to common out unwind code setup for those codes that can +// belong to both prolog and epilog. +// There are three types of Windows ARM SEH codes. They can +// 1) take no operands: SEH_Nop, SEH_PrologEnd, SEH_EpilogStart, SEH_EpilogEnd +// 2) take an offset: SEH_StackAlloc, SEH_SaveFPLR, SEH_SaveFPLR_X +// 3) take a register and an offset/size: all others +void ARMTargetWinCOFFStreamer::emitARMWinUnwindCode(unsigned UnwindCode, + int Reg, int Offset) { + auto &S = getStreamer(); + WinEH::FrameInfo *CurFrame = S.EnsureValidWinFrameInfo(SMLoc()); + if (!CurFrame) + return; + MCSymbol *Label = S.emitCFILabel(); + auto Inst = WinEH::Instruction(UnwindCode, Label, Reg, Offset); + if (InEpilogCFI) + CurFrame->EpilogMap[CurrentEpilog].push_back(Inst); + else + CurFrame->Instructions.push_back(Inst); +} + +void ARMTargetWinCOFFStreamer::emitARMWinCFIAllocStack(unsigned Size, + bool Wide) { + unsigned Op = Win64EH::UOP_AllocSmall; + if (!Wide) { + if (Size / 4 > 0xffff) + Op = Win64EH::UOP_AllocHuge; + else if (Size / 4 > 0x7f) + Op = Win64EH::UOP_AllocLarge; + } else { + Op = Win64EH::UOP_WideAllocMedium; + if (Size / 4 > 0xffff) + Op = Win64EH::UOP_WideAllocHuge; + else if (Size / 4 > 0x3ff) + Op = Win64EH::UOP_WideAllocLarge; + } + emitARMWinUnwindCode(Op, -1, Size); +} + +void ARMTargetWinCOFFStreamer::emitARMWinCFISaveRegMask(unsigned Mask, + bool Wide) { + assert(Mask != 0); + int Lr = (Mask & 0x4000) ? 1 : 0; + Mask &= ~0x4000; + if (Wide) + assert((Mask & ~0x1fff) == 0); + else + assert((Mask & ~0x00ff) == 0); + if (Mask && ((Mask + (1 << 4)) & Mask) == 0) { + if (Wide && (Mask & 0x1000) == 0 && (Mask & 0xff) == 0xf0) { + // One continuous range from r4 to r8-r11 + for (int I = 11; I >= 8; I--) { + if (Mask & (1 << I)) { + emitARMWinUnwindCode(Win64EH::UOP_WideSaveRegsR4R11LR, I, Lr); + return; + } + } + // If it actually was from r4 to r4-r7, continue below. + } else if (!Wide) { + // One continuous range from r4 to r4-r7 + for (int I = 7; I >= 4; I--) { + if (Mask & (1 << I)) { + emitARMWinUnwindCode(Win64EH::UOP_SaveRegsR4R7LR, I, Lr); + return; + } + } + llvm_unreachable("logic error"); + } + } + Mask |= Lr << 14; + if (Wide) + emitARMWinUnwindCode(Win64EH::UOP_WideSaveRegMask, Mask, 0); + else + emitARMWinUnwindCode(Win64EH::UOP_SaveRegMask, Mask, 0); +} + +void ARMTargetWinCOFFStreamer::emitARMWinCFISetFP(unsigned Reg) { + emitARMWinUnwindCode(Win64EH::UOP_SetFP, Reg, 0); +} + +void ARMTargetWinCOFFStreamer::emitARMWinCFISaveFRegs(unsigned First, + unsigned Last) { + assert(First <= Last); + assert(First >= 16 || Last < 16); + assert(First <= 31 && Last <= 31); + if (First == 8) + emitARMWinUnwindCode(Win64EH::UOP_SaveFRegD8D15, Last, 0); + else if (First <= 15) + emitARMWinUnwindCode(Win64EH::UOP_SaveFRegD0D15, First, Last); + else + emitARMWinUnwindCode(Win64EH::UOP_SaveFRegD16D31, First, Last); +} + +void ARMTargetWinCOFFStreamer::emitARMWinCFISaveLR(unsigned Offset) { + emitARMWinUnwindCode(Win64EH::UOP_SaveLR, 0, Offset); +} + +void ARMTargetWinCOFFStreamer::emitARMWinCFINop(bool Wide) { + if (Wide) + emitARMWinUnwindCode(Win64EH::UOP_WideNop, -1, 0); + else + emitARMWinUnwindCode(Win64EH::UOP_Nop, -1, 0); +} + +void ARMTargetWinCOFFStreamer::emitARMWinCFIPrologEnd() { + auto &S = getStreamer(); + WinEH::FrameInfo *CurFrame = S.EnsureValidWinFrameInfo(SMLoc()); + if (!CurFrame) + return; + + MCSymbol *Label = S.emitCFILabel(); + CurFrame->PrologEnd = Label; + WinEH::Instruction Inst = + WinEH::Instruction(Win64EH::UOP_End, /*Label=*/nullptr, -1, 0); + auto it = CurFrame->Instructions.begin(); + CurFrame->Instructions.insert(it, Inst); +} + +void ARMTargetWinCOFFStreamer::emitARMWinCFIEpilogStart() { + auto &S = getStreamer(); + WinEH::FrameInfo *CurFrame = S.EnsureValidWinFrameInfo(SMLoc()); + if (!CurFrame) + return; + + InEpilogCFI = true; + CurrentEpilog = S.emitCFILabel(); +} + +void ARMTargetWinCOFFStreamer::emitARMWinCFIEpilogEnd(bool Nop, bool WideNop) { + if (Nop && WideNop) + emitARMWinCFIEpilogEnd(Win64EH::UOP_WideEndNop); + else if (Nop) + emitARMWinCFIEpilogEnd(Win64EH::UOP_EndNop); + else + emitARMWinCFIEpilogEnd(Win64EH::UOP_End); +} + +void ARMTargetWinCOFFStreamer::emitARMWinCFIEpilogEnd(unsigned UnwindCode) { + auto &S = getStreamer(); + WinEH::FrameInfo *CurFrame = S.EnsureValidWinFrameInfo(SMLoc()); + if (!CurFrame) + return; + + InEpilogCFI = false; + WinEH::Instruction Inst = WinEH::Instruction(UnwindCode, nullptr, -1, 0); + CurFrame->EpilogMap[CurrentEpilog].push_back(Inst); + CurrentEpilog = nullptr; +} + +void ARMTargetWinCOFFStreamer::emitARMWinCFICustom(unsigned Opcode) { + emitARMWinUnwindCode(Win64EH::UOP_Custom, 0, Opcode); +} + +} // end anonymous namespace + +MCTargetStreamer *llvm::createARMObjectTargetWinCOFFStreamer(MCStreamer &S) { + return new ARMTargetWinCOFFStreamer(S); +} diff --git a/llvm/test/MC/ARM/seh.s b/llvm/test/MC/ARM/seh.s new file mode 100644 --- /dev/null +++ b/llvm/test/MC/ARM/seh.s @@ -0,0 +1,271 @@ +// This test checks that the SEH directives emit the correct unwind data. + +// RUN: llvm-mc -triple thumbv7-pc-win32 -filetype=obj %s | llvm-readobj -S -r -u - | FileCheck %s + +// Check that the output assembler directives also can be parsed, and +// that they produce equivalent output: + +// RUN: llvm-mc -triple thumbv7-pc-win32 -filetype=asm %s | llvm-mc -triple thumbv7-pc-win32 -filetype=obj - | llvm-readobj -S -r -u - | FileCheck %s + +// CHECK: Sections [ +// CHECK: Section { +// CHECK: Name: .text +// CHECK: RelocationCount: 2 +// CHECK: Characteristics [ +// CHECK-NEXT: ALIGN_4BYTES +// CHECK-NEXT: CNT_CODE +// CHECK-NEXT: MEM_16BIT +// CHECK-NEXT: MEM_EXECUTE +// CHECK-NEXT: MEM_PURGEABLE +// CHECK-NEXT: MEM_READ +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK: Section { +// CHECK: Name: .xdata +// CHECK: RawDataSize: 92 +// CHECK: RelocationCount: 1 +// CHECK: Characteristics [ +// CHECK-NEXT: ALIGN_4BYTES +// CHECK-NEXT: CNT_INITIALIZED_DATA +// CHECK-NEXT: MEM_READ +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK: Section { +// CHECK: Name: .pdata +// CHECK: RelocationCount: 6 +// CHECK: Characteristics [ +// CHECK-NEXT: ALIGN_4BYTES +// CHECK-NEXT: CNT_INITIALIZED_DATA +// CHECK-NEXT: MEM_READ +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ] + +// CHECK-NEXT: Relocations [ +// CHECK-NEXT: Section (1) .text { +// CHECK-NEXT: 0x5C IMAGE_REL_ARM_BRANCH24T tailcall +// CHECK-NEXT: 0x62 IMAGE_REL_ARM_BRANCH24T otherfunc +// CHECK-NEXT: } +// CHECK-NEXT: Section (4) .xdata { +// CHECK-NEXT: 0x38 IMAGE_REL_ARM_ADDR32NB __C_specific_handler +// CHECK-NEXT: } +// CHECK-NEXT: Section (5) .pdata { +// CHECK-NEXT: 0x0 IMAGE_REL_ARM_ADDR32NB .text +// CHECK-NEXT: 0x4 IMAGE_REL_ARM_ADDR32NB .xdata +// CHECK-NEXT: 0x8 IMAGE_REL_ARM_ADDR32NB .text +// CHECK-NEXT: 0xC IMAGE_REL_ARM_ADDR32NB .xdata +// CHECK-NEXT: 0x10 IMAGE_REL_ARM_ADDR32NB .text +// CHECK-NEXT: 0x14 IMAGE_REL_ARM_ADDR32NB .xdata +// CHECK-NEXT: } +// CHECK-NEXT: ] + +// CHECK-NEXT: UnwindInformation [ +// CHECK-NEXT: RuntimeFunction { +// CHECK-NEXT: Function: func +// CHECK-NEXT: ExceptionRecord: .xdata +// CHECK-NEXT: ExceptionData { +// CHECK-NEXT: FunctionLength: 86 +// CHECK: Prologue [ +// CHECK-NEXT: 0xed 0xf8 ; push {r3-r7, lr} +// CHECK-NEXT: 0xf6 0x27 ; vpush {d18-d23} +// CHECK-NEXT: 0xf5 0x7e ; vpush {d7-d14} +// CHECK-NEXT: 0xfb ; nop +// CHECK-NEXT: 0xce ; mov r14, sp +// CHECK-NEXT: 0xe3 ; vpush {d8-d11} +// CHECK-NEXT: 0xe6 ; vpush {d8-d14} +// CHECK-NEXT: 0xed 0xf8 ; push {r3-r7, lr} +// CHECK-NEXT: 0xbd 0x50 ; push.w {r4, r6, r8, r10-r12, lr} +// CHECK-NEXT: 0xd7 ; push {r4-r7, lr} +// CHECK-NEXT: 0xdd ; push.w {r4-r9, lr} +// CHECK-NEXT: 0xfa 0x01 0x00 0x00 ; sub.w sp, sp, #(65536 * 4) +// CHECK-NEXT: 0xfc ; nop.w +// CHECK-NEXT: 0xfc ; nop.w +// CHECK-NEXT: 0xf9 0x04 0x00 ; sub.w sp, sp, #(1024 * 4) +// CHECK-NEXT: 0xe8 0x80 ; sub.w sp, #(128 * 4) +// CHECK-NEXT: 0xe8 0x80 ; sub.w sp, #(128 * 4) +// CHECK-NEXT: 0x06 ; sub sp, #(6 * 4) +// CHECK-NEXT: ] +// CHECK-NEXT: EpilogueScopes [ +// CHECK-NEXT: EpilogueScope { +// CHECK-NEXT: StartOffset: 31 +// CHECK-NEXT: Condition: 14 +// CHECK-NEXT: EpilogueStartIndex: 31 +// CHECK-NEXT: Opcodes [ +// CHECK-NEXT: 0xfc ; nop.w +// CHECK-NEXT: 0xf7 0x00 0x80 ; add sp, sp, #(128 * 4) +// CHECK-NEXT: 0xfc ; nop.w +// CHECK-NEXT: 0xfc ; nop.w +// CHECK-NEXT: 0xf8 0x01 0x00 0x00 ; add sp, sp, #(65536 * 4) +// CHECK-NEXT: 0x06 ; add sp, #(6 * 4) +// CHECK-NEXT: 0xef 0x04 ; ldr.w lr, [sp], #16 +// CHECK-NEXT: 0xfd ; bx +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: ExceptionHandler [ +// CHECK-NEXT: Routine: __C_specific_handler +// CHECK-NEXT: Parameter: 0x0 +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: RuntimeFunction { +// CHECK-NEXT: Function: func2 +// CHECK: Prologue [ +// CHECK-NEXT: 0xd3 ; push {r4-r7} +// CHECK-NEXT: ] +// CHECK-NEXT: EpilogueScopes [ +// CHECK-NEXT: EpilogueScope { +// CHECK: Opcodes [ +// CHECK-NEXT: 0xd2 ; pop {r4-r6} +// CHECK-NEXT: 0xfe ; b.w +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: RuntimeFunction { +// CHECK-NEXT: Function: func3 +// CHECK: FunctionLength: 8 +// CHECK: Prologue [ +// CHECK-NEXT: 0xd5 ; push {r4-r5, lr} +// CHECK-NEXT: ] +// CHECK-NEXT: EpilogueScopes [ +// CHECK-NEXT: EpilogueScope { +// CHECK-NEXT: StartOffset: 3 +// CHECK-NEXT: Condition: 14 +// CHECK-NEXT: EpilogueStartIndex: 2 +// CHECK-NEXT: Opcodes [ +// CHECK-NEXT: 0xd6 ; pop {r4-r6, pc} +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: ] + + .text + .syntax unified + .globl func + .def func + .scl 2 + .type 32 + .endef + .seh_proc func +func: + sub sp, sp, #24 + .seh_stackalloc 24 + sub sp, sp, #512 + .seh_stackalloc_w 512 + sub sp, sp, #512 + .seh_stackalloc_w 512 + sub sp, sp, #4096 + .seh_stackalloc_w 4096 + movw r7, #0 + .seh_nop_w + movt r7, #0x4 // 0x40000 + .seh_nop_w + sub sp, sp, r7 + .seh_stackalloc_w 0x40000 + push {r4-r8,lr} + .seh_save_regs_w {r4-r9,lr} + push {r4-r7,lr} + .seh_save_regs {r4-r7,lr} + push {r4,r6,r8,r10,r11,r12,lr} + .seh_save_regs_w {r4,r6,r8,r10,r11,r12,lr} + push {r3-r7,lr} + .seh_save_regs {r3-r7,lr} + vpush {d8-d14} + .seh_save_fregs {d8-d14} + vpush {q4-q5} + .seh_save_fregs {q4-q5} + mov lr, sp + .seh_set_fp lr + nop + .seh_nop + vpush {d7-d14} + .seh_save_fregs {d7-d14} + vpush {d18-d23} + .seh_save_fregs {d18-d23} + push {r3-r7,lr} + .seh_custom 0xed, 0xf8 + .seh_endprologue + nop + .seh_startepilogue + mov r7, #512 + .seh_nop_w + add sp, sp, r7 + .seh_stackalloc 512 + movw r7, #0 + .seh_nop_w + movt r7, #0x4 // 0x40000 + .seh_nop_w + add sp, sp, r7 + .seh_stackalloc 0x40000 + add sp, sp, #24 + .seh_stackalloc 24 + ldr lr, [sp], #16 + .seh_save_lr 16 + bx lr + .seh_endepilogue_nop + .seh_handler __C_specific_handler, %except + .seh_handlerdata + .long 0 + .text + .seh_endproc + + .seh_proc func2 +func2: + push {r4-r7} + .seh_save_regs {r4-r7} + .seh_endprologue + nop + .seh_startepilogue + pop {r4-r6} + .seh_save_regs {r4-r6} + b.w tailcall + .seh_endepilogue_nop_w + .seh_endproc + + .seh_proc func3 +func3: + push {r4-r5,lr} + .seh_save_regs {r4-r5,lr} + .seh_endprologue + // A function with an indeterminate length; the size of the b + // instruction isn't known until later. + b.w otherfunc + .seh_startepilogue + pop {r4-r6,pc} + .seh_save_regs {r4-r6,pc} + .seh_endepilogue + .seh_endproc + + // Function with no .seh directives; no pdata/xdata entries are + // generated. + .globl smallFunc + .def smallFunc + .scl 2 + .type 32 + .endef + .seh_proc smallFunc +smallFunc: + bx lr + .seh_endproc + + // Function with no .seh directives, but with .seh_handlerdata. + // No xdata/pdata entries are generated, but the custom handler data + // (the .long after .seh_handlerdata) is left orphaned in the xdata + // section. + .globl handlerFunc + .def handlerFunc + .scl 2 + .type 32 + .endef + .seh_proc handlerFunc +handlerFunc: + bx lr + .seh_handler __C_specific_handler, %except + .seh_handlerdata + .long 0 + .text + .seh_endproc