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