1 //===-- ARMWinCOFFStreamer.cpp - ARM Target WinCOFF Streamer ----*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "ARMMCTargetDesc.h"
10 #include "Utils/ARMBaseInfo.h"
11 #include "llvm/MC/MCAsmBackend.h"
12 #include "llvm/MC/MCAssembler.h"
13 #include "llvm/MC/MCCodeEmitter.h"
14 #include "llvm/MC/MCContext.h"
15 #include "llvm/MC/MCObjectWriter.h"
16 #include "llvm/MC/MCWin64EH.h"
17 #include "llvm/MC/MCWinCOFFStreamer.h"
18 
19 using namespace llvm;
20 
21 namespace {
22 class ARMWinCOFFStreamer : public MCWinCOFFStreamer {
23   Win64EH::ARMUnwindEmitter EHStreamer;
24 
25 public:
26   ARMWinCOFFStreamer(MCContext &C, std::unique_ptr<MCAsmBackend> AB,
27                      std::unique_ptr<MCCodeEmitter> CE,
28                      std::unique_ptr<MCObjectWriter> OW)
29       : MCWinCOFFStreamer(C, std::move(AB), std::move(CE), std::move(OW)) {}
30 
31   void emitWinEHHandlerData(SMLoc Loc) override;
32   void emitWindowsUnwindTables() override;
33   void emitWindowsUnwindTables(WinEH::FrameInfo *Frame) override;
34 
35   void emitThumbFunc(MCSymbol *Symbol) override;
36   void finishImpl() override;
37 };
38 
39 void ARMWinCOFFStreamer::emitWinEHHandlerData(SMLoc Loc) {
40   MCStreamer::emitWinEHHandlerData(Loc);
41 
42   // We have to emit the unwind info now, because this directive
43   // actually switches to the .xdata section!
44   EHStreamer.EmitUnwindInfo(*this, getCurrentWinFrameInfo(),
45                             /* HandlerData = */ true);
46 }
47 
48 void ARMWinCOFFStreamer::emitWindowsUnwindTables(WinEH::FrameInfo *Frame) {
49   EHStreamer.EmitUnwindInfo(*this, Frame, /* HandlerData = */ false);
50 }
51 
52 void ARMWinCOFFStreamer::emitWindowsUnwindTables() {
53   if (!getNumWinFrameInfos())
54     return;
55   EHStreamer.Emit(*this);
56 }
57 
58 void ARMWinCOFFStreamer::emitThumbFunc(MCSymbol *Symbol) {
59   getAssembler().setIsThumbFunc(Symbol);
60 }
61 
62 void ARMWinCOFFStreamer::finishImpl() {
63   emitFrames(nullptr);
64   emitWindowsUnwindTables();
65 
66   MCWinCOFFStreamer::finishImpl();
67 }
68 }
69 
70 MCStreamer *llvm::createARMWinCOFFStreamer(
71     MCContext &Context, std::unique_ptr<MCAsmBackend> &&MAB,
72     std::unique_ptr<MCObjectWriter> &&OW,
73     std::unique_ptr<MCCodeEmitter> &&Emitter, bool RelaxAll,
74     bool IncrementalLinkerCompatible) {
75   auto *S = new ARMWinCOFFStreamer(Context, std::move(MAB), std::move(Emitter),
76                                    std::move(OW));
77   S->getAssembler().setIncrementalLinkerCompatible(IncrementalLinkerCompatible);
78   return S;
79 }
80 
81 namespace {
82 class ARMTargetWinCOFFStreamer : public llvm::ARMTargetStreamer {
83 private:
84   // True if we are processing SEH directives in an epilogue.
85   bool InEpilogCFI = false;
86 
87   // Symbol of the current epilog for which we are processing SEH directives.
88   MCSymbol *CurrentEpilog = nullptr;
89 
90 public:
91   ARMTargetWinCOFFStreamer(llvm::MCStreamer &S) : ARMTargetStreamer(S) {}
92 
93   // The unwind codes on ARM Windows are documented at
94   // https://docs.microsoft.com/en-us/cpp/build/arm-exception-handling
95   void emitARMWinCFIAllocStack(unsigned Size, bool Wide) override;
96   void emitARMWinCFISaveRegMask(unsigned Mask, bool Wide) override;
97   void emitARMWinCFISaveSP(unsigned Reg) override;
98   void emitARMWinCFISaveFRegs(unsigned First, unsigned Last) override;
99   void emitARMWinCFISaveLR(unsigned Offset) override;
100   void emitARMWinCFIPrologEnd(bool Fragment) override;
101   void emitARMWinCFINop(bool Wide) override;
102   void emitARMWinCFIEpilogStart(unsigned Condition) override;
103   void emitARMWinCFIEpilogEnd() override;
104   void emitARMWinCFICustom(unsigned Opcode) override;
105 
106 private:
107   void emitARMWinUnwindCode(unsigned UnwindCode, int Reg, int Offset);
108 };
109 
110 // Helper function to common out unwind code setup for those codes that can
111 // belong to both prolog and epilog.
112 void ARMTargetWinCOFFStreamer::emitARMWinUnwindCode(unsigned UnwindCode,
113                                                     int Reg, int Offset) {
114   auto &S = getStreamer();
115   WinEH::FrameInfo *CurFrame = S.EnsureValidWinFrameInfo(SMLoc());
116   if (!CurFrame)
117     return;
118   MCSymbol *Label = S.emitCFILabel();
119   auto Inst = WinEH::Instruction(UnwindCode, Label, Reg, Offset);
120   if (InEpilogCFI)
121     CurFrame->EpilogMap[CurrentEpilog].Instructions.push_back(Inst);
122   else
123     CurFrame->Instructions.push_back(Inst);
124 }
125 
126 void ARMTargetWinCOFFStreamer::emitARMWinCFIAllocStack(unsigned Size,
127                                                        bool Wide) {
128   unsigned Op = Win64EH::UOP_AllocSmall;
129   if (!Wide) {
130     if (Size / 4 > 0xffff)
131       Op = Win64EH::UOP_AllocHuge;
132     else if (Size / 4 > 0x7f)
133       Op = Win64EH::UOP_AllocLarge;
134   } else {
135     Op = Win64EH::UOP_WideAllocMedium;
136     if (Size / 4 > 0xffff)
137       Op = Win64EH::UOP_WideAllocHuge;
138     else if (Size / 4 > 0x3ff)
139       Op = Win64EH::UOP_WideAllocLarge;
140   }
141   emitARMWinUnwindCode(Op, -1, Size);
142 }
143 
144 void ARMTargetWinCOFFStreamer::emitARMWinCFISaveRegMask(unsigned Mask,
145                                                         bool Wide) {
146   assert(Mask != 0);
147   int Lr = (Mask & 0x4000) ? 1 : 0;
148   Mask &= ~0x4000;
149   if (Wide)
150     assert((Mask & ~0x1fff) == 0);
151   else
152     assert((Mask & ~0x00ff) == 0);
153   if (Mask && ((Mask + (1 << 4)) & Mask) == 0) {
154     if (Wide && (Mask & 0x1000) == 0 && (Mask & 0xff) == 0xf0) {
155       // One continuous range from r4 to r8-r11
156       for (int I = 11; I >= 8; I--) {
157         if (Mask & (1 << I)) {
158           emitARMWinUnwindCode(Win64EH::UOP_WideSaveRegsR4R11LR, I, Lr);
159           return;
160         }
161       }
162       // If it actually was from r4 to r4-r7, continue below.
163     } else if (!Wide) {
164       // One continuous range from r4 to r4-r7
165       for (int I = 7; I >= 4; I--) {
166         if (Mask & (1 << I)) {
167           emitARMWinUnwindCode(Win64EH::UOP_SaveRegsR4R7LR, I, Lr);
168           return;
169         }
170       }
171       llvm_unreachable("logic error");
172     }
173   }
174   Mask |= Lr << 14;
175   if (Wide)
176     emitARMWinUnwindCode(Win64EH::UOP_WideSaveRegMask, Mask, 0);
177   else
178     emitARMWinUnwindCode(Win64EH::UOP_SaveRegMask, Mask, 0);
179 }
180 
181 void ARMTargetWinCOFFStreamer::emitARMWinCFISaveSP(unsigned Reg) {
182   emitARMWinUnwindCode(Win64EH::UOP_SaveSP, Reg, 0);
183 }
184 
185 void ARMTargetWinCOFFStreamer::emitARMWinCFISaveFRegs(unsigned First,
186                                                       unsigned Last) {
187   assert(First <= Last);
188   assert(First >= 16 || Last < 16);
189   assert(First <= 31 && Last <= 31);
190   if (First == 8)
191     emitARMWinUnwindCode(Win64EH::UOP_SaveFRegD8D15, Last, 0);
192   else if (First <= 15)
193     emitARMWinUnwindCode(Win64EH::UOP_SaveFRegD0D15, First, Last);
194   else
195     emitARMWinUnwindCode(Win64EH::UOP_SaveFRegD16D31, First, Last);
196 }
197 
198 void ARMTargetWinCOFFStreamer::emitARMWinCFISaveLR(unsigned Offset) {
199   emitARMWinUnwindCode(Win64EH::UOP_SaveLR, 0, Offset);
200 }
201 
202 void ARMTargetWinCOFFStreamer::emitARMWinCFINop(bool Wide) {
203   if (Wide)
204     emitARMWinUnwindCode(Win64EH::UOP_WideNop, -1, 0);
205   else
206     emitARMWinUnwindCode(Win64EH::UOP_Nop, -1, 0);
207 }
208 
209 void ARMTargetWinCOFFStreamer::emitARMWinCFIPrologEnd(bool Fragment) {
210   auto &S = getStreamer();
211   WinEH::FrameInfo *CurFrame = S.EnsureValidWinFrameInfo(SMLoc());
212   if (!CurFrame)
213     return;
214 
215   MCSymbol *Label = S.emitCFILabel();
216   CurFrame->PrologEnd = Label;
217   WinEH::Instruction Inst =
218       WinEH::Instruction(Win64EH::UOP_End, /*Label=*/nullptr, -1, 0);
219   auto it = CurFrame->Instructions.begin();
220   CurFrame->Instructions.insert(it, Inst);
221   CurFrame->Fragment = Fragment;
222 }
223 
224 void ARMTargetWinCOFFStreamer::emitARMWinCFIEpilogStart(unsigned Condition) {
225   auto &S = getStreamer();
226   WinEH::FrameInfo *CurFrame = S.EnsureValidWinFrameInfo(SMLoc());
227   if (!CurFrame)
228     return;
229 
230   InEpilogCFI = true;
231   CurrentEpilog = S.emitCFILabel();
232   CurFrame->EpilogMap[CurrentEpilog].Condition = Condition;
233 }
234 
235 void ARMTargetWinCOFFStreamer::emitARMWinCFIEpilogEnd() {
236   auto &S = getStreamer();
237   WinEH::FrameInfo *CurFrame = S.EnsureValidWinFrameInfo(SMLoc());
238   if (!CurFrame)
239     return;
240 
241   if (!CurrentEpilog) {
242     S.getContext().reportError(SMLoc(), "Stray .seh_endepilogue in " +
243                                             CurFrame->Function->getName());
244     return;
245   }
246 
247   std::vector<WinEH::Instruction> &Epilog =
248       CurFrame->EpilogMap[CurrentEpilog].Instructions;
249 
250   unsigned UnwindCode = Win64EH::UOP_End;
251   if (!Epilog.empty()) {
252     WinEH::Instruction EndInstr = Epilog.back();
253     if (EndInstr.Operation == Win64EH::UOP_Nop) {
254       UnwindCode = Win64EH::UOP_EndNop;
255       Epilog.pop_back();
256     } else if (EndInstr.Operation == Win64EH::UOP_WideNop) {
257       UnwindCode = Win64EH::UOP_WideEndNop;
258       Epilog.pop_back();
259     }
260   }
261 
262   InEpilogCFI = false;
263   WinEH::Instruction Inst = WinEH::Instruction(UnwindCode, nullptr, -1, 0);
264   CurFrame->EpilogMap[CurrentEpilog].Instructions.push_back(Inst);
265   MCSymbol *Label = S.emitCFILabel();
266   CurFrame->EpilogMap[CurrentEpilog].End = Label;
267   CurrentEpilog = nullptr;
268 }
269 
270 void ARMTargetWinCOFFStreamer::emitARMWinCFICustom(unsigned Opcode) {
271   emitARMWinUnwindCode(Win64EH::UOP_Custom, 0, Opcode);
272 }
273 
274 } // end anonymous namespace
275 
276 MCTargetStreamer *llvm::createARMObjectTargetWinCOFFStreamer(MCStreamer &S) {
277   return new ARMTargetWinCOFFStreamer(S);
278 }
279