Skip to content

Commit a0d86b2

Browse files
authored
[SandboxVec][Scheduler] Notify scheduler about instruction creation (llvm#126141)
This patch implements the vectorizer's callback for getting notified about new instructions being created. This updates the scheduler state, which may involve removing dependent instructions from the ready list and update the "scheduled" flag. Since we need to remove elements from the ready list, this patch also implements the `remove()` operation.
1 parent 464f65a commit a0d86b2

File tree

5 files changed

+221
-2
lines changed

5 files changed

+221
-2
lines changed

llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ class DGNode {
121121
assert(UnscheduledSuccs > 0 && "Counting error!");
122122
--UnscheduledSuccs;
123123
}
124+
void incrUnscheduledSuccs() { ++UnscheduledSuccs; }
124125
/// \Returns true if all dependent successors have been scheduled.
125126
bool ready() const { return UnscheduledSuccs == 0; }
126127
/// \Returns true if this node has been scheduled.

llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/Scheduler.h

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,22 @@ class ReadyListContainer {
5454
}
5555
bool empty() const { return List.empty(); }
5656
void clear() { List = {}; }
57+
/// \Removes \p N if found in the ready list.
58+
void remove(DGNode *N) {
59+
// TODO: Use a more efficient data-structure for the ready list because the
60+
// priority queue does not support fast removals.
61+
SmallVector<DGNode *, 8> Keep;
62+
Keep.reserve(List.size());
63+
while (!List.empty()) {
64+
auto *Top = List.top();
65+
List.pop();
66+
if (Top == N)
67+
break;
68+
Keep.push_back(Top);
69+
}
70+
for (auto *KeepN : Keep)
71+
List.push(KeepN);
72+
}
5773
#ifndef NDEBUG
5874
void dump(raw_ostream &OS) const;
5975
LLVM_DUMP_METHOD void dump() const;
@@ -116,6 +132,8 @@ class Scheduler {
116132
/// The dependency graph is used by the scheduler to determine the legal
117133
/// ordering of instructions.
118134
DependencyGraph DAG;
135+
friend class SchedulerInternalsAttorney; // For DAG.
136+
Context &Ctx;
119137
/// This is the top of the schedule, i.e. the location where the scheduler
120138
/// is about to place the scheduled instructions. It gets updated as we
121139
/// schedule.
@@ -124,6 +142,12 @@ class Scheduler {
124142
DenseMap<SchedBundle *, std::unique_ptr<SchedBundle>> Bndls;
125143
/// The BB that we are currently scheduling.
126144
BasicBlock *ScheduledBB = nullptr;
145+
/// The ID of the callback we register with Sandbox IR.
146+
std::optional<Context::CallbackID> CreateInstrCB;
147+
/// Called by Sandbox IR's callback system, after \p I has been created.
148+
/// NOTE: This should run after DAG's callback has run.
149+
// TODO: Perhaps call DAG's notify function from within this one?
150+
void notifyCreateInstr(Instruction *I);
127151

128152
/// \Returns a scheduling bundle containing \p Instrs.
129153
SchedBundle *createBundle(ArrayRef<Instruction *> Instrs);
@@ -153,8 +177,16 @@ class Scheduler {
153177
Scheduler &operator=(const Scheduler &) = delete;
154178

155179
public:
156-
Scheduler(AAResults &AA, Context &Ctx) : DAG(AA, Ctx) {}
157-
~Scheduler() {}
180+
Scheduler(AAResults &AA, Context &Ctx) : DAG(AA, Ctx), Ctx(Ctx) {
181+
// NOTE: The scheduler's callback depends on the DAG's callback running
182+
// before it and updating the DAG accordingly.
183+
CreateInstrCB = Ctx.registerCreateInstrCallback(
184+
[this](Instruction *I) { notifyCreateInstr(I); });
185+
}
186+
~Scheduler() {
187+
if (CreateInstrCB)
188+
Ctx.unregisterCreateInstrCallback(*CreateInstrCB);
189+
}
158190
/// Tries to build a schedule that includes all of \p Instrs scheduled at the
159191
/// same scheduling cycle. This essentially checks that there are no
160192
/// dependencies among \p Instrs. This function may involve scheduling
@@ -180,6 +212,13 @@ class Scheduler {
180212
#endif
181213
};
182214

215+
/// A client-attorney class for accessing the Scheduler's internals (used for
216+
/// unit tests).
217+
class SchedulerInternalsAttorney {
218+
public:
219+
static DependencyGraph &getDAG(Scheduler &Sched) { return Sched.DAG; }
220+
};
221+
183222
} // namespace llvm::sandboxir
184223

185224
#endif // LLVM_TRANSFORMS_VECTORIZE_SANDBOXVECTORIZER_SCHEDULER_H

llvm/lib/Transforms/Vectorize/SandboxVectorizer/Scheduler.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,31 @@ void Scheduler::scheduleAndUpdateReadyList(SchedBundle &Bndl) {
8686
}
8787
}
8888

89+
void Scheduler::notifyCreateInstr(Instruction *I) {
90+
// The DAG notifier should have run by now.
91+
auto *N = DAG.getNode(I);
92+
// If there is no DAG node for `I` it means that this is out of scope for the
93+
// DAG and as such out of scope for the scheduler too, so nothing to do.
94+
if (N == nullptr)
95+
return;
96+
// If the instruction is inserted below the top-of-schedule then we mark it as
97+
// "scheduled".
98+
bool IsScheduled = ScheduleTopItOpt &&
99+
*ScheduleTopItOpt != I->getParent()->end() &&
100+
(*ScheduleTopItOpt.value()).comesBefore(I);
101+
if (IsScheduled)
102+
N->setScheduled(true);
103+
// If the new instruction is above the top of schedule we need to remove its
104+
// dependency predecessors from the ready list and increment their
105+
// `UnscheduledSuccs` counters.
106+
if (!IsScheduled) {
107+
for (auto *PredN : N->preds(DAG)) {
108+
ReadyList.remove(PredN);
109+
PredN->incrUnscheduledSuccs();
110+
}
111+
}
112+
}
113+
89114
SchedBundle *Scheduler::createBundle(ArrayRef<Instruction *> Instrs) {
90115
SchedBundle::ContainerTy Nodes;
91116
Nodes.reserve(Instrs.size());
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
2+
; RUN: opt -passes=sandbox-vectorizer -sbvec-vec-reg-bits=1024 -sbvec-allow-non-pow2 -sbvec-passes="bottom-up-vec<>" %s -S | FileCheck %s
3+
4+
; This used to crash because the newly added pack instructions would not update
5+
; the DAG and scheduler, leading to def-after-use.
6+
define void @check_dag_scheduler_update(ptr noalias %p, ptr noalias %p1) {
7+
; CHECK-LABEL: define void @check_dag_scheduler_update(
8+
; CHECK-SAME: ptr noalias [[P:%.*]], ptr noalias [[P1:%.*]]) {
9+
; CHECK-NEXT: [[I:%.*]] = load i32, ptr [[P]], align 4
10+
; CHECK-NEXT: [[ARRAYIDX4:%.*]] = getelementptr i32, ptr [[P]], i64 32
11+
; CHECK-NEXT: [[I2:%.*]] = load i32, ptr [[ARRAYIDX4]], align 4
12+
; CHECK-NEXT: [[ARRAYIDX11:%.*]] = getelementptr i32, ptr [[P]], i64 33
13+
; CHECK-NEXT: [[I4:%.*]] = load i32, ptr [[ARRAYIDX11]], align 4
14+
; CHECK-NEXT: [[ARRAYIDX18:%.*]] = getelementptr i32, ptr [[P]], i64 34
15+
; CHECK-NEXT: [[I6:%.*]] = load i32, ptr [[ARRAYIDX18]], align 4
16+
; CHECK-NEXT: [[PACK:%.*]] = insertelement <4 x i32> poison, i32 [[I]], i32 0
17+
; CHECK-NEXT: [[PACK1:%.*]] = insertelement <4 x i32> [[PACK]], i32 [[I2]], i32 1
18+
; CHECK-NEXT: [[PACK2:%.*]] = insertelement <4 x i32> [[PACK1]], i32 [[I4]], i32 2
19+
; CHECK-NEXT: [[PACK3:%.*]] = insertelement <4 x i32> [[PACK2]], i32 [[I6]], i32 3
20+
; CHECK-NEXT: [[VECL:%.*]] = load <4 x i32>, ptr [[P]], align 4
21+
; CHECK-NEXT: [[VEC:%.*]] = add nsw <4 x i32> [[PACK3]], [[VECL]]
22+
; CHECK-NEXT: store <4 x i32> [[VEC]], ptr [[P1]], align 4
23+
; CHECK-NEXT: ret void
24+
;
25+
%i = load i32, ptr %p
26+
%i1 = load i32, ptr %p
27+
%add = add nsw i32 %i, %i1
28+
store i32 %add, ptr %p1
29+
%arrayidx4 = getelementptr i32, ptr %p, i64 32
30+
%i2 = load i32, ptr %arrayidx4
31+
%arrayidx6 = getelementptr i32, ptr %p, i64 1
32+
%i3 = load i32, ptr %arrayidx6
33+
%add7 = add nsw i32 %i2, %i3
34+
%arrayidx9 = getelementptr i32, ptr %p1, i64 1
35+
store i32 %add7, ptr %arrayidx9
36+
%arrayidx11 = getelementptr i32, ptr %p, i64 33
37+
%i4 = load i32, ptr %arrayidx11
38+
%arrayidx13 = getelementptr i32, ptr %p, i64 2
39+
%i5 = load i32, ptr %arrayidx13
40+
%add14 = add nsw i32 %i4, %i5
41+
%arrayidx16 = getelementptr i32, ptr %p1, i64 2
42+
store i32 %add14, ptr %arrayidx16
43+
%arrayidx18 = getelementptr i32, ptr %p, i64 34
44+
%i6 = load i32, ptr %arrayidx18
45+
%arrayidx19 = getelementptr i32, ptr %p, i64 3
46+
%i7 = load i32, ptr %arrayidx19
47+
%add21 = add nsw i32 %i6, %i7
48+
%arrayidx23 = getelementptr i32, ptr %p1, i64 3
49+
store i32 %add21, ptr %arrayidx23
50+
ret void
51+
}

llvm/unittests/Transforms/Vectorize/SandboxVectorizer/SchedulerTest.cpp

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,3 +289,106 @@ define void @foo(ptr noalias %ptr0, ptr noalias %ptr1, i8 %v0, i8 %v1) {
289289
EXPECT_FALSE(Sched.trySchedule({S0, S1}));
290290
}
291291
}
292+
293+
TEST_F(SchedulerTest, NotifyCreateInst) {
294+
parseIR(C, R"IR(
295+
define void @foo(ptr noalias %ptr, ptr noalias %ptr1, ptr noalias %ptr2) {
296+
%ld0 = load i8, ptr %ptr
297+
store i8 %ld0, ptr %ptr
298+
ret void
299+
}
300+
)IR");
301+
llvm::Function *LLVMF = &*M->getFunction("foo");
302+
sandboxir::Context Ctx(C);
303+
auto *F = Ctx.createFunction(LLVMF);
304+
auto *BB = &*F->begin();
305+
auto It = BB->begin();
306+
auto *L0 = cast<sandboxir::LoadInst>(&*It++);
307+
auto *S0 = cast<sandboxir::StoreInst>(&*It++);
308+
auto *Ret = cast<sandboxir::ReturnInst>(&*It++);
309+
auto *Ptr1 = F->getArg(1);
310+
auto *Ptr2 = F->getArg(2);
311+
312+
sandboxir::Scheduler Sched(getAA(*LLVMF), Ctx);
313+
// Schedule Ret and S0. The top of schedule should be at S0.
314+
EXPECT_TRUE(Sched.trySchedule({Ret}));
315+
EXPECT_TRUE(Sched.trySchedule({S0}));
316+
auto &DAG = sandboxir::SchedulerInternalsAttorney::getDAG(Sched);
317+
DAG.extend({L0});
318+
auto *L0N = DAG.getNode(L0);
319+
EXPECT_EQ(L0N->getNumUnscheduledSuccs(), 0u);
320+
// We should have DAG nodes for all instructions at this point
321+
322+
// Now create a new instruction below S0.
323+
sandboxir::StoreInst *NewS1 =
324+
sandboxir::StoreInst::create(L0, Ptr1, Align(8), Ret->getIterator(),
325+
/*IsVolatile=*/false, Ctx);
326+
// Check that it is marked as "scheduled".
327+
auto *NewS1N = DAG.getNode(NewS1);
328+
EXPECT_TRUE(NewS1N->scheduled());
329+
// Check that L0's UnscheduledSuccs are still == 0 since NewS1 is "scheduled".
330+
EXPECT_EQ(L0N->getNumUnscheduledSuccs(), 0u);
331+
332+
// Now create a new instruction above S0.
333+
sandboxir::StoreInst *NewS2 =
334+
sandboxir::StoreInst::create(L0, Ptr2, Align(8), S0->getIterator(),
335+
/*IsVolatile=*/false, Ctx);
336+
// Check that it is not marked as "scheduled".
337+
auto *NewS2N = DAG.getNode(NewS2);
338+
EXPECT_FALSE(NewS2N->scheduled());
339+
// Check that L0's UnscheduledSuccs got updated because of NewS2.
340+
EXPECT_EQ(L0N->getNumUnscheduledSuccs(), 1u);
341+
342+
sandboxir::ReadyListContainer ReadyList;
343+
// Check empty().
344+
EXPECT_TRUE(ReadyList.empty());
345+
}
346+
347+
TEST_F(SchedulerTest, ReadyList) {
348+
parseIR(C, R"IR(
349+
define void @foo(ptr %ptr) {
350+
%ld0 = load i8, ptr %ptr
351+
store i8 %ld0, ptr %ptr
352+
ret void
353+
}
354+
)IR");
355+
llvm::Function *LLVMF = &*M->getFunction("foo");
356+
sandboxir::Context Ctx(C);
357+
auto *F = Ctx.createFunction(LLVMF);
358+
auto *BB = &*F->begin();
359+
auto It = BB->begin();
360+
auto *L0 = cast<sandboxir::LoadInst>(&*It++);
361+
auto *S0 = cast<sandboxir::StoreInst>(&*It++);
362+
auto *Ret = cast<sandboxir::ReturnInst>(&*It++);
363+
364+
sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx);
365+
DAG.extend({&*BB->begin(), BB->getTerminator()});
366+
auto *L0N = DAG.getNode(L0);
367+
auto *S0N = DAG.getNode(S0);
368+
auto *RetN = DAG.getNode(Ret);
369+
370+
sandboxir::ReadyListContainer ReadyList;
371+
// Check empty().
372+
EXPECT_TRUE(ReadyList.empty());
373+
// Check insert(), pop().
374+
ReadyList.insert(L0N);
375+
EXPECT_FALSE(ReadyList.empty());
376+
EXPECT_EQ(ReadyList.pop(), L0N);
377+
// Check clear().
378+
ReadyList.insert(L0N);
379+
EXPECT_FALSE(ReadyList.empty());
380+
ReadyList.clear();
381+
EXPECT_TRUE(ReadyList.empty());
382+
// Check remove().
383+
EXPECT_TRUE(ReadyList.empty());
384+
ReadyList.remove(L0N); // Removing a non-existing node should be valid.
385+
ReadyList.insert(L0N);
386+
ReadyList.insert(S0N);
387+
ReadyList.insert(RetN);
388+
ReadyList.remove(S0N);
389+
DenseSet<sandboxir::DGNode *> Nodes;
390+
Nodes.insert(ReadyList.pop());
391+
Nodes.insert(ReadyList.pop());
392+
EXPECT_TRUE(ReadyList.empty());
393+
EXPECT_THAT(Nodes, testing::UnorderedElementsAre(L0N, RetN));
394+
}

0 commit comments

Comments
 (0)