Skip to content

Commit 5e8c1c4

Browse files
committed
[SandboxIR] IR Tracker
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 80e18b9 commit 5e8c1c4

File tree

9 files changed

+470
-1
lines changed

9 files changed

+470
-1
lines changed

llvm/docs/SandboxIR.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,14 @@ 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+
59+
To save the state and enable tracking the user needs to call `sandboxir::Context::save()`.
60+
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.
61+
The changes are accumulated in a vector within the tracker.
62+
63+
To rollback to the saved state the user needs to call `sandboxir::Context::revert()`.
64+
Reverting back to the saved state is a matter of going over all the accumulated states in reverse and undoing each individual change.

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/SandboxIRTracker.h"
6465
#include "llvm/SandboxIR/Use.h"
6566
#include "llvm/Support/raw_ostream.h"
6667
#include <iterator>
@@ -167,6 +168,7 @@ class Value {
167168

168169
friend class Context; // For getting `Val`.
169170
friend class User; // For getting `Val`.
171+
friend class Use; // For getting `Val`.
170172

171173
/// All values point to the context.
172174
Context &Ctx;
@@ -630,6 +632,8 @@ class BasicBlock : public Value {
630632
class Context {
631633
protected:
632634
LLVMContext &LLVMCtx;
635+
SandboxIRTracker IRTracker;
636+
633637
/// Maps LLVM Value to the corresponding sandboxir::Value. Owns all
634638
/// SandboxIR objects.
635639
DenseMap<llvm::Value *, std::unique_ptr<sandboxir::Value>>
@@ -667,6 +671,14 @@ class Context {
667671
public:
668672
Context(LLVMContext &LLVMCtx) : LLVMCtx(LLVMCtx) {}
669673

674+
SandboxIRTracker &getTracker() { return IRTracker; }
675+
/// Convenience function for `getTracker().save()`
676+
void save() { IRTracker.save(); }
677+
/// Convenience function for `getTracker().revert()`
678+
void revert() { IRTracker.revert(); }
679+
/// Convenience function for `getTracker().accept()`
680+
void accept() { IRTracker.accept(); }
681+
670682
sandboxir::Value *getValue(llvm::Value *V) const;
671683
const sandboxir::Value *getValue(const llvm::Value *V) const {
672684
return getValue(const_cast<llvm::Value *>(V));
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
//===- SandboxIRTracker.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 `SandboxIRTracker::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 `SandboxIRTracker::track()` function. The change objects
19+
// are saved in the order they are registered with the tracker and are stored in
20+
// the `SandboxIRTracker::Changes` vector. All of this is done transparently to
21+
// the user.
22+
//
23+
// Reverting changes
24+
// -----------------
25+
// Calling `SandboxIRTracker::revert()` will restore the state saved when
26+
// `SandboxIRTracker::save()` was called. Internally this goes through the
27+
// change objects in `SandboxIRTracker::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, or else the tracker destructor will cause a crash.
34+
// This is the job of `SandboxIRTracker::accept()`. Internally this will go
35+
// through the change objects in `SandboxIRTracker::Changes` in order, calling
36+
// `IRChangeBase::accept()`.
37+
//
38+
//===----------------------------------------------------------------------===//
39+
40+
#ifndef LLVM_SANDBOXIR_SANDBOXIRTRACKER_H
41+
#define LLVM_SANDBOXIR_SANDBOXIRTRACKER_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+
56+
/// Each IR change type has an ID.
57+
enum class TrackID {
58+
UseSet,
59+
};
60+
61+
#ifndef NDEBUG
62+
static const char *trackIDToStr(TrackID ID) {
63+
switch (ID) {
64+
case TrackID::UseSet:
65+
return "UseSet";
66+
}
67+
llvm_unreachable("Unimplemented ID");
68+
}
69+
#endif // NDEBUG
70+
71+
class SandboxIRTracker;
72+
73+
/// The base class for IR Change classes.
74+
class IRChangeBase {
75+
protected:
76+
#ifndef NDEBUG
77+
unsigned Idx = 0;
78+
#endif
79+
const TrackID ID;
80+
SandboxIRTracker &Parent;
81+
82+
public:
83+
IRChangeBase(TrackID ID, SandboxIRTracker &Parent);
84+
TrackID getTrackID() const { return ID; }
85+
/// This runs when changes get reverted.
86+
virtual void revert() = 0;
87+
/// This runs when changes get accepted.
88+
virtual void accept() = 0;
89+
virtual ~IRChangeBase() = default;
90+
#ifndef NDEBUG
91+
void dumpCommon(raw_ostream &OS) const {
92+
OS << Idx << ". " << trackIDToStr(ID);
93+
}
94+
virtual void dump(raw_ostream &OS) const = 0;
95+
LLVM_DUMP_METHOD virtual void dump() const = 0;
96+
#endif
97+
};
98+
99+
/// Change the source Value of a sandboxir::Use.
100+
class UseSet : public IRChangeBase {
101+
Use U;
102+
Value *OrigV = nullptr;
103+
104+
public:
105+
UseSet(const Use &U, SandboxIRTracker &Tracker)
106+
: IRChangeBase(TrackID::UseSet, Tracker), U(U), OrigV(U.get()) {}
107+
// For isa<> etc.
108+
static bool classof(const IRChangeBase *Other) {
109+
return Other->getTrackID() == TrackID::UseSet;
110+
}
111+
void revert() final { U.set(OrigV); }
112+
void accept() final {}
113+
#ifndef NDEBUG
114+
void dump(raw_ostream &OS) const final { dumpCommon(OS); }
115+
LLVM_DUMP_METHOD void dump() const final;
116+
friend raw_ostream &operator<<(raw_ostream &OS, const UseSet &C) {
117+
C.dump(OS);
118+
return OS;
119+
}
120+
#endif
121+
};
122+
123+
/// The tracker collects all the change objects and implements the main API for
124+
/// saving / reverting / accepting.
125+
class SandboxIRTracker {
126+
public:
127+
enum class TrackerState {
128+
Disabled, ///> Tracking is disabled
129+
Record, ///> Tracking changes
130+
Revert, ///> Undoing changes
131+
Accept, ///> Accepting changes
132+
};
133+
134+
private:
135+
/// The list of changes that are being tracked.
136+
SmallVector<std::unique_ptr<IRChangeBase>> Changes;
137+
/// The current state of the tracker.
138+
TrackerState State = TrackerState::Disabled;
139+
140+
public:
141+
#ifndef NDEBUG
142+
/// Helps catch bugs where we are creating new change objects while in the
143+
/// middle of creating other change objects.
144+
bool InMiddleOfCreatingChange = false;
145+
#endif // NDEBUG
146+
147+
SandboxIRTracker() = default;
148+
~SandboxIRTracker();
149+
/// Record \p Change and take ownership. This is the main function used to
150+
/// track Sandbox IR changes.
151+
void track(std::unique_ptr<IRChangeBase> &&Change);
152+
/// \Returns true if the tracker is recording changes.
153+
bool tracking() const { return State == TrackerState::Record; }
154+
/// \Returns the current state of the tracker.
155+
TrackerState getState() const { return State; }
156+
/// Turns on IR tracking.
157+
void save();
158+
/// Stops tracking and accept changes.
159+
void accept();
160+
/// Stops tracking and reverts to saved state.
161+
void revert();
162+
/// \Returns the number of change entries recorded so far.
163+
unsigned size() const { return Changes.size(); }
164+
/// \Returns true if there are no change entries recorded so far.
165+
bool empty() const { return Changes.empty(); }
166+
167+
#ifndef NDEBUG
168+
/// \Returns the \p Idx'th change. This is used for testing.
169+
IRChangeBase *getChange(unsigned Idx) const { return Changes[Idx].get(); }
170+
void dump(raw_ostream &OS) const;
171+
LLVM_DUMP_METHOD void dump() const;
172+
friend raw_ostream &operator<<(raw_ostream &OS, const SandboxIRTracker &C) {
173+
C.dump(OS);
174+
return OS;
175+
}
176+
#endif // NDEBUG
177+
};
178+
179+
} // namespace llvm::sandboxir
180+
181+
#endif // LLVM_SANDBOXIR_SANDBOXIRTRACKER_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+
SandboxIRTracker.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
@@ -112,13 +114,24 @@ void Value::replaceUsesWithIf(
112114
User *DstU = cast_or_null<User>(Ctx.getValue(LLVMUse.getUser()));
113115
if (DstU == nullptr)
114116
return false;
115-
return ShouldReplace(Use(&LLVMUse, DstU, Ctx));
117+
Use UseToReplace(&LLVMUse, DstU, Ctx);
118+
if (!ShouldReplace(UseToReplace))
119+
return false;
120+
auto &Tracker = Ctx.getTracker();
121+
if (Tracker.tracking())
122+
Tracker.track(std::make_unique<UseSet>(UseToReplace, Tracker));
123+
return true;
116124
});
117125
}
118126

119127
void Value::replaceAllUsesWith(Value *Other) {
120128
assert(getType() == Other->getType() &&
121129
"Replacing with Value of different type!");
130+
auto &Tracker = Ctx.getTracker();
131+
if (Tracker.tracking()) {
132+
for (auto Use : uses())
133+
Tracker.track(std::make_unique<UseSet>(Use, Tracker));
134+
}
122135
Val->replaceAllUsesWith(Other->Val);
123136
}
124137

@@ -208,10 +221,21 @@ bool User::classof(const Value *From) {
208221

209222
void User::setOperand(unsigned OperandIdx, Value *Operand) {
210223
assert(isa<llvm::User>(Val) && "No operands!");
224+
auto &Tracker = Ctx.getTracker();
225+
if (Tracker.tracking())
226+
Tracker.track(std::make_unique<UseSet>(getOperandUse(OperandIdx), Tracker));
211227
cast<llvm::User>(Val)->setOperand(OperandIdx, Operand->Val);
212228
}
213229

214230
bool User::replaceUsesOfWith(Value *FromV, Value *ToV) {
231+
auto &Tracker = Ctx.getTracker();
232+
if (Tracker.tracking()) {
233+
for (auto OpIdx : seq<unsigned>(0, getNumOperands())) {
234+
auto Use = getOperandUse(OpIdx);
235+
if (Use.get() == FromV)
236+
Tracker.track(std::make_unique<UseSet>(Use, Tracker));
237+
}
238+
}
215239
return cast<llvm::User>(Val)->replaceUsesOfWith(FromV->Val, ToV->Val);
216240
}
217241

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//===- SandboxIRTracker.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/SandboxIRTracker.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(TrackID ID, SandboxIRTracker &Parent)
19+
: ID(ID), Parent(Parent) {
20+
#ifndef NDEBUG
21+
Idx = Parent.size();
22+
23+
assert(!Parent.InMiddleOfCreatingChange &&
24+
"We are in the middle of creating another change!");
25+
if (Parent.tracking())
26+
Parent.InMiddleOfCreatingChange = true;
27+
#endif // NDEBUG
28+
}
29+
30+
#ifndef NDEBUG
31+
void UseSet::dump() const {
32+
dump(dbgs());
33+
dbgs() << "\n";
34+
}
35+
#endif // NDEBUG
36+
37+
SandboxIRTracker::~SandboxIRTracker() {
38+
assert(Changes.empty() && "You must accept or revert changes!");
39+
}
40+
41+
void SandboxIRTracker::track(std::unique_ptr<IRChangeBase> &&Change) {
42+
#ifndef NDEBUG
43+
assert(State != TrackerState::Revert &&
44+
"No changes should be tracked during revert()!");
45+
#endif // NDEBUG
46+
Changes.push_back(std::move(Change));
47+
48+
#ifndef NDEBUG
49+
InMiddleOfCreatingChange = false;
50+
#endif
51+
}
52+
53+
void SandboxIRTracker::save() { State = TrackerState::Record; }
54+
55+
void SandboxIRTracker::revert() {
56+
auto SavedState = State;
57+
State = TrackerState::Revert;
58+
for (auto &Change : reverse(Changes))
59+
Change->revert();
60+
Changes.clear();
61+
State = SavedState;
62+
}
63+
64+
void SandboxIRTracker::accept() {
65+
auto SavedState = State;
66+
State = TrackerState::Accept;
67+
for (auto &Change : Changes)
68+
Change->accept();
69+
Changes.clear();
70+
State = SavedState;
71+
}
72+
73+
#ifndef NDEBUG
74+
void SandboxIRTracker::dump(raw_ostream &OS) const {
75+
for (const auto &ChangePtr : Changes) {
76+
ChangePtr->dump(OS);
77+
OS << "\n";
78+
}
79+
}
80+
void SandboxIRTracker::dump() const {
81+
dump(dbgs());
82+
dbgs() << "\n";
83+
}
84+
#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+
SandboxIRTrackerTest.cpp
910
)

0 commit comments

Comments
 (0)