• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //===- Tracker.h ------------------------------------------------*- 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 is the component of SandboxIR that tracks all changes made to its
10 // state, such that we can revert the state when needed.
11 //
12 // Tracking changes
13 // ----------------
14 // The user needs to call `Tracker::save()` to enable tracking changes
15 // made to SandboxIR. From that point on, any change made to SandboxIR, will
16 // automatically create a change tracking object and register it with the
17 // tracker. IR-change objects are subclasses of `IRChangeBase` and get
18 // registered with the `Tracker::track()` function. The change objects
19 // are saved in the order they are registered with the tracker and are stored in
20 // the `Tracker::Changes` vector. All of this is done transparently to
21 // the user.
22 //
23 // Reverting changes
24 // -----------------
25 // Calling `Tracker::revert()` will restore the state saved when
26 // `Tracker::save()` was called. Internally this goes through the
27 // change objects in `Tracker::Changes` in reverse order, calling their
28 // `IRChangeBase::revert()` function one by one.
29 //
30 // Accepting changes
31 // -----------------
32 // The user needs to either revert or accept changes before the tracker object
33 // is destroyed. This is enforced in the tracker's destructor.
34 // This is the job of `Tracker::accept()`. Internally this will go
35 // through the change objects in `Tracker::Changes` in order, calling
36 // `IRChangeBase::accept()`.
37 //
38 //===----------------------------------------------------------------------===//
39 
40 #ifndef LLVM_SANDBOXIR_TRACKER_H
41 #define LLVM_SANDBOXIR_TRACKER_H
42 
43 #include "llvm/ADT/PointerUnion.h"
44 #include "llvm/ADT/SmallVector.h"
45 #include "llvm/IR/IRBuilder.h"
46 #include "llvm/IR/Instruction.h"
47 #include "llvm/IR/Module.h"
48 #include "llvm/SandboxIR/Use.h"
49 #include "llvm/Support/Debug.h"
50 #include <memory>
51 #include <regex>
52 
53 namespace llvm::sandboxir {
54 
55 class BasicBlock;
56 class CallBrInst;
57 class LoadInst;
58 class StoreInst;
59 class Instruction;
60 class Tracker;
61 class AllocaInst;
62 class CatchSwitchInst;
63 class SwitchInst;
64 class ConstantInt;
65 class ShuffleVectorInst;
66 
67 /// The base class for IR Change classes.
68 class IRChangeBase {
69 protected:
70   friend class Tracker; // For Parent.
71 
72 public:
73   /// This runs when changes get reverted.
74   virtual void revert(Tracker &Tracker) = 0;
75   /// This runs when changes get accepted.
76   virtual void accept() = 0;
77   virtual ~IRChangeBase() = default;
78 #ifndef NDEBUG
79   virtual void dump(raw_ostream &OS) const = 0;
80   LLVM_DUMP_METHOD virtual void dump() const = 0;
81   friend raw_ostream &operator<<(raw_ostream &OS, const IRChangeBase &C) {
82     C.dump(OS);
83     return OS;
84   }
85 #endif
86 };
87 
88 /// Tracks the change of the source Value of a sandboxir::Use.
89 class UseSet : public IRChangeBase {
90   Use U;
91   Value *OrigV = nullptr;
92 
93 public:
UseSet(const Use & U)94   UseSet(const Use &U) : U(U), OrigV(U.get()) {}
revert(Tracker & Tracker)95   void revert(Tracker &Tracker) final { U.set(OrigV); }
accept()96   void accept() final {}
97 #ifndef NDEBUG
dump(raw_ostream & OS)98   void dump(raw_ostream &OS) const final { OS << "UseSet"; }
99   LLVM_DUMP_METHOD void dump() const final;
100 #endif
101 };
102 
103 class PHIRemoveIncoming : public IRChangeBase {
104   PHINode *PHI;
105   unsigned RemovedIdx;
106   Value *RemovedV;
107   BasicBlock *RemovedBB;
108 
109 public:
110   PHIRemoveIncoming(PHINode *PHI, unsigned RemovedIdx);
111   void revert(Tracker &Tracker) final;
accept()112   void accept() final {}
113 #ifndef NDEBUG
dump(raw_ostream & OS)114   void dump(raw_ostream &OS) const final { OS << "PHISetIncoming"; }
115   LLVM_DUMP_METHOD void dump() const final;
116 #endif
117 };
118 
119 class PHIAddIncoming : public IRChangeBase {
120   PHINode *PHI;
121   unsigned Idx;
122 
123 public:
124   PHIAddIncoming(PHINode *PHI);
125   void revert(Tracker &Tracker) final;
accept()126   void accept() final {}
127 #ifndef NDEBUG
dump(raw_ostream & OS)128   void dump(raw_ostream &OS) const final { OS << "PHISetIncoming"; }
129   LLVM_DUMP_METHOD void dump() const final;
130 #endif
131 };
132 
133 /// Tracks swapping a Use with another Use.
134 class UseSwap : public IRChangeBase {
135   Use ThisUse;
136   Use OtherUse;
137 
138 public:
UseSwap(const Use & ThisUse,const Use & OtherUse)139   UseSwap(const Use &ThisUse, const Use &OtherUse)
140       : ThisUse(ThisUse), OtherUse(OtherUse) {
141     assert(ThisUse.getUser() == OtherUse.getUser() && "Expected same user!");
142   }
revert(Tracker & Tracker)143   void revert(Tracker &Tracker) final { ThisUse.swap(OtherUse); }
accept()144   void accept() final {}
145 #ifndef NDEBUG
dump(raw_ostream & OS)146   void dump(raw_ostream &OS) const final { OS << "UseSwap"; }
147   LLVM_DUMP_METHOD void dump() const final;
148 #endif
149 };
150 
151 class EraseFromParent : public IRChangeBase {
152   /// Contains all the data we need to restore an "erased" (i.e., detached)
153   /// instruction: the instruction itself and its operands in order.
154   struct InstrAndOperands {
155     /// The operands that got dropped.
156     SmallVector<llvm::Value *> Operands;
157     /// The instruction that got "erased".
158     llvm::Instruction *LLVMI;
159   };
160   /// The instruction data is in reverse program order, which helps create the
161   /// original program order during revert().
162   SmallVector<InstrAndOperands> InstrData;
163   /// This is either the next Instruction in the stream, or the parent
164   /// BasicBlock if at the end of the BB.
165   PointerUnion<llvm::Instruction *, llvm::BasicBlock *> NextLLVMIOrBB;
166   /// We take ownership of the "erased" instruction.
167   std::unique_ptr<sandboxir::Value> ErasedIPtr;
168 
169 public:
170   EraseFromParent(std::unique_ptr<sandboxir::Value> &&IPtr);
171   void revert(Tracker &Tracker) final;
172   void accept() final;
173 #ifndef NDEBUG
dump(raw_ostream & OS)174   void dump(raw_ostream &OS) const final { OS << "EraseFromParent"; }
175   LLVM_DUMP_METHOD void dump() const final;
176   friend raw_ostream &operator<<(raw_ostream &OS, const EraseFromParent &C) {
177     C.dump(OS);
178     return OS;
179   }
180 #endif
181 };
182 
183 class RemoveFromParent : public IRChangeBase {
184   /// The instruction that is about to get removed.
185   Instruction *RemovedI = nullptr;
186   /// This is either the next instr, or the parent BB if at the end of the BB.
187   PointerUnion<Instruction *, BasicBlock *> NextInstrOrBB;
188 
189 public:
190   RemoveFromParent(Instruction *RemovedI);
191   void revert(Tracker &Tracker) final;
accept()192   void accept() final {};
getInstruction()193   Instruction *getInstruction() const { return RemovedI; }
194 #ifndef NDEBUG
dump(raw_ostream & OS)195   void dump(raw_ostream &OS) const final { OS << "RemoveFromParent"; }
196   LLVM_DUMP_METHOD void dump() const final;
197 #endif // NDEBUG
198 };
199 
200 /// This class can be used for tracking most instruction setters.
201 /// The two template arguments are:
202 /// - GetterFn: The getter member function pointer (e.g., `&Foo::get`)
203 /// - SetterFn: The setter member function pointer (e.g., `&Foo::set`)
204 /// Upon construction, it saves a copy of the original value by calling the
205 /// getter function. Revert sets the value back to the one saved, using the
206 /// setter function provided.
207 ///
208 /// Example:
209 ///  Tracker.track(std::make_unique<
210 ///                GenericSetter<&FooInst::get, &FooInst::set>>(I, Tracker));
211 ///
212 template <auto GetterFn, auto SetterFn>
213 class GenericSetter final : public IRChangeBase {
214   /// Traits for getting the class type from GetterFn type.
215   template <typename> struct GetClassTypeFromGetter;
216   template <typename RetT, typename ClassT>
217   struct GetClassTypeFromGetter<RetT (ClassT::*)() const> {
218     using ClassType = ClassT;
219   };
220   using InstrT = typename GetClassTypeFromGetter<decltype(GetterFn)>::ClassType;
221   using SavedValT = std::invoke_result_t<decltype(GetterFn), InstrT>;
222   InstrT *I;
223   SavedValT OrigVal;
224 
225 public:
226   GenericSetter(InstrT *I) : I(I), OrigVal((I->*GetterFn)()) {}
227   void revert(Tracker &Tracker) final { (I->*SetterFn)(OrigVal); }
228   void accept() final {}
229 #ifndef NDEBUG
230   void dump(raw_ostream &OS) const final { OS << "GenericSetter"; }
231   LLVM_DUMP_METHOD void dump() const final {
232     dump(dbgs());
233     dbgs() << "\n";
234   }
235 #endif
236 };
237 
238 /// Similar to GenericSetter but the setters/getters have an index as their
239 /// first argument. This is commont in cases like: getOperand(unsigned Idx)
240 template <auto GetterFn, auto SetterFn>
241 class GenericSetterWithIdx final : public IRChangeBase {
242   /// Helper for getting the class type from the getter
243   template <typename ClassT, typename RetT>
244   static ClassT getClassTypeFromGetter(RetT (ClassT::*Fn)(unsigned) const);
245   template <typename ClassT, typename RetT>
246   static ClassT getClassTypeFromGetter(RetT (ClassT::*Fn)(unsigned));
247 
248   using InstrT = decltype(getClassTypeFromGetter(GetterFn));
249   using SavedValT = std::invoke_result_t<decltype(GetterFn), InstrT, unsigned>;
250   InstrT *I;
251   SavedValT OrigVal;
252   unsigned Idx;
253 
254 public:
255   GenericSetterWithIdx(InstrT *I, unsigned Idx)
256       : I(I), OrigVal((I->*GetterFn)(Idx)), Idx(Idx) {}
257   void revert(Tracker &Tracker) final { (I->*SetterFn)(Idx, OrigVal); }
258   void accept() final {}
259 #ifndef NDEBUG
260   void dump(raw_ostream &OS) const final { OS << "GenericSetterWithIdx"; }
261   LLVM_DUMP_METHOD void dump() const final {
262     dump(dbgs());
263     dbgs() << "\n";
264   }
265 #endif
266 };
267 
268 class CatchSwitchAddHandler : public IRChangeBase {
269   CatchSwitchInst *CSI;
270   unsigned HandlerIdx;
271 
272 public:
273   CatchSwitchAddHandler(CatchSwitchInst *CSI);
274   void revert(Tracker &Tracker) final;
275   void accept() final {}
276 #ifndef NDEBUG
277   void dump(raw_ostream &OS) const final { OS << "CatchSwitchAddHandler"; }
278   LLVM_DUMP_METHOD void dump() const final {
279     dump(dbgs());
280     dbgs() << "\n";
281   }
282 #endif // NDEBUG
283 };
284 
285 class SwitchAddCase : public IRChangeBase {
286   SwitchInst *Switch;
287   ConstantInt *Val;
288 
289 public:
290   SwitchAddCase(SwitchInst *Switch, ConstantInt *Val)
291       : Switch(Switch), Val(Val) {}
292   void revert(Tracker &Tracker) final;
293   void accept() final {}
294 #ifndef NDEBUG
295   void dump(raw_ostream &OS) const final { OS << "SwitchAddCase"; }
296   LLVM_DUMP_METHOD void dump() const final;
297 #endif // NDEBUG
298 };
299 
300 class SwitchRemoveCase : public IRChangeBase {
301   SwitchInst *Switch;
302   ConstantInt *Val;
303   BasicBlock *Dest;
304 
305 public:
306   SwitchRemoveCase(SwitchInst *Switch, ConstantInt *Val, BasicBlock *Dest)
307       : Switch(Switch), Val(Val), Dest(Dest) {}
308   void revert(Tracker &Tracker) final;
309   void accept() final {}
310 #ifndef NDEBUG
311   void dump(raw_ostream &OS) const final { OS << "SwitchRemoveCase"; }
312   LLVM_DUMP_METHOD void dump() const final;
313 #endif // NDEBUG
314 };
315 
316 class MoveInstr : public IRChangeBase {
317   /// The instruction that moved.
318   Instruction *MovedI;
319   /// This is either the next instruction in the block, or the parent BB if at
320   /// the end of the BB.
321   PointerUnion<Instruction *, BasicBlock *> NextInstrOrBB;
322 
323 public:
324   MoveInstr(sandboxir::Instruction *I);
325   void revert(Tracker &Tracker) final;
326   void accept() final {}
327 #ifndef NDEBUG
328   void dump(raw_ostream &OS) const final { OS << "MoveInstr"; }
329   LLVM_DUMP_METHOD void dump() const final;
330 #endif // NDEBUG
331 };
332 
333 class InsertIntoBB final : public IRChangeBase {
334   Instruction *InsertedI = nullptr;
335 
336 public:
337   InsertIntoBB(Instruction *InsertedI);
338   void revert(Tracker &Tracker) final;
339   void accept() final {}
340 #ifndef NDEBUG
341   void dump(raw_ostream &OS) const final { OS << "InsertIntoBB"; }
342   LLVM_DUMP_METHOD void dump() const final;
343 #endif // NDEBUG
344 };
345 
346 class CreateAndInsertInst final : public IRChangeBase {
347   Instruction *NewI = nullptr;
348 
349 public:
350   CreateAndInsertInst(Instruction *NewI) : NewI(NewI) {}
351   void revert(Tracker &Tracker) final;
352   void accept() final {}
353 #ifndef NDEBUG
354   void dump(raw_ostream &OS) const final { OS << "CreateAndInsertInst"; }
355   LLVM_DUMP_METHOD void dump() const final;
356 #endif
357 };
358 
359 class ShuffleVectorSetMask final : public IRChangeBase {
360   ShuffleVectorInst *SVI;
361   SmallVector<int, 8> PrevMask;
362 
363 public:
364   ShuffleVectorSetMask(ShuffleVectorInst *SVI);
365   void revert(Tracker &Tracker) final;
366   void accept() final {}
367 #ifndef NDEBUG
368   void dump(raw_ostream &OS) const final { OS << "ShuffleVectorSetMask"; }
369   LLVM_DUMP_METHOD void dump() const final;
370 #endif
371 };
372 
373 /// The tracker collects all the change objects and implements the main API for
374 /// saving / reverting / accepting.
375 class Tracker {
376 public:
377   enum class TrackerState {
378     Disabled, ///> Tracking is disabled
379     Record,   ///> Tracking changes
380   };
381 
382 private:
383   /// The list of changes that are being tracked.
384   SmallVector<std::unique_ptr<IRChangeBase>> Changes;
385   /// The current state of the tracker.
386   TrackerState State = TrackerState::Disabled;
387   Context &Ctx;
388 
389 public:
390 #ifndef NDEBUG
391   /// Helps catch bugs where we are creating new change objects while in the
392   /// middle of creating other change objects.
393   bool InMiddleOfCreatingChange = false;
394 #endif // NDEBUG
395 
396   explicit Tracker(Context &Ctx) : Ctx(Ctx) {}
397   ~Tracker();
398   Context &getContext() const { return Ctx; }
399   /// Record \p Change and take ownership. This is the main function used to
400   /// track Sandbox IR changes.
401   void track(std::unique_ptr<IRChangeBase> &&Change) {
402     assert(State == TrackerState::Record && "The tracker should be tracking!");
403 #ifndef NDEBUG
404     assert(!InMiddleOfCreatingChange &&
405            "We are in the middle of creating another change!");
406     if (isTracking())
407       InMiddleOfCreatingChange = true;
408 #endif // NDEBUG
409     Changes.push_back(std::move(Change));
410 
411 #ifndef NDEBUG
412     InMiddleOfCreatingChange = false;
413 #endif
414   }
415   /// A convenience wrapper for `track()` that constructs and tracks the Change
416   /// object if tracking is enabled. \Returns true if tracking is enabled.
417   template <typename ChangeT, typename... ArgsT>
418   bool emplaceIfTracking(ArgsT... Args) {
419     if (!isTracking())
420       return false;
421     track(std::make_unique<ChangeT>(Args...));
422     return true;
423   }
424   /// \Returns true if the tracker is recording changes.
425   bool isTracking() const { return State == TrackerState::Record; }
426   /// \Returns the current state of the tracker.
427   TrackerState getState() const { return State; }
428   /// Turns on IR tracking.
429   void save();
430   /// Stops tracking and accept changes.
431   void accept();
432   /// Stops tracking and reverts to saved state.
433   void revert();
434 
435 #ifndef NDEBUG
436   void dump(raw_ostream &OS) const;
437   LLVM_DUMP_METHOD void dump() const;
438   friend raw_ostream &operator<<(raw_ostream &OS, const Tracker &Tracker) {
439     Tracker.dump(OS);
440     return OS;
441   }
442 #endif // NDEBUG
443 };
444 
445 } // namespace llvm::sandboxir
446 
447 #endif // LLVM_SANDBOXIR_TRACKER_H
448