1 //===- WholeProgramDevirt.h - Whole-program devirt pass ---------*- 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 // This file defines parts of the whole-program devirtualization pass
10 // implementation that may be usefully unit tested.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #ifndef LLVM_TRANSFORMS_IPO_WHOLEPROGRAMDEVIRT_H
15 #define LLVM_TRANSFORMS_IPO_WHOLEPROGRAMDEVIRT_H
16 
17 #include "llvm/IR/GlobalValue.h"
18 #include "llvm/IR/PassManager.h"
19 #include <cassert>
20 #include <cstdint>
21 #include <map>
22 #include <set>
23 #include <utility>
24 #include <vector>
25 
26 namespace llvm {
27 class Module;
28 
29 template <typename T> class ArrayRef;
30 template <typename T> class MutableArrayRef;
31 class Function;
32 class GlobalVariable;
33 class ModuleSummaryIndex;
34 struct ValueInfo;
35 
36 namespace wholeprogramdevirt {
37 
38 // A bit vector that keeps track of which bits are used. We use this to
39 // pack constant values compactly before and after each virtual table.
40 struct AccumBitVector {
41   std::vector<uint8_t> Bytes;
42 
43   // Bits in BytesUsed[I] are 1 if matching bit in Bytes[I] is used, 0 if not.
44   std::vector<uint8_t> BytesUsed;
45 
46   std::pair<uint8_t *, uint8_t *> getPtrToData(uint64_t Pos, uint8_t Size) {
47     if (Bytes.size() < Pos + Size) {
48       Bytes.resize(Pos + Size);
49       BytesUsed.resize(Pos + Size);
50     }
51     return std::make_pair(Bytes.data() + Pos, BytesUsed.data() + Pos);
52   }
53 
54   // Set little-endian value Val with size Size at bit position Pos,
55   // and mark bytes as used.
56   void setLE(uint64_t Pos, uint64_t Val, uint8_t Size) {
57     assert(Pos % 8 == 0);
58     auto DataUsed = getPtrToData(Pos / 8, Size);
59     for (unsigned I = 0; I != Size; ++I) {
60       DataUsed.first[I] = Val >> (I * 8);
61       assert(!DataUsed.second[I]);
62       DataUsed.second[I] = 0xff;
63     }
64   }
65 
66   // Set big-endian value Val with size Size at bit position Pos,
67   // and mark bytes as used.
68   void setBE(uint64_t Pos, uint64_t Val, uint8_t Size) {
69     assert(Pos % 8 == 0);
70     auto DataUsed = getPtrToData(Pos / 8, Size);
71     for (unsigned I = 0; I != Size; ++I) {
72       DataUsed.first[Size - I - 1] = Val >> (I * 8);
73       assert(!DataUsed.second[Size - I - 1]);
74       DataUsed.second[Size - I - 1] = 0xff;
75     }
76   }
77 
78   // Set bit at bit position Pos to b and mark bit as used.
79   void setBit(uint64_t Pos, bool b) {
80     auto DataUsed = getPtrToData(Pos / 8, 1);
81     if (b)
82       *DataUsed.first |= 1 << (Pos % 8);
83     assert(!(*DataUsed.second & (1 << Pos % 8)));
84     *DataUsed.second |= 1 << (Pos % 8);
85   }
86 };
87 
88 // The bits that will be stored before and after a particular vtable.
89 struct VTableBits {
90   // The vtable global.
91   GlobalVariable *GV;
92 
93   // Cache of the vtable's size in bytes.
94   uint64_t ObjectSize = 0;
95 
96   // The bit vector that will be laid out before the vtable. Note that these
97   // bytes are stored in reverse order until the globals are rebuilt. This means
98   // that any values in the array must be stored using the opposite endianness
99   // from the target.
100   AccumBitVector Before;
101 
102   // The bit vector that will be laid out after the vtable.
103   AccumBitVector After;
104 };
105 
106 // Information about a member of a particular type identifier.
107 struct TypeMemberInfo {
108   // The VTableBits for the vtable.
109   VTableBits *Bits;
110 
111   // The offset in bytes from the start of the vtable (i.e. the address point).
112   uint64_t Offset;
113 
114   bool operator<(const TypeMemberInfo &other) const {
115     return Bits < other.Bits || (Bits == other.Bits && Offset < other.Offset);
116   }
117 };
118 
119 // A virtual call target, i.e. an entry in a particular vtable.
120 struct VirtualCallTarget {
121   VirtualCallTarget(Function *Fn, const TypeMemberInfo *TM);
122 
123   // For testing only.
124   VirtualCallTarget(const TypeMemberInfo *TM, bool IsBigEndian)
125       : Fn(nullptr), TM(TM), IsBigEndian(IsBigEndian), WasDevirt(false) {}
126 
127   // The function stored in the vtable.
128   Function *Fn;
129 
130   // A pointer to the type identifier member through which the pointer to Fn is
131   // accessed.
132   const TypeMemberInfo *TM;
133 
134   // When doing virtual constant propagation, this stores the return value for
135   // the function when passed the currently considered argument list.
136   uint64_t RetVal;
137 
138   // Whether the target is big endian.
139   bool IsBigEndian;
140 
141   // Whether at least one call site to the target was devirtualized.
142   bool WasDevirt;
143 
144   // The minimum byte offset before the address point. This covers the bytes in
145   // the vtable object before the address point (e.g. RTTI, access-to-top,
146   // vtables for other base classes) and is equal to the offset from the start
147   // of the vtable object to the address point.
148   uint64_t minBeforeBytes() const { return TM->Offset; }
149 
150   // The minimum byte offset after the address point. This covers the bytes in
151   // the vtable object after the address point (e.g. the vtable for the current
152   // class and any later base classes) and is equal to the size of the vtable
153   // object minus the offset from the start of the vtable object to the address
154   // point.
155   uint64_t minAfterBytes() const { return TM->Bits->ObjectSize - TM->Offset; }
156 
157   // The number of bytes allocated (for the vtable plus the byte array) before
158   // the address point.
159   uint64_t allocatedBeforeBytes() const {
160     return minBeforeBytes() + TM->Bits->Before.Bytes.size();
161   }
162 
163   // The number of bytes allocated (for the vtable plus the byte array) after
164   // the address point.
165   uint64_t allocatedAfterBytes() const {
166     return minAfterBytes() + TM->Bits->After.Bytes.size();
167   }
168 
169   // Set the bit at position Pos before the address point to RetVal.
170   void setBeforeBit(uint64_t Pos) {
171     assert(Pos >= 8 * minBeforeBytes());
172     TM->Bits->Before.setBit(Pos - 8 * minBeforeBytes(), RetVal);
173   }
174 
175   // Set the bit at position Pos after the address point to RetVal.
176   void setAfterBit(uint64_t Pos) {
177     assert(Pos >= 8 * minAfterBytes());
178     TM->Bits->After.setBit(Pos - 8 * minAfterBytes(), RetVal);
179   }
180 
181   // Set the bytes at position Pos before the address point to RetVal.
182   // Because the bytes in Before are stored in reverse order, we use the
183   // opposite endianness to the target.
184   void setBeforeBytes(uint64_t Pos, uint8_t Size) {
185     assert(Pos >= 8 * minBeforeBytes());
186     if (IsBigEndian)
187       TM->Bits->Before.setLE(Pos - 8 * minBeforeBytes(), RetVal, Size);
188     else
189       TM->Bits->Before.setBE(Pos - 8 * minBeforeBytes(), RetVal, Size);
190   }
191 
192   // Set the bytes at position Pos after the address point to RetVal.
193   void setAfterBytes(uint64_t Pos, uint8_t Size) {
194     assert(Pos >= 8 * minAfterBytes());
195     if (IsBigEndian)
196       TM->Bits->After.setBE(Pos - 8 * minAfterBytes(), RetVal, Size);
197     else
198       TM->Bits->After.setLE(Pos - 8 * minAfterBytes(), RetVal, Size);
199   }
200 };
201 
202 // Find the minimum offset that we may store a value of size Size bits at. If
203 // IsAfter is set, look for an offset before the object, otherwise look for an
204 // offset after the object.
205 uint64_t findLowestOffset(ArrayRef<VirtualCallTarget> Targets, bool IsAfter,
206                           uint64_t Size);
207 
208 // Set the stored value in each of Targets to VirtualCallTarget::RetVal at the
209 // given allocation offset before the vtable address. Stores the computed
210 // byte/bit offset to OffsetByte/OffsetBit.
211 void setBeforeReturnValues(MutableArrayRef<VirtualCallTarget> Targets,
212                            uint64_t AllocBefore, unsigned BitWidth,
213                            int64_t &OffsetByte, uint64_t &OffsetBit);
214 
215 // Set the stored value in each of Targets to VirtualCallTarget::RetVal at the
216 // given allocation offset after the vtable address. Stores the computed
217 // byte/bit offset to OffsetByte/OffsetBit.
218 void setAfterReturnValues(MutableArrayRef<VirtualCallTarget> Targets,
219                           uint64_t AllocAfter, unsigned BitWidth,
220                           int64_t &OffsetByte, uint64_t &OffsetBit);
221 
222 } // end namespace wholeprogramdevirt
223 
224 struct WholeProgramDevirtPass : public PassInfoMixin<WholeProgramDevirtPass> {
225   ModuleSummaryIndex *ExportSummary;
226   const ModuleSummaryIndex *ImportSummary;
227   bool UseCommandLine = false;
228   WholeProgramDevirtPass()
229       : ExportSummary(nullptr), ImportSummary(nullptr), UseCommandLine(true) {}
230   WholeProgramDevirtPass(ModuleSummaryIndex *ExportSummary,
231                          const ModuleSummaryIndex *ImportSummary)
232       : ExportSummary(ExportSummary), ImportSummary(ImportSummary) {
233     assert(!(ExportSummary && ImportSummary));
234   }
235   PreservedAnalyses run(Module &M, ModuleAnalysisManager &);
236 };
237 
238 struct VTableSlotSummary {
239   StringRef TypeID;
240   uint64_t ByteOffset;
241 };
242 bool hasWholeProgramVisibility(bool WholeProgramVisibilityEnabledInLTO);
243 void updatePublicTypeTestCalls(Module &M,
244                                bool WholeProgramVisibilityEnabledInLTO);
245 void updateVCallVisibilityInModule(
246     Module &M, bool WholeProgramVisibilityEnabledInLTO,
247     const DenseSet<GlobalValue::GUID> &DynamicExportSymbols);
248 void updateVCallVisibilityInIndex(
249     ModuleSummaryIndex &Index, bool WholeProgramVisibilityEnabledInLTO,
250     const DenseSet<GlobalValue::GUID> &DynamicExportSymbols);
251 
252 /// Perform index-based whole program devirtualization on the \p Summary
253 /// index. Any devirtualized targets used by a type test in another module
254 /// are added to the \p ExportedGUIDs set. For any local devirtualized targets
255 /// only used within the defining module, the information necessary for
256 /// locating the corresponding WPD resolution is recorded for the ValueInfo
257 /// in case it is exported by cross module importing (in which case the
258 /// devirtualized target name will need adjustment).
259 void runWholeProgramDevirtOnIndex(
260     ModuleSummaryIndex &Summary, std::set<GlobalValue::GUID> &ExportedGUIDs,
261     std::map<ValueInfo, std::vector<VTableSlotSummary>> &LocalWPDTargetsMap);
262 
263 /// Call after cross-module importing to update the recorded single impl
264 /// devirt target names for any locals that were exported.
265 void updateIndexWPDForExports(
266     ModuleSummaryIndex &Summary,
267     function_ref<bool(StringRef, ValueInfo)> isExported,
268     std::map<ValueInfo, std::vector<VTableSlotSummary>> &LocalWPDTargetsMap);
269 
270 } // end namespace llvm
271 
272 #endif // LLVM_TRANSFORMS_IPO_WHOLEPROGRAMDEVIRT_H
273