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