Skip to content

Commit 5338bd3

Browse files
authored
[SandboxIR] IR Tracker (#99238)
This is the first patch in a series of patches for the IR change tracking component of SandboxIR. The tracker collects changes in a vector of `IRChangeBase` objects and provides a `save()`/`accept()`/`revert()` API. Each type of IR changing event is captured by a dedicated subclass of `IRChangeBase`. This patch implements only one of them, that for updating a `sandboxir::Use` source value, named `UseSet`.
1 parent 464d321 commit 5338bd3

File tree

9 files changed

+443
-1
lines changed

9 files changed

+443
-1
lines changed

llvm/docs/SandboxIR.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,21 @@ For example, for `sandboxir::User::setOperand(OpIdx, sandboxir::Value *Op)`:
5151
- We get the corresponding LLVM User: `llvm::User *LLVMU = cast<llvm::User>(Val)`
5252
- Next we get the corresponding LLVM Operand: `llvm::Value *LLVMOp = Op->Val`
5353
- Finally we modify `LLVMU`'s operand: `LLVMU->setOperand(OpIdx, LLVMOp)
54+
55+
## IR Change Tracking
56+
Sandbox IR's state can be saved and restored.
57+
This is done with the help of the tracker component that is tightly coupled to the public Sandbox IR API functions.
58+
Please note that nested saves/restores are currently not supported.
59+
60+
To save the state and enable tracking the user needs to call `sandboxir::Context::save()`.
61+
From this point on any change made to the Sandbox IR state will automatically create a change object and register it with the tracker, without any intervention from the user.
62+
The changes are accumulated in a vector within the tracker.
63+
64+
To rollback to the saved state the user needs to call `sandboxir::Context::revert()`.
65+
Reverting back to the saved state is a matter of going over all the accumulated changes in reverse and undoing each individual change.
66+
67+
To accept the changes made to the IR the user needs to call `sandboxir::Context::accept()`.
68+
Internally this will go through the changes and run any finalization required.
69+
70+
Please note that after a call to `revert()` or `accept()` tracking will stop.
71+
To start tracking again, the user needs to call `save()`.

llvm/include/llvm/SandboxIR/SandboxIR.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
#include "llvm/IR/Function.h"
6262
#include "llvm/IR/User.h"
6363
#include "llvm/IR/Value.h"
64+
#include "llvm/SandboxIR/Tracker.h"
6465
#include "llvm/SandboxIR/Use.h"
6566
#include "llvm/Support/raw_ostream.h"
6667
#include <iterator>
@@ -171,6 +172,7 @@ class Value {
171172

172173
friend class Context; // For getting `Val`.
173174
friend class User; // For getting `Val`.
175+
friend class Use; // For getting `Val`.
174176

175177
/// All values point to the context.
176178
Context &Ctx;
@@ -641,6 +643,8 @@ class BasicBlock : public Value {
641643
class Context {
642644
protected:
643645
LLVMContext &LLVMCtx;
646+
Tracker IRTracker;
647+
644648
/// Maps LLVM Value to the corresponding sandboxir::Value. Owns all
645649
/// SandboxIR objects.
646650
DenseMap<llvm::Value *, std::unique_ptr<sandboxir::Value>>
@@ -680,6 +684,14 @@ class Context {
680684
public:
681685
Context(LLVMContext &LLVMCtx) : LLVMCtx(LLVMCtx) {}
682686

687+
Tracker &getTracker() { return IRTracker; }
688+
/// Convenience function for `getTracker().save()`
689+
void save() { IRTracker.save(); }
690+
/// Convenience function for `getTracker().revert()`
691+
void revert() { IRTracker.revert(); }
692+
/// Convenience function for `getTracker().accept()`
693+
void accept() { IRTracker.accept(); }
694+
683695
sandboxir::Value *getValue(llvm::Value *V) const;
684696
const sandboxir::Value *getValue(const llvm::Value *V) const {
685697
return getValue(const_cast<llvm::Value *>(V));

llvm/include/llvm/SandboxIR/Tracker.h

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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/SmallVector.h"
44+
#include "llvm/IR/IRBuilder.h"
45+
#include "llvm/IR/Instruction.h"
46+
#include "llvm/IR/Module.h"
47+
#include "llvm/SandboxIR/Use.h"
48+
#include "llvm/Support/Debug.h"
49+
#include <memory>
50+
#include <regex>
51+
52+
namespace llvm::sandboxir {
53+
54+
class BasicBlock;
55+
class Tracker;
56+
57+
/// The base class for IR Change classes.
58+
class IRChangeBase {
59+
protected:
60+
Tracker &Parent;
61+
62+
public:
63+
IRChangeBase(Tracker &Parent);
64+
/// This runs when changes get reverted.
65+
virtual void revert() = 0;
66+
/// This runs when changes get accepted.
67+
virtual void accept() = 0;
68+
virtual ~IRChangeBase() = default;
69+
#ifndef NDEBUG
70+
/// \Returns the index of this change by iterating over all changes in the
71+
/// tracker. This is only used for debugging.
72+
unsigned getIdx() const;
73+
void dumpCommon(raw_ostream &OS) const { OS << getIdx() << ". "; }
74+
virtual void dump(raw_ostream &OS) const = 0;
75+
LLVM_DUMP_METHOD virtual void dump() const = 0;
76+
friend raw_ostream &operator<<(raw_ostream &OS, const IRChangeBase &C) {
77+
C.dump(OS);
78+
return OS;
79+
}
80+
#endif
81+
};
82+
83+
/// Tracks the change of the source Value of a sandboxir::Use.
84+
class UseSet : public IRChangeBase {
85+
Use U;
86+
Value *OrigV = nullptr;
87+
88+
public:
89+
UseSet(const Use &U, Tracker &Tracker)
90+
: IRChangeBase(Tracker), U(U), OrigV(U.get()) {}
91+
void revert() final { U.set(OrigV); }
92+
void accept() final {}
93+
#ifndef NDEBUG
94+
void dump(raw_ostream &OS) const final {
95+
dumpCommon(OS);
96+
OS << "UseSet";
97+
}
98+
LLVM_DUMP_METHOD void dump() const final;
99+
#endif
100+
};
101+
102+
/// The tracker collects all the change objects and implements the main API for
103+
/// saving / reverting / accepting.
104+
class Tracker {
105+
public:
106+
enum class TrackerState {
107+
Disabled, ///> Tracking is disabled
108+
Record, ///> Tracking changes
109+
};
110+
111+
private:
112+
/// The list of changes that are being tracked.
113+
SmallVector<std::unique_ptr<IRChangeBase>> Changes;
114+
#ifndef NDEBUG
115+
friend unsigned IRChangeBase::getIdx() const; // For accessing `Changes`.
116+
#endif
117+
/// The current state of the tracker.
118+
TrackerState State = TrackerState::Disabled;
119+
120+
public:
121+
#ifndef NDEBUG
122+
/// Helps catch bugs where we are creating new change objects while in the
123+
/// middle of creating other change objects.
124+
bool InMiddleOfCreatingChange = false;
125+
#endif // NDEBUG
126+
127+
Tracker() = default;
128+
~Tracker();
129+
/// Record \p Change and take ownership. This is the main function used to
130+
/// track Sandbox IR changes.
131+
void track(std::unique_ptr<IRChangeBase> &&Change);
132+
/// \Returns true if the tracker is recording changes.
133+
bool isTracking() const { return State == TrackerState::Record; }
134+
/// \Returns the current state of the tracker.
135+
TrackerState getState() const { return State; }
136+
/// Turns on IR tracking.
137+
void save();
138+
/// Stops tracking and accept changes.
139+
void accept();
140+
/// Stops tracking and reverts to saved state.
141+
void revert();
142+
143+
#ifndef NDEBUG
144+
void dump(raw_ostream &OS) const;
145+
LLVM_DUMP_METHOD void dump() const;
146+
friend raw_ostream &operator<<(raw_ostream &OS, const Tracker &Tracker) {
147+
Tracker.dump(OS);
148+
return OS;
149+
}
150+
#endif // NDEBUG
151+
};
152+
153+
} // namespace llvm::sandboxir
154+
155+
#endif // LLVM_SANDBOXIR_TRACKER_H

llvm/include/llvm/SandboxIR/Use.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class Use {
4444
public:
4545
operator Value *() const { return get(); }
4646
Value *get() const;
47+
void set(Value *V);
4748
class User *getUser() const { return Usr; }
4849
unsigned getOperandNo() const;
4950
Context *getContext() const { return Ctx; }

llvm/lib/SandboxIR/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
add_llvm_component_library(LLVMSandboxIR
22
SandboxIR.cpp
3+
Tracker.cpp
34

45
ADDITIONAL_HEADER_DIRS
56
${LLVM_MAIN_INCLUDE_DIR}/llvm/Transforms/SandboxIR

llvm/lib/SandboxIR/SandboxIR.cpp

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ using namespace llvm::sandboxir;
1616

1717
Value *Use::get() const { return Ctx->getValue(LLVMUse->get()); }
1818

19+
void Use::set(Value *V) { LLVMUse->set(V->Val); }
20+
1921
unsigned Use::getOperandNo() const { return Usr->getUseOperandNo(*this); }
2022

2123
#ifndef NDEBUG
@@ -115,13 +117,24 @@ void Value::replaceUsesWithIf(
115117
User *DstU = cast_or_null<User>(Ctx.getValue(LLVMUse.getUser()));
116118
if (DstU == nullptr)
117119
return false;
118-
return ShouldReplace(Use(&LLVMUse, DstU, Ctx));
120+
Use UseToReplace(&LLVMUse, DstU, Ctx);
121+
if (!ShouldReplace(UseToReplace))
122+
return false;
123+
auto &Tracker = Ctx.getTracker();
124+
if (Tracker.isTracking())
125+
Tracker.track(std::make_unique<UseSet>(UseToReplace, Tracker));
126+
return true;
119127
});
120128
}
121129

122130
void Value::replaceAllUsesWith(Value *Other) {
123131
assert(getType() == Other->getType() &&
124132
"Replacing with Value of different type!");
133+
auto &Tracker = Ctx.getTracker();
134+
if (Tracker.isTracking()) {
135+
for (auto Use : uses())
136+
Tracker.track(std::make_unique<UseSet>(Use, Tracker));
137+
}
125138
// We are delegating RAUW to LLVM IR's RAUW.
126139
Val->replaceAllUsesWith(Other->Val);
127140
}
@@ -212,11 +225,22 @@ bool User::classof(const Value *From) {
212225

213226
void User::setOperand(unsigned OperandIdx, Value *Operand) {
214227
assert(isa<llvm::User>(Val) && "No operands!");
228+
auto &Tracker = Ctx.getTracker();
229+
if (Tracker.isTracking())
230+
Tracker.track(std::make_unique<UseSet>(getOperandUse(OperandIdx), Tracker));
215231
// We are delegating to llvm::User::setOperand().
216232
cast<llvm::User>(Val)->setOperand(OperandIdx, Operand->Val);
217233
}
218234

219235
bool User::replaceUsesOfWith(Value *FromV, Value *ToV) {
236+
auto &Tracker = Ctx.getTracker();
237+
if (Tracker.isTracking()) {
238+
for (auto OpIdx : seq<unsigned>(0, getNumOperands())) {
239+
auto Use = getOperandUse(OpIdx);
240+
if (Use.get() == FromV)
241+
Tracker.track(std::make_unique<UseSet>(Use, Tracker));
242+
}
243+
}
220244
// We are delegating RUOW to LLVM IR's RUOW.
221245
return cast<llvm::User>(Val)->replaceUsesOfWith(FromV->Val, ToV->Val);
222246
}

llvm/lib/SandboxIR/Tracker.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//===- Tracker.cpp --------------------------------------------------------===//
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 "llvm/SandboxIR/Tracker.h"
10+
#include "llvm/ADT/STLExtras.h"
11+
#include "llvm/IR/BasicBlock.h"
12+
#include "llvm/IR/Instruction.h"
13+
#include "llvm/SandboxIR/SandboxIR.h"
14+
#include <sstream>
15+
16+
using namespace llvm::sandboxir;
17+
18+
IRChangeBase::IRChangeBase(Tracker &Parent) : Parent(Parent) {
19+
#ifndef NDEBUG
20+
assert(!Parent.InMiddleOfCreatingChange &&
21+
"We are in the middle of creating another change!");
22+
if (Parent.isTracking())
23+
Parent.InMiddleOfCreatingChange = true;
24+
#endif // NDEBUG
25+
}
26+
27+
#ifndef NDEBUG
28+
unsigned IRChangeBase::getIdx() const {
29+
auto It =
30+
find_if(Parent.Changes, [this](auto &Ptr) { return Ptr.get() == this; });
31+
return It - Parent.Changes.begin();
32+
}
33+
34+
void UseSet::dump() const {
35+
dump(dbgs());
36+
dbgs() << "\n";
37+
}
38+
#endif // NDEBUG
39+
40+
Tracker::~Tracker() {
41+
assert(Changes.empty() && "You must accept or revert changes!");
42+
}
43+
44+
void Tracker::track(std::unique_ptr<IRChangeBase> &&Change) {
45+
assert(State == TrackerState::Record && "The tracker should be tracking!");
46+
Changes.push_back(std::move(Change));
47+
48+
#ifndef NDEBUG
49+
InMiddleOfCreatingChange = false;
50+
#endif
51+
}
52+
53+
void Tracker::save() { State = TrackerState::Record; }
54+
55+
void Tracker::revert() {
56+
assert(State == TrackerState::Record && "Forgot to save()!");
57+
State = TrackerState::Disabled;
58+
for (auto &Change : reverse(Changes))
59+
Change->revert();
60+
Changes.clear();
61+
}
62+
63+
void Tracker::accept() {
64+
assert(State == TrackerState::Record && "Forgot to save()!");
65+
State = TrackerState::Disabled;
66+
for (auto &Change : Changes)
67+
Change->accept();
68+
Changes.clear();
69+
}
70+
71+
#ifndef NDEBUG
72+
void Tracker::dump(raw_ostream &OS) const {
73+
for (const auto &ChangePtr : Changes) {
74+
ChangePtr->dump(OS);
75+
OS << "\n";
76+
}
77+
}
78+
void Tracker::dump() const {
79+
dump(dbgs());
80+
dbgs() << "\n";
81+
}
82+
#endif // NDEBUG

llvm/unittests/SandboxIR/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ set(LLVM_LINK_COMPONENTS
66

77
add_llvm_unittest(SandboxIRTests
88
SandboxIRTest.cpp
9+
TrackerTest.cpp
910
)

0 commit comments

Comments
 (0)